1 /*
2     This file is part of darktable,
3     Copyright (C) 2010-2021 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <gdk/gdkkeysyms.h>
20 #include <strings.h>
21 
22 #include "bauhaus/bauhaus.h"
23 #include "common/darktable.h"
24 #include "common/debug.h"
25 #include "common/file_location.h"
26 #include "common/l10n.h"
27 #include "common/presets.h"
28 #include "control/control.h"
29 #include "develop/imageop.h"
30 #include "gui/accelerators.h"
31 #include "gui/draw.h"
32 #include "gui/gtk.h"
33 #include "gui/preferences.h"
34 #include "gui/presets.h"
35 #include "libs/lib.h"
36 #include "preferences_gen.h"
37 #ifdef USE_LUA
38 #include "lua/preferences.h"
39 #endif
40 #ifdef GDK_WINDOWING_QUARTZ
41 #include "osx/osx.h"
42 #endif
43 #define ICON_SIZE 13
44 
45 typedef struct dt_gui_accel_search_t
46 {
47   GtkWidget *tree, *search_box;
48   gchar *last_search_term;
49   int last_found_count, curr_found_count;
50 } dt_gui_accel_search_t;
51 
52 typedef struct dt_gui_themetweak_widgets_t
53 {
54   GtkWidget *apply_toggle, *save_button, *css_text_view;
55 } dt_gui_themetweak_widgets_t;
56 
57 // FIXME: this is copypasta from gui/presets.c. better put these somewhere so that all places can access the
58 // same data.
59 static const int dt_gui_presets_exposure_value_cnt = 24;
60 static const float dt_gui_presets_exposure_value[]
61     = { 0.,       1. / 8000, 1. / 4000, 1. / 2000, 1. / 1000, 1. / 1000, 1. / 500, 1. / 250,
62         1. / 125, 1. / 60,   1. / 30,   1. / 15,   1. / 15,   1. / 8,    1. / 4,   1. / 2,
63         1,        2,         4,         8,         15,        30,        60,       FLT_MAX };
64 static const char *dt_gui_presets_exposure_value_str[]
65     = { "0",     "1/8000", "1/4000", "1/2000", "1/1000", "1/1000", "1/500", "1/250",
66         "1/125", "1/60",   "1/30",   "1/15",   "1/15",   "1/8",    "1/4",   "1/2",
67         "1\"",   "2\"",    "4\"",    "8\"",    "15\"",   "30\"",   "60\"",  "+" };
68 static const int dt_gui_presets_aperture_value_cnt = 19;
69 static const float dt_gui_presets_aperture_value[]
70     = { 0,    0.5,  0.7,  1.0,  1.4,  2.0,  2.8,  4.0,   5.6,    8.0,
71         11.0, 16.0, 22.0, 32.0, 45.0, 64.0, 90.0, 128.0, FLT_MAX };
72 static const char *dt_gui_presets_aperture_value_str[]
73     = { "f/0",  "f/0.5", "f/0.7", "f/1.0", "f/1.4", "f/2",  "f/2.8", "f/4",   "f/5.6", "f/8",
74         "f/11", "f/16",  "f/22",  "f/32",  "f/45",  "f/64", "f/90",  "f/128", "f/+" };
75 
76 // Values for the accelerators/presets treeview
77 
78 enum
79 {
80   A_ACCEL_COLUMN,
81   A_BINDING_COLUMN,
82   A_TRANS_COLUMN,
83   A_N_COLUMNS
84 };
85 enum
86 {
87   P_ROWID_COLUMN,
88   P_OPERATION_COLUMN,
89   P_MODULE_COLUMN,
90   P_EDITABLE_COLUMN,
91   P_NAME_COLUMN,
92   P_MODEL_COLUMN,
93   P_MAKER_COLUMN,
94   P_LENS_COLUMN,
95   P_ISO_COLUMN,
96   P_EXPOSURE_COLUMN,
97   P_APERTURE_COLUMN,
98   P_FOCAL_LENGTH_COLUMN,
99   P_AUTOAPPLY_COLUMN,
100   P_N_COLUMNS
101 };
102 
103 static void init_tab_presets(GtkWidget *stack);
104 static void init_tab_accels(GtkWidget *stack, dt_gui_accel_search_t *search_data);
105 static gboolean accel_search(gpointer widget, gpointer data);
106 static void tree_insert_accel(gpointer accel_struct, gpointer model_link);
107 static void tree_insert_rec(GtkTreeStore *model, GtkTreeIter *parent, const gchar *accel_path,
108                             const gchar *translated_path, guint accel_key, GdkModifierType accel_mods);
109 static void path_to_accel(GtkTreeModel *model, GtkTreePath *path, gchar *str, size_t str_len);
110 static void update_accels_model(gpointer widget, gpointer data);
111 static void update_accels_model_rec(GtkTreeModel *model, GtkTreeIter *parent, gchar *path, size_t path_len);
112 static void delete_matching_accels(gpointer path, gpointer key_event);
113 static void import_export(GtkButton *button, gpointer data);
114 static void restore_defaults(GtkButton *button, gpointer data);
115 static gint compare_rows_accels(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data);
116 static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data);
117 static void import_preset(GtkButton *button, gpointer data);
118 static void export_preset(GtkButton *button, gpointer data);
119 
120 // Signal handlers
121 static void tree_row_activated_accels(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
122                                       gpointer data);
123 static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
124                                        gpointer data);
125 static void tree_selection_changed(GtkTreeSelection *selection, gpointer data);
126 static gboolean tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data);
127 static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data);
128 
129 static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module);
130 
131 static GtkWidget *_preferences_dialog;
132 
133 ///////////// gui theme selection
134 
load_themes_dir(const char * basedir)135 static void load_themes_dir(const char *basedir)
136 {
137   char *themes_dir = g_build_filename(basedir, "themes", NULL);
138   GDir *dir = g_dir_open(themes_dir, 0, NULL);
139   if(dir)
140   {
141     dt_print(DT_DEBUG_DEV, "adding themes directory: %s\n", themes_dir);
142 
143     const gchar *d_name;
144     while((d_name = g_dir_read_name(dir)))
145       darktable.themes = g_list_append(darktable.themes, g_strdup(d_name));
146     g_dir_close(dir);
147   }
148   g_free(themes_dir);
149 }
150 
load_themes(void)151 static void load_themes(void)
152 {
153   // Clear theme list...
154   g_list_free_full(darktable.themes, g_free);
155   darktable.themes = NULL;
156 
157   // check themes dirs
158   gchar configdir[PATH_MAX] = { 0 };
159   gchar datadir[PATH_MAX] = { 0 };
160   dt_loc_get_datadir(datadir, sizeof(datadir));
161   dt_loc_get_user_config_dir(configdir, sizeof(configdir));
162 
163   load_themes_dir(datadir);
164   load_themes_dir(configdir);
165 }
166 
reload_ui_last_theme(void)167 static void reload_ui_last_theme(void)
168 {
169   gchar *theme = dt_conf_get_string("ui_last/theme");
170   dt_gui_load_theme(theme);
171   g_free(theme);
172   dt_bauhaus_load_theme();
173 }
174 
theme_callback(GtkWidget * widget,gpointer user_data)175 static void theme_callback(GtkWidget *widget, gpointer user_data)
176 {
177   const int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
178   gchar *theme = g_list_nth(darktable.themes, selected)->data;
179   gchar *i = g_strrstr(theme, ".");
180   if(i) *i = '\0';
181   dt_gui_load_theme(theme);
182   dt_bauhaus_load_theme();
183 }
184 
usercss_callback(GtkWidget * widget,gpointer user_data)185 static void usercss_callback(GtkWidget *widget, gpointer user_data)
186 {
187   dt_conf_set_bool("themes/usercss", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
188   reload_ui_last_theme();
189 }
190 
font_size_changed_callback(GtkWidget * widget,gpointer user_data)191 static void font_size_changed_callback(GtkWidget *widget, gpointer user_data)
192 {
193   dt_conf_set_float("font_size", gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
194   reload_ui_last_theme();
195 }
196 
use_performance_callback(GtkWidget * widget,gpointer user_data)197 static void use_performance_callback(GtkWidget *widget, gpointer user_data)
198 {
199   dt_conf_set_bool("ui/performance", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
200   dt_configure_ppd_dpi(darktable.gui);
201 }
202 
dpi_scaling_changed_callback(GtkWidget * widget,gpointer user_data)203 static void dpi_scaling_changed_callback(GtkWidget *widget, gpointer user_data)
204 {
205   float dpi = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
206   if(dpi > 0.0) dpi = fmax(64, dpi); // else <= 0 -> use system default
207   dt_conf_set_float("screen_dpi_overwrite", dpi);
208   restart_required = TRUE;
209   dt_configure_ppd_dpi(darktable.gui);
210   dt_bauhaus_load_theme();
211 }
212 
use_sys_font_callback(GtkWidget * widget,gpointer user_data)213 static void use_sys_font_callback(GtkWidget *widget, gpointer user_data)
214 {
215   dt_conf_set_bool("use_system_font", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
216   if(dt_conf_get_bool("use_system_font"))
217     gtk_widget_set_state_flags(GTK_WIDGET(user_data), GTK_STATE_FLAG_INSENSITIVE, TRUE);
218   else
219     gtk_widget_set_state_flags(GTK_WIDGET(user_data), GTK_STATE_FLAG_NORMAL, TRUE);
220 
221   reload_ui_last_theme();
222 }
223 
save_usercss(GtkTextBuffer * buffer)224 static void save_usercss(GtkTextBuffer *buffer)
225 {
226   //get file locations
227   char usercsspath[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
228   dt_loc_get_user_config_dir(configdir, sizeof(configdir));
229   g_snprintf(usercsspath, sizeof(usercsspath), "%s/user.css", configdir);
230 
231   //get the text
232   GtkTextIter start, end;
233   gtk_text_buffer_get_start_iter(buffer, &start);
234   gtk_text_buffer_get_end_iter(buffer, &end);
235   const gchar *usercsscontent = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
236 
237   //write to file
238   GError *error = NULL;
239   if(!g_file_set_contents(usercsspath, usercsscontent, -1, &error))
240   {
241     fprintf(stderr, "%s: error saving css to %s: %s\n", G_STRFUNC, usercsspath, error->message);
242     g_clear_error(&error);
243   }
244 
245 }
246 
save_usercss_callback(GtkWidget * widget,gpointer user_data)247 static void save_usercss_callback(GtkWidget *widget, gpointer user_data)
248 {
249   dt_gui_themetweak_widgets_t *tw = (dt_gui_themetweak_widgets_t *)user_data;
250   GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->css_text_view));
251 
252   save_usercss(buffer);
253 
254   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tw->apply_toggle)))
255   {
256     //reload the theme
257     reload_ui_last_theme();
258   }
259   else
260   {
261     //toggle the apply button, which will also reload the theme
262     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tw->apply_toggle), TRUE);
263   }
264 }
265 
usercss_dialog_callback(GtkDialog * dialog,gint response_id,gpointer user_data)266 static void usercss_dialog_callback(GtkDialog *dialog, gint response_id, gpointer user_data)
267 {
268   //just save the latest css but don't reload the theme
269   dt_gui_themetweak_widgets_t *tw = (dt_gui_themetweak_widgets_t *)user_data;
270   GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->css_text_view));
271   save_usercss(buffer);
272 }
273 
274 ///////////// gui language and theme selection
275 
language_callback(GtkWidget * widget,gpointer user_data)276 static void language_callback(GtkWidget *widget, gpointer user_data)
277 {
278   int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
279   dt_l10n_language_t *language = (dt_l10n_language_t *)g_list_nth_data(darktable.l10n->languages, selected);
280   if(darktable.l10n->sys_default == selected)
281   {
282     dt_conf_set_string("ui_last/gui_language", "");
283     darktable.l10n->selected = darktable.l10n->sys_default;
284   }
285   else
286   {
287     dt_conf_set_string("ui_last/gui_language", language->code);
288     darktable.l10n->selected = selected;
289   }
290   restart_required = TRUE;
291 }
292 
reset_language_widget(GtkWidget * label,GdkEventButton * event,GtkWidget * widget)293 static gboolean reset_language_widget(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
294 {
295   if(event->type == GDK_2BUTTON_PRESS)
296   {
297     gtk_combo_box_set_active(GTK_COMBO_BOX(widget), darktable.l10n->sys_default);
298     return TRUE;
299   }
300   return FALSE;
301 }
302 
init_tab_general(GtkWidget * dialog,GtkWidget * stack,dt_gui_themetweak_widgets_t * tw)303 static void init_tab_general(GtkWidget *dialog, GtkWidget *stack, dt_gui_themetweak_widgets_t *tw)
304 {
305 
306   GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
307   GtkWidget *grid = gtk_grid_new();
308   gtk_grid_set_row_spacing(GTK_GRID(grid), DT_PIXEL_APPLY_DPI(3));
309   gtk_grid_set_column_spacing(GTK_GRID(grid), DT_PIXEL_APPLY_DPI(5));
310   gtk_widget_set_valign(grid, GTK_ALIGN_START);
311   int line = 0;
312 
313   gtk_box_pack_start(GTK_BOX(container), grid, FALSE, FALSE, 0);
314 
315   gtk_stack_add_titled(GTK_STACK(stack), container, _("general"), _("general"));
316 
317   // language
318 
319   GtkWidget *label = gtk_label_new(_("interface language"));
320   gtk_widget_set_halign(label, GTK_ALIGN_START);
321   GtkWidget *labelev = gtk_event_box_new();
322   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
323   gtk_container_add(GTK_CONTAINER(labelev), label);
324   GtkWidget *widget = gtk_combo_box_text_new();
325 
326   for(GList *iter = darktable.l10n->languages; iter; iter = g_list_next(iter))
327   {
328     const char *name = dt_l10n_get_name(iter->data);
329     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), name);
330   }
331 
332   gtk_combo_box_set_active(GTK_COMBO_BOX(widget), darktable.l10n->selected);
333   g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(language_callback), 0);
334   gtk_widget_set_tooltip_text(labelev,  _("double click to reset to the system language"));
335   gtk_event_box_set_visible_window(GTK_EVENT_BOX(labelev), FALSE);
336   gtk_widget_set_tooltip_text(widget, _("set the language of the user interface. the system default is marked with an * (needs a restart)"));
337   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
338   gtk_grid_attach_next_to(GTK_GRID(grid), widget, labelev, GTK_POS_RIGHT, 1, 1);
339   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_language_widget), (gpointer)widget);
340 
341   // theme
342 
343   load_themes();
344 
345   label = gtk_label_new(_("theme"));
346   gtk_widget_set_halign(label, GTK_ALIGN_START);
347   widget = gtk_combo_box_text_new();
348   labelev = gtk_event_box_new();
349   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
350   gtk_container_add(GTK_CONTAINER(labelev), label);
351   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
352   gtk_grid_attach_next_to(GTK_GRID(grid), widget, labelev, GTK_POS_RIGHT, 1, 1);
353 
354   // read all themes
355   char *theme_name = dt_conf_get_string("ui_last/theme");
356   int selected = 0;
357   int k = 0;
358   for(GList *iter = darktable.themes; iter; iter = g_list_next(iter))
359   {
360     gchar *name = g_strdup((gchar*)(iter->data));
361     // remove extension
362     gchar *i = g_strrstr(name, ".");
363     if(i) *i = '\0';
364     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), name);
365     if(!g_strcmp0(name, theme_name)) selected = k;
366     k++;
367   }
368   g_free(theme_name);
369 
370   gtk_combo_box_set_active(GTK_COMBO_BOX(widget), selected);
371 
372   g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(theme_callback), 0);
373   gtk_widget_set_tooltip_text(widget, _("set the theme for the user interface"));
374 
375   GtkWidget *useperfmode = gtk_check_button_new();
376   label = gtk_label_new(_("prefer performance over quality"));
377   gtk_widget_set_halign(label, GTK_ALIGN_START);
378   labelev = gtk_event_box_new();
379   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
380   gtk_container_add(GTK_CONTAINER(labelev), label);
381   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
382   gtk_grid_attach_next_to(GTK_GRID(grid), useperfmode, labelev, GTK_POS_RIGHT, 1, 1);
383   gtk_widget_set_tooltip_text(useperfmode,
384                               _("if switched on, thumbnails and previews are rendered at lower quality but 4 times faster"));
385   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(useperfmode), dt_conf_get_bool("ui/performance"));
386   g_signal_connect(G_OBJECT(useperfmode), "toggled", G_CALLBACK(use_performance_callback), 0);
387 
388   //Font size check and spin buttons
389   GtkWidget *usesysfont = gtk_check_button_new();
390   GtkWidget *fontsize = gtk_spin_button_new_with_range(5.0f, 30.0f, 0.2f);
391 
392   //checkbox to use system font size
393   if(dt_conf_get_bool("use_system_font"))
394     gtk_widget_set_state_flags(fontsize, GTK_STATE_FLAG_INSENSITIVE, TRUE);
395   else
396     gtk_widget_set_state_flags(fontsize, GTK_STATE_FLAG_NORMAL, TRUE);
397 
398   label = gtk_label_new(_("use system font size"));
399   gtk_widget_set_halign(label, GTK_ALIGN_START);
400   labelev = gtk_event_box_new();
401   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
402   gtk_container_add(GTK_CONTAINER(labelev), label);
403   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
404   gtk_grid_attach_next_to(GTK_GRID(grid), usesysfont, labelev, GTK_POS_RIGHT, 1, 1);
405   gtk_widget_set_tooltip_text(usesysfont, _("use system font size"));
406   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(usesysfont), dt_conf_get_bool("use_system_font"));
407   g_signal_connect(G_OBJECT(usesysfont), "toggled", G_CALLBACK(use_sys_font_callback), (gpointer)fontsize);
408 
409 
410   //font size selector
411   if(dt_conf_get_float("font_size") < 5.0f || dt_conf_get_float("font_size") > 20.0f)
412     dt_conf_set_float("font_size", 12.0f);
413 
414   label = gtk_label_new(_("font size in points"));
415   gtk_widget_set_halign(label, GTK_ALIGN_START);
416   labelev = gtk_event_box_new();
417   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
418   gtk_container_add(GTK_CONTAINER(labelev), label);
419   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
420   gtk_grid_attach_next_to(GTK_GRID(grid), fontsize, labelev, GTK_POS_RIGHT, 1, 1);
421   gtk_widget_set_tooltip_text(fontsize, _("font size in points"));
422   gtk_spin_button_set_value(GTK_SPIN_BUTTON(fontsize), dt_conf_get_float("font_size"));
423   g_signal_connect(G_OBJECT(fontsize), "value_changed", G_CALLBACK(font_size_changed_callback), 0);
424 
425   GtkWidget *screen_dpi_overwrite = gtk_spin_button_new_with_range(-1.0f, 360, 1.f);
426   label = gtk_label_new(_("GUI controls and text DPI"));
427   gtk_widget_set_halign(label, GTK_ALIGN_START);
428   labelev = gtk_event_box_new();
429   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
430   gtk_container_add(GTK_CONTAINER(labelev), label);
431   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
432   gtk_grid_attach_next_to(GTK_GRID(grid), screen_dpi_overwrite, labelev, GTK_POS_RIGHT, 1, 1);
433   gtk_widget_set_tooltip_text(screen_dpi_overwrite, _("adjust the global GUI resolution to rescale controls, buttons, labels, etc.\n"
434                                                       "increase for a magnified GUI, decrease to fit more content in window.\n"
435                                                       "set to -1 to use the system-defined global resolution.\n"
436                                                       "default is 96 DPI on most systems.\n"
437                                                       "(needs a restart)."));
438   gtk_spin_button_set_value(GTK_SPIN_BUTTON(screen_dpi_overwrite), dt_conf_get_float("screen_dpi_overwrite"));
439   g_signal_connect(G_OBJECT(screen_dpi_overwrite), "value_changed", G_CALLBACK(dpi_scaling_changed_callback), 0);
440 
441   //checkbox to allow user to modify theme with user.css
442   label = gtk_label_new(_("modify selected theme with CSS tweaks below"));
443   gtk_widget_set_halign(label, GTK_ALIGN_START);
444   tw->apply_toggle = gtk_check_button_new();
445   labelev = gtk_event_box_new();
446   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
447   gtk_container_add(GTK_CONTAINER(labelev), label);
448   gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
449   gtk_grid_attach_next_to(GTK_GRID(grid), tw->apply_toggle, labelev, GTK_POS_RIGHT, 1, 1);
450   gtk_widget_set_tooltip_text(tw->apply_toggle, _("modify theme with CSS keyed below (saved to user.css)"));
451   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tw->apply_toggle), dt_conf_get_bool("themes/usercss"));
452   g_signal_connect(G_OBJECT(tw->apply_toggle), "toggled", G_CALLBACK(usercss_callback), 0);
453 
454   //scrollable textarea with save button to allow user to directly modify user.css file
455   GtkWidget *usercssbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
456   gtk_box_pack_start(GTK_BOX(container), usercssbox, TRUE, TRUE, 0);
457   gtk_widget_set_name(usercssbox, "usercss_box");
458 
459   GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
460   tw->css_text_view= gtk_text_view_new_with_buffer(buffer);
461   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tw->css_text_view), GTK_WRAP_WORD);
462   gtk_widget_set_hexpand(tw->css_text_view, TRUE);
463   gtk_widget_set_halign(tw->css_text_view, GTK_ALIGN_FILL);
464 
465   GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
466   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
467   gtk_container_add(GTK_CONTAINER(scroll), tw->css_text_view);
468   gtk_box_pack_start(GTK_BOX(usercssbox), scroll, TRUE, TRUE, 0);
469 
470   tw->save_button = gtk_button_new_with_label(C_("usercss", "save CSS and apply"));
471   g_signal_connect(G_OBJECT(tw->save_button), "clicked", G_CALLBACK(save_usercss_callback), tw);
472   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(usercss_dialog_callback), tw);
473   GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
474   gtk_box_pack_end(GTK_BOX(hbox), tw->save_button, FALSE, TRUE, 0);
475   gtk_box_pack_start(GTK_BOX(usercssbox), hbox, FALSE, FALSE, 0);
476   gtk_widget_set_tooltip_text(tw->save_button, _("click to save and apply the CSS tweaks entered in this editor"));
477 
478   //set textarea text from file or default
479   char usercsspath[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
480   dt_loc_get_user_config_dir(configdir, sizeof(configdir));
481   g_snprintf(usercsspath, sizeof(usercsspath), "%s/user.css", configdir);
482 
483   if(g_file_test(usercsspath, G_FILE_TEST_EXISTS))
484   {
485     gchar *usercsscontent = NULL;
486     //load file into buffer
487     if(g_file_get_contents(usercsspath, &usercsscontent, NULL, NULL))
488     {
489       gtk_text_buffer_set_text(buffer, usercsscontent, -1);
490     }
491     else
492     {
493       //load default text with some pointers
494       gtk_text_buffer_set_text(buffer, _("/* ERROR Loading user.css */"), -1);
495     }
496     g_free(usercsscontent);
497   }
498   else
499   {
500     //load default text
501     gtk_text_buffer_set_text(buffer, _("/* Enter CSS theme tweaks here */\n\n"), -1);
502   }
503 
504 }
505 
506 ///////////// end of gui and theme language selection
507 
508 #if 0
509 // FIXME! this makes some systems hang forever. I don't reproduce.
510 gboolean preferences_window_deleted(GtkWidget *widget, GdkEvent *event, gpointer data)
511 {
512   // redraw the whole UI in case sizes have changed
513   gtk_widget_queue_resize(dt_ui_center(darktable.gui->ui));
514   gtk_widget_queue_resize(dt_ui_main_window(darktable.gui->ui));
515 
516   gtk_widget_queue_draw(dt_ui_main_window(darktable.gui->ui));
517   gtk_widget_queue_draw(dt_ui_center(darktable.gui->ui));
518 
519   gtk_widget_hide(widget);
520   return TRUE;
521 }
522 #endif
523 
_resize_dialog(GtkWidget * widget)524 static void _resize_dialog(GtkWidget *widget)
525 {
526   GtkAllocation allocation;
527   gtk_widget_get_allocation(widget, &allocation);
528   dt_conf_set_int("ui_last/preferences_dialog_width", allocation.width);
529   dt_conf_set_int("ui_last/preferences_dialog_height", allocation.height);
530 }
531 
dt_gui_preferences_show()532 void dt_gui_preferences_show()
533 {
534   GtkWindow *win = GTK_WINDOW(dt_ui_main_window(darktable.gui->ui));
535   _preferences_dialog = gtk_dialog_new_with_buttons(_("darktable preferences"), win,
536                                                     GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
537                                                     NULL, NULL);
538 #if 0
539   // FIXME! this makes some systems hang forever. I don't reproduce.
540   g_signal_connect(G_OBJECT(_preferences_dialog), "delete-event", G_CALLBACK(preferences_window_deleted), NULL);
541 #endif
542 
543   gtk_window_set_default_size(GTK_WINDOW(_preferences_dialog),
544                               dt_conf_get_int("ui_last/preferences_dialog_width"),
545                               dt_conf_get_int("ui_last/preferences_dialog_height"));
546   g_signal_connect(G_OBJECT(_preferences_dialog), "check-resize", G_CALLBACK(_resize_dialog), NULL);
547 #ifdef GDK_WINDOWING_QUARTZ
548   dt_osx_disallow_fullscreen(_preferences_dialog);
549 #endif
550   gtk_window_set_position(GTK_WINDOW(_preferences_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
551   gtk_widget_set_name(_preferences_dialog, "preferences_notebook");
552 
553   //grab the content area of the dialog
554   GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(_preferences_dialog));
555   gtk_widget_set_name(content, "preferences_content");
556   gtk_container_set_border_width(GTK_CONTAINER(content), 0);
557 
558   //place a box in the content area
559   GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
560   gtk_widget_set_name(box, "preferences_box");
561   gtk_container_set_border_width(GTK_CONTAINER(box), 0);
562   gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
563 
564   //create stack and sidebar and pack into the box
565   GtkWidget *stack = gtk_stack_new();
566   GtkWidget *stacksidebar = gtk_stack_sidebar_new();
567   gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(stacksidebar), GTK_STACK(stack));
568   gtk_box_pack_start(GTK_BOX(box), stacksidebar, FALSE, FALSE, 0);
569   gtk_box_pack_start(GTK_BOX(box), stack, TRUE, TRUE, 0);
570 
571   // Make sure remap mode is off initially
572   darktable.control->accel_remap_str = NULL;
573   darktable.control->accel_remap_path = NULL;
574 
575   dt_gui_accel_search_t *search_data = (dt_gui_accel_search_t *)malloc(sizeof(dt_gui_accel_search_t));
576   dt_gui_themetweak_widgets_t *tweak_widgets = (dt_gui_themetweak_widgets_t *)malloc(sizeof(dt_gui_themetweak_widgets_t));
577 
578   restart_required = FALSE;
579 
580   //setup tabs
581   init_tab_general(_preferences_dialog, stack, tweak_widgets);
582   init_tab_import(_preferences_dialog, stack);
583   init_tab_lighttable(_preferences_dialog, stack);
584   init_tab_darkroom(_preferences_dialog, stack);
585   init_tab_other_views(_preferences_dialog, stack);
586   init_tab_processing(_preferences_dialog, stack);
587   init_tab_security(_preferences_dialog, stack);
588   init_tab_cpugpu(_preferences_dialog, stack);
589   init_tab_storage(_preferences_dialog, stack);
590   init_tab_misc(_preferences_dialog, stack);
591   init_tab_accels(stack, search_data);
592   init_tab_presets(stack);
593 
594   //open in the appropriate tab if currently in darkroom or lighttable view
595   const gchar *current_view = darktable.view_manager->current_view->name(darktable.view_manager->current_view);
596   if(strcmp(current_view, "darkroom") == 0 || strcmp(current_view, "lighttable") == 0)
597   {
598     gtk_stack_set_visible_child(GTK_STACK(stack), gtk_stack_get_child_by_name(GTK_STACK(stack), current_view));
599   }
600 
601 #ifdef USE_LUA
602   GtkGrid* lua_grid = init_tab_lua(_preferences_dialog, stack);
603 #endif
604   gtk_widget_show_all(_preferences_dialog);
605   (void)gtk_dialog_run(GTK_DIALOG(_preferences_dialog));
606 
607 #ifdef USE_LUA
608   destroy_tab_lua(lua_grid);
609 #endif
610 
611   g_free(search_data->last_search_term);
612   free(search_data);
613   free(tweak_widgets);
614   gtk_widget_destroy(_preferences_dialog);
615 
616   if(restart_required)
617     dt_control_log(_("darktable needs to be restarted for settings to take effect"));
618 
619   // Cleaning up any memory still allocated for remapping
620   if(darktable.control->accel_remap_path)
621   {
622     gtk_tree_path_free(darktable.control->accel_remap_path);
623     darktable.control->accel_remap_path = NULL;
624   }
625 
626   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE);
627 }
628 
cairo_destroy_from_pixbuf(guchar * pixels,gpointer data)629 static void cairo_destroy_from_pixbuf(guchar *pixels, gpointer data)
630 {
631   cairo_destroy((cairo_t *)data);
632 }
633 
_module_can_autoapply(const gchar * operation)634 static gboolean _module_can_autoapply(const gchar *operation)
635 {
636   for (const GList * lib_modules = darktable.lib->plugins; lib_modules; lib_modules = g_list_next(lib_modules))
637   {
638     dt_lib_module_t *lib_module = (dt_lib_module_t *)lib_modules->data;
639     if(!strcmp(lib_module->plugin_name, operation))
640     {
641       return dt_lib_presets_can_autoapply(lib_module);
642     }
643   }
644   return TRUE;
645 }
646 
tree_insert_presets(GtkTreeStore * tree_model)647 static void tree_insert_presets(GtkTreeStore *tree_model)
648 {
649   GtkTreeIter iter, parent;
650   sqlite3_stmt *stmt;
651   gchar *last_module = NULL;
652 
653   // Create a GdkPixbuf with a cairo drawing.
654   // lock
655   cairo_surface_t *lock_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DT_PIXEL_APPLY_DPI(ICON_SIZE),
656                                                          DT_PIXEL_APPLY_DPI(ICON_SIZE));
657   cairo_t *lock_cr = cairo_create(lock_cst);
658   cairo_set_source_rgb(lock_cr, 0.7, 0.7, 0.7);
659   dtgtk_cairo_paint_lock(lock_cr, 0, 0, DT_PIXEL_APPLY_DPI(ICON_SIZE), DT_PIXEL_APPLY_DPI(ICON_SIZE), 0, NULL);
660   cairo_surface_flush(lock_cst);
661   guchar *data = cairo_image_surface_get_data(lock_cst);
662   dt_draw_cairo_to_gdk_pixbuf(data, DT_PIXEL_APPLY_DPI(ICON_SIZE), DT_PIXEL_APPLY_DPI(ICON_SIZE));
663   GdkPixbuf *lock_pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
664                                                     DT_PIXEL_APPLY_DPI(ICON_SIZE), DT_PIXEL_APPLY_DPI(ICON_SIZE),
665                                                     cairo_image_surface_get_stride(lock_cst),
666                                                     cairo_destroy_from_pixbuf, lock_cr);
667 
668   // check mark
669   cairo_surface_t *check_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DT_PIXEL_APPLY_DPI(ICON_SIZE),
670                                                           DT_PIXEL_APPLY_DPI(ICON_SIZE));
671   cairo_t *check_cr = cairo_create(check_cst);
672   cairo_set_source_rgb(check_cr, 0.7, 0.7, 0.7);
673   dtgtk_cairo_paint_check_mark(check_cr, 0, 0, DT_PIXEL_APPLY_DPI(ICON_SIZE), DT_PIXEL_APPLY_DPI(ICON_SIZE), 0, NULL);
674   cairo_surface_flush(check_cst);
675   data = cairo_image_surface_get_data(check_cst);
676   dt_draw_cairo_to_gdk_pixbuf(data, DT_PIXEL_APPLY_DPI(ICON_SIZE), DT_PIXEL_APPLY_DPI(ICON_SIZE));
677   GdkPixbuf *check_pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
678                                                      DT_PIXEL_APPLY_DPI(ICON_SIZE), DT_PIXEL_APPLY_DPI(ICON_SIZE),
679                                                      cairo_image_surface_get_stride(check_cst),
680                                                      cairo_destroy_from_pixbuf, check_cr);
681 
682   DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
683                               "SELECT rowid, name, operation, autoapply, model, maker, lens, iso_min, "
684                               "iso_max, exposure_min, exposure_max, aperture_min, aperture_max, "
685                               "focal_length_min, focal_length_max, writeprotect FROM data.presets ORDER BY "
686                               "operation, name",
687                               -1, &stmt, NULL);
688   while(sqlite3_step(stmt) == SQLITE_ROW)
689   {
690     const gint rowid = sqlite3_column_int(stmt, 0);
691     const gchar *name = (gchar *)sqlite3_column_text(stmt, 1);
692     const gchar *operation = (gchar *)sqlite3_column_text(stmt, 2);
693     const gboolean autoapply = (sqlite3_column_int(stmt, 3) == 0 ? FALSE : TRUE);
694     const gchar *model = (gchar *)sqlite3_column_text(stmt, 4);
695     const gchar *maker = (gchar *)sqlite3_column_text(stmt, 5);
696     const gchar *lens = (gchar *)sqlite3_column_text(stmt, 6);
697     const float iso_min = sqlite3_column_double(stmt, 7);
698     const float iso_max = sqlite3_column_double(stmt, 8);
699     const float exposure_min = sqlite3_column_double(stmt, 9);
700     const float exposure_max = sqlite3_column_double(stmt, 10);
701     const float aperture_min = sqlite3_column_double(stmt, 11);
702     const float aperture_max = sqlite3_column_double(stmt, 12);
703     const int focal_length_min = sqlite3_column_double(stmt, 13);
704     const int focal_length_max = sqlite3_column_double(stmt, 14);
705     const gboolean writeprotect = (sqlite3_column_int(stmt, 15) == 0 ? FALSE : TRUE);
706 
707     gchar *iso = NULL, *exposure = NULL, *aperture = NULL, *focal_length = NULL, *smaker = NULL, *smodel = NULL, *slens = NULL;
708     int min, max;
709 
710     gchar *module = g_strdup(dt_iop_get_localized_name(operation));
711     if(module == NULL) module = g_strdup(dt_lib_get_localized_name(operation));
712     if(module == NULL) module = g_strdup(operation);
713 
714     if(!_module_can_autoapply(operation))
715     {
716       iso = g_strdup("");
717       exposure = g_strdup("");
718       aperture = g_strdup("");
719       focal_length = g_strdup("");
720       smaker = g_strdup("");
721       smodel = g_strdup("");
722       slens = g_strdup("");
723     }
724     else
725     {
726       smaker = g_strdup(maker);
727       smodel = g_strdup(model);
728       slens = g_strdup(lens);
729 
730       if(iso_min == 0.0 && iso_max == FLT_MAX)
731         iso = g_strdup("%");
732       else
733         iso = g_strdup_printf("%zu – %zu", (size_t)iso_min, (size_t)iso_max);
734 
735       for(min = 0; min < dt_gui_presets_exposure_value_cnt && exposure_min > dt_gui_presets_exposure_value[min]; min++)
736         ;
737       for(max = 0; max < dt_gui_presets_exposure_value_cnt && exposure_max > dt_gui_presets_exposure_value[max]; max++)
738         ;
739       if(min == 0 && max == dt_gui_presets_exposure_value_cnt - 1)
740         exposure = g_strdup("%");
741       else
742         exposure = g_strdup_printf("%s – %s", dt_gui_presets_exposure_value_str[min],
743                                    dt_gui_presets_exposure_value_str[max]);
744 
745       for(min = 0; min < dt_gui_presets_aperture_value_cnt && aperture_min > dt_gui_presets_aperture_value[min]; min++)
746         ;
747       for(max = 0; max < dt_gui_presets_aperture_value_cnt && aperture_max > dt_gui_presets_aperture_value[max]; max++)
748         ;
749       if(min == 0 && max == dt_gui_presets_aperture_value_cnt - 1)
750         aperture = g_strdup("%");
751       else
752         aperture = g_strdup_printf("%s – %s", dt_gui_presets_aperture_value_str[min],
753                                    dt_gui_presets_aperture_value_str[max]);
754 
755       if(focal_length_min == 0.0 && focal_length_max == 1000.0)
756         focal_length = g_strdup("%");
757       else
758         focal_length = g_strdup_printf("%d – %d", focal_length_min, focal_length_max);
759     }
760 
761     if(g_strcmp0(last_module, operation) != 0)
762     {
763       gtk_tree_store_insert_with_values(tree_model, &iter, NULL, -1,
764                          P_ROWID_COLUMN, 0, P_OPERATION_COLUMN, "", P_MODULE_COLUMN,
765                          _(module), P_EDITABLE_COLUMN, NULL, P_NAME_COLUMN, "", P_MODEL_COLUMN, "",
766                          P_MAKER_COLUMN, "", P_LENS_COLUMN, "", P_ISO_COLUMN, "", P_EXPOSURE_COLUMN, "",
767                          P_APERTURE_COLUMN, "", P_FOCAL_LENGTH_COLUMN, "", P_AUTOAPPLY_COLUMN, NULL, -1);
768       g_free(last_module);
769       last_module = g_strdup(operation);
770       parent = iter;
771     }
772 
773     gtk_tree_store_insert_with_values(tree_model, &iter, &parent, -1,
774                        P_ROWID_COLUMN, rowid, P_OPERATION_COLUMN, operation,
775                        P_MODULE_COLUMN, "", P_EDITABLE_COLUMN, writeprotect ? lock_pixbuf : NULL,
776                        P_NAME_COLUMN, name, P_MODEL_COLUMN, smodel, P_MAKER_COLUMN, smaker, P_LENS_COLUMN, slens,
777                        P_ISO_COLUMN, iso, P_EXPOSURE_COLUMN, exposure, P_APERTURE_COLUMN, aperture,
778                        P_FOCAL_LENGTH_COLUMN, focal_length, P_AUTOAPPLY_COLUMN,
779                        autoapply ? check_pixbuf : NULL, -1);
780 
781     g_free(focal_length);
782     g_free(aperture);
783     g_free(exposure);
784     g_free(iso);
785     g_free(module);
786     g_free(smaker);
787     g_free(smodel);
788     g_free(slens);
789   }
790   g_free(last_module);
791   sqlite3_finalize(stmt);
792 
793   g_object_unref(lock_pixbuf);
794   cairo_surface_destroy(lock_cst);
795   g_object_unref(check_pixbuf);
796   cairo_surface_destroy(check_cst);
797 }
798 
init_tab_presets(GtkWidget * stack)799 static void init_tab_presets(GtkWidget *stack)
800 {
801   GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
802   GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
803   GtkWidget *tree = gtk_tree_view_new();
804   GtkTreeStore *model = gtk_tree_store_new(
805       P_N_COLUMNS, G_TYPE_INT /*rowid*/, G_TYPE_STRING /*operation*/, G_TYPE_STRING /*module*/,
806       GDK_TYPE_PIXBUF /*editable*/, G_TYPE_STRING /*name*/, G_TYPE_STRING /*model*/, G_TYPE_STRING /*maker*/,
807       G_TYPE_STRING /*lens*/, G_TYPE_STRING /*iso*/, G_TYPE_STRING /*exposure*/, G_TYPE_STRING /*aperture*/,
808       G_TYPE_STRING /*focal length*/, GDK_TYPE_PIXBUF /*auto*/);
809   GtkCellRenderer *renderer;
810   GtkTreeViewColumn *column;
811 
812   // Adding the outer container
813   gtk_stack_add_titled(GTK_STACK(stack), container, _("presets"), _("presets"));
814 
815   tree_insert_presets(model);
816 
817   // Setting a custom sort functions so expandable groups rise to the top
818   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), P_MODULE_COLUMN, GTK_SORT_ASCENDING);
819   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), P_MODULE_COLUMN, compare_rows_presets, NULL, NULL);
820 
821   // Setting up the cell renderers
822   renderer = gtk_cell_renderer_text_new();
823   column = gtk_tree_view_column_new_with_attributes(_("module"), renderer, "text", P_MODULE_COLUMN, NULL);
824   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
825 
826   renderer = gtk_cell_renderer_pixbuf_new();
827   column = gtk_tree_view_column_new_with_attributes("", renderer, "pixbuf", P_EDITABLE_COLUMN, NULL);
828   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
829 
830   renderer = gtk_cell_renderer_text_new();
831   column = gtk_tree_view_column_new_with_attributes(_("name"), renderer, "text", P_NAME_COLUMN, NULL);
832   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
833 
834   renderer = gtk_cell_renderer_text_new();
835   column = gtk_tree_view_column_new_with_attributes(_("model"), renderer, "text", P_MODEL_COLUMN, NULL);
836   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
837 
838   renderer = gtk_cell_renderer_text_new();
839   column = gtk_tree_view_column_new_with_attributes(_("maker"), renderer, "text", P_MAKER_COLUMN, NULL);
840   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
841 
842   renderer = gtk_cell_renderer_text_new();
843   column = gtk_tree_view_column_new_with_attributes(_("lens"), renderer, "text", P_LENS_COLUMN, NULL);
844   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
845 
846   renderer = gtk_cell_renderer_text_new();
847   column = gtk_tree_view_column_new_with_attributes(_("ISO"), renderer, "text", P_ISO_COLUMN, NULL);
848   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
849 
850   renderer = gtk_cell_renderer_text_new();
851   column = gtk_tree_view_column_new_with_attributes(_("exposure"), renderer, "text", P_EXPOSURE_COLUMN, NULL);
852   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
853 
854   renderer = gtk_cell_renderer_text_new();
855   column = gtk_tree_view_column_new_with_attributes(_("aperture"), renderer, "text", P_APERTURE_COLUMN, NULL);
856   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
857 
858   renderer = gtk_cell_renderer_text_new();
859   column = gtk_tree_view_column_new_with_attributes(_("focal length"), renderer, "text",
860                                                     P_FOCAL_LENGTH_COLUMN, NULL);
861   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
862 
863   renderer = gtk_cell_renderer_pixbuf_new();
864   column = gtk_tree_view_column_new_with_attributes(_("auto"), renderer, "pixbuf", P_AUTOAPPLY_COLUMN, NULL);
865   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
866 
867   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
868   gtk_box_pack_start(GTK_BOX(container), scroll, TRUE, TRUE, 0);
869 
870   // Adding the import/export buttons
871   GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
872   gtk_widget_set_name(hbox, "preset_controls");
873 
874   GtkWidget *button = gtk_button_new_with_label(C_("preferences", "import..."));
875   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
876   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(import_preset), (gpointer)model);
877 
878   button = gtk_button_new_with_label(C_("preferences", "export..."));
879   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
880   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(export_preset), (gpointer)model);
881 
882   gtk_box_pack_start(GTK_BOX(container), hbox, FALSE, FALSE, 0);
883 
884   // Attaching treeview signals
885 
886   // row-activated either expands/collapses a row or activates editing
887   g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(tree_row_activated_presets), NULL);
888 
889   // A keypress may delete preset
890   g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK(tree_key_press_presets), (gpointer)model);
891 
892   // Setting up the search functionality
893   gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), P_NAME_COLUMN);
894   gtk_tree_view_set_enable_search(GTK_TREE_VIEW(tree), TRUE);
895 
896   // Attaching the model to the treeview
897   gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(model));
898 
899   // Adding the treeview to its containers
900   gtk_container_add(GTK_CONTAINER(scroll), tree);
901   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
902 
903   g_object_unref(G_OBJECT(model));
904 }
905 
init_tab_accels(GtkWidget * stack,dt_gui_accel_search_t * search_data)906 static void init_tab_accels(GtkWidget *stack, dt_gui_accel_search_t *search_data)
907 {
908   GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
909   GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
910   GtkWidget *tree = gtk_tree_view_new();
911   GtkWidget *button, *searchentry;
912   GtkWidget *hbox;
913   GtkTreeStore *model = gtk_tree_store_new(A_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
914   GtkCellRenderer *renderer;
915   GtkTreeViewColumn *column;
916 
917   // Adding the outer container
918   gtk_stack_add_titled(GTK_STACK(stack), container, _("shortcuts"), _("shortcuts"));
919 
920   // Building the accelerator tree
921   g_list_foreach(darktable.control->accelerator_list, tree_insert_accel, (gpointer)model);
922 
923   // Setting a custom sort functions so expandable groups rise to the top
924   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), A_TRANS_COLUMN, GTK_SORT_ASCENDING);
925   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), A_TRANS_COLUMN, compare_rows_accels, NULL, NULL);
926 
927   // Setting up the cell renderers
928   renderer = gtk_cell_renderer_text_new();
929   column = gtk_tree_view_column_new_with_attributes(_("shortcut"), renderer, "text", A_TRANS_COLUMN, NULL);
930   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
931 
932   renderer = gtk_cell_renderer_text_new();
933   column = gtk_tree_view_column_new_with_attributes(_("binding"), renderer, "text", A_BINDING_COLUMN, NULL);
934   gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
935 
936   // Attaching treeview signals
937 
938   // row-activated either expands/collapses a row or activates remapping
939   g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(tree_row_activated_accels), NULL);
940 
941   // A selection change will cancel a currently active remapping
942   g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree))), "changed",
943                    G_CALLBACK(tree_selection_changed), NULL);
944 
945   // A keypress may remap an accel or delete one
946   g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK(tree_key_press), (gpointer)model);
947 
948   // Attaching the model to the treeview
949   gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(model));
950 
951   // Adding the treeview to its containers
952   gtk_container_add(GTK_CONTAINER(scroll), tree);
953   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
954   gtk_box_pack_start(GTK_BOX(container), scroll, TRUE, TRUE, 0);
955 
956   // Adding toolbar at bottom of treeview
957   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
958   gtk_widget_set_name(hbox, "shortcut_controls");
959 
960   // Adding search box
961   searchentry = gtk_entry_new();
962   g_signal_connect(G_OBJECT(searchentry), "activate", G_CALLBACK(accel_search), (gpointer)search_data);
963 
964   gtk_box_pack_start(GTK_BOX(hbox), searchentry, FALSE, TRUE, 10);
965 
966   // Adding the search button
967   button = gtk_button_new_with_label(C_("preferences", "search"));
968   gtk_widget_set_tooltip_text(GTK_WIDGET(button), _("click or press enter to search\nclick or press enter again to cycle through results"));
969   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
970   search_data->tree = tree;
971   search_data->search_box = searchentry;
972   search_data->last_search_term = NULL;
973   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(accel_search), (gpointer)search_data);
974 
975   // Adding the restore defaults button
976   button = gtk_button_new_with_label(C_("preferences", "default"));
977   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
978   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(restore_defaults), NULL);
979   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(update_accels_model), (gpointer)model);
980 
981   // Adding the import/export buttons
982 
983   button = gtk_button_new_with_label(C_("preferences", "import..."));
984   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
985   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(import_export), (gpointer)0);
986   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(update_accels_model), (gpointer)model);
987 
988   button = gtk_button_new_with_label(_("export..."));
989   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
990   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(import_export), (gpointer)1);
991 
992   gtk_box_pack_start(GTK_BOX(container), hbox, FALSE, FALSE, 0);
993 
994   g_object_unref(G_OBJECT(model));
995 }
996 
tree_insert_accel(gpointer accel_struct,gpointer model_link)997 static void tree_insert_accel(gpointer accel_struct, gpointer model_link)
998 {
999   GtkTreeStore *model = (GtkTreeStore *)model_link;
1000   dt_accel_t *accel = (dt_accel_t *)accel_struct;
1001   GtkAccelKey key;
1002 
1003   // Getting the first significant parts of the paths
1004   const char *accel_path = accel->path;
1005   const char *translated_path = accel->translated_path;
1006 
1007   /* if prefixed lets forward pointer */
1008   if(!strncmp(accel_path, "<Darktable>", strlen("<Darktable>")))
1009   {
1010     accel_path += strlen("<Darktable>") + 1;
1011     translated_path += strlen("<Darktable>") + 1;
1012   }
1013 
1014   // Getting the accelerator keys
1015   gtk_accel_map_lookup_entry(accel->path, &key);
1016 
1017   /* lets recurse path */
1018   tree_insert_rec(model, NULL, accel_path, translated_path, key.accel_key, key.accel_mods);
1019 }
1020 
tree_insert_rec(GtkTreeStore * model,GtkTreeIter * parent,const gchar * accel_path,const gchar * translated_path,guint accel_key,GdkModifierType accel_mods)1021 static void tree_insert_rec(GtkTreeStore *model, GtkTreeIter *parent, const gchar *accel_path,
1022                             const gchar *translated_path, guint accel_key, GdkModifierType accel_mods)
1023 {
1024   gboolean found = FALSE;
1025   gchar *val_str;
1026   GtkTreeIter iter;
1027 
1028   /* if we are on end of path lets bail out of recursive insert */
1029   if(*accel_path == 0) return;
1030 
1031   /* check if we are on a leaf or a branch  */
1032   const gchar *end = strchr(accel_path, '/');
1033   const gchar *trans_end = strchr(translated_path, '/');
1034   if(!end || !trans_end)
1035   {
1036     gchar *translated_path_slashed = g_strdelimit(g_strdup(translated_path), "`", '/');
1037 
1038     /* we are on a leaf lets add */
1039     gchar *name = gtk_accelerator_get_label(accel_key, accel_mods);
1040     gtk_tree_store_insert_with_values(model, &iter, parent, -1,
1041                                       A_ACCEL_COLUMN, accel_path,
1042                                       A_BINDING_COLUMN, g_dpgettext2("gtk30", "keyboard label", name),
1043                                       A_TRANS_COLUMN, translated_path_slashed, -1);
1044     g_free(name);
1045     g_free(translated_path_slashed);
1046   }
1047   else
1048   {
1049     gchar *trans_node = g_strndup(translated_path, trans_end - translated_path);
1050     gchar *trans_scan = trans_node;
1051     while((trans_scan = strchr(trans_scan, '`')))
1052     {
1053       *(trans_scan) = '/';
1054       if(end) end = strchr(++end, '/');
1055     }
1056 
1057     // safeguard against broken translations
1058     if(!end)
1059     {
1060       fprintf(stderr, "error: translation mismatch: `%s' vs. `%s'\n", accel_path, trans_node);
1061       g_free(trans_node);
1062       return;
1063     }
1064 
1065 
1066     gchar *node = g_strndup(accel_path, end - accel_path);
1067 
1068     /* search the tree if we already have a sibling with node name */
1069     int siblings = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), parent);
1070     for(int i = 0; i < siblings; i++)
1071     {
1072       gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(model), &iter, parent, i);
1073       gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, A_ACCEL_COLUMN, &val_str, -1);
1074 
1075       /* do we match current sibling */
1076       if(!strcmp(val_str, node)) found = TRUE;
1077 
1078       g_free(val_str);
1079 
1080       /* if we found a matching node let's break out */
1081       if(found) break;
1082     }
1083 
1084     /* if not found let's add a branch */
1085     if(!found)
1086     {
1087       gtk_tree_store_insert_with_values(model, &iter, parent, -1,
1088                                         A_ACCEL_COLUMN, node,
1089                                         A_BINDING_COLUMN, "",
1090                                         A_TRANS_COLUMN, trans_node, -1);
1091     }
1092 
1093     /* recurse further down the path */
1094     tree_insert_rec(model, &iter, accel_path + strlen(node) + 1, translated_path + strlen(trans_node) + 1,
1095                     accel_key, accel_mods);
1096 
1097     /* free up data */
1098     g_free(node);
1099     g_free(trans_node);
1100   }
1101 }
1102 
path_to_accel(GtkTreeModel * model,GtkTreePath * path,gchar * str,size_t str_len)1103 static void path_to_accel(GtkTreeModel *model, GtkTreePath *path, gchar *str, size_t str_len)
1104 {
1105   gint depth;
1106   gint *indices;
1107   GtkTreeIter parent;
1108   GtkTreeIter child;
1109   gint i;
1110   gchar *data_str;
1111 
1112   // Start out with the base <Darktable>
1113   g_strlcpy(str, "<Darktable>", str_len);
1114 
1115   // For each index in the path, append a '/' and that section of the path
1116   depth = gtk_tree_path_get_depth(path);
1117   indices = gtk_tree_path_get_indices(path);
1118   for(i = 0; i < depth; i++)
1119   {
1120     g_strlcat(str, "/", str_len);
1121     gtk_tree_model_iter_nth_child(model, &child, i == 0 ? NULL : &parent, indices[i]);
1122     gtk_tree_model_get(model, &child, A_ACCEL_COLUMN, &data_str, -1);
1123     g_strlcat(str, data_str, str_len);
1124     g_free(data_str);
1125     parent = child;
1126   }
1127 }
1128 
update_accels_model(gpointer widget,gpointer data)1129 static void update_accels_model(gpointer widget, gpointer data)
1130 {
1131   GtkTreeModel *model = (GtkTreeModel *)data;
1132   GtkTreeIter iter;
1133   gchar path[256];
1134   gchar *end;
1135   gint i;
1136 
1137   g_strlcpy(path, "<Darktable>", sizeof(path));
1138   end = path + strlen(path);
1139 
1140   for(i = 0; i < gtk_tree_model_iter_n_children(model, NULL); i++)
1141   {
1142     gtk_tree_model_iter_nth_child(model, &iter, NULL, i);
1143     update_accels_model_rec(model, &iter, path, sizeof(path));
1144     *end = '\0'; // Trimming the string back to the base for the next iteration
1145   }
1146 }
1147 
accel_search_children(dt_gui_accel_search_t * search_data,GtkTreeIter * parent)1148 gboolean accel_search_children(dt_gui_accel_search_t *search_data, GtkTreeIter *parent)
1149 {
1150   GtkTreeView *tv = GTK_TREE_VIEW(search_data->tree);
1151   GtkTreeModel *tvmodel = gtk_tree_view_get_model(tv);
1152   const gchar *search_term = gtk_entry_get_text(GTK_ENTRY(search_data->search_box));
1153 
1154   gchar *row_data;
1155   GtkTreeIter iter;
1156 
1157   //check the current item for a match
1158   gtk_tree_model_get(tvmodel, parent, A_TRANS_COLUMN, &row_data, -1);
1159 
1160   GtkTreePath *childpath = gtk_tree_model_get_path(tvmodel, parent);
1161 
1162   const gboolean match = strstr(row_data, search_term) != NULL;
1163   g_free(row_data);
1164   if(match)
1165   {
1166     search_data->curr_found_count++;
1167     if(search_data->curr_found_count > search_data->last_found_count)
1168     {
1169       gtk_tree_view_expand_to_path(tv, childpath);
1170       gtk_tree_view_set_cursor(tv, childpath, gtk_tree_view_get_column(tv, A_TRANS_COLUMN), FALSE);
1171       gtk_tree_path_free(childpath);
1172       search_data->last_found_count++;
1173       return TRUE;
1174     }
1175   }
1176   gtk_tree_path_free(childpath);
1177 
1178   if(gtk_tree_model_iter_has_child(tvmodel, parent))
1179   {
1180     //match not found then call again for each child, each time exiting if matched
1181     const int siblings = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(tvmodel), parent);
1182     for(int i = 0; i < siblings; i++)
1183     {
1184       gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(tvmodel), &iter, parent, i);
1185       if(accel_search_children(search_data, &iter))
1186         return TRUE;
1187     }
1188   }
1189 
1190   return FALSE;
1191 }
1192 
accel_search(gpointer widget,gpointer data)1193 static gboolean accel_search(gpointer widget, gpointer data)
1194 {
1195   dt_gui_accel_search_t *search_data = (dt_gui_accel_search_t *)data;
1196   GtkTreeView *tv = GTK_TREE_VIEW(search_data->tree);
1197   GtkTreeModel *tvmodel = gtk_tree_view_get_model(tv);
1198   const gchar *search_term = gtk_entry_get_text(GTK_ENTRY(search_data->search_box));
1199   if(!search_data->last_search_term || strcmp(search_data->last_search_term, search_term) != 0)
1200   {
1201     g_free(search_data->last_search_term);
1202     search_data->last_search_term = g_strdup(search_term);
1203     search_data->last_found_count = 0;
1204   }
1205   search_data->curr_found_count = 0;
1206   GtkTreeIter childiter;
1207 
1208   gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv));
1209 
1210   const int siblings = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(tvmodel), NULL);
1211   for(int i = 0; i < siblings; i++)
1212   {
1213     gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(tvmodel), &childiter, NULL, i);
1214     if(accel_search_children(search_data, &childiter))
1215       return TRUE;
1216   }
1217   search_data->last_found_count = 0;
1218   return FALSE;
1219 }
1220 
update_accels_model_rec(GtkTreeModel * model,GtkTreeIter * parent,gchar * path,size_t path_len)1221 static void update_accels_model_rec(GtkTreeModel *model, GtkTreeIter *parent, gchar *path, size_t path_len)
1222 {
1223   GtkAccelKey key;
1224   GtkTreeIter iter;
1225   gchar *str_data;
1226 
1227   // First concatenating this part of the key
1228   g_strlcat(path, "/", path_len);
1229   gtk_tree_model_get(model, parent, A_ACCEL_COLUMN, &str_data, -1);
1230   g_strlcat(path, str_data, path_len);
1231   g_free(str_data);
1232 
1233   if(gtk_tree_model_iter_has_child(model, parent))
1234   {
1235     // Branch node, carry on with recursion
1236     gchar *end = path + strlen(path);
1237 
1238     for(gint i = 0; i < gtk_tree_model_iter_n_children(model, parent); i++)
1239     {
1240       gtk_tree_model_iter_nth_child(model, &iter, parent, i);
1241       update_accels_model_rec(model, &iter, path, path_len);
1242       *end = '\0';
1243     }
1244   }
1245   else
1246   {
1247     // Leaf node, update the text
1248 
1249     gtk_accel_map_lookup_entry(path, &key);
1250     gchar *name = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
1251     gtk_tree_store_set(GTK_TREE_STORE(model), parent, A_BINDING_COLUMN, name, -1);
1252     g_free(name);
1253   }
1254 }
1255 
delete_matching_accels(gpointer current,gpointer mapped)1256 static void delete_matching_accels(gpointer current, gpointer mapped)
1257 {
1258   const dt_accel_t *current_accel = (dt_accel_t *)current;
1259   const dt_accel_t *mapped_accel = (dt_accel_t *)mapped;
1260   GtkAccelKey current_key;
1261   GtkAccelKey mapped_key;
1262 
1263   // Make sure we're not deleting the key we just remapped
1264   if(!strcmp(current_accel->path, mapped_accel->path)) return;
1265 
1266   // Finding the relevant keyboard shortcuts
1267   gtk_accel_map_lookup_entry(current_accel->path, &current_key);
1268   gtk_accel_map_lookup_entry(mapped_accel->path, &mapped_key);
1269 
1270   if(current_key.accel_key == mapped_key.accel_key                 // Key code matches
1271      && current_key.accel_mods == mapped_key.accel_mods            // Key state matches
1272      && !(current_accel->local && mapped_accel->local              // Not both local to
1273           && strcmp(current_accel->module, mapped_accel->module))
1274      && (current_accel->views & mapped_accel->views) != 0) // diff mods
1275     gtk_accel_map_change_entry(current_accel->path, 0, 0, TRUE);
1276 }
1277 
_accelcmp(gconstpointer a,gconstpointer b)1278 static gint _accelcmp(gconstpointer a, gconstpointer b)
1279 {
1280   return (gint)(strcmp(((dt_accel_t *)a)->path, ((dt_accel_t *)b)->path));
1281 }
1282 
1283 // TODO: remember which sections were collapsed/expanded and where the view was scrolled to and restore that
1284 // after editing is done
1285 //      Alternative: change edit_preset_response to not clear+refill the tree, but to update the single row
1286 //      which changed.
tree_row_activated_presets(GtkTreeView * tree,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)1287 static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
1288                                        gpointer data)
1289 {
1290   GtkTreeIter iter;
1291   GtkTreeModel *model = gtk_tree_view_get_model(tree);
1292 
1293   gtk_tree_model_get_iter(model, &iter, path);
1294 
1295   if(gtk_tree_model_iter_has_child(model, &iter))
1296   {
1297     // For branch nodes, toggle expansion on activation
1298     if(gtk_tree_view_row_expanded(tree, path))
1299       gtk_tree_view_collapse_row(tree, path);
1300     else
1301       gtk_tree_view_expand_row(tree, path, FALSE);
1302   }
1303   else
1304   {
1305     // For leaf nodes, open editing window if the preset is not writeprotected
1306     gint rowid;
1307     gchar *name, *operation;
1308     GdkPixbuf *editable;
1309     gtk_tree_model_get(model, &iter, P_ROWID_COLUMN, &rowid, P_NAME_COLUMN, &name, P_OPERATION_COLUMN,
1310                        &operation, P_EDITABLE_COLUMN, &editable, -1);
1311     if(editable == NULL)
1312       edit_preset(tree, rowid, name, operation);
1313     else
1314       g_object_unref(editable);
1315     g_free(name);
1316     g_free(operation);
1317   }
1318 }
1319 
tree_row_activated_accels(GtkTreeView * tree,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)1320 static void tree_row_activated_accels(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
1321                                       gpointer data)
1322 {
1323   GtkTreeIter iter;
1324   GtkTreeModel *model = gtk_tree_view_get_model(tree);
1325 
1326   static gchar accel_path[256];
1327 
1328   gtk_tree_model_get_iter(model, &iter, path);
1329 
1330   if(gtk_tree_model_iter_has_child(model, &iter))
1331   {
1332     // For branch nodes, toggle expansion on activation
1333     if(gtk_tree_view_row_expanded(tree, path))
1334       gtk_tree_view_collapse_row(tree, path);
1335     else
1336       gtk_tree_view_expand_row(tree, path, FALSE);
1337   }
1338   else
1339   {
1340     // For leaf nodes, enter remapping mode
1341 
1342     // Assembling the full accelerator path
1343     path_to_accel(model, path, accel_path, sizeof(accel_path));
1344 
1345     // Setting the notification text
1346     gtk_tree_store_set(GTK_TREE_STORE(model), &iter, A_BINDING_COLUMN, _("press key combination to remap..."),
1347                        -1);
1348 
1349     // Activating remapping
1350     darktable.control->accel_remap_str = accel_path;
1351     darktable.control->accel_remap_path = gtk_tree_path_copy(path);
1352   }
1353 }
1354 
tree_selection_changed(GtkTreeSelection * selection,gpointer data)1355 static void tree_selection_changed(GtkTreeSelection *selection, gpointer data)
1356 {
1357   GtkTreeModel *model;
1358   GtkTreeIter iter;
1359 
1360   GtkAccelKey key;
1361 
1362   // If remapping is currently activated, it needs to be deactivated
1363   if(!darktable.control->accel_remap_str) return;
1364 
1365   model = gtk_tree_view_get_model(gtk_tree_selection_get_tree_view(selection));
1366   gtk_tree_model_get_iter(model, &iter, darktable.control->accel_remap_path);
1367 
1368   // Restoring the A_BINDING_COLUMN text
1369   gtk_accel_map_lookup_entry(darktable.control->accel_remap_str, &key);
1370   gchar *name = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
1371   gtk_tree_store_set(GTK_TREE_STORE(model), &iter, A_BINDING_COLUMN, name, -1);
1372   g_free(name);
1373 
1374   // Cleaning up the darktable.gui info
1375   darktable.control->accel_remap_str = NULL;
1376   gtk_tree_path_free(darktable.control->accel_remap_path);
1377   darktable.control->accel_remap_path = NULL;
1378 }
1379 
tree_key_press(GtkWidget * widget,GdkEventKey * event,gpointer data)1380 static gboolean tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
1381 {
1382   GtkTreeModel *model = (GtkTreeModel *)data;
1383   GtkTreeIter iter;
1384   GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1385   GtkTreePath *path;
1386   dt_accel_t query;
1387 
1388   gchar accel[256];
1389   gchar datadir[PATH_MAX] = { 0 };
1390   gchar accelpath[PATH_MAX] = { 0 };
1391 
1392   // We can just ignore mod key presses outright
1393   if(event->is_modifier) return FALSE;
1394 
1395   dt_loc_get_user_config_dir(datadir, sizeof(datadir));
1396   snprintf(accelpath, sizeof(accelpath), "%s/keyboardrc", datadir);
1397 
1398   // Otherwise, determine whether we're in remap mode or not
1399   if(darktable.control->accel_remap_str)
1400   {
1401     const guint event_mods = dt_gui_translated_key_state(event);
1402 
1403     // First locate the accel list entry
1404     g_strlcpy(query.path, darktable.control->accel_remap_str, sizeof(query.path));
1405     GList *remapped = g_list_find_custom(darktable.control->accelerator_list, (gpointer)&query, _accelcmp);
1406     const dt_accel_t *accel_current = (dt_accel_t *)remapped->data;
1407 
1408     // let's search for conflicts
1409     dt_accel_t *accel_conflict = NULL;
1410     for(const GList *l = darktable.control->accelerator_list; l; l = g_list_next(l))
1411     {
1412       dt_accel_t *a = (dt_accel_t *)l->data;
1413       GtkAccelKey key;
1414       if (a != accel_current && gtk_accel_map_lookup_entry(a->path, &key))
1415       {
1416         if (key.accel_key == gdk_keyval_to_lower(event->keyval) &&
1417             key.accel_mods == event_mods &&
1418             !(a->local && accel_current->local && strcmp(a->module, accel_current->module)) &&
1419             (a->views & accel_current->views) != 0)
1420         {
1421           accel_conflict = a;
1422           break;
1423         }
1424       }
1425     }
1426 
1427     if(!accel_conflict)
1428     {
1429       // no conflict
1430       gtk_accel_map_change_entry(darktable.control->accel_remap_str, gdk_keyval_to_lower(event->keyval),
1431                                  event_mods, TRUE);
1432     }
1433     else
1434     {
1435       // we ask for confirmation
1436       gchar *accel_txt
1437           = gtk_accelerator_get_label(gdk_keyval_to_lower(event->keyval), event_mods);
1438       gchar txt[512] = { 0 };
1439       if(g_str_has_prefix(accel_conflict->translated_path, "<Darktable>/"))
1440         g_strlcpy(txt, accel_conflict->translated_path + 12, sizeof(txt));
1441       else
1442         g_strlcpy(txt, accel_conflict->translated_path, sizeof(txt));
1443       GtkWidget *dialog = gtk_message_dialog_new(
1444         GTK_WINDOW(_preferences_dialog), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1445           _("%s accel is already mapped to\n%s.\ndo you want to replace it ?"), accel_txt, txt);
1446       g_free(accel_txt);
1447 #ifdef GDK_WINDOWING_QUARTZ
1448       dt_osx_disallow_fullscreen(dialog);
1449 #endif
1450 
1451       gtk_window_set_title(GTK_WINDOW(dialog), _("accel conflict"));
1452       gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1453       gtk_widget_destroy(dialog);
1454       if(res == GTK_RESPONSE_YES)
1455       {
1456         // Change the accel map entry
1457         if(gtk_accel_map_change_entry(darktable.control->accel_remap_str, gdk_keyval_to_lower(event->keyval),
1458                                       event_mods, TRUE))
1459         {
1460           // Then remove conflicts
1461           g_list_foreach(darktable.control->accelerator_list, delete_matching_accels, (gpointer)(accel_current));
1462         }
1463       }
1464     }
1465 
1466     // Then update the text in the A_BINDING_COLUMN of each row
1467     update_accels_model(NULL, model);
1468 
1469     // Finally clear the remap state
1470     darktable.control->accel_remap_str = NULL;
1471     gtk_tree_path_free(darktable.control->accel_remap_path);
1472     darktable.control->accel_remap_path = NULL;
1473 
1474     // Save the changed keybindings
1475     gtk_accel_map_save(accelpath);
1476 
1477     return TRUE;
1478   }
1479   else if(event->keyval == GDK_KEY_BackSpace)
1480   {
1481     // If a leaf node is selected, clear that accelerator
1482 
1483     // If nothing is selected, or branch node selected, just return
1484     if(!gtk_tree_selection_get_selected(selection, &model, &iter)
1485        || gtk_tree_model_iter_has_child(model, &iter))
1486       return FALSE;
1487 
1488     // Otherwise, construct the proper accelerator path and delete its entry
1489     g_strlcpy(accel, "<Darktable>", sizeof(accel));
1490     path = gtk_tree_model_get_path(model, &iter);
1491     path_to_accel(model, path, accel, sizeof(accel));
1492     gtk_tree_path_free(path);
1493 
1494     gtk_accel_map_change_entry(accel, 0, 0, TRUE);
1495     update_accels_model(NULL, model);
1496 
1497     // Saving the changed bindings
1498     gtk_accel_map_save(accelpath);
1499 
1500     return TRUE;
1501   }
1502   else
1503   {
1504     return FALSE;
1505   }
1506 }
1507 
tree_key_press_presets(GtkWidget * widget,GdkEventKey * event,gpointer data)1508 static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data)
1509 {
1510 
1511   GtkTreeModel *model = (GtkTreeModel *)data;
1512   GtkTreeIter iter;
1513   GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1514 
1515   // We can just ignore mod key presses outright
1516   if(event->is_modifier) return FALSE;
1517 
1518   if(event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_BackSpace)
1519   {
1520     // If a leaf node is selected, delete that preset
1521 
1522     // If nothing is selected, or branch node selected, just return
1523     if(!gtk_tree_selection_get_selected(selection, &model, &iter)
1524        || gtk_tree_model_iter_has_child(model, &iter))
1525       return FALSE;
1526 
1527     // For leaf nodes, open delete confirmation window if the preset is not writeprotected
1528     gint rowid;
1529     gchar *name;
1530     GdkPixbuf *editable;
1531     gtk_tree_model_get(model, &iter, P_ROWID_COLUMN, &rowid, P_NAME_COLUMN, &name,
1532                        P_EDITABLE_COLUMN, &editable, -1);
1533     if(editable == NULL)
1534     {
1535       sqlite3_stmt *stmt;
1536       gchar* operation = NULL;
1537 
1538       DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1539                               "SELECT name, operation FROM data.presets WHERE rowid = ?1",
1540                               -1, &stmt, NULL);
1541       DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, rowid);
1542       if(sqlite3_step(stmt) == SQLITE_ROW)
1543       {
1544         operation = g_strdup( (const char*)sqlite3_column_text(stmt,1));
1545       }
1546       sqlite3_finalize(stmt);
1547 
1548       GtkWidget *dialog = gtk_message_dialog_new
1549         (GTK_WINDOW(_preferences_dialog), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1550          GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1551          _("do you really want to delete the preset `%s'?"), name);
1552 #ifdef GDK_WINDOWING_QUARTZ
1553       dt_osx_disallow_fullscreen(dialog);
1554 #endif
1555       gtk_window_set_title(GTK_WINDOW(dialog), _("delete preset?"));
1556 
1557       if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1558       {
1559         //deregistering accel...
1560         if(operation)
1561         {
1562           gchar accel[256];
1563           gchar datadir[PATH_MAX] = { 0 };
1564           gchar accelpath[PATH_MAX] = { 0 };
1565 
1566           dt_loc_get_user_config_dir(datadir, sizeof(datadir));
1567           snprintf(accelpath, sizeof(accelpath), "%s/keyboardrc", datadir);
1568 
1569           gchar *preset_name = g_strdup_printf("%s`%s", N_("preset"), name);
1570           dt_accel_path_iop(accel, sizeof(accel), operation, preset_name);
1571           g_free(preset_name);
1572 
1573           gtk_accel_map_change_entry(accel, 0, 0, TRUE);
1574 
1575           // Saving the changed bindings
1576           gtk_accel_map_save(accelpath);
1577         }
1578 
1579         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1580                                     "DELETE FROM data.presets WHERE rowid=?1 AND writeprotect=0", -1, &stmt, NULL);
1581         DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, rowid);
1582         sqlite3_step(stmt);
1583         sqlite3_finalize(stmt);
1584         GtkTreeStore *tree_store = GTK_TREE_STORE(model);
1585         gtk_tree_store_clear(tree_store);
1586         tree_insert_presets(tree_store);
1587       }
1588       gtk_widget_destroy(dialog);
1589       if(operation)
1590         g_free(operation);
1591     }
1592     else
1593       g_object_unref(editable);
1594     g_free(name);
1595 
1596     return TRUE;
1597   }
1598   else
1599   {
1600     return FALSE;
1601   }
1602 }
1603 
import_export(GtkButton * button,gpointer data)1604 static void import_export(GtkButton *button, gpointer data)
1605 {
1606   GtkWidget *chooser;
1607   GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1608   gchar confdir[PATH_MAX] = { 0 };
1609   gchar accelpath[PATH_MAX] = { 0 };
1610 
1611   if(data)
1612   {
1613     // Non-zero value indicates export
1614     chooser = gtk_file_chooser_dialog_new(_("select file to export"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SAVE,
1615                                           _("_cancel"), GTK_RESPONSE_CANCEL, _("_save"), GTK_RESPONSE_ACCEPT,
1616                                           NULL);
1617 #ifdef GDK_WINDOWING_QUARTZ
1618     dt_osx_disallow_fullscreen(chooser);
1619 #endif
1620     gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE);
1621     gchar *exported_path = dt_conf_get_string("ui_last/export_path");
1622     if(exported_path != NULL)
1623     {
1624       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), exported_path);
1625       g_free(exported_path);
1626     }
1627     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser), "keyboardrc");
1628     if(gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1629     {
1630       gtk_accel_map_save(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser)));
1631       gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(chooser));
1632       dt_conf_set_string("ui_last/export_path", folder);
1633       g_free(folder);
1634     }
1635     gtk_widget_destroy(chooser);
1636   }
1637   else
1638   {
1639     // Zero value indicates import
1640     chooser = gtk_file_chooser_dialog_new(_("select file to import"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
1641                                           _("_cancel"), GTK_RESPONSE_CANCEL, _("_open"), GTK_RESPONSE_ACCEPT,
1642                                           NULL);
1643 #ifdef GDK_WINDOWING_QUARTZ
1644     dt_osx_disallow_fullscreen(chooser);
1645 #endif
1646 
1647     gchar *import_path = dt_conf_get_string("ui_last/import_path");
1648     if(import_path != NULL)
1649     {
1650       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), import_path);
1651       g_free(import_path);
1652     }
1653     if(gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1654     {
1655       if(g_file_test(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser)), G_FILE_TEST_EXISTS))
1656       {
1657         // Loading the file
1658         gtk_accel_map_load(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser)));
1659 
1660         // Saving to the permanent keyboardrc
1661         dt_loc_get_user_config_dir(confdir, sizeof(confdir));
1662         snprintf(accelpath, sizeof(accelpath), "%s/keyboardrc", confdir);
1663         gtk_accel_map_save(accelpath);
1664 
1665         gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(chooser));
1666         dt_conf_set_string("ui_last/import_path", folder);
1667         g_free(folder);
1668       }
1669     }
1670     gtk_widget_destroy(chooser);
1671   }
1672 }
1673 
restore_defaults(GtkButton * button,gpointer data)1674 static void restore_defaults(GtkButton *button, gpointer data)
1675 {
1676   gchar accelpath[256];
1677   gchar dir[PATH_MAX] = { 0 };
1678   gchar path[PATH_MAX] = { 0 };
1679 
1680   GtkWidget *message
1681       = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
1682                                _("are you sure you want to restore the default keybindings?  this will "
1683                                  "erase any modifications you have made."));
1684 #ifdef GDK_WINDOWING_QUARTZ
1685   dt_osx_disallow_fullscreen(message);
1686 #endif
1687   if(gtk_dialog_run(GTK_DIALOG(message)) == GTK_RESPONSE_OK)
1688   {
1689     // First load the default keybindings for immediate effect
1690     dt_loc_get_user_config_dir(dir, sizeof(dir));
1691     snprintf(path, sizeof(path), "%s/keyboardrc_default", dir);
1692     gtk_accel_map_load(path);
1693 
1694     // Now deleting any iop show shortcuts
1695     for(const GList *ops = darktable.iop; ops; ops = g_list_next(ops))
1696     {
1697       dt_iop_module_so_t *op = (dt_iop_module_so_t *)ops->data;
1698       snprintf(accelpath, sizeof(accelpath), "<Darktable>/darkroom/modules/%s/show", op->op);
1699       gtk_accel_map_change_entry(accelpath, 0, 0, TRUE);
1700     }
1701 
1702     // Then delete any changes to the user's keyboardrc so it gets reset
1703     // on next startup
1704     dt_loc_get_user_config_dir(dir, sizeof(dir));
1705     snprintf(path, sizeof(path), "%s/keyboardrc", dir);
1706 
1707     GFile *gpath = g_file_new_for_path(path);
1708     g_file_delete(gpath, NULL, NULL);
1709     g_object_unref(gpath);
1710   }
1711   gtk_widget_destroy(message);
1712 }
1713 
_import_preset_from_file(const gchar * filename)1714 static void _import_preset_from_file(const gchar* filename)
1715 {
1716   if(!dt_presets_import_from_file(filename))
1717   {
1718     dt_control_log(_("failed to import preset %s"), filename);
1719   }
1720 }
1721 
import_preset(GtkButton * button,gpointer data)1722 static void import_preset(GtkButton *button, gpointer data)
1723 {
1724   GtkTreeModel *model = (GtkTreeModel *)data;
1725   GtkWidget *chooser;
1726   GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1727 
1728   // Zero value indicates import
1729   chooser = gtk_file_chooser_dialog_new(_("select preset to import"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
1730                                         _("_cancel"), GTK_RESPONSE_CANCEL, _("_open"), GTK_RESPONSE_ACCEPT,
1731                                         NULL);
1732 #ifdef GDK_WINDOWING_QUARTZ
1733   dt_osx_disallow_fullscreen(chooser);
1734 #endif
1735 
1736   gchar *import_path = dt_conf_get_string("ui_last/import_path");
1737   if(import_path != NULL)
1738   {
1739     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), import_path);
1740     g_free(import_path);
1741   }
1742   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE);
1743 
1744   GtkFileFilter *filter;
1745   filter = GTK_FILE_FILTER(gtk_file_filter_new());
1746   gtk_file_filter_add_pattern(filter, "*.dtpreset");
1747   gtk_file_filter_add_pattern(filter, "*.DTPRESET");
1748   gtk_file_filter_set_name(filter, _("darktable style files"));
1749   gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
1750 
1751   filter = GTK_FILE_FILTER(gtk_file_filter_new());
1752   gtk_file_filter_add_pattern(filter, "*");
1753   gtk_file_filter_set_name(filter, _("all files"));
1754 
1755   gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
1756 
1757   if(gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1758   {
1759     GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chooser));
1760     g_slist_foreach(filenames, (GFunc)_import_preset_from_file, NULL);
1761     g_slist_free_full(filenames, g_free);
1762 
1763     GtkTreeStore *tree_store = GTK_TREE_STORE(model);
1764     gtk_tree_store_clear(tree_store);
1765     tree_insert_presets(tree_store);
1766 
1767     gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(chooser));
1768     dt_conf_set_string("ui_last/import_path", folder);
1769     g_free(folder);
1770   }
1771   gtk_widget_destroy(chooser);
1772 }
1773 
export_preset(GtkButton * button,gpointer data)1774 static void export_preset(GtkButton *button, gpointer data)
1775 {
1776   GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1777   GtkWidget *filechooser = gtk_file_chooser_dialog_new(
1778       _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("_cancel"),
1779       GTK_RESPONSE_CANCEL, _("_save"), GTK_RESPONSE_ACCEPT, (char *)NULL);
1780 #ifdef GDK_WINDOWING_QUARTZ
1781   dt_osx_disallow_fullscreen(filechooser);
1782 #endif
1783   gchar *import_path = dt_conf_get_string("ui_last/export_path");
1784   if(import_path != NULL)
1785   {
1786     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), import_path);
1787     g_free(import_path);
1788   }
1789   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
1790 
1791   if(gtk_dialog_run(GTK_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1792   {
1793     gchar *filedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1794     sqlite3_stmt *stmt;
1795 
1796     // we have n+1 selects for saving presets, using single transaction for whole process saves us microlocks
1797     DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "BEGIN TRANSACTION", NULL, NULL, NULL);
1798 
1799     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1800                                 "SELECT rowid, name, operation FROM data.presets WHERE writeprotect = 0",
1801                                 -1, &stmt, NULL);
1802 
1803     while(sqlite3_step(stmt) == SQLITE_ROW)
1804     {
1805       const gint rowid = sqlite3_column_int(stmt, 0);
1806       const gchar *name = (gchar *)sqlite3_column_text(stmt, 1);
1807       const gchar *operation = (gchar *)sqlite3_column_text(stmt, 2);
1808       gchar* preset_name = g_strdup_printf("%s_%s", operation, name);
1809 
1810       dt_presets_save_to_file(rowid, preset_name, filedir);
1811 
1812       g_free(preset_name);
1813     }
1814 
1815     sqlite3_finalize(stmt);
1816 
1817     DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "END TRANSACTION", NULL, NULL, NULL);
1818 
1819     gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(filechooser));
1820     dt_conf_set_string("ui_last/export_path", folder);
1821     g_free(folder);
1822 
1823     g_free(filedir);
1824   }
1825   gtk_widget_destroy(filechooser);
1826 }
1827 
1828 // Custom sort function for TreeModel entries for accels list
compare_rows_accels(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer data)1829 static gint compare_rows_accels(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
1830 {
1831   int res = 0;
1832 
1833   gchar *a_text;
1834   gchar *b_text;
1835 
1836   // First prioritize branch nodes over leaves
1837   if(gtk_tree_model_iter_has_child(model, a)) res -= 2;
1838   if(gtk_tree_model_iter_has_child(model, b)) res += 2;
1839 
1840   // Otherwise just return alphabetical order
1841   gtk_tree_model_get(model, a, A_TRANS_COLUMN, &a_text, -1);
1842   gtk_tree_model_get(model, b, A_TRANS_COLUMN, &b_text, -1);
1843 
1844   // but put default actions (marked with space at end) first
1845   if(a_text[strlen(a_text)-1] == ' ') res = -4; // ignore children
1846   if(b_text[strlen(b_text)-1] == ' ') res += 4;
1847 
1848   res += strcoll(a_text, b_text) < 0 ? -1 : 1;
1849 
1850   g_free(a_text);
1851   g_free(b_text);
1852 
1853   return res;
1854 }
1855 
1856 // Custom sort function for TreeModel entries for presets list
compare_rows_presets(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer data)1857 static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
1858 {
1859   gchar *a_text;
1860   gchar *b_text;
1861 
1862   gtk_tree_model_get(model, a, P_MODULE_COLUMN, &a_text, -1);
1863   gtk_tree_model_get(model, b, P_MODULE_COLUMN, &b_text, -1);
1864   if(*a_text == '\0' && *b_text == '\0')
1865   {
1866     g_free(a_text);
1867     g_free(b_text);
1868 
1869     gtk_tree_model_get(model, a, P_NAME_COLUMN, &a_text, -1);
1870     gtk_tree_model_get(model, b, P_NAME_COLUMN, &b_text, -1);
1871   }
1872 
1873   const int res = strcoll(a_text, b_text);
1874 
1875   g_free(a_text);
1876   g_free(b_text);
1877 
1878   return res;
1879 }
1880 
edit_preset_response(dt_gui_presets_edit_dialog_t * g)1881 static void edit_preset_response(dt_gui_presets_edit_dialog_t *g)
1882 {
1883   GtkTreeStore *tree_store = GTK_TREE_STORE(gtk_tree_view_get_model((GtkTreeView *)g->data));
1884   gtk_tree_store_clear(tree_store);
1885   tree_insert_presets(tree_store);
1886 }
1887 
edit_preset(GtkTreeView * tree,const gint rowid,const gchar * name,const gchar * module)1888 static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module)
1889 {
1890   dt_gui_presets_show_edit_dialog(name, module, rowid, G_CALLBACK(edit_preset_response), tree, FALSE, TRUE, TRUE,
1891                                   GTK_WINDOW(_preferences_dialog));
1892 }
1893 
1894 static void
_gui_preferences_bool_callback(GtkWidget * widget,gpointer data)1895 _gui_preferences_bool_callback(GtkWidget *widget, gpointer data)
1896 {
1897   dt_conf_set_bool((char *)data, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
1898 }
1899 
dt_gui_preferences_bool_reset(GtkWidget * widget)1900 void dt_gui_preferences_bool_reset(GtkWidget *widget)
1901 {
1902   const char *key = gtk_widget_get_name(widget);
1903   const gboolean def = dt_confgen_get_bool(key, DT_DEFAULT);
1904   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), def);
1905 }
1906 
1907 static gboolean
_gui_preferences_bool_reset(GtkWidget * label,GdkEventButton * event,GtkWidget * widget)1908 _gui_preferences_bool_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1909 {
1910   if(event->type == GDK_2BUTTON_PRESS)
1911   {
1912     dt_gui_preferences_bool_reset(widget);
1913     return TRUE;
1914   }
1915   return FALSE;
1916 }
1917 
dt_gui_preferences_bool_update(GtkWidget * widget)1918 void dt_gui_preferences_bool_update(GtkWidget *widget)
1919 {
1920   const char *key = gtk_widget_get_name(widget);
1921   const gboolean val = dt_conf_get_bool(key);
1922   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), val);
1923 }
1924 
dt_gui_preferences_bool(GtkGrid * grid,const char * key,const guint col,const guint line,const gboolean swap)1925 GtkWidget *dt_gui_preferences_bool(GtkGrid *grid, const char *key, const guint col,
1926                                    const guint line, const gboolean swap)
1927 {
1928   GtkWidget *w_label = dt_ui_label_new(_(dt_confgen_get_label(key)));
1929   gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1930   GtkWidget *labelev = gtk_event_box_new();
1931   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1932   gtk_container_add(GTK_CONTAINER(labelev), w_label);
1933   GtkWidget *w = gtk_check_button_new();
1934   gtk_widget_set_name(w, key);
1935   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), dt_conf_get_bool(key));
1936   gtk_grid_attach(GTK_GRID(grid), labelev, swap ? (col + 1) : col, line, 1, 1);
1937   gtk_grid_attach(GTK_GRID(grid), w, swap ? col : (col + 1), line, 1, 1);
1938   g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(_gui_preferences_bool_callback), (gpointer)key);
1939   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_bool_reset), (gpointer)w);
1940   return w;
1941 }
1942 
1943 static void
_gui_preferences_int_callback(GtkWidget * widget,gpointer data)1944 _gui_preferences_int_callback(GtkWidget *widget, gpointer data)
1945 {
1946   dt_conf_set_int((char *)data, gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
1947 }
1948 
dt_gui_preferences_int_reset(GtkWidget * widget)1949 void dt_gui_preferences_int_reset(GtkWidget *widget)
1950 {
1951   const char *key = gtk_widget_get_name(widget);
1952   const int def = dt_confgen_get_int(key, DT_DEFAULT);
1953   gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), def);
1954 }
1955 
1956 static gboolean
_gui_preferences_int_reset(GtkWidget * label,GdkEventButton * event,GtkWidget * widget)1957 _gui_preferences_int_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1958 {
1959   if(event->type == GDK_2BUTTON_PRESS)
1960   {
1961     dt_gui_preferences_int_reset(widget);
1962     return TRUE;
1963   }
1964   return FALSE;
1965 }
1966 
dt_gui_preferences_int_update(GtkWidget * widget)1967 void dt_gui_preferences_int_update(GtkWidget *widget)
1968 {
1969   const char *key = gtk_widget_get_name(widget);
1970   const int val = dt_conf_get_int(key);
1971   gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), val);
1972 }
1973 
dt_gui_preferences_int(GtkGrid * grid,const char * key,const guint col,const guint line)1974 GtkWidget *dt_gui_preferences_int(GtkGrid *grid, const char *key, const guint col,
1975                                   const guint line)
1976 {
1977   GtkWidget *w_label = dt_ui_label_new(_(dt_confgen_get_label(key)));
1978   gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1979   GtkWidget *labelev = gtk_event_box_new();
1980   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1981   gtk_container_add(GTK_CONTAINER(labelev), w_label);
1982   gint min = MAX(G_MININT, dt_confgen_get_int(key, DT_MIN));
1983   gint max = MIN(G_MAXINT, dt_confgen_get_int(key, DT_MAX));
1984   GtkWidget *w = gtk_spin_button_new_with_range(min, max, 1.0);
1985   gtk_widget_set_name(w, key);
1986   gtk_widget_set_hexpand(w, FALSE);
1987   dt_gui_key_accel_block_on_focus_connect(w);
1988   gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 0);
1989   gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), dt_conf_get_int(key));
1990   gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1991   gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
1992   g_signal_connect(G_OBJECT(w), "value-changed", G_CALLBACK(_gui_preferences_int_callback), (gpointer)key);
1993   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_int_reset), (gpointer)w);
1994   return w;
1995 }
1996 
1997 static void
_gui_preferences_enum_callback(GtkWidget * widget,gpointer data)1998 _gui_preferences_enum_callback(GtkWidget *widget, gpointer data)
1999 {
2000   GtkTreeIter iter;
2001   if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
2002   {
2003     gchar *s = NULL;
2004     gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)), &iter, 0, &s, -1);
2005     dt_conf_set_string((char *)data, s);
2006     g_free(s);
2007   }
2008 }
2009 
_gui_preferences_enum_set(GtkWidget * widget,const char * str)2010 void _gui_preferences_enum_set(GtkWidget *widget, const char *str)
2011 {
2012   GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
2013   GtkTreeIter iter;
2014   gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
2015   gint i = 0;
2016   gboolean found = FALSE;
2017   while(valid)
2018   {
2019     char *value;
2020     gtk_tree_model_get(model, &iter, 0, &value, -1);
2021     if(!g_strcmp0(value, str))
2022     {
2023       g_free(value);
2024       found = TRUE;
2025       break;
2026     }
2027     i++;
2028     g_free(value);
2029     valid = gtk_tree_model_iter_next(model, &iter);
2030   }
2031   if(found)
2032     gtk_combo_box_set_active(GTK_COMBO_BOX(widget), i);
2033 }
2034 
dt_gui_preferences_enum_reset(GtkWidget * widget)2035 void dt_gui_preferences_enum_reset(GtkWidget *widget)
2036 {
2037   const char *key = gtk_widget_get_name(widget);
2038   const char *str = dt_confgen_get(key, DT_DEFAULT);
2039   _gui_preferences_enum_set(widget, str);
2040 }
2041 
2042 static gboolean
_gui_preferences_enum_reset(GtkWidget * label,GdkEventButton * event,GtkWidget * widget)2043 _gui_preferences_enum_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
2044 {
2045   if(event->type == GDK_2BUTTON_PRESS)
2046   {
2047     dt_gui_preferences_enum_reset(widget);
2048     return TRUE;
2049   }
2050   return FALSE;
2051 }
2052 
dt_gui_preferences_enum_update(GtkWidget * widget)2053 void dt_gui_preferences_enum_update(GtkWidget *widget)
2054 {
2055   const char *key = gtk_widget_get_name(widget);
2056   char *str = dt_conf_get_string(key);
2057   _gui_preferences_enum_set(widget, str);
2058   g_free(str);
2059 }
2060 
dt_gui_preferences_enum(GtkGrid * grid,const char * key,const guint col,const guint line)2061 GtkWidget *dt_gui_preferences_enum(GtkGrid *grid, const char *key, const guint col,
2062                                    const guint line)
2063 {
2064   GtkWidget *w_label = dt_ui_label_new(_(dt_confgen_get_label(key)));
2065   gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
2066   GtkWidget *labelev = gtk_event_box_new();
2067   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
2068   gtk_container_add(GTK_CONTAINER(labelev), w_label);
2069 
2070   GtkTreeIter iter;
2071   GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
2072   gchar *str = dt_conf_get_string(key);
2073   const char *values = dt_confgen_get(key, DT_VALUES);
2074   gint i = 0;
2075   gint pos = -1;
2076   GList *vals = dt_util_str_to_glist("][", values);
2077   for(GList *val = vals; val; val = g_list_next(val))
2078   {
2079     char *item = (char *)val->data;
2080     // remove remaining [ or ]
2081     if(item[0] == '[') item++;
2082     else if(item[strlen(item) - 1] == ']') item[strlen(item) - 1] = '\0';
2083     gtk_list_store_append(store, &iter);
2084     gtk_list_store_set(store, &iter, 0, item, 1, g_dpgettext2(NULL, "preferences", item), -1);
2085     if(pos == -1 && !g_strcmp0(str, item))
2086     {
2087       pos = i;
2088     }
2089     i++;
2090   }
2091   g_list_free_full(vals, g_free);
2092   g_free(str);
2093 
2094   GtkWidget *w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
2095   gtk_widget_set_name(w, key);
2096   gtk_widget_set_hexpand(w, FALSE);
2097   g_object_unref(store);
2098   GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
2099   gtk_cell_renderer_set_padding(renderer, 0, 0);
2100   gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), renderer, TRUE);
2101   gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), renderer, "text", 1, NULL);
2102   gtk_combo_box_set_active(GTK_COMBO_BOX(w), pos);
2103 
2104   gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
2105   gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
2106   g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_gui_preferences_enum_callback), (gpointer)key);
2107   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_enum_reset), (gpointer)w);
2108   return w;
2109 }
2110 
2111 static void
_gui_preferences_string_callback(GtkWidget * widget,gpointer data)2112 _gui_preferences_string_callback(GtkWidget *widget, gpointer data)
2113 {
2114   const char *str = gtk_entry_get_text(GTK_ENTRY(widget));
2115   dt_conf_set_string((char *)data, str);
2116 }
2117 
dt_gui_preferences_string_reset(GtkWidget * widget)2118 void dt_gui_preferences_string_reset(GtkWidget *widget)
2119 {
2120   const char *key = gtk_widget_get_name(widget);
2121   const char *str = dt_confgen_get(key, DT_DEFAULT);
2122   gtk_entry_set_text(GTK_ENTRY(widget), str);
2123 }
2124 
2125 static gboolean
_gui_preferences_string_reset(GtkWidget * label,GdkEventButton * event,GtkWidget * widget)2126 _gui_preferences_string_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
2127 {
2128   if(event->type == GDK_2BUTTON_PRESS)
2129   {
2130     dt_gui_preferences_string_reset(widget);
2131     return TRUE;
2132   }
2133   return FALSE;
2134 }
2135 
dt_gui_preferences_string_update(GtkWidget * widget)2136 void dt_gui_preferences_string_update(GtkWidget *widget)
2137 {
2138   const char *key = gtk_widget_get_name(widget);
2139   char *str = dt_conf_get_string(key);
2140   gtk_entry_set_text(GTK_ENTRY(widget), str);
2141   g_free(str);
2142 }
2143 
dt_gui_preferences_string(GtkGrid * grid,const char * key,const guint col,const guint line)2144 GtkWidget *dt_gui_preferences_string(GtkGrid *grid, const char *key, const guint col,
2145                                      const guint line)
2146 {
2147   GtkWidget *w_label = dt_ui_label_new(_(dt_confgen_get_label(key)));
2148   gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
2149   GtkWidget *labelev = gtk_event_box_new();
2150   gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
2151   gtk_container_add(GTK_CONTAINER(labelev), w_label);
2152 
2153   GtkWidget *w = gtk_entry_new();
2154   gchar *str = dt_conf_get_string(key);
2155   gtk_entry_set_text(GTK_ENTRY(w), str);
2156   g_free(str);
2157   gtk_widget_set_hexpand(w, TRUE);
2158   gtk_widget_set_name(w, key);
2159 
2160   gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
2161   gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
2162   g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_gui_preferences_string_callback), (gpointer)key);
2163   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_string_reset), (gpointer)w);
2164   return w;
2165 }
2166 
2167 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
2168 // vim: shiftwidth=2 expandtab tabstop=2 cindent
2169 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2170