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, ¤t_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