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*/
46#include "common/act_on.h"
48#include "common/collection.h"
49#include "common/styles.h"
51#include "system/macros.h"
52#include "system/mem_alloc.h"
53#include "common/logging.h"
54#include "common/glib_utils.h"
56#include "control/signal.h"
57#include "database/database.h"
58#include "common/conf.h"
60
61#include "gui/application.h"
62#include "gui/styles.h"
63#include "libs/lib.h"
64#include "libs/lib_api.h"
65#ifdef GDK_WINDOWING_QUARTZ
66#include "osx/osx.h"
67#endif
68#include <gdk/gdkkeysyms.h>
69#include <gtk/gtk.h>
70#include <stdlib.h>
71#include <libxml/parser.h>
72#include "widgets/scroll_wrap.h"
73
74DT_MODULE(1)
75
76typedef struct dt_lib_styles_t
77{
78 GtkEntry *entry;
80 GtkTreeView *tree;
81 GtkWidget *create_button, *edit_button, *delete_button, *import_button, *export_button, *apply_button;
83
84
85const char *name(struct dt_lib_module_t *self)
86{
87 return _("apply styles");
88}
89
90const char **views(dt_lib_module_t *self)
91{
92 // Goes in popup
93 static const char *v[] = {"special", NULL};
94 return v;
95}
96
98{
100}
101
103{
104 return 0;
105}
106
114
115static gboolean _get_node_for_name(GtkTreeModel *model, gboolean root, GtkTreeIter *iter, const gchar *parent_name)
116{
117 GtkTreeIter parent = *iter;
118
119 if(root)
120 {
121 // iter is null, we are at the top level
122 // if we have no nodes in this tree, let's create it now
123 if(!gtk_tree_model_get_iter_first(model, iter))
124 {
125 gtk_tree_store_append(GTK_TREE_STORE(model), iter, NULL);
126 return FALSE;
127 }
128 }
129 else
130 {
131 // if we have no children, create one, this is our node
132 if(!gtk_tree_model_iter_children(GTK_TREE_MODEL(model), iter, &parent))
133 {
134 gtk_tree_store_append(GTK_TREE_STORE(model), iter, &parent);
135 return FALSE;
136 }
137 }
138
139 // here we have iter to be on the right level, let's check if we can find parent_name
140 do
141 {
142 gchar *name;
143 gtk_tree_model_get(model, iter, DT_STYLES_COL_NAME, &name, -1);
144 const gboolean match = !g_strcmp0(name, parent_name);
145 dt_free(name);
146 if(match)
147 {
148 return TRUE;
149 }
150 }
151 while(gtk_tree_model_iter_next(model, iter));
152
153 // not found, create it under parent
154 gtk_tree_store_append(GTK_TREE_STORE(model), iter, root?NULL:&parent);
155
156 return FALSE;
157}
158
160{
161 /* clear current list */
162 GtkTreeIter iter;
163 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->tree));
164 g_object_ref(model);
165 gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree), NULL);
166 gtk_tree_store_clear(GTK_TREE_STORE(model));
167
168 GList *result = dt_styles_get_list(gtk_entry_get_text(d->entry));
169 if(result)
170 {
171 for(const GList *res_iter = result; res_iter; res_iter = g_list_next(res_iter))
172 {
173 dt_style_t *style = (dt_style_t *)res_iter->data;
174
175 gchar *items_string = (gchar *)dt_styles_get_item_list_as_string(style->name);
176 gchar *tooltip = NULL;
177
178 if(style->description && *style->description)
179 {
180 gchar *escaped_description = g_markup_escape_text(style->description, -1);
181 tooltip = g_strconcat("<b>", escaped_description, "</b>\n", items_string, NULL);
182 dt_free(escaped_description);
183 }
184 else
185 {
186 tooltip = g_strdup(items_string);
187 }
188
189 gchar **split = g_strsplit(style->name, "|", 0);
190 int k = 0;
191
192 while(split[k])
193 {
194 const gchar *s = split[k];
195 const gboolean node_found = _get_node_for_name(model, k==0, &iter, s);
196
197 if(!node_found)
198 {
199 if(split[k+1])
200 {
201 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, DT_STYLES_COL_NAME, s, -1);
202 }
203 else
204 {
205 // a leaf
206 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
208 }
209 }
210 k++;
211 }
212 g_strfreev(split);
213
214 dt_free(items_string);
216 }
217 g_list_free_full(result, dt_style_free);
218 result = NULL;
219 }
220
221 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(d->tree), DT_STYLES_COL_TOOLTIP);
222 gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree), model);
223 g_object_unref(model);
224}
225
226static void _styles_row_activated_callback(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col,
227 gpointer user_data)
228{
229 // This works on double click, so it's for single style
230 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
231
232 GtkTreeModel *model;
233 GtkTreeIter iter;
234 model = gtk_tree_view_get_model(d->tree);
235
236 if(!gtk_tree_model_get_iter(model, &iter, path)) return;
237
238 gchar *name;
239 gtk_tree_model_get(model, &iter, DT_STYLES_COL_FULLNAME, &name, -1);
240
241 GList *list = dt_act_on_get_images();
242 if(name)
243 {
244 dt_history_style_on_list(list, name, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
245 dt_free(name);
246 }
247 g_list_free(list);
248 list = NULL;
249}
250
251// get list of style names from selection
252// free returned list with g_list_free_full(list, dt_free_gpointer)
253GList* _get_selected_style_names(GList* selected_styles, GtkTreeModel *model)
254{
255 GtkTreeIter iter;
256 GList *style_names = NULL;
257 for (const GList *style = selected_styles; style; style = g_list_next(style))
258 {
259 GValue value = {0,};
260 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)style->data);
261 gtk_tree_model_get_value(model, &iter, DT_STYLES_COL_FULLNAME, &value);
262 if(G_VALUE_HOLDS_STRING(&value))
263 style_names = g_list_prepend(style_names, g_strdup(g_value_get_string(&value)));
264 g_value_unset(&value);
265 }
266 return g_list_reverse(style_names); // list was built in reverse order, so un-reverse it
267}
268
269static void apply_clicked(GtkWidget *w, gpointer user_data)
270{
271 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
272 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
273
274 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
275
276 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
277 GList *selected_styles = gtk_tree_selection_get_selected_rows(selection, &model);
278 GList *style_names = _get_selected_style_names(selected_styles, model);
279 g_list_free_full(selected_styles, (GDestroyNotify) gtk_tree_path_free);
280 selected_styles = NULL;
281
282 if(IS_NULL_PTR(style_names)) return;
283
284 GList *list = dt_act_on_get_images();
285
286 if(list) dt_multiple_styles_apply_to_list(style_names, list, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
287
288 g_list_free_full(style_names, dt_free_gpointer);
289 style_names = NULL;
290 g_list_free(list);
291 list = NULL;
292}
293
294static void create_clicked(GtkWidget *w, gpointer user_data)
295{
296 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
297
298 GList *list = dt_act_on_get_images();
300 g_list_free(list);
301 list = NULL;
303}
304
305static void edit_clicked(GtkWidget *w, gpointer user_data)
306{
307 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
308 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
309
310 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
311
312 GtkTreeIter iter;
313 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
314
315 GList *styles = gtk_tree_selection_get_selected_rows(selection, &model);
316 for (const GList *style = styles; style; style = g_list_next(style))
317 {
318 char *name = NULL;
319 GValue value = {0,};
320 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)style->data);
321 gtk_tree_model_get_value(model, &iter, DT_STYLES_COL_FULLNAME, &value);
322 if(G_VALUE_HOLDS_STRING(&value))
323 name = g_strdup(g_value_get_string(&value));
324 g_value_unset(&value);
325
326 if(name)
327 {
330 dt_free(name);
331 }
332 }
333 g_list_free_full (styles, (GDestroyNotify) gtk_tree_path_free);
334}
335
336gboolean _ask_before_delete_style(const gint style_cnt)
337{
338 gint res = GTK_RESPONSE_YES;
339
340 if(dt_conf_get_bool("plugins/lighttable/style/ask_before_delete_style"))
341 {
342 const GtkWidget *win = dt_gui_main_window();
343 GtkWidget *dialog = gtk_message_dialog_new
344 (GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
345 ngettext("do you really want to remove %d style?", "do you really want to remove %d styles?", style_cnt),
346 style_cnt);
347#ifdef GDK_WINDOWING_QUARTZ
349#endif
350
351 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("remove style?", "remove styles?", style_cnt));
352 res = gtk_dialog_run(GTK_DIALOG(dialog));
353 gtk_widget_destroy(dialog);
354 }
355
356 return res == GTK_RESPONSE_YES;
357}
358
359static void delete_clicked(GtkWidget *w, gpointer user_data)
360{
361 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
362
363 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
364
365 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
366
367 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
368 GList *selected_styles = gtk_tree_selection_get_selected_rows(selection, &model);
369 GList *style_names = _get_selected_style_names(selected_styles, model);
370 g_list_free_full(selected_styles, (GDestroyNotify) gtk_tree_path_free);
371 selected_styles = NULL;
372
373 if(IS_NULL_PTR(style_names)) return;
374
375 const gint select_cnt = g_list_length(style_names);
376 const gboolean single_raise = (select_cnt == 1);
377
378 const gboolean can_delete = _ask_before_delete_style(select_cnt);
379
380 if(can_delete)
381 {
383
384 for (const GList *style = style_names; style; style = g_list_next(style))
385 {
386 dt_styles_delete_by_name_adv((char*)style->data, single_raise);
387 }
388
389 if(!single_raise) {
390 // raise signal at the end of processing all styles if we have more than 1 to delete
391 // this also calls _gui_styles_update_view
393 }
395 }
396 g_list_free_full(style_names, dt_free_gpointer);
397 style_names = NULL;
398}
399
400static void export_clicked(GtkWidget *w, gpointer user_data)
401{
402 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
403
404 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
405
406 if(gtk_tree_selection_count_selected_rows(selection) == 0) return;
407
408 GtkTreeModel *model= gtk_tree_view_get_model(d->tree);
409 GList *selected_styles = gtk_tree_selection_get_selected_rows(selection, &model);
410 GList *style_names = _get_selected_style_names(selected_styles, model);
411 g_list_free_full(selected_styles, (GDestroyNotify) gtk_tree_path_free);
412 selected_styles = NULL;
413
414 if(IS_NULL_PTR(style_names)) return;
415
416 /* variables for overwrite dialog */
417 gint overwrite_check_button = 0;
418 gint overwrite = 0;
419
421 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
422 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
423 _("_save"), _("_cancel"));
424
425 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
426 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
427
428 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
429 {
430 char *filedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
431
432 for (const GList *style = style_names; style; style = g_list_next(style))
433 {
434 char stylename[520];
435
436 /* check if file exists before overwriting */
437 snprintf(stylename, sizeof(stylename), "%s/%s.dtstyle", filedir, (char*)style->data);
438
439 if(g_file_test(stylename, G_FILE_TEST_EXISTS) == TRUE)
440 {
441 /* do not run overwrite dialog */
442 if(overwrite_check_button == 1)
443 {
444 if(overwrite == 1)
445 {
446 // save style with overwrite
447 dt_styles_save_to_file((char*)style->data, filedir, TRUE);
448 }
449 else if(overwrite == 2)
450 {
451 continue;
452 }
453 else
454 {
455 break;
456 }
457 }
458 else
459 {
460 /* create and run dialog */
461 char overwrite_str[256];
462
463 gint overwrite_dialog_res = GTK_RESPONSE_ACCEPT;
464 gint overwrite_dialog_check_button_res = TRUE;
465
466 if(dt_conf_get_bool("plugins/lighttable/style/ask_before_delete_style"))
467 {
468 GtkWidget *dialog_overwrite_export = gtk_dialog_new_with_buttons(_("overwrite style?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
469 _("cancel"), GTK_RESPONSE_CANCEL,
470 _("skip"), GTK_RESPONSE_NONE,
471 _("overwrite"), GTK_RESPONSE_ACCEPT, NULL);
472
473 // contents for dialog
474 GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_overwrite_export));
475 sprintf(overwrite_str, _("style `%s' already exists.\ndo you want to overwrite existing style?\n"), (char*)style->data);
476 GtkWidget *label = gtk_label_new(overwrite_str);
477 GtkWidget *overwrite_dialog_check_button = gtk_check_button_new_with_label(_("apply this option to all existing styles"));
478
479 gtk_container_add(GTK_CONTAINER(content_area), label);
480 gtk_container_add(GTK_CONTAINER(content_area), overwrite_dialog_check_button);
481 gtk_widget_show_all(dialog_overwrite_export);
482
483 // disable check button and skip button when only one style is selected
484 if(g_list_is_singleton(style_names))
485 {
486 gtk_widget_set_sensitive(GTK_WIDGET(overwrite_dialog_check_button), FALSE);
487 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_overwrite_export), GTK_RESPONSE_NONE, FALSE);
488 }
489
490#ifdef GDK_WINDOWING_QUARTZ
491 dt_osx_disallow_fullscreen(dialog_overwrite_export);
492#endif
493
494 overwrite_dialog_res = gtk_dialog_run(GTK_DIALOG(dialog_overwrite_export));
495 overwrite_dialog_check_button_res = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overwrite_dialog_check_button));
496 gtk_widget_destroy(dialog_overwrite_export);
497 }
498
499 if(overwrite_dialog_res == GTK_RESPONSE_ACCEPT)
500 {
501 overwrite = 1;
502
503 /* do not run dialog on the next conflict when set to 1 */
504 if(overwrite_dialog_check_button_res == TRUE)
505 {
506 overwrite_check_button = 1;
507 }
508 else
509 {
510 overwrite_check_button = 0;
511 }
512 }
513 else if(overwrite_dialog_res == GTK_RESPONSE_NONE)
514 {
515 overwrite = 2;
516
517 /* do not run dialog on the next conflict when set to 1 */
518 if(overwrite_dialog_check_button_res == TRUE)
519 {
520 overwrite_check_button = 1;
521 }
522 else
523 {
524 overwrite_check_button = 0;
525 }
526 continue;
527 }
528 else
529 {
530 break;
531 }
532
533 dt_styles_save_to_file((char*)style->data, filedir, TRUE);
534 }
535 }
536 else
537 {
538 dt_styles_save_to_file((char*)style->data, filedir, FALSE);
539 }
540 dt_control_log(_("style %s was successfully exported"), (char*)style->data);
541 }
542 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
543 dt_free(filedir);
544 }
545 g_object_unref(filechooser);
546 g_list_free_full(style_names, dt_free_gpointer);
547 style_names = NULL;
548}
549
550static void import_clicked(GtkWidget *w, gpointer user_data)
551{
552 /* variables for overwrite dialog */
553 gint overwrite_check_button = 0;
554 gint overwrite = 0;
555
557 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
558 _("select style"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
559 _("_open"), _("_cancel"));
560
561 dt_conf_get_folder_to_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(filechooser));
562 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), TRUE);
563
564 GtkFileFilter *filter;
565 filter = GTK_FILE_FILTER(gtk_file_filter_new());
566 gtk_file_filter_add_pattern(filter, "*.dtstyle");
567 gtk_file_filter_add_pattern(filter, "*.DTSTYLE");
568 gtk_file_filter_set_name(filter, _("Ansel style files"));
569 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
570
571 filter = GTK_FILE_FILTER(gtk_file_filter_new());
572 gtk_file_filter_add_pattern(filter, "*");
573 gtk_file_filter_set_name(filter, _("all files"));
574
575 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
576
577 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
578 {
579 GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(filechooser));
580
581 for(const GSList *filename = filenames; filename; filename = g_slist_next(filename))
582 {
583 /* extract name from xml file */
584 gchar *bname = NULL;
585 xmlDoc *document = xmlReadFile((char*)filename->data, NULL, XML_PARSE_NOBLANKS);
586 xmlNode *root = NULL;
587 if(!IS_NULL_PTR(document))
588 root = xmlDocGetRootElement(document);
589
590 if(IS_NULL_PTR(document) || IS_NULL_PTR(root) || xmlStrcmp(root->name, BAD_CAST "darktable_style"))
591 {
593 "[styles] file %s is not a style file\n", (char*)filename->data);
594 if(document)
595 xmlFreeDoc(document);
596 continue;
597 }
598
599 for(xmlNode *node = root->children->children; node; node = node->next)
600 {
601 if(node->type == XML_ELEMENT_NODE)
602 {
603 if(strcmp((char*)node->name, "name") == 0)
604 {
605 bname = g_strdup((char*)xmlNodeGetContent(node));
606 break;
607 }
608 }
609 }
610
611 // xml doc is not necessary after this point
612 xmlFreeDoc(document);
613
614 if(IS_NULL_PTR(bname)){
616 "[styles] file %s is malformed style file\n", (char*)filename->data);
617 continue;
618 }
619
620 // check if style exists
621 if(dt_styles_exists(bname))
622 {
623 /* do not run overwrite dialog */
624 if(overwrite_check_button == 1)
625 {
626 if(overwrite == 1)
627 {
628 // remove style then import
630 dt_styles_import_from_file((char*)filename->data);
631 }
632 else if(overwrite == 2)
633 {
634 continue;
635 }
636 else
637 {
638 break;
639 }
640 }
641 else
642 {
643 /* create and run dialog */
644 char overwrite_str[256];
645
646 gint overwrite_dialog_res = GTK_RESPONSE_ACCEPT;
647 gint overwrite_dialog_check_button_res = TRUE;
648
649 // use security check/option
650 if(dt_conf_get_bool("plugins/lighttable/style/ask_before_delete_style"))
651 {
652 GtkWidget *dialog_overwrite_import = gtk_dialog_new_with_buttons(_("overwrite style?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
653 _("cancel"), GTK_RESPONSE_CANCEL,
654 _("skip"), GTK_RESPONSE_NONE,
655 _("overwrite"), GTK_RESPONSE_ACCEPT, NULL);
656
657 // contents for dialog
658 GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_overwrite_import));
659 sprintf(overwrite_str, _("style `%s' already exists.\ndo you want to overwrite existing style?\n"), (char*)filename->data);
660 GtkWidget *label = gtk_label_new(overwrite_str);
661 GtkWidget *overwrite_dialog_check_button = gtk_check_button_new_with_label(_("apply this option to all existing styles"));
662
663 gtk_container_add(GTK_CONTAINER(content_area), label);
664 gtk_container_add(GTK_CONTAINER(content_area), overwrite_dialog_check_button);
665 gtk_widget_show_all(dialog_overwrite_import);
666
667 // disable check button and skip button when dealing with one style
668 if(g_slist_length(filenames) == 1)
669 {
670 gtk_widget_set_sensitive(GTK_WIDGET(overwrite_dialog_check_button), FALSE);
671 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_overwrite_import), GTK_RESPONSE_NONE, FALSE);
672 }
673
674#ifdef GDK_WINDOWING_QUARTZ
675 dt_osx_disallow_fullscreen(dialog_overwrite_import);
676#endif
677
678 overwrite_dialog_res = gtk_dialog_run(GTK_DIALOG(dialog_overwrite_import));
679 overwrite_dialog_check_button_res = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overwrite_dialog_check_button));
680 gtk_widget_destroy(dialog_overwrite_import);
681 }
682
683 if(overwrite_dialog_res == GTK_RESPONSE_ACCEPT)
684 {
685 overwrite = 1;
686
687 /* do not run dialog on next conflict when set to 1 */
688 if(overwrite_dialog_check_button_res == TRUE)
689 {
690 overwrite_check_button = 1;
691 }
692 else
693 {
694 overwrite_check_button = 0;
695 }
696 }
697 else if(overwrite_dialog_res == GTK_RESPONSE_NONE)
698 {
699 overwrite = 2;
700
701
702 /* do not run dialog on next conflict when set to 1 */
703 if(overwrite_dialog_check_button_res == TRUE)
704 {
705 overwrite_check_button = 1;
706 }
707 else
708 {
709 overwrite_check_button = 0;
710 }
711 continue;
712 }
713 else
714 {
715 break;
716 }
717
719 dt_styles_import_from_file((char*)filename->data);
720 }
721 }
722 else
723 {
724 dt_styles_import_from_file((char*)filename->data);
725 }
726 dt_free(bname);
727 }
728 g_slist_free_full(filenames, dt_free_gpointer);
729
730 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
732 dt_conf_set_folder_from_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(filechooser));
733 }
734 g_object_unref(filechooser);
735}
736
737static gboolean entry_callback(GtkEntry *entry, gpointer user_data)
738{
739 _gui_styles_update_view(user_data);
740 return FALSE;
741}
742
743static gboolean entry_activated(GtkEntry *entry, gpointer user_data)
744{
745 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
746 const gchar *name = gtk_entry_get_text(d->entry);
747 if(name)
748 {
749 GList *imgs = dt_act_on_get_images();
750 dt_history_style_on_list(imgs, name, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
751 g_list_free(imgs);
752 imgs = NULL;
753 }
754
755 return FALSE;
756}
757
758static gboolean duplicate_callback(GtkEntry *entry, gpointer user_data)
759{
760 dt_lib_styles_t *d = (dt_lib_styles_t *)user_data;
761 dt_conf_set_bool("ui_last/styles_create_duplicate",
762 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->duplicate)));
763 return FALSE;
764}
765
766static void _update(dt_lib_module_t *self)
767{
770
771 const gboolean has_act_on = (dt_act_on_get_images_nb(TRUE, FALSE) > 0);
772
773 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree));
774 const gint sel_styles_cnt = gtk_tree_selection_count_selected_rows(selection);
775
776 gtk_widget_set_sensitive(GTK_WIDGET(d->create_button), has_act_on);
777 gtk_widget_set_sensitive(GTK_WIDGET(d->edit_button), sel_styles_cnt > 0);
778 gtk_widget_set_sensitive(GTK_WIDGET(d->delete_button), sel_styles_cnt > 0);
779
780 //import is ALWAYS enabled.
781 gtk_widget_set_sensitive(GTK_WIDGET(d->export_button), sel_styles_cnt > 0);
782
783 gtk_widget_set_sensitive(GTK_WIDGET(d->apply_button), has_act_on && sel_styles_cnt > 0);
784}
785
786static void _styles_changed_callback(gpointer instance, gpointer user_data)
787{
788 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
791 _update(self);
792}
793
794static void _image_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
795{
796 _update(self);
797}
798
799static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change,
800 dt_collection_properties_t changed_property, gpointer imgs, int next,
801 dt_lib_module_t *self)
802{
803 _update(self);
804}
805
806static void _mouse_over_image_callback(gpointer instance, dt_lib_module_t *self)
807{
809}
810
811static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
812{
813 _update((dt_lib_module_t *)data);
814}
815
817{
818 dt_lib_styles_t *d = (dt_lib_styles_t *)malloc(sizeof(dt_lib_styles_t));
819 self->data = (void *)d;
820 self->timeout_handle = 0;
821 d->edit_button = NULL;
822 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
823 GtkWidget *w;
824
825 /* tree */
826 d->tree = GTK_TREE_VIEW(gtk_tree_view_new());
827 gtk_tree_view_set_headers_visible(d->tree, FALSE);
828 GtkTreeStore *treestore = gtk_tree_store_new(DT_STYLES_NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
829 GtkTreeViewColumn *col = gtk_tree_view_column_new();
830 gtk_tree_view_append_column(GTK_TREE_VIEW(d->tree), col);
831 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
832 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
833 gtk_tree_view_column_pack_start(col, renderer, TRUE);
834 gtk_tree_view_column_add_attribute(col, renderer, "text", DT_STYLES_COL_NAME);
835
836 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree)), GTK_SELECTION_MULTIPLE);
837 gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree), GTK_TREE_MODEL(treestore));
838 g_object_unref(treestore);
839
840 gtk_widget_set_tooltip_text(GTK_WIDGET(d->tree), _("available styles,\ndoubleclick to apply"));
841 g_signal_connect(d->tree, "row-activated", G_CALLBACK(_styles_row_activated_callback), d);
842 g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->tree)), "changed", G_CALLBACK(_tree_selection_changed), self);
843
844 /* filter entry */
845 w = gtk_entry_new();
846 d->entry = GTK_ENTRY(w);
847 gtk_entry_set_placeholder_text(GTK_ENTRY(d->entry), _("filter style names"));
848 gtk_widget_set_tooltip_text(w, _("filter style names"));
849 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
850 g_signal_connect(d->entry, "changed", G_CALLBACK(entry_callback), d);
851 g_signal_connect(d->entry, "activate", G_CALLBACK(entry_activated), d);
852
853
854 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->entry), TRUE, TRUE, 0);
855 GtkWidget *style_tree_sw = dt_ui_scroll_wrap(GTK_WIDGET(d->tree), 250, "plugins/lighttable/style/windowheight",
857 gtk_box_pack_start(GTK_BOX(self->widget), style_tree_sw, FALSE, FALSE, 0);
858
859 d->duplicate = gtk_check_button_new_with_label(_("create duplicate"));
860 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->duplicate))), PANGO_ELLIPSIZE_START);
861 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->duplicate), TRUE, FALSE, 0);
862 g_signal_connect(d->duplicate, "toggled", G_CALLBACK(duplicate_callback), d);
863 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->duplicate),
864 dt_conf_get_bool("ui_last/styles_create_duplicate"));
865 gtk_widget_set_tooltip_text(d->duplicate, _("creates a duplicate of the image before applying style"));
866
867 GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
868 GtkWidget *hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
869 GtkWidget *hbox3 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
870 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox1), TRUE, FALSE, 0);
871 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox2), TRUE, FALSE, 0);
872 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox3), TRUE, FALSE, 0);
873
874 // create
875 d->create_button = dt_action_button_new(self, N_("create..."), create_clicked, d, _("create styles from history stack of selected images"), 0, 0);
876 gtk_box_pack_start(GTK_BOX(hbox1), d->create_button, TRUE, TRUE, 0);
877
878 // edit
879 d->edit_button = dt_action_button_new(self, N_("edit..."), edit_clicked, d, _("edit the selected styles in list above"), 0, 0);
880 gtk_box_pack_start(GTK_BOX(hbox1), d->edit_button, TRUE, TRUE, 0);
881
882 // delete
883 d->delete_button = dt_action_button_new(self, N_("remove"), delete_clicked, d, _("removes the selected styles in list above"), 0, 0);
884 gtk_box_pack_start(GTK_BOX(hbox1), d->delete_button, TRUE, TRUE, 0);
885
886 // import button
887 d->import_button = dt_action_button_new(self, N_("import..."), import_clicked, d, _("import styles from a style files"), 0, 0);
888 gtk_box_pack_start(GTK_BOX(hbox2), d->import_button, TRUE, TRUE, 0);
889
890 // export button
891 d->export_button = dt_action_button_new(self, N_("export..."), export_clicked, d, _("export the selected styles into a style files"), 0, 0);
892 gtk_box_pack_start(GTK_BOX(hbox2), d->export_button, TRUE, TRUE, 0);
893
894 // apply button
895 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);
896 gtk_box_pack_start(GTK_BOX(hbox3), d->apply_button, TRUE, TRUE, 0);
897
898 // add entry completion
899 GtkEntryCompletion *completion = gtk_entry_completion_new();
900 gtk_entry_completion_set_model(completion, gtk_tree_view_get_model(GTK_TREE_VIEW(d->tree)));
901 gtk_entry_completion_set_text_column(completion, 0);
902 gtk_entry_completion_set_inline_completion(completion, TRUE);
903 gtk_entry_set_completion(d->entry, completion);
904
905 /* update filtered list */
907
909
911 G_CALLBACK(_image_selection_changed_callback), self);
913 G_CALLBACK(_mouse_over_image_callback), self);
915 G_CALLBACK(_collection_updated_callback), self);
916
917 _update(self);
918}
919
932
934{
936
937 GList *all_styles = dt_styles_get_list("");
938
939 if(IS_NULL_PTR(all_styles))
940 {
942 return;
943 }
944
945 const gint styles_cnt = g_list_length(all_styles);
946 const gboolean can_delete = _ask_before_delete_style(styles_cnt);
947
948 if(can_delete)
949 {
950 for (const GList *result = all_styles; result; result = g_list_next(result))
951 {
952 dt_style_t *style = (dt_style_t *)result->data;
954 }
956 }
957 g_list_free_full(all_styles, dt_style_free);
958 all_styles = NULL;
960 _update(self);
961}
962
963
964// clang-format off
965// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
966// vim: shiftwidth=2 expandtab tabstop=2 cindent
967// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
968// clang-format on
int dt_act_on_get_images_nb(const gboolean only_visible, const gboolean force)
Definition act_on.c:53
GList * dt_act_on_get_images()
Definition act_on.c:38
GtkWidget * dt_gui_main_window(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
dt_collection_properties_t
Definition collection.h:120
dt_collection_change_t
Definition collection.h:160
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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:824
#define dt_database_start_transaction()
Definition database.h:290
#define dt_database_release_transaction()
Definition database.h:291
GtkWidget * view
the tree view
const int res
Definition dtpthread.h:351
#define g_list_is_singleton(list)
Definition glib_utils.h:33
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:310
const char * model
void dt_lib_cancel_postponed_update(dt_lib_module_t *mod)
Definition lib.c:1309
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:1336
void dt_lib_queue_postponed_update(dt_lib_module_t *mod, void(*update_fn)(dt_lib_module_t *self))
Definition lib.c:1297
@ DT_DEBUG_CONTROL
Definition logging.h:52
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
const char * name
Definition pdf.h:90
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:42
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_STYLE_CHANGED
This signal is raised when a style is added/deleted/changed
Definition signal.h:150
@ 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:62
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:130
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:125
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
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_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)
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
guint timeout_handle
Definition lib.h:89
GtkTreeView * tree
GtkWidget * duplicate
GtkWidget * apply_button
gchar * description
gchar * name
void dt_styles_create_from_list(const GList *list)
Definition styles_gui.c:31
Telling the user something happened.
#define DT_GUI_BOX_SPACING
@ DT_UI_CONTAINER_SIZE