Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
src/libs/styles.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 calca.
4 Copyright (C) 2010-2011, 2013 Henrik Andersson.
5 Copyright (C) 2010-2011 Olivier Tribout.
6 Copyright (C) 2010-2012, 2014-2017 Tobias Ellinghaus.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011-2013 johannes hanika.
9 Copyright (C) 2011, 2013 Jérémy Rosen.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2012 Frédéric Grollier.
12 Copyright (C) 2012 Richard Wonka.
13 Copyright (C) 2013 Pascal de Bruijn.
14 Copyright (C) 2013-2014, 2019-2022 Pascal Obry.
15 Copyright (C) 2014, 2016 Roman Lebedev.
16 Copyright (C) 2017 parafin.
17 Copyright (C) 2018, 2023 Maurizio Paglia.
18 Copyright (C) 2018 rawfiner.
19 Copyright (C) 2019, 2022-2023, 2025 Aurélien PIERRE.
20 Copyright (C) 2020-2021 Aldric Renaudin.
21 Copyright (C) 2020 Chris Elston.
22 Copyright (C) 2020 darkelectron.
23 Copyright (C) 2020-2022 Diederik Ter Rahe.
24 Copyright (C) 2020 EdgarLux.
25 Copyright (C) 2020-2021 Hubert Kowalski.
26 Copyright (C) 2020 Philippe Weyland.
27 Copyright (C) 2020-2021 Ralf Brown.
28 Copyright (C) 2021 Marco Carrarini.
29 Copyright (C) 2021 Victor Forsiuk.
30 Copyright (C) 2022 Martin Bařinka.
31
32 darktable is free software: you can redistribute it and/or modify
33 it under the terms of the GNU General Public License as published by
34 the Free Software Foundation, either version 3 of the License, or
35 (at your option) any later version.
36
37 darktable is distributed in the hope that it will be useful,
38 but WITHOUT ANY WARRANTY; without even the implied warranty of
39 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 GNU General Public License for more details.
41
42 You should have received a copy of the GNU General Public License
43 along with darktable. If not, see <http://www.gnu.org/licenses/>.
44*/
45#include "bauhaus/bauhaus.h"
46#include "common/collection.h"
47#include "common/history.h"
48#include "common/styles.h"
49#include "common/darktable.h"
50#include "control/conf.h"
51#include "control/control.h"
52#include "control/jobs.h"
53#include "dtgtk/button.h"
54
55#include "gui/gtk.h"
56#include "gui/styles.h"
57#include "libs/lib.h"
58#include "libs/lib_api.h"
59#ifdef GDK_WINDOWING_QUARTZ
60#include "osx/osx.h"
61#endif
62#include <gdk/gdkkeysyms.h>
63#include <gtk/gtk.h>
64#include <stdlib.h>
65#include <libxml/parser.h>
66
67DT_MODULE(1)
68
69typedef struct dt_lib_styles_t
70{
71 GtkEntry *entry;
73 GtkTreeView *tree;
74 GtkWidget *create_button, *edit_button, *delete_button, *import_button, *export_button, *apply_button;
76
77
78const char *name(struct dt_lib_module_t *self)
79{
80 return _("apply styles");
81}
82
83const char **views(dt_lib_module_t *self)
84{
85 // Goes in popup
86 static const char *v[] = {"special", NULL};
87 return v;
88}
89
91{
93}
94
96{
97 return 0;
98}
99
107
108static gboolean _get_node_for_name(GtkTreeModel *model, gboolean root, GtkTreeIter *iter, const gchar *parent_name)
109{
110 GtkTreeIter parent = *iter;
111
112 if(root)
113 {
114 // iter is null, we are at the top level
115 // if we have no nodes in this tree, let's create it now
116 if(!gtk_tree_model_get_iter_first(model, iter))
117 {
118 gtk_tree_store_append(GTK_TREE_STORE(model), iter, NULL);
119 return FALSE;
120 }
121 }
122 else
123 {
124 // if we have no children, create one, this is our node
125 if(!gtk_tree_model_iter_children(GTK_TREE_MODEL(model), iter, &parent))
126 {
127 gtk_tree_store_append(GTK_TREE_STORE(model), iter, &parent);
128 return FALSE;
129 }
130 }
131
132 // here we have iter to be on the right level, let's check if we can find parent_name
133 do
134 {
135 gchar *name;
136 gtk_tree_model_get(model, iter, DT_STYLES_COL_NAME, &name, -1);
137 const gboolean match = !g_strcmp0(name, parent_name);
138 dt_free(name);
139 if(match)
140 {
141 return TRUE;
142 }
143 }
144 while(gtk_tree_model_iter_next(model, iter));
145
146 // not found, create it under parent
147 gtk_tree_store_append(GTK_TREE_STORE(model), iter, root?NULL:&parent);
148
149 return FALSE;
150}
151
153{
154 /* clear current list */
155 GtkTreeIter iter;
156 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->tree));
157 g_object_ref(model);
158 gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree), NULL);
159 gtk_tree_store_clear(GTK_TREE_STORE(model));
160
161 GList *result = dt_styles_get_list(gtk_entry_get_text(d->entry));
162 if(result)
163 {
164 for(const GList *res_iter = result; res_iter; res_iter = g_list_next(res_iter))
165 {
166 dt_style_t *style = (dt_style_t *)res_iter->data;
167
168 gchar *items_string = (gchar *)dt_styles_get_item_list_as_string(style->name);
169 gchar *tooltip = NULL;
170
171 if(style->description && *style->description)
172 {
173 gchar *escaped_description = g_markup_escape_text(style->description, -1);
174 tooltip = g_strconcat("<b>", escaped_description, "</b>\n", items_string, NULL);
175 dt_free(escaped_description);
176 }
177 else
178 {
179 tooltip = g_strdup(items_string);
180 }
181
182 gchar **split = g_strsplit(style->name, "|", 0);
183 int k = 0;
184
185 while(split[k])
186 {
187 const gchar *s = split[k];
188 const gboolean node_found = _get_node_for_name(model, k==0, &iter, s);
189
190 if(!node_found)
191 {
192 if(split[k+1])
193 {
194 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, DT_STYLES_COL_NAME, s, -1);
195 }
196 else
197 {
198 // a leaf
199 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
201 }
202 }
203 k++;
204 }
205 g_strfreev(split);
206
207 dt_free(items_string);
209 }
210 g_list_free_full(result, dt_style_free);
211 result = NULL;
212 }
213
214 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(d->tree), DT_STYLES_COL_TOOLTIP);
215 gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree), model);
216 g_object_unref(model);
217}
218
219static void _styles_row_activated_callback(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col,
220 gpointer user_data)
221{
222 // This works on double click, so it's for single style
223 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
224
225 GtkTreeModel *model;
226 GtkTreeIter iter;
227 model = gtk_tree_view_get_model(d->tree);
228
229 if(!gtk_tree_model_get_iter(model, &iter, path)) return;
230
231 gchar *name;
232 gtk_tree_model_get(model, &iter, DT_STYLES_COL_FULLNAME, &name, -1);
233
234 GList *list = dt_act_on_get_images();
235 if(name)
236 {
237 dt_history_style_on_list(list, name, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
238 dt_free(name);
239 }
240 g_list_free(list);
241 list = NULL;
242}
243
244// get list of style names from selection
245// free returned list with g_list_free_full(list, dt_free_gpointer)
246GList* _get_selected_style_names(GList* selected_styles, GtkTreeModel *model)
247{
248 GtkTreeIter iter;
249 GList *style_names = NULL;
250 for (const GList *style = selected_styles; style; style = g_list_next(style))
251 {
252 GValue value = {0,};
253 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)style->data);
254 gtk_tree_model_get_value(model, &iter, DT_STYLES_COL_FULLNAME, &value);
255 if(G_VALUE_HOLDS_STRING(&value))
256 style_names = g_list_prepend(style_names, g_strdup(g_value_get_string(&value)));
257 g_value_unset(&value);
258 }
259 return g_list_reverse(style_names); // list was built in reverse order, so un-reverse it
260}
261
262static void apply_clicked(GtkWidget *w, gpointer user_data)
263{
264 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
265 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
266
267 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
268
269 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
270 GList *selected_styles = gtk_tree_selection_get_selected_rows(selection, &model);
271 GList *style_names = _get_selected_style_names(selected_styles, model);
272 g_list_free_full(selected_styles, (GDestroyNotify) gtk_tree_path_free);
273 selected_styles = NULL;
274
275 if(IS_NULL_PTR(style_names)) return;
276
277 GList *list = dt_act_on_get_images();
278
279 if(list) dt_multiple_styles_apply_to_list(style_names, list, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
280
281 g_list_free_full(style_names, dt_free_gpointer);
282 style_names = NULL;
283 g_list_free(list);
284 list = NULL;
285}
286
287static void create_clicked(GtkWidget *w, gpointer user_data)
288{
289 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
290
291 GList *list = dt_act_on_get_images();
293 g_list_free(list);
294 list = NULL;
296}
297
298static void edit_clicked(GtkWidget *w, gpointer user_data)
299{
300 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
301 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
302
303 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
304
305 GtkTreeIter iter;
306 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
307
308 GList *styles = gtk_tree_selection_get_selected_rows(selection, &model);
309 for (const GList *style = styles; style; style = g_list_next(style))
310 {
311 char *name = NULL;
312 GValue value = {0,};
313 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)style->data);
314 gtk_tree_model_get_value(model, &iter, DT_STYLES_COL_FULLNAME, &value);
315 if(G_VALUE_HOLDS_STRING(&value))
316 name = g_strdup(g_value_get_string(&value));
317 g_value_unset(&value);
318
319 if(name)
320 {
323 dt_free(name);
324 }
325 }
326 g_list_free_full (styles, (GDestroyNotify) gtk_tree_path_free);
327}
328
329gboolean _ask_before_delete_style(const gint style_cnt)
330{
331 gint res = GTK_RESPONSE_YES;
332
333 if(dt_conf_get_bool("plugins/lighttable/style/ask_before_delete_style"))
334 {
336 GtkWidget *dialog = gtk_message_dialog_new
337 (GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
338 ngettext("do you really want to remove %d style?", "do you really want to remove %d styles?", style_cnt),
339 style_cnt);
340#ifdef GDK_WINDOWING_QUARTZ
342#endif
343
344 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("remove style?", "remove styles?", style_cnt));
345 res = gtk_dialog_run(GTK_DIALOG(dialog));
346 gtk_widget_destroy(dialog);
347 }
348
349 return res == GTK_RESPONSE_YES;
350}
351
352static void delete_clicked(GtkWidget *w, gpointer user_data)
353{
354 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
355
356 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
357
358 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
359
360 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
361 GList *selected_styles = gtk_tree_selection_get_selected_rows(selection, &model);
362 GList *style_names = _get_selected_style_names(selected_styles, model);
363 g_list_free_full(selected_styles, (GDestroyNotify) gtk_tree_path_free);
364 selected_styles = NULL;
365
366 if(IS_NULL_PTR(style_names)) return;
367
368 const gint select_cnt = g_list_length(style_names);
369 const gboolean single_raise = (select_cnt == 1);
370
371 const gboolean can_delete = _ask_before_delete_style(select_cnt);
372
373 if(can_delete)
374 {
376
377 for (const GList *style = style_names; style; style = g_list_next(style))
378 {
379 dt_styles_delete_by_name_adv((char*)style->data, single_raise);
380 }
381
382 if(!single_raise) {
383 // raise signal at the end of processing all styles if we have more than 1 to delete
384 // this also calls _gui_styles_update_view
386 }
388 }
389 g_list_free_full(style_names, dt_free_gpointer);
390 style_names = NULL;
391}
392
393static void export_clicked(GtkWidget *w, gpointer user_data)
394{
395 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
396
397 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
398
399 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
400
401 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
402 GList *selected_styles = gtk_tree_selection_get_selected_rows(selection, &model);
403 GList *style_names = _get_selected_style_names(selected_styles, model);
404 g_list_free_full(selected_styles, (GDestroyNotify) gtk_tree_path_free);
405 selected_styles = NULL;
406
407 if(IS_NULL_PTR(style_names)) return;
408
409 /* variables for overwrite dialog */
410 gint overwrite_check_button = 0;
411 gint overwrite = 0;
412
414 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
415 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
416 _("_save"), _("_cancel"));
417
418 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
419 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
420
421 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
422 {
423 char *filedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
424
425 for (const GList *style = style_names; style; style = g_list_next(style))
426 {
427 char stylename[520];
428
429 /* check if file exists before overwriting */
430 snprintf(stylename, sizeof(stylename), "%s/%s.dtstyle", filedir, (char*)style->data);
431
432 if(g_file_test(stylename, G_FILE_TEST_EXISTS) == TRUE)
433 {
434 /* do not run overwrite dialog */
435 if(overwrite_check_button == 1)
436 {
437 if(overwrite == 1)
438 {
439 // save style with overwrite
440 dt_styles_save_to_file((char*)style->data, filedir, TRUE);
441 }
442 else if(overwrite == 2)
443 {
444 continue;
445 }
446 else
447 {
448 break;
449 }
450 }
451 else
452 {
453 /* create and run dialog */
454 char overwrite_str[256];
455
456 gint overwrite_dialog_res = GTK_RESPONSE_ACCEPT;
457 gint overwrite_dialog_check_button_res = TRUE;
458
459 if(dt_conf_get_bool("plugins/lighttable/style/ask_before_delete_style"))
460 {
461 GtkWidget *dialog_overwrite_export = gtk_dialog_new_with_buttons(_("overwrite style?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
462 _("cancel"), GTK_RESPONSE_CANCEL,
463 _("skip"), GTK_RESPONSE_NONE,
464 _("overwrite"), GTK_RESPONSE_ACCEPT, NULL);
465
466 // contents for dialog
467 GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_overwrite_export));
468 sprintf(overwrite_str, _("style `%s' already exists.\ndo you want to overwrite existing style?\n"), (char*)style->data);
469 GtkWidget *label = gtk_label_new(overwrite_str);
470 GtkWidget *overwrite_dialog_check_button = gtk_check_button_new_with_label(_("apply this option to all existing styles"));
471
472 gtk_container_add(GTK_CONTAINER(content_area), label);
473 gtk_container_add(GTK_CONTAINER(content_area), overwrite_dialog_check_button);
474 gtk_widget_show_all(dialog_overwrite_export);
475
476 // disable check button and skip button when only one style is selected
477 if(g_list_is_singleton(style_names))
478 {
479 gtk_widget_set_sensitive(GTK_WIDGET(overwrite_dialog_check_button), FALSE);
480 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_overwrite_export), GTK_RESPONSE_NONE, FALSE);
481 }
482
483#ifdef GDK_WINDOWING_QUARTZ
484 dt_osx_disallow_fullscreen(dialog_overwrite_export);
485#endif
486
487 overwrite_dialog_res = gtk_dialog_run(GTK_DIALOG(dialog_overwrite_export));
488 overwrite_dialog_check_button_res = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overwrite_dialog_check_button));
489 gtk_widget_destroy(dialog_overwrite_export);
490 }
491
492 if(overwrite_dialog_res == GTK_RESPONSE_ACCEPT)
493 {
494 overwrite = 1;
495
496 /* do not run dialog on the next conflict when set to 1 */
497 if(overwrite_dialog_check_button_res == TRUE)
498 {
499 overwrite_check_button = 1;
500 }
501 else
502 {
503 overwrite_check_button = 0;
504 }
505 }
506 else if(overwrite_dialog_res == GTK_RESPONSE_NONE)
507 {
508 overwrite = 2;
509
510 /* do not run dialog on the next conflict when set to 1 */
511 if(overwrite_dialog_check_button_res == TRUE)
512 {
513 overwrite_check_button = 1;
514 }
515 else
516 {
517 overwrite_check_button = 0;
518 }
519 continue;
520 }
521 else
522 {
523 break;
524 }
525
526 dt_styles_save_to_file((char*)style->data, filedir, TRUE);
527 }
528 }
529 else
530 {
531 dt_styles_save_to_file((char*)style->data, filedir, FALSE);
532 }
533 dt_control_log(_("style %s was successfully exported"), (char*)style->data);
534 }
535 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
536 dt_free(filedir);
537 }
538 g_object_unref(filechooser);
539 g_list_free_full(style_names, dt_free_gpointer);
540 style_names = NULL;
541}
542
543static void import_clicked(GtkWidget *w, gpointer user_data)
544{
545 /* variables for overwrite dialog */
546 gint overwrite_check_button = 0;
547 gint overwrite = 0;
548
550 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
551 _("select style"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
552 _("_open"), _("_cancel"));
553
554 dt_conf_get_folder_to_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(filechooser));
555 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), TRUE);
556
557 GtkFileFilter *filter;
558 filter = GTK_FILE_FILTER(gtk_file_filter_new());
559 gtk_file_filter_add_pattern(filter, "*.dtstyle");
560 gtk_file_filter_add_pattern(filter, "*.DTSTYLE");
561 gtk_file_filter_set_name(filter, _("Ansel style files"));
562 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
563
564 filter = GTK_FILE_FILTER(gtk_file_filter_new());
565 gtk_file_filter_add_pattern(filter, "*");
566 gtk_file_filter_set_name(filter, _("all files"));
567
568 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
569
570 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
571 {
572 GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(filechooser));
573
574 for(const GSList *filename = filenames; filename; filename = g_slist_next(filename))
575 {
576 /* extract name from xml file */
577 gchar *bname = NULL;
578 xmlDoc *document = xmlReadFile((char*)filename->data, NULL, XML_PARSE_NOBLANKS);
579 xmlNode *root = NULL;
580 if(!IS_NULL_PTR(document))
581 root = xmlDocGetRootElement(document);
582
583 if(IS_NULL_PTR(document) || IS_NULL_PTR(root) || xmlStrcmp(root->name, BAD_CAST "darktable_style"))
584 {
586 "[styles] file %s is not a style file\n", (char*)filename->data);
587 if(document)
588 xmlFreeDoc(document);
589 continue;
590 }
591
592 for(xmlNode *node = root->children->children; node; node = node->next)
593 {
594 if(node->type == XML_ELEMENT_NODE)
595 {
596 if(strcmp((char*)node->name, "name") == 0)
597 {
598 bname = g_strdup((char*)xmlNodeGetContent(node));
599 break;
600 }
601 }
602 }
603
604 // xml doc is not necessary after this point
605 xmlFreeDoc(document);
606
607 if(IS_NULL_PTR(bname)){
609 "[styles] file %s is malformed style file\n", (char*)filename->data);
610 continue;
611 }
612
613 // check if style exists
614 if(dt_styles_exists(bname))
615 {
616 /* do not run overwrite dialog */
617 if(overwrite_check_button == 1)
618 {
619 if(overwrite == 1)
620 {
621 // remove style then import
623 dt_styles_import_from_file((char*)filename->data);
624 }
625 else if(overwrite == 2)
626 {
627 continue;
628 }
629 else
630 {
631 break;
632 }
633 }
634 else
635 {
636 /* create and run dialog */
637 char overwrite_str[256];
638
639 gint overwrite_dialog_res = GTK_RESPONSE_ACCEPT;
640 gint overwrite_dialog_check_button_res = TRUE;
641
642 // use security check/option
643 if(dt_conf_get_bool("plugins/lighttable/style/ask_before_delete_style"))
644 {
645 GtkWidget *dialog_overwrite_import = gtk_dialog_new_with_buttons(_("overwrite style?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
646 _("cancel"), GTK_RESPONSE_CANCEL,
647 _("skip"), GTK_RESPONSE_NONE,
648 _("overwrite"), GTK_RESPONSE_ACCEPT, NULL);
649
650 // contents for dialog
651 GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_overwrite_import));
652 sprintf(overwrite_str, _("style `%s' already exists.\ndo you want to overwrite existing style?\n"), (char*)filename->data);
653 GtkWidget *label = gtk_label_new(overwrite_str);
654 GtkWidget *overwrite_dialog_check_button = gtk_check_button_new_with_label(_("apply this option to all existing styles"));
655
656 gtk_container_add(GTK_CONTAINER(content_area), label);
657 gtk_container_add(GTK_CONTAINER(content_area), overwrite_dialog_check_button);
658 gtk_widget_show_all(dialog_overwrite_import);
659
660 // disable check button and skip button when dealing with one style
661 if(g_slist_length(filenames) == 1)
662 {
663 gtk_widget_set_sensitive(GTK_WIDGET(overwrite_dialog_check_button), FALSE);
664 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_overwrite_import), GTK_RESPONSE_NONE, FALSE);
665 }
666
667#ifdef GDK_WINDOWING_QUARTZ
668 dt_osx_disallow_fullscreen(dialog_overwrite_import);
669#endif
670
671 overwrite_dialog_res = gtk_dialog_run(GTK_DIALOG(dialog_overwrite_import));
672 overwrite_dialog_check_button_res = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overwrite_dialog_check_button));
673 gtk_widget_destroy(dialog_overwrite_import);
674 }
675
676 if(overwrite_dialog_res == GTK_RESPONSE_ACCEPT)
677 {
678 overwrite = 1;
679
680 /* do not run dialog on next conflict when set to 1 */
681 if(overwrite_dialog_check_button_res == TRUE)
682 {
683 overwrite_check_button = 1;
684 }
685 else
686 {
687 overwrite_check_button = 0;
688 }
689 }
690 else if(overwrite_dialog_res == GTK_RESPONSE_NONE)
691 {
692 overwrite = 2;
693
694
695 /* do not run dialog on next conflict when set to 1 */
696 if(overwrite_dialog_check_button_res == TRUE)
697 {
698 overwrite_check_button = 1;
699 }
700 else
701 {
702 overwrite_check_button = 0;
703 }
704 continue;
705 }
706 else
707 {
708 break;
709 }
710
712 dt_styles_import_from_file((char*)filename->data);
713 }
714 }
715 else
716 {
717 dt_styles_import_from_file((char*)filename->data);
718 }
719 dt_free(bname);
720 }
721 g_slist_free_full(filenames, dt_free_gpointer);
722
723 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
725 dt_conf_set_folder_from_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(filechooser));
726 }
727 g_object_unref(filechooser);
728}
729
730static gboolean entry_callback(GtkEntry *entry, gpointer user_data)
731{
732 _gui_styles_update_view(user_data);
733 return FALSE;
734}
735
736static gboolean entry_activated(GtkEntry *entry, gpointer user_data)
737{
738 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
739 const gchar *name = gtk_entry_get_text(d->entry);
740 if(name)
741 {
742 GList *imgs = dt_act_on_get_images();
743 dt_history_style_on_list(imgs, name, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
744 g_list_free(imgs);
745 imgs = NULL;
746 }
747
748 return FALSE;
749}
750
751static gboolean duplicate_callback(GtkEntry *entry, gpointer user_data)
752{
753 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
754 dt_conf_set_bool("ui_last/styles_create_duplicate",
755 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
756 return FALSE;
757}
758
759static void _update(dt_lib_module_t *self)
760{
763
764 const gboolean has_act_on = (dt_act_on_get_images_nb(TRUE, FALSE) > 0);
765
766 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
767 const gint sel_styles_cnt = gtk_tree_selection_count_selected_rows(selection);
768
769 gtk_widget_set_sensitive(GTK_WIDGET(d->create_button), has_act_on);
770 gtk_widget_set_sensitive(GTK_WIDGET(d->edit_button), sel_styles_cnt > 0);
771 gtk_widget_set_sensitive(GTK_WIDGET(d->delete_button), sel_styles_cnt > 0);
772
773 //import is ALWAYS enabled.
774 gtk_widget_set_sensitive(GTK_WIDGET(d->export_button), sel_styles_cnt > 0);
775
776 gtk_widget_set_sensitive(GTK_WIDGET(d->apply_button), has_act_on && sel_styles_cnt > 0);
777}
778
779static void _styles_changed_callback(gpointer instance, gpointer user_data)
780{
781 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
784 _update(self);
785}
786
787static void _image_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
788{
789 _update(self);
790}
791
792static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change,
793 dt_collection_properties_t changed_property, gpointer imgs, int next,
794 dt_lib_module_t *self)
795{
796 _update(self);
797}
798
799static void _mouse_over_image_callback(gpointer instance, dt_lib_module_t *self)
800{
802}
803
804static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
805{
806 _update((dt_lib_module_t *)data);
807}
808
810{
811 dt_lib_styles_t *d = (dt_lib_styles_t *)malloc(sizeof(dt_lib_styles_t));
812 self->data = (void *)d;
813 self->timeout_handle = 0;
814 d->edit_button = NULL;
815 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
816 GtkWidget *w;
817
818 /* tree */
819 d->tree = GTK_TREE_VIEW(gtk_tree_view_new());
820 gtk_tree_view_set_headers_visible(d->tree, FALSE);
821 GtkTreeStore *treestore = gtk_tree_store_new(DT_STYLES_NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
822 GtkTreeViewColumn *col = gtk_tree_view_column_new();
823 gtk_tree_view_append_column(GTK_TREE_VIEW(d->tree), col);
824 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
825 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
826 gtk_tree_view_column_pack_start(col, renderer, TRUE);
827 gtk_tree_view_column_add_attribute(col, renderer, "text", DT_STYLES_COL_NAME);
828
829 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree)), GTK_SELECTION_MULTIPLE);
830 gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree), GTK_TREE_MODEL(treestore));
831 g_object_unref(treestore);
832
833 gtk_widget_set_tooltip_text(GTK_WIDGET(d->tree), _("available styles,\ndoubleclick to apply"));
834 g_signal_connect(d->tree, "row-activated", G_CALLBACK(_styles_row_activated_callback), d);
835 g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree)), "changed", G_CALLBACK(_tree_selection_changed), self);
836
837 /* filter entry */
838 w = gtk_entry_new();
839 d->entry = GTK_ENTRY(w);
840 gtk_entry_set_placeholder_text(GTK_ENTRY(d->entry), _("filter style names"));
841 gtk_widget_set_tooltip_text(w, _("filter style names"));
842 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
843 g_signal_connect(d->entry, "changed", G_CALLBACK(entry_callback), d);
844 g_signal_connect(d->entry, "activate", G_CALLBACK(entry_activated), d);
845
846
847 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->entry), TRUE, TRUE, 0);
848 GtkWidget *style_tree_sw = dt_ui_scroll_wrap(GTK_WIDGET(d->tree), 250, "plugins/lighttable/style/windowheight",
850 gtk_box_pack_start(GTK_BOX(self->widget), style_tree_sw, FALSE, FALSE, 0);
851
852 d->duplicate = gtk_check_button_new_with_label(_("create duplicate"));
853 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->duplicate))), PANGO_ELLIPSIZE_START);
854 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->duplicate), TRUE, FALSE, 0);
855 g_signal_connect(d->duplicate, "toggled", G_CALLBACK(duplicate_callback), d);
856 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->duplicate),
857 dt_conf_get_bool("ui_last/styles_create_duplicate"));
858 gtk_widget_set_tooltip_text(d->duplicate, _("creates a duplicate of the image before applying style"));
859
860 GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
861 GtkWidget *hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
862 GtkWidget *hbox3 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
863 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox1), TRUE, FALSE, 0);
864 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox2), TRUE, FALSE, 0);
865 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox3), TRUE, FALSE, 0);
866
867 // create
868 d->create_button = dt_action_button_new(self, N_("create..."), create_clicked, d, _("create styles from history stack of selected images"), 0, 0);
869 gtk_box_pack_start(GTK_BOX(hbox1), d->create_button, TRUE, TRUE, 0);
870
871 // edit
872 d->edit_button = dt_action_button_new(self, N_("edit..."), edit_clicked, d, _("edit the selected styles in list above"), 0, 0);
873 gtk_box_pack_start(GTK_BOX(hbox1), d->edit_button, TRUE, TRUE, 0);
874
875 // delete
876 d->delete_button = dt_action_button_new(self, N_("remove"), delete_clicked, d, _("removes the selected styles in list above"), 0, 0);
877 gtk_box_pack_start(GTK_BOX(hbox1), d->delete_button, TRUE, TRUE, 0);
878
879 // import button
880 d->import_button = dt_action_button_new(self, N_("import..."), import_clicked, d, _("import styles from a style files"), 0, 0);
881 gtk_box_pack_start(GTK_BOX(hbox2), d->import_button, TRUE, TRUE, 0);
882
883 // export button
884 d->export_button = dt_action_button_new(self, N_("export..."), export_clicked, d, _("export the selected styles into a style files"), 0, 0);
885 gtk_box_pack_start(GTK_BOX(hbox2), d->export_button, TRUE, TRUE, 0);
886
887 // apply button
888 d->apply_button = dt_action_button_new(self, N_("apply"), apply_clicked, d, _("apply the selected styles in list above to selected images"), 0, 0);
889 gtk_box_pack_start(GTK_BOX(hbox3), d->apply_button, TRUE, TRUE, 0);
890
891 // add entry completion
892 GtkEntryCompletion *completion = gtk_entry_completion_new();
893 gtk_entry_completion_set_model(completion, gtk_tree_view_get_model(GTK_TREE_VIEW(d->tree)));
894 gtk_entry_completion_set_text_column(completion, 0);
895 gtk_entry_completion_set_inline_completion(completion, TRUE);
896 gtk_entry_set_completion(d->entry, completion);
897
898 /* update filtered list */
900
902
904 G_CALLBACK(_image_selection_changed_callback), self);
906 G_CALLBACK(_mouse_over_image_callback), self);
908 G_CALLBACK(_collection_updated_callback), self);
909
910 _update(self);
911}
912
925
927{
929
930 GList *all_styles = dt_styles_get_list("");
931
932 if(IS_NULL_PTR(all_styles))
933 {
935 return;
936 }
937
938 const gint styles_cnt = g_list_length(all_styles);
939 const gboolean can_delete = _ask_before_delete_style(styles_cnt);
940
941 if(can_delete)
942 {
943 for (const GList *result = all_styles; result; result = g_list_next(result))
944 {
945 dt_style_t *style = (dt_style_t *)result->data;
947 }
949 }
950 g_list_free_full(all_styles, dt_style_free);
951 all_styles = NULL;
953 _update(self);
954}
955
956
957// clang-format off
958// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
959// vim: shiftwidth=2 expandtab tabstop=2 cindent
960// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
961// clang-format on
int dt_act_on_get_images_nb(const gboolean only_visible, const gboolean force)
Definition act_on.c:54
GList * dt_act_on_get_images()
Definition act_on.c:39
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
dt_collection_properties_t
Definition collection.h:107
dt_collection_change_t
Definition collection.h:147
char * name
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
void dt_conf_set_folder_from_file_chooser(const char *name, GtkFileChooser *chooser)
gboolean dt_conf_get_folder_to_file_chooser(const char *name, GtkFileChooser *chooser)
void dt_control_log(const char *msg,...)
Definition control.c:761
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_CONTROL
Definition darktable.h:716
#define g_list_is_singleton(list)
Definition darktable.h:938
#define DT_MODULE(MODVER)
Definition darktable.h:140
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define dt_database_start_transaction(db)
Definition database.h:77
#define dt_database_release_transaction(db)
Definition database.h:78
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2713
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
void dt_gui_styles_dialog_edit(const char *name)
gboolean dt_history_style_on_list(const GList *list, const char *name, const gboolean duplicate)
const char * tooltip
Definition image.h:251
const char * model
const float v
void dt_lib_cancel_postponed_update(dt_lib_module_t *mod)
Definition lib.c:1536
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1563
void dt_lib_queue_postponed_update(dt_lib_module_t *mod, void(*update_fn)(dt_lib_module_t *self))
Definition lib.c:1524
float *const restrict const size_t k
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_STYLE_CHANGED
This signal is raised when a style is added/deleted/changed
Definition signal.h:147
@ DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
Definition signal.h:59
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:127
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:122
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
gboolean dt_styles_exists(const char *name)
char * dt_styles_get_item_list_as_string(const char *name)
void dt_multiple_styles_apply_to_list(GList *styles, const GList *list, gboolean duplicate)
void dt_styles_delete_by_name_adv(const char *name, const gboolean raise)
void dt_styles_save_to_file(const char *style_name, const char *filedir, gboolean overwrite)
void dt_style_free(gpointer data)
void dt_styles_delete_by_name(const char *name)
void dt_styles_create_from_list(const GList *list)
void dt_styles_import_from_file(const char *style_path)
GList * dt_styles_get_list(const char *filter)
gboolean _ask_before_delete_style(const gint style_cnt)
void gui_reset(dt_lib_module_t *self)
static void create_clicked(GtkWidget *w, gpointer user_data)
static void _update(dt_lib_module_t *self)
static void export_clicked(GtkWidget *w, gpointer user_data)
static void _styles_changed_callback(gpointer instance, gpointer user_data)
static gboolean entry_callback(GtkEntry *entry, gpointer user_data)
static void delete_clicked(GtkWidget *w, gpointer user_data)
void gui_cleanup(dt_lib_module_t *self)
static gboolean _get_node_for_name(GtkTreeModel *model, gboolean root, GtkTreeIter *iter, const gchar *parent_name)
GList * _get_selected_style_names(GList *selected_styles, GtkTreeModel *model)
static void import_clicked(GtkWidget *w, gpointer user_data)
static gboolean duplicate_callback(GtkEntry *entry, gpointer user_data)
static void _mouse_over_image_callback(gpointer instance, dt_lib_module_t *self)
static void _gui_styles_update_view(dt_lib_styles_t *d)
static void apply_clicked(GtkWidget *w, gpointer user_data)
static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, int next, dt_lib_module_t *self)
uint32_t container(dt_lib_module_t *self)
_styles_columns_t
@ DT_STYLES_COL_FULLNAME
@ DT_STYLES_NUM_COLS
@ DT_STYLES_COL_NAME
@ DT_STYLES_COL_TOOLTIP
static gboolean entry_activated(GtkEntry *entry, gpointer user_data)
static void edit_clicked(GtkWidget *w, gpointer user_data)
static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
void gui_init(dt_lib_module_t *self)
int position()
const char ** views(dt_lib_module_t *self)
static void _image_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
static void _styles_row_activated_callback(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data)
struct dt_gui_gtk_t * gui
Definition darktable.h:775
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
dt_ui_t * ui
Definition gtk.h:164
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
guint timeout_handle
Definition lib.h:90
GtkTreeView * tree
GtkWidget * duplicate
GtkWidget * apply_button
gchar * description
gchar * name
@ DT_UI_CONTAINER_SIZE