Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
preferences.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2011 johannes hanika.
4 Copyright (C) 2010-2011, 2014 Henrik Andersson.
5 Copyright (C) 2011 Robert Bieber.
6 Copyright (C) 2011-2018, 2020 Tobias Ellinghaus.
7 Copyright (C) 2011 Ulrich Pegelow.
8 Copyright (C) 2012-2013, 2016 Jérémy Rosen.
9 Copyright (C) 2012, 2014, 2017-2018, 2020 parafin.
10 Copyright (C) 2012 Richard Wonka.
11 Copyright (C) 2013 Moritz Lipp.
12 Copyright (C) 2013, 2019-2022 Pascal Obry.
13 Copyright (C) 2013-2017, 2020 Roman Lebedev.
14 Copyright (C) 2013-2014 Ronny Kahl.
15 Copyright (C) 2013 Simon Spannagel.
16 Copyright (C) 2013 Thomas Pryds.
17 Copyright (C) 2017 Rikard Öxler.
18 Copyright (C) 2019-2021 Aldric Renaudin.
19 Copyright (C) 2019 Alexis Mousset.
20 Copyright (C) 2019-2020, 2022-2023, 2025 Aurélien PIERRE.
21 Copyright (C) 2019-2020 Heiko Bauke.
22 Copyright (C) 2019 jakubfi.
23 Copyright (C) 2019 luzpaz.
24 Copyright (C) 2019 maruncz.
25 Copyright (C) 2019 PaoloAst.
26 Copyright (C) 2019 Sam Smith.
27 Copyright (C) 2020-2022 Chris Elston.
28 Copyright (C) 2020 David-Tillmann Schaefer.
29 Copyright (C) 2020-2021 Diederik Ter Rahe.
30 Copyright (C) 2020, 2022 Hanno Schwalm.
31 Copyright (C) 2020-2021 Hubert Kowalski.
32 Copyright (C) 2020-2021 Philippe Weyland.
33 Copyright (C) 2021 Marco Carrarini.
34 Copyright (C) 2021 Mark-64.
35 Copyright (C) 2021-2022 Nicolas Auffray.
36 Copyright (C) 2021 Ralf Brown.
37 Copyright (C) 2022 Martin Bařinka.
38 Copyright (C) 2022 Victor Forsiuk.
39 Copyright (C) 2023 Luca Zulberti.
40
41 darktable is free software: you can redistribute it and/or modify
42 it under the terms of the GNU General Public License as published by
43 the Free Software Foundation, either version 3 of the License, or
44 (at your option) any later version.
45
46 darktable is distributed in the hope that it will be useful,
47 but WITHOUT ANY WARRANTY; without even the implied warranty of
48 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49 GNU General Public License for more details.
50
51 You should have received a copy of the GNU General Public License
52 along with darktable. If not, see <http://www.gnu.org/licenses/>.
53*/
54
55#include <gdk/gdkkeysyms.h>
56#include <strings.h>
57
58#include "bauhaus/bauhaus.h"
59#include "common/darktable.h"
60#include "gui/gdkkeys.h"
61#include "common/debug.h"
63#include "common/l10n.h"
64#include "common/opencl.h"
65#include "common/presets.h"
66#include "control/control.h"
67#include "develop/imageop.h"
68
69#include "gui/draw.h"
70#include "gui/gtk.h"
71#include "gui/preferences.h"
72#include "gui/presets.h"
73#include "libs/lib.h"
74#include "preferences_gen.h"
75#ifdef GDK_WINDOWING_QUARTZ
76#include "osx/osx.h"
77#endif
78#define ICON_SIZE 13
79
84
85// link to values in gui/presets.c
86// move to presets.h if needed elsewhere
88extern const float dt_gui_presets_exposure_value[];
89extern const char *dt_gui_presets_exposure_value_str[];
91extern const float dt_gui_presets_aperture_value[];
92extern const char *dt_gui_presets_aperture_value_str[];
93
94static void _opencl_device_enabled_callback(GtkToggleButton *button, gpointer user_data)
95{
96 const int detected = GPOINTER_TO_INT(user_data);
97 const gboolean device_enabled = gtk_toggle_button_get_active(button);
98 if(dt_opencl_set_detected_device_enabled(detected, device_enabled) == 0)
99 restart_required = TRUE;
100
101 // Per-device settings (pinned memory, headroom) are meaningless while the GPU itself is
102 // disabled -- grey them out and lock them so the GUI doesn't suggest they still apply.
103 GList *dependents = (GList *)g_object_get_data(G_OBJECT(button), "dependent-widgets");
104 for(GList *l = dependents; l; l = l->next) gtk_widget_set_sensitive(GTK_WIDGET(l->data), device_enabled);
105}
106
107static void _opencl_device_pinned_memory_callback(GtkToggleButton *button, gpointer user_data)
108{
109 // Applies immediately (dt_opencl_set_detected_device_pinned_memory() updates the live device
110 // state, not just conf) -- unlike "Enable" above, no restart needed.
111 const int detected = GPOINTER_TO_INT(user_data);
112 const gboolean pinned_memory = gtk_toggle_button_get_active(button);
113 dt_opencl_set_detected_device_pinned_memory(detected, pinned_memory);
114}
115
116static void _opencl_device_headroom_callback(GtkSpinButton *button, gpointer user_data)
117{
118 // Applies immediately, same as pinned memory above.
119 const int detected = GPOINTER_TO_INT(user_data);
120 const size_t headroom = gtk_spin_button_get_value_as_int(button);
121 dt_opencl_set_detected_device_headroom(detected, headroom);
122}
123
124// Values for the accelerators/presets treeview
125
126enum
127{
143
144static void init_tab_presets(GtkWidget *stack);
145static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data);
146static void import_preset(GtkButton *button, gpointer data);
147static void export_preset(GtkButton *button, gpointer data);
148
149// Signal handlers
150static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
151 gpointer data);
152static void tree_selection_changed(GtkTreeSelection *selection, gpointer data);
153static gboolean tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data);
154static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data);
155
156static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module);
157
159
161
162static void load_themes_dir(const char *basedir)
163{
164 char *themes_dir = g_build_filename(basedir, "themes", NULL);
165 GDir *dir = g_dir_open(themes_dir, 0, NULL);
166 if(dir)
167 {
168 dt_print(DT_DEBUG_DEV, "adding themes directory: %s\n", themes_dir);
169
170 const gchar *d_name;
171 while((d_name = g_dir_read_name(dir)))
172 darktable.themes = g_list_append(darktable.themes, g_strdup(d_name));
173 g_dir_close(dir);
174 }
175 dt_free(themes_dir);
176}
177
178static void load_themes(void)
179{
180 // Clear theme list...
181 g_list_free_full(darktable.themes, dt_free_gpointer);
182 darktable.themes = NULL;
183
184 // check themes dirs
185 gchar configdir[PATH_MAX] = { 0 };
186 gchar datadir[PATH_MAX] = { 0 };
187 dt_loc_get_datadir(datadir, sizeof(datadir));
188 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
189
190 load_themes_dir(datadir);
191 load_themes_dir(configdir);
192}
193
194static void reload_ui_last_theme(void)
195{
196 const char *theme = dt_conf_get_string_const("ui_last/theme");
197 dt_gui_load_theme(theme);
199}
200
201static void theme_callback(GtkWidget *widget, gpointer user_data)
202{
203 const int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
204 gchar *theme = g_list_nth(darktable.themes, selected)->data;
205 gchar *i = g_strrstr(theme, ".");
206 if(i) *i = '\0';
207 dt_gui_load_theme(theme);
209}
210
211static void usercss_callback(GtkWidget *widget, gpointer user_data)
212{
213 dt_conf_set_bool("themes/usercss", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
215}
216
217static void font_size_changed_callback(GtkWidget *widget, gpointer user_data)
218{
219 dt_conf_set_float("font_size", gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
221}
222
223static void dpi_scaling_changed_callback(GtkWidget *widget, gpointer user_data)
224{
225 float dpi = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
226 if(dpi > 0.0) dpi = fmax(64, dpi); // else <= 0 -> use system default
227 dt_conf_set_float("screen_dpi_overwrite", dpi);
228 restart_required = TRUE;
231}
232
233static void use_sys_font_callback(GtkWidget *widget, gpointer user_data)
234{
235 dt_conf_set_bool("use_system_font", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
236 if(dt_conf_get_bool("use_system_font"))
237 gtk_widget_set_state_flags(GTK_WIDGET(user_data), GTK_STATE_FLAG_INSENSITIVE, TRUE);
238 else
239 gtk_widget_set_state_flags(GTK_WIDGET(user_data), GTK_STATE_FLAG_NORMAL, TRUE);
240
242}
243
244static void save_usercss(GtkTextBuffer *buffer)
245{
246 //get file locations
247 char usercsspath[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
248 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
249 g_snprintf(usercsspath, sizeof(usercsspath), "%s/user.css", configdir);
250
251 //get the text
252 GtkTextIter start, end;
253 gtk_text_buffer_get_start_iter(buffer, &start);
254 gtk_text_buffer_get_end_iter(buffer, &end);
255 gchar *usercsscontent = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
256
257 //write to file
258 GError *error = NULL;
259 if(!g_file_set_contents(usercsspath, usercsscontent, -1, &error))
260 {
261 fprintf(stderr, "%s: error saving css to %s: %s\n", G_STRFUNC, usercsspath, error->message);
262 g_clear_error(&error);
263 }
264 dt_free(usercsscontent);
265}
266
267static void save_usercss_callback(GtkWidget *widget, gpointer user_data)
268{
270 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->css_text_view));
271
272 save_usercss(buffer);
273
274 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tw->apply_toggle)))
275 {
276 //reload the theme
278 }
279 else
280 {
281 //toggle the apply button, which will also reload the theme
282 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tw->apply_toggle), TRUE);
283 }
284}
285
286static void usercss_dialog_callback(GtkDialog *dialog, gint response_id, gpointer user_data)
287{
288 //just save the latest css but don't reload the theme
290 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->css_text_view));
291 save_usercss(buffer);
292}
293
295
296static void language_callback(GtkWidget *widget, gpointer user_data)
297{
298 int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
299 dt_l10n_language_t *language = (dt_l10n_language_t *)g_list_nth_data(darktable.l10n->languages, selected);
300 if(darktable.l10n->sys_default == selected)
301 {
302 dt_conf_set_string("ui_last/gui_language", "");
304 }
305 else
306 {
307 dt_conf_set_string("ui_last/gui_language", language->code);
308 darktable.l10n->selected = selected;
309 }
310 restart_required = TRUE;
311}
312
313static gboolean reset_language_widget(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
314{
315 if(event->type == GDK_2BUTTON_PRESS)
316 {
317 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), darktable.l10n->sys_default);
318 return TRUE;
319 }
320 return FALSE;
321}
322
324{
325
326 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
327 GtkWidget *grid = gtk_grid_new();
328 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
329 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
330 gtk_widget_set_valign(grid, GTK_ALIGN_START);
331 int line = 0;
332
333 gtk_box_pack_start(GTK_BOX(container), grid, FALSE, FALSE, 0);
334
335 gtk_stack_add_titled(GTK_STACK(stack), container, _("general"), _("general"));
336
337 // language
338
339 GtkWidget *label = gtk_label_new(_("interface language"));
340 gtk_widget_set_halign(label, GTK_ALIGN_START);
341 GtkWidget *labelev = gtk_event_box_new();
342 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
343 gtk_container_add(GTK_CONTAINER(labelev), label);
344 GtkWidget *widget = gtk_combo_box_text_new();
345
346 for(GList *iter = darktable.l10n->languages; iter; iter = g_list_next(iter))
347 {
348 const char *name = dt_l10n_get_name(iter->data);
349 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), name);
350 }
351
352 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), darktable.l10n->selected);
353 g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(language_callback), 0);
354 gtk_widget_set_tooltip_text(labelev, _("double-click to reset to the system language"));
355 gtk_event_box_set_visible_window(GTK_EVENT_BOX(labelev), FALSE);
356 gtk_widget_set_tooltip_text(widget, _("set the language of the user interface. the system default is marked with an * (needs a restart)"));
357 gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
358 gtk_grid_attach_next_to(GTK_GRID(grid), widget, labelev, GTK_POS_RIGHT, 1, 1);
359 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_language_widget), (gpointer)widget);
360
361 // theme
362
363 load_themes();
364
365 label = gtk_label_new(_("theme"));
366 gtk_widget_set_halign(label, GTK_ALIGN_START);
367 widget = gtk_combo_box_text_new();
368 labelev = gtk_event_box_new();
369 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
370 gtk_container_add(GTK_CONTAINER(labelev), label);
371 gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
372 gtk_grid_attach_next_to(GTK_GRID(grid), widget, labelev, GTK_POS_RIGHT, 1, 1);
373
374 // read all themes
375 char *theme_name = dt_conf_get_string("ui_last/theme");
376 int selected = 0;
377 int k = 0;
378 for(GList *iter = darktable.themes; iter; iter = g_list_next(iter))
379 {
380 gchar *name = g_strdup((gchar*)(iter->data));
381 // remove extension
382 gchar *i = g_strrstr(name, ".");
383 if(i) *i = '\0';
384 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), name);
385 if(!g_strcmp0(name, theme_name)) selected = k;
386 g_free(name);
387 k++;
388 }
389 dt_free(theme_name);
390
391 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), selected);
392
393 g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(theme_callback), 0);
394 gtk_widget_set_tooltip_text(widget, _("set the theme for the user interface"));
395
396 //Font size check and spin buttons
397 GtkWidget *usesysfont = gtk_check_button_new();
398 GtkWidget *fontsize = gtk_spin_button_new_with_range(5.0f, 30.0f, 0.2f);
399 int i = dt_conf_get_bool("font_prefs_align_right") ? gtk_widget_set_hexpand(fontsize, TRUE), 2 : 0;
400
401 //checkbox to use system font size
402 if(dt_conf_get_bool("use_system_font"))
403 gtk_widget_set_state_flags(fontsize, GTK_STATE_FLAG_INSENSITIVE, TRUE);
404 else
405 gtk_widget_set_state_flags(fontsize, GTK_STATE_FLAG_NORMAL, TRUE);
406
407 label = gtk_label_new(_("use system font size"));
408 gtk_widget_set_halign(label, GTK_ALIGN_START);
409 labelev = gtk_event_box_new();
410 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
411 gtk_container_add(GTK_CONTAINER(labelev), label);
412 gtk_grid_attach(GTK_GRID(grid), labelev, i, i?2:line++, 1, 1);
413 gtk_grid_attach_next_to(GTK_GRID(grid), usesysfont, labelev, GTK_POS_RIGHT, 1, 1);
414 gtk_widget_set_tooltip_text(usesysfont, _("use system font size"));
415 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(usesysfont), dt_conf_get_bool("use_system_font"));
416 g_signal_connect(G_OBJECT(usesysfont), "toggled", G_CALLBACK(use_sys_font_callback), (gpointer)fontsize);
417
418
419 //font size selector
420 if(dt_conf_get_float("font_size") < 5.0f || dt_conf_get_float("font_size") > 20.0f)
421 dt_conf_set_float("font_size", 12.0f);
422
423 label = gtk_label_new(_("font size in points"));
424 gtk_widget_set_halign(label, GTK_ALIGN_START);
425 labelev = gtk_event_box_new();
426 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
427 gtk_container_add(GTK_CONTAINER(labelev), label);
428 gtk_grid_attach(GTK_GRID(grid), labelev, i, i?0:line++, 1, 1);
429 gtk_grid_attach_next_to(GTK_GRID(grid), fontsize, labelev, GTK_POS_RIGHT, 1, 1);
430 gtk_widget_set_tooltip_text(fontsize, _("font size in points"));
431 gtk_spin_button_set_value(GTK_SPIN_BUTTON(fontsize), dt_conf_get_float("font_size"));
432 g_signal_connect(G_OBJECT(fontsize), "value_changed", G_CALLBACK(font_size_changed_callback), 0);
433
434 GtkWidget *screen_dpi_overwrite = gtk_spin_button_new_with_range(-1.0f, 360, 1.f);
435 label = gtk_label_new(_("GUI controls and text DPI"));
436 gtk_widget_set_halign(label, GTK_ALIGN_START);
437 labelev = gtk_event_box_new();
438 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
439 gtk_container_add(GTK_CONTAINER(labelev), label);
440 gtk_grid_attach(GTK_GRID(grid), labelev, i, i?1:line++, 1, 1);
441 gtk_grid_attach_next_to(GTK_GRID(grid), screen_dpi_overwrite, labelev, GTK_POS_RIGHT, 1, 1);
442 gtk_widget_set_tooltip_text(screen_dpi_overwrite, _("adjust the global GUI resolution to rescale controls, buttons, labels, etc.\n"
443 "increase for a magnified GUI, decrease to fit more content in window.\n"
444 "set to -1 to use the system-defined global resolution.\n"
445 "default is 96 DPI on most systems.\n"
446 "(needs a restart)."));
447 gtk_spin_button_set_value(GTK_SPIN_BUTTON(screen_dpi_overwrite), dt_conf_get_float("screen_dpi_overwrite"));
448 g_signal_connect(G_OBJECT(screen_dpi_overwrite), "value_changed", G_CALLBACK(dpi_scaling_changed_callback), 0);
449
450 //checkbox to allow user to modify theme with user.css
451 label = gtk_label_new(_("modify selected theme with CSS tweaks below"));
452 gtk_widget_set_halign(label, GTK_ALIGN_START);
453 tw->apply_toggle = gtk_check_button_new();
454 labelev = gtk_event_box_new();
455 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
456 gtk_container_add(GTK_CONTAINER(labelev), label);
457 gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
458 gtk_grid_attach_next_to(GTK_GRID(grid), tw->apply_toggle, labelev, GTK_POS_RIGHT, 1, 1);
459 gtk_widget_set_tooltip_text(tw->apply_toggle, _("modify theme with CSS keyed below (saved to user.css)"));
460 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tw->apply_toggle), dt_conf_get_bool("themes/usercss"));
461 g_signal_connect(G_OBJECT(tw->apply_toggle), "toggled", G_CALLBACK(usercss_callback), 0);
462
463 //scrollable textarea with save button to allow user to directly modify user.css file
464 GtkWidget *usercssbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
465 gtk_box_pack_start(GTK_BOX(container), usercssbox, TRUE, TRUE, 0);
466 gtk_widget_set_name(usercssbox, "usercss-box");
467
468 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
469 tw->css_text_view = gtk_text_view_new_with_buffer(buffer);
471 dt_gui_textview_set_padding(GTK_TEXT_VIEW(tw->css_text_view));
472 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tw->css_text_view), GTK_WRAP_WORD);
473 dt_gui_add_class(tw->css_text_view, "dt_monospace");
474 gtk_widget_set_hexpand(tw->css_text_view, TRUE);
475 gtk_widget_set_halign(tw->css_text_view, GTK_ALIGN_FILL);
476
477 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
478 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
479 gtk_container_add(GTK_CONTAINER(scroll), tw->css_text_view);
480 gtk_box_pack_start(GTK_BOX(usercssbox), scroll, TRUE, TRUE, 0);
481
482 tw->save_button = gtk_button_new_with_label(C_("usercss", "save CSS and apply"));
483 g_signal_connect(G_OBJECT(tw->save_button), "clicked", G_CALLBACK(save_usercss_callback), tw);
484 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(usercss_dialog_callback), tw);
485 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
486 gtk_box_pack_end(GTK_BOX(hbox), tw->save_button, FALSE, TRUE, 0);
487 gtk_box_pack_start(GTK_BOX(usercssbox), hbox, FALSE, FALSE, 0);
488 gtk_widget_set_tooltip_text(tw->save_button, _("click to save and apply the CSS tweaks entered in this editor"));
489
490 //set textarea text from file or default
491 char usercsspath[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
492 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
493 g_snprintf(usercsspath, sizeof(usercsspath), "%s/user.css", configdir);
494
495 if(g_file_test(usercsspath, G_FILE_TEST_EXISTS))
496 {
497 gchar *usercsscontent = NULL;
498 //load file into buffer
499 if(g_file_get_contents(usercsspath, &usercsscontent, NULL, NULL))
500 {
501 gtk_text_buffer_set_text(buffer, usercsscontent, -1);
502 }
503 else
504 {
505 //load default text with some pointers
506 gchar *errtext = g_strconcat("/* ", _("ERROR Loading user.css"), " */", NULL);
507 gtk_text_buffer_set_text(buffer, errtext, -1);
508 dt_free(errtext);
509 }
510 dt_free(usercsscontent);
511 }
512 else
513 {
514 //load default text
515 gchar *deftext = g_strconcat("/* ", _("Enter CSS theme tweaks here"), " */\n\n", NULL);
516 gtk_text_buffer_set_text(buffer, deftext, -1);
517 dt_free(deftext);
518 }
519
520}
521
523
524#if 0
525// FIXME! this makes some systems hang forever. I don't reproduce.
526gboolean preferences_window_deleted(GtkWidget *widget, GdkEvent *event, gpointer data)
527{
528 // redraw the whole UI in case sizes have changed
529 gtk_widget_queue_resize(dt_ui_center(darktable.gui->ui));
530 gtk_widget_queue_resize(dt_ui_main_window(darktable.gui->ui));
531
532 gtk_widget_queue_draw(dt_ui_main_window(darktable.gui->ui));
533 gtk_widget_queue_draw(dt_ui_center(darktable.gui->ui));
534
535 gtk_widget_hide(widget);
536 return TRUE;
537}
538#endif
539
540static void _resize_dialog(GtkWidget *widget)
541{
542 GtkAllocation allocation;
543 gtk_widget_get_allocation(widget, &allocation);
544 dt_conf_set_int("ui_last/preferences_dialog_width", allocation.width);
545 dt_conf_set_int("ui_last/preferences_dialog_height", allocation.height);
546}
547
549{
550 GtkWindow *win = GTK_WINDOW(dt_ui_main_window(darktable.gui->ui));
551 _preferences_dialog = gtk_dialog_new_with_buttons(_("Ansel preferences"), win,
552 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
553 NULL, NULL);
554#if 0
555 // FIXME! this makes some systems hang forever. I don't reproduce.
556 g_signal_connect(G_OBJECT(_preferences_dialog), "delete-event", G_CALLBACK(preferences_window_deleted), NULL);
557#endif
558
559 gtk_window_set_default_size(GTK_WINDOW(_preferences_dialog),
560 dt_conf_get_int("ui_last/preferences_dialog_width"),
561 dt_conf_get_int("ui_last/preferences_dialog_height"));
562 g_signal_connect(G_OBJECT(_preferences_dialog), "check-resize", G_CALLBACK(_resize_dialog), NULL);
563#ifdef GDK_WINDOWING_QUARTZ
565#endif
566 gtk_window_set_position(GTK_WINDOW(_preferences_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
567 gtk_widget_set_name(_preferences_dialog, "preferences-notebook");
568
569 //grab the content area of the dialog
570 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(_preferences_dialog));
571 gtk_widget_set_name(content, "preferences-content");
572 gtk_container_set_border_width(GTK_CONTAINER(content), 0);
573
574 //place a box in the content area
575 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
576 gtk_widget_set_name(box, "preferences-box");
577 gtk_container_set_border_width(GTK_CONTAINER(box), 0);
578 gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
579
580 //create stack and sidebar and pack into the box
581 GtkWidget *stack = gtk_stack_new();
582 GtkWidget *stacksidebar = gtk_stack_sidebar_new();
583 gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(stacksidebar), GTK_STACK(stack));
584 gtk_box_pack_start(GTK_BOX(box), stacksidebar, FALSE, FALSE, 0);
585 gtk_box_pack_start(GTK_BOX(box), stack, TRUE, TRUE, 0);
586
588
589 restart_required = FALSE;
590
591 //setup tabs
592 init_tab_general(_preferences_dialog, stack, tweak_widgets);
593 init_tab_views(_preferences_dialog, stack);
594 init_tab_processing(_preferences_dialog, stack);
595
596 // `init_tab_processing()` is generated from `data/anselconfig.xml.in`, so it can only create static
597 // preferences. OpenCL devices are detected at runtime, therefore their per-device switches are added
598 // after the generated processing tab exists.
599 {
600 GtkWidget *scroll = gtk_stack_get_child_by_name(GTK_STACK(stack), _("processing"));
601 GtkWidget *viewport = GTK_IS_BIN(scroll) ? gtk_bin_get_child(GTK_BIN(scroll)) : NULL;
602 GtkWidget *grid = GTK_IS_BIN(viewport) ? gtk_bin_get_child(GTK_BIN(viewport)) : NULL;
603
604 const int detected_devices = dt_opencl_get_detected_device_count();
605 if(!IS_NULL_PTR(grid) && detected_devices > 0)
606 {
607 int line = 0;
608 int insert_line = -1;
609 GList *children = gtk_container_get_children(GTK_CONTAINER(grid));
610 // Walk over the generated preference rows to find where the dynamic OpenCL rows must be inserted.
611 // We also keep the current bottom row as a fallback when the expected anchor is not present.
612 for(GList *child = children; child; child = g_list_next(child))
613 {
614 int top = 0;
615 int height = 1;
616 gtk_container_child_get(GTK_CONTAINER(grid), GTK_WIDGET(child->data), "top-attach", &top, "height", &height,
617 NULL);
618 line = MAX(line, top + height);
619
620 if(GTK_IS_CONTAINER(child->data))
621 {
622 // Find the disk thumbnail cache row. The per-GPU OpenCL switches belong just
623 // after it, before the remaining GPU memory/runtime preferences.
624 GList *box_children = gtk_container_get_children(GTK_CONTAINER(child->data));
625 for(GList *box_child = box_children; box_child; box_child = g_list_next(box_child))
626 {
627 const char *name = gtk_widget_get_name(GTK_WIDGET(box_child->data));
628 if(!IS_NULL_PTR(name) && strcmp(name, "cache_disk_backend") == 0)
629 {
630 insert_line = top + height;
631 break;
632 }
633 }
634 g_list_free(box_children);
635 }
636 }
637 g_list_free(children);
638
639 // title + enable + pinned memory + headroom, per device -- how many grid rows the
640 // per-GPU blocks below need, now that they're attached directly into the shared grid.
641 const int device_rows = detected_devices * 4;
642
643 if(insert_line < 0)
644 insert_line = line;
645 else
646 {
647 children = gtk_container_get_children(GTK_CONTAINER(grid));
648 // Shift all generated rows below the insertion point down, leaving room for the
649 // per-GPU option rows attached directly below.
650 for(GList *child = children; child; child = g_list_next(child))
651 {
652 int top = 0;
653 gtk_container_child_get(GTK_CONTAINER(grid), GTK_WIDGET(child->data), "top-attach", &top, NULL);
654 if(top >= insert_line)
655 gtk_container_child_set(GTK_CONTAINER(grid), GTK_WIDGET(child->data), "top-attach",
656 top + device_rows, NULL);
657 }
658 g_list_free(children);
659 line = insert_line;
660 }
661
662 // Create one block per detected GPU, attached directly into the shared grid (not a nested
663 // grid of our own) so its label/control columns get auto-sized together with the rest of
664 // the tab and line up with every other preference row, instead of drifting off on their own
665 // independently-sized columns. The OpenCL subsystem owns the device-specific configuration
666 // keys and keeps the global OpenCL preference in sync.
667 for(int dev = 0; dev < detected_devices; dev++)
668 {
670 if(IS_NULL_PTR(device)) continue;
671
672 const char *device_name = !IS_NULL_PTR(device->name) ? device->name
673 : (!IS_NULL_PTR(device->cname) ? device->cname : "");
674 gchar *label_text = g_strdup_printf("%d: %s", device->config_id, device_name);
675 GtkWidget *device_title = gtk_label_new(label_text);
676 GtkWidget *title_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
677 gtk_box_pack_start(GTK_BOX(title_box), device_title, FALSE, FALSE, 0);
678 gtk_widget_set_margin_top(title_box, dev > 0 ? DT_PIXEL_APPLY_DPI(8) : 0);
679 // title_box spans all 3 grid columns below, so indenting it here is self-contained: it
680 // doesn't feed into column 0's width negotiation the way a single-column label would,
681 // so the per-parameter rows' controls stay aligned with the rest of the tab.
682 gtk_widget_set_margin_start(title_box, DT_PIXEL_APPLY_DPI(24));
683 gtk_widget_set_name(title_box, "pref_subsection");
684 gtk_grid_attach(GTK_GRID(grid), title_box, 0, line++, 3, 1);
685 dt_free(label_text);
686
687 GtkWidget *enable_label = gtk_label_new(_("Enable"));
688 gtk_widget_set_halign(enable_label, GTK_ALIGN_START);
689 gtk_widget_set_hexpand(enable_label, TRUE);
690 gtk_widget_set_margin_start(enable_label, DT_PIXEL_APPLY_DPI(24));
691 gtk_grid_attach(GTK_GRID(grid), enable_label, 0, line, 1, 1);
692 GtkWidget *enable_labdef = gtk_label_new("");
693 gtk_widget_set_name(enable_labdef, "preference_non_default");
694 gtk_grid_attach(GTK_GRID(grid), enable_labdef, 1, line, 1, 1);
695
696 GtkWidget *enable = gtk_check_button_new();
697 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable), dt_opencl_detected_device_enabled(dev));
698 gtk_widget_set_tooltip_text(enable, _("Enable or disable OpenCL processing for this GPU"));
699 g_signal_connect(G_OBJECT(enable), "toggled", G_CALLBACK(_opencl_device_enabled_callback),
700 GINT_TO_POINTER(dev));
701 gtk_grid_attach(GTK_GRID(grid), enable, 2, line++, 1, 1);
702
703 GtkWidget *pinned_memory_label = gtk_label_new(_("pinned memory"));
704 gtk_widget_set_halign(pinned_memory_label, GTK_ALIGN_START);
705 gtk_widget_set_hexpand(pinned_memory_label, TRUE);
706 gtk_widget_set_margin_start(pinned_memory_label, DT_PIXEL_APPLY_DPI(24));
707 gtk_grid_attach(GTK_GRID(grid), pinned_memory_label, 0, line, 1, 1);
708 GtkWidget *pinned_memory_labdef = gtk_label_new("");
709 gtk_widget_set_name(pinned_memory_labdef, "preference_non_default");
710 gtk_grid_attach(GTK_GRID(grid), pinned_memory_labdef, 1, line, 1, 1);
711
712 GtkWidget *pinned_memory = gtk_check_button_new();
713 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pinned_memory),
715 gtk_widget_set_tooltip_text(pinned_memory, _("Use pinned host memory for OpenCL transfers on this GPU"));
716 g_signal_connect(G_OBJECT(pinned_memory), "toggled", G_CALLBACK(_opencl_device_pinned_memory_callback),
717 GINT_TO_POINTER(dev));
718 gtk_grid_attach(GTK_GRID(grid), pinned_memory, 2, line++, 1, 1);
719
720 // Integrated GPUs have no vRAM of their own: they share system RAM with the OS, the
721 // desktop and every other process. Label the setting accordingly so users don't read
722 // this as a dedicated GPU memory budget on hardware where that concept doesn't apply.
723 GtkWidget *headroom_label = gtk_label_new(device->host_unified_memory
724 ? _("GPU memory headroom, shared with system RAM (MiB)")
725 : _("GPU vRAM headroom (MiB)"));
726 gtk_widget_set_halign(headroom_label, GTK_ALIGN_START);
727 gtk_widget_set_hexpand(headroom_label, TRUE);
728 gtk_widget_set_margin_start(headroom_label, DT_PIXEL_APPLY_DPI(24));
729 gtk_grid_attach(GTK_GRID(grid), headroom_label, 0, line, 1, 1);
730 GtkWidget *headroom_labdef = gtk_label_new("");
731 gtk_widget_set_name(headroom_labdef, "preference_non_default");
732 gtk_grid_attach(GTK_GRID(grid), headroom_labdef, 1, line, 1, 1);
733
734 GtkWidget *headroom = gtk_spin_button_new_with_range(0, G_MAXINT, 1);
735 gtk_widget_set_hexpand(headroom, FALSE);
736 gtk_spin_button_set_digits(GTK_SPIN_BUTTON(headroom), 0);
737 gtk_spin_button_set_value(GTK_SPIN_BUTTON(headroom), dt_opencl_detected_device_headroom(dev));
738 gtk_widget_set_tooltip_text(headroom, device->host_unified_memory
739 ? _("This GPU has no dedicated vRAM -- it shares memory with the "
740 "system. Memory reserved here for the OS, desktop and other "
741 "applications")
742 : _("GPU memory reserved for the system and other applications"));
743 g_signal_connect(G_OBJECT(headroom), "value-changed", G_CALLBACK(_opencl_device_headroom_callback),
744 GINT_TO_POINTER(dev));
745 gtk_grid_attach(GTK_GRID(grid), headroom, 2, line++, 1, 1);
746
747 // Pinned memory and headroom only make sense for a GPU that OpenCL will actually use --
748 // tie their sensitivity to the "Enable" toggle so a disabled device can't leave the user
749 // editing settings that have no effect. _opencl_device_enabled_callback() keeps this in
750 // sync on every toggle.
751 GList *dependents = NULL;
752 dependents = g_list_append(dependents, pinned_memory_label);
753 dependents = g_list_append(dependents, pinned_memory_labdef);
754 dependents = g_list_append(dependents, pinned_memory);
755 dependents = g_list_append(dependents, headroom_label);
756 dependents = g_list_append(dependents, headroom_labdef);
757 dependents = g_list_append(dependents, headroom);
758 g_object_set_data_full(G_OBJECT(enable), "dependent-widgets", dependents, (GDestroyNotify)g_list_free);
759
760 const gboolean device_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(enable));
761 for(GList *l = dependents; l; l = l->next) gtk_widget_set_sensitive(GTK_WIDGET(l->data), device_enabled);
762 }
763 }
764 }
765
766 init_tab_security(_preferences_dialog, stack);
767 init_tab_storage(_preferences_dialog, stack);
768 init_tab_misc(_preferences_dialog, stack);
769 init_tab_presets(stack);
770
771 gtk_widget_show_all(_preferences_dialog);
772 (void)gtk_dialog_run(GTK_DIALOG(_preferences_dialog));
773
774 dt_free(tweak_widgets);
775 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(_preferences_dialog));
776 gtk_widget_destroy(_preferences_dialog);
777 dt_gui_refocus_parent(dialog_parent);
778
779 if(restart_required)
780 dt_control_log(_("Ansel needs to be restarted for settings to take effect"));
781
783}
784
785static void cairo_destroy_from_pixbuf(guchar *pixels, gpointer data)
786{
787 cairo_destroy((cairo_t *)data);
788}
789
790static void tree_insert_presets(GtkTreeStore *tree_model)
791{
792 GtkTreeIter iter, parent;
793 sqlite3_stmt *stmt;
794 gchar *last_module = NULL;
795
796 // Create a GdkPixbuf with a cairo drawing.
797 // lock
798 cairo_surface_t *lock_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DT_PIXEL_APPLY_DPI(ICON_SIZE),
800 cairo_t *lock_cr = cairo_create(lock_cst);
801 cairo_set_source_rgb(lock_cr, 0.7, 0.7, 0.7);
803 cairo_surface_flush(lock_cst);
804 guchar *data = cairo_image_surface_get_data(lock_cst);
806 GdkPixbuf *lock_pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
808 cairo_image_surface_get_stride(lock_cst),
810
811 // check mark
812 cairo_surface_t *check_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DT_PIXEL_APPLY_DPI(ICON_SIZE),
814 cairo_t *check_cr = cairo_create(check_cst);
815 cairo_set_source_rgb(check_cr, 0.7, 0.7, 0.7);
817 cairo_surface_flush(check_cst);
818 data = cairo_image_surface_get_data(check_cst);
820 GdkPixbuf *check_pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
822 cairo_image_surface_get_stride(check_cst),
823 cairo_destroy_from_pixbuf, check_cr);
824 // clang-format off
826 "SELECT rowid, name, operation, autoapply, model, maker, lens, iso_min, "
827 "iso_max, exposure_min, exposure_max, aperture_min, aperture_max, "
828 "focal_length_min, focal_length_max, writeprotect FROM data.presets ORDER BY "
829 "operation, name",
830 -1, &stmt, NULL);
831 // clang-format on
832 while(sqlite3_step(stmt) == SQLITE_ROW)
833 {
834 const gint rowid = sqlite3_column_int(stmt, 0);
835 const gchar *name = (gchar *)sqlite3_column_text(stmt, 1);
836 const gchar *operation = (gchar *)sqlite3_column_text(stmt, 2);
837 const gboolean autoapply = (sqlite3_column_int(stmt, 3) == 0 ? FALSE : TRUE);
838 const gchar *model = (gchar *)sqlite3_column_text(stmt, 4);
839 const gchar *maker = (gchar *)sqlite3_column_text(stmt, 5);
840 const gchar *lens = (gchar *)sqlite3_column_text(stmt, 6);
841 const float iso_min = sqlite3_column_double(stmt, 7);
842 const float iso_max = sqlite3_column_double(stmt, 8);
843 const float exposure_min = sqlite3_column_double(stmt, 9);
844 const float exposure_max = sqlite3_column_double(stmt, 10);
845 const float aperture_min = sqlite3_column_double(stmt, 11);
846 const float aperture_max = sqlite3_column_double(stmt, 12);
847 const int focal_length_min = sqlite3_column_double(stmt, 13);
848 const int focal_length_max = sqlite3_column_double(stmt, 14);
849 const gboolean writeprotect = (sqlite3_column_int(stmt, 15) == 0 ? FALSE : TRUE);
850
851 gchar *iso = NULL, *exposure = NULL, *aperture = NULL, *focal_length = NULL, *smaker = NULL, *smodel = NULL, *slens = NULL;
852 int min, max;
853
854 gchar *module = g_strdup(dt_iop_get_localized_name(operation));
855 if(IS_NULL_PTR(module)) module = g_strdup(dt_lib_get_localized_name(operation));
856 if(IS_NULL_PTR(module)) module = g_strdup(operation);
857
858 if(!dt_presets_module_can_autoapply(operation))
859 {
860 iso = g_strdup("");
861 exposure = g_strdup("");
862 aperture = g_strdup("");
863 focal_length = g_strdup("");
864 smaker = g_strdup("");
865 smodel = g_strdup("");
866 slens = g_strdup("");
867 }
868 else
869 {
870 smaker = g_strdup(maker);
871 smodel = g_strdup(model);
872 slens = g_strdup(lens);
873
874 if(iso_min == 0.0 && iso_max == FLT_MAX)
875 iso = g_strdup("%");
876 else
877 iso = g_strdup_printf("%" G_GSIZE_FORMAT " - %" G_GSIZE_FORMAT "", (size_t)iso_min, (size_t)iso_max);
878
879 for(min = 0; min < dt_gui_presets_exposure_value_cnt && exposure_min > dt_gui_presets_exposure_value[min]; min++)
880 ;
881 for(max = 0; max < dt_gui_presets_exposure_value_cnt && exposure_max > dt_gui_presets_exposure_value[max]; max++)
882 ;
883 if(min == 0 && max == dt_gui_presets_exposure_value_cnt - 1)
884 exposure = g_strdup("%");
885 else
886 exposure = g_strdup_printf("%s - %s", dt_gui_presets_exposure_value_str[min],
888
889 for(min = 0; min < dt_gui_presets_aperture_value_cnt && aperture_min > dt_gui_presets_aperture_value[min]; min++)
890 ;
891 for(max = 0; max < dt_gui_presets_aperture_value_cnt && aperture_max > dt_gui_presets_aperture_value[max]; max++)
892 ;
893 if(min == 0 && max == dt_gui_presets_aperture_value_cnt - 1)
894 aperture = g_strdup("%");
895 else
896 aperture = g_strdup_printf("%s - %s", dt_gui_presets_aperture_value_str[min],
898
899 if(focal_length_min == 0.0 && focal_length_max == 1000.0)
900 focal_length = g_strdup("%");
901 else
902 focal_length = g_strdup_printf("%d - %d", focal_length_min, focal_length_max);
903 }
904
905 if(g_strcmp0(last_module, operation) != 0)
906 {
907 gtk_tree_store_insert_with_values(tree_model, &iter, NULL, -1,
909 _(module), P_EDITABLE_COLUMN, NULL, P_NAME_COLUMN, "", P_MODEL_COLUMN, "",
912 dt_free(last_module);
913 last_module = g_strdup(operation);
914 parent = iter;
915 }
916
917 gtk_tree_store_insert_with_values(tree_model, &iter, &parent, -1,
918 P_ROWID_COLUMN, rowid, P_OPERATION_COLUMN, operation,
919 P_MODULE_COLUMN, "", P_EDITABLE_COLUMN, writeprotect ? lock_pixbuf : NULL,
921 P_ISO_COLUMN, iso, P_EXPOSURE_COLUMN, exposure, P_APERTURE_COLUMN, aperture,
923 autoapply ? check_pixbuf : NULL, -1);
924
925 dt_free(focal_length);
926 dt_free(aperture);
927 dt_free(exposure);
928 dt_free(iso);
929 dt_free(module);
930 dt_free(smaker);
931 dt_free(smodel);
932 dt_free(slens);
933 }
934 dt_free(last_module);
935 sqlite3_finalize(stmt);
936
937 g_object_unref(lock_pixbuf);
938 cairo_surface_destroy(lock_cst);
939 g_object_unref(check_pixbuf);
940 cairo_surface_destroy(check_cst);
941}
942
943static gboolean _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
944{
945 gchar *key_case = g_utf8_casefold(key, -1), *label = NULL;
946
947 gtk_tree_model_get(model, iter, P_NAME_COLUMN, &label, -1);
948 gchar *name_case = g_utf8_casefold(label, -1);
949 dt_free(label);
950 gtk_tree_model_get(model, iter, P_MODULE_COLUMN, &label, -1);
951 gchar *module_case = g_utf8_casefold(label, -1);
952 dt_free(label);
953
954 const gboolean different = !((name_case && strstr(name_case, key_case))
955 || (module_case && strstr(module_case, key_case)));
956
957 dt_free(name_case);
958 dt_free(module_case);
959 dt_free(key_case);
960
961 if(!different)
962 {
963 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
964 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(search_data), path);
965 gtk_tree_path_free(path);
966
967 return FALSE;
968 }
969
970 GtkTreeIter child;
971 if(gtk_tree_model_iter_children(model, &child, iter))
972 {
973 do
974 {
975 _search_func(model, column, key, &child, search_data);
976 }
977 while(gtk_tree_model_iter_next(model, &child));
978 }
979
980 return TRUE;
981}
982
983static void init_tab_presets(GtkWidget *stack)
984{
985 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
986 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
987 GtkTreeView *tree = GTK_TREE_VIEW(gtk_tree_view_new());
988 GtkTreeStore *model = gtk_tree_store_new(
989 P_N_COLUMNS, G_TYPE_INT /*rowid*/, G_TYPE_STRING /*operation*/, G_TYPE_STRING /*module*/,
990 GDK_TYPE_PIXBUF /*editable*/, G_TYPE_STRING /*name*/, G_TYPE_STRING /*model*/, G_TYPE_STRING /*maker*/,
991 G_TYPE_STRING /*lens*/, G_TYPE_STRING /*iso*/, G_TYPE_STRING /*exposure*/, G_TYPE_STRING /*aperture*/,
992 G_TYPE_STRING /*focal length*/, GDK_TYPE_PIXBUF /*auto*/);
993 GtkCellRenderer *renderer;
994 GtkTreeViewColumn *column;
995
996 // Adding the outer container
997 gtk_stack_add_titled(GTK_STACK(stack), container, _("presets"), _("presets"));
998
1000
1001 // Setting a custom sort functions so expandable groups rise to the top
1002 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), P_MODULE_COLUMN, GTK_SORT_ASCENDING);
1003 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), P_MODULE_COLUMN, compare_rows_presets, NULL, NULL);
1004
1005 // Setting up the cell renderers
1006 renderer = gtk_cell_renderer_text_new();
1007 column = gtk_tree_view_column_new_with_attributes(_("module"), renderer, "text", P_MODULE_COLUMN, NULL);
1008 gtk_tree_view_append_column(tree, column);
1009
1010 renderer = gtk_cell_renderer_pixbuf_new();
1011 column = gtk_tree_view_column_new_with_attributes("", renderer, "pixbuf", P_EDITABLE_COLUMN, NULL);
1012 gtk_tree_view_append_column(tree, column);
1013
1014 renderer = gtk_cell_renderer_text_new();
1015 column = gtk_tree_view_column_new_with_attributes(_("name"), renderer, "text", P_NAME_COLUMN, NULL);
1016 gtk_tree_view_append_column(tree, column);
1017
1018 renderer = gtk_cell_renderer_text_new();
1019 column = gtk_tree_view_column_new_with_attributes(_("model"), renderer, "text", P_MODEL_COLUMN, NULL);
1020 gtk_tree_view_append_column(tree, column);
1021
1022 renderer = gtk_cell_renderer_text_new();
1023 column = gtk_tree_view_column_new_with_attributes(_("maker"), renderer, "text", P_MAKER_COLUMN, NULL);
1024 gtk_tree_view_append_column(tree, column);
1025
1026 renderer = gtk_cell_renderer_text_new();
1027 column = gtk_tree_view_column_new_with_attributes(_("lens"), renderer, "text", P_LENS_COLUMN, NULL);
1028 gtk_tree_view_append_column(tree, column);
1029
1030 renderer = gtk_cell_renderer_text_new();
1031 column = gtk_tree_view_column_new_with_attributes(_("ISO"), renderer, "text", P_ISO_COLUMN, NULL);
1032 gtk_tree_view_append_column(tree, column);
1033
1034 renderer = gtk_cell_renderer_text_new();
1035 column = gtk_tree_view_column_new_with_attributes(_("exposure"), renderer, "text", P_EXPOSURE_COLUMN, NULL);
1036 gtk_tree_view_append_column(tree, column);
1037
1038 renderer = gtk_cell_renderer_text_new();
1039 column = gtk_tree_view_column_new_with_attributes(_("aperture"), renderer, "text", P_APERTURE_COLUMN, NULL);
1040 gtk_tree_view_append_column(tree, column);
1041
1042 renderer = gtk_cell_renderer_text_new();
1043 column = gtk_tree_view_column_new_with_attributes(_("focal length"), renderer, "text",
1044 P_FOCAL_LENGTH_COLUMN, NULL);
1045 gtk_tree_view_append_column(tree, column);
1046
1047 renderer = gtk_cell_renderer_pixbuf_new();
1048 column = gtk_tree_view_column_new_with_attributes(_("auto"), renderer, "pixbuf", P_AUTOAPPLY_COLUMN, NULL);
1049 gtk_tree_view_append_column(tree, column);
1050
1051 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1052 gtk_box_pack_start(GTK_BOX(container), scroll, TRUE, TRUE, 0);
1053
1054 // Adding the import/export buttons
1055 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1056 gtk_widget_set_name(hbox, "preset_controls");
1057
1058 GtkWidget *search_presets = gtk_search_entry_new();
1059 dt_accels_disconnect_on_text_input(search_presets);
1060
1061 gtk_box_pack_start(GTK_BOX(hbox), search_presets, FALSE, TRUE, 0);
1062 gtk_entry_set_placeholder_text(GTK_ENTRY(search_presets), _("search presets list"));
1063 gtk_widget_set_tooltip_text(GTK_WIDGET(search_presets), _("incrementally search the list of presets\npress up or down keys to cycle through matches"));
1064 g_signal_connect(G_OBJECT(search_presets), "activate", G_CALLBACK(dt_gui_search_stop), tree);
1065 g_signal_connect(G_OBJECT(search_presets), "stop-search", G_CALLBACK(dt_gui_search_stop), tree);
1066 g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK(dt_gui_search_start), search_presets);
1067 gtk_tree_view_set_search_entry(tree, GTK_ENTRY(search_presets));
1068
1069 GtkWidget *button = gtk_button_new_with_label(C_("preferences", "import..."));
1070 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1071 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(import_preset), (gpointer)model);
1072
1073 button = gtk_button_new_with_label(C_("preferences", "export..."));
1074 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1075 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(export_preset), (gpointer)model);
1076
1077 gtk_box_pack_start(GTK_BOX(container), hbox, FALSE, FALSE, 0);
1078
1079 // Attaching treeview signals
1080
1081 // row-activated either expands/collapses a row or activates editing
1082 g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(tree_row_activated_presets), NULL);
1083
1084 // A keypress may delete preset
1085 g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK(tree_key_press_presets), (gpointer)model);
1086
1087 // Setting up the search functionality
1088 gtk_tree_view_set_search_equal_func(tree, _search_func, tree, NULL);
1089
1090 // Attaching the model to the treeview
1091 gtk_tree_view_set_model(tree, GTK_TREE_MODEL(model));
1092
1093 // Adding the treeview to its containers
1094 dt_gui_add_class(scroll, "dt_recessed_scroll");
1095 gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(tree));
1096 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1097
1098 g_object_unref(G_OBJECT(model));
1099}
1100
1101// TODO: remember which sections were collapsed/expanded and where the view was scrolled to and restore that
1102// after editing is done
1103// Alternative: change edit_preset_response to not clear+refill the tree, but to update the single row
1104// which changed.
1105static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
1106 gpointer data)
1107{
1108 GtkTreeIter iter;
1109 GtkTreeModel *model = gtk_tree_view_get_model(tree);
1110
1111 gtk_tree_model_get_iter(model, &iter, path);
1112
1113 if(gtk_tree_model_iter_has_child(model, &iter))
1114 {
1115 // For branch nodes, toggle expansion on activation
1116 if(gtk_tree_view_row_expanded(tree, path))
1117 gtk_tree_view_collapse_row(tree, path);
1118 else
1119 gtk_tree_view_expand_row(tree, path, FALSE);
1120 }
1121 else
1122 {
1123 // For leaf nodes, open editing window if the preset is not writeprotected
1124 gint rowid;
1125 gchar *name, *operation;
1126 GdkPixbuf *editable;
1127 gtk_tree_model_get(model, &iter, P_ROWID_COLUMN, &rowid, P_NAME_COLUMN, &name, P_OPERATION_COLUMN,
1128 &operation, P_EDITABLE_COLUMN, &editable, -1);
1129 if(IS_NULL_PTR(editable))
1130 edit_preset(tree, rowid, name, operation);
1131 else
1132 g_object_unref(editable);
1133 dt_free(name);
1134 dt_free(operation);
1135 }
1136}
1137
1138
1139
1140static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data)
1141{
1142
1143 GtkTreeModel *model = (GtkTreeModel *)data;
1144 GtkTreeIter iter;
1145 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1146
1147 // We can just ignore mod key presses outright
1148 if(event->is_modifier) return FALSE;
1149 guint key = dt_keys_mainpad_alternatives(event->keyval);
1150
1151 if(key == GDK_KEY_Delete || key == GDK_KEY_BackSpace)
1152 {
1153 // If a leaf node is selected, delete that preset
1154
1155 // If nothing is selected, or branch node selected, just return
1156 if(!gtk_tree_selection_get_selected(selection, &model, &iter)
1157 || gtk_tree_model_iter_has_child(model, &iter))
1158 return FALSE;
1159
1160 // For leaf nodes, open delete confirmation window if the preset is not writeprotected
1161 gint rowid;
1162 gchar *name;
1163 GdkPixbuf *editable;
1164 gtk_tree_model_get(model, &iter, P_ROWID_COLUMN, &rowid, P_NAME_COLUMN, &name,
1165 P_EDITABLE_COLUMN, &editable, -1);
1166 if(IS_NULL_PTR(editable))
1167 {
1168 sqlite3_stmt *stmt;
1169 gchar* operation = NULL;
1170
1172 "SELECT name, operation FROM data.presets WHERE rowid = ?1",
1173 -1, &stmt, NULL);
1174 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, rowid);
1175 if(sqlite3_step(stmt) == SQLITE_ROW)
1176 {
1177 operation = g_strdup( (const char*)sqlite3_column_text(stmt,1));
1178 }
1179 sqlite3_finalize(stmt);
1180
1182
1183 GtkTreeStore *tree_store = GTK_TREE_STORE(model);
1184 gtk_tree_store_clear(tree_store);
1185 tree_insert_presets(tree_store);
1186
1187 if(operation)
1188 {
1189 dt_free(operation);
1190 }
1191 }
1192 else
1193 g_object_unref(editable);
1194
1195 dt_free(name);
1196
1197 return TRUE;
1198 }
1199 else
1200 {
1201 return FALSE;
1202 }
1203}
1204
1205static void _import_preset_from_file(const gchar* filename)
1206{
1207 if(!dt_presets_import_from_file(filename))
1208 {
1209 dt_control_log(_("failed to import preset %s"), filename);
1210 }
1211}
1212
1213static void import_preset(GtkButton *button, gpointer data)
1214{
1215 GtkTreeModel *model = (GtkTreeModel *)data;
1216 GtkWindow *win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
1217
1218 // Zero value indicates import
1219 GtkFileChooserNative *chooser = gtk_file_chooser_native_new(
1220 _("select preset(s) to import"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
1221 _("_open"), _("_cancel"));
1222
1223 dt_conf_get_folder_to_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(chooser));
1224 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE);
1225
1226 GtkFileFilter *filter;
1227 filter = GTK_FILE_FILTER(gtk_file_filter_new());
1228 gtk_file_filter_add_pattern(filter, "*.dtpreset");
1229 gtk_file_filter_add_pattern(filter, "*.DTPRESET");
1230 gtk_file_filter_set_name(filter, _("Ansel preset files"));
1231 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
1232
1233 filter = GTK_FILE_FILTER(gtk_file_filter_new());
1234 gtk_file_filter_add_pattern(filter, "*");
1235 gtk_file_filter_set_name(filter, _("all files"));
1236
1237 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
1238
1239 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1240 {
1241 GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chooser));
1242 g_slist_foreach(filenames, (GFunc)_import_preset_from_file, NULL);
1243 g_slist_free_full(filenames, dt_free_gpointer);
1244
1245 GtkTreeStore *tree_store = GTK_TREE_STORE(model);
1246 gtk_tree_store_clear(tree_store);
1247 tree_insert_presets(tree_store);
1248
1249 dt_conf_set_folder_from_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(chooser));
1250 }
1251 g_object_unref(chooser);
1252}
1253
1254static void export_preset(GtkButton *button, gpointer data)
1255{
1256 GtkWindow *win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
1257
1258 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1259 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1260 _("_save"), _("_cancel"));
1261
1262 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1263
1264 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1265 {
1266 gchar *filedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1267 sqlite3_stmt *stmt;
1268
1269 // we have n+1 selects for saving presets, using single transaction for whole process saves us microlocks
1271
1273 "SELECT rowid, name, operation FROM data.presets WHERE writeprotect = 0",
1274 -1, &stmt, NULL);
1275
1276 while(sqlite3_step(stmt) == SQLITE_ROW)
1277 {
1278 const gint rowid = sqlite3_column_int(stmt, 0);
1279 const gchar *name = (gchar *)sqlite3_column_text(stmt, 1);
1280 const gchar *operation = (gchar *)sqlite3_column_text(stmt, 2);
1281 gchar* preset_name = g_strdup_printf("%s_%s", operation, name);
1282
1283 dt_presets_save_to_file(rowid, preset_name, filedir);
1284
1285 dt_free(preset_name);
1286 }
1287
1288 sqlite3_finalize(stmt);
1289
1291
1292 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1293
1294 dt_free(filedir);
1295 }
1296 g_object_unref(filechooser);
1297}
1298
1299// Custom sort function for TreeModel entries for presets list
1300static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
1301{
1302 gchar *a_text;
1303 gchar *b_text;
1304
1305 gtk_tree_model_get(model, a, P_MODULE_COLUMN, &a_text, -1);
1306 gtk_tree_model_get(model, b, P_MODULE_COLUMN, &b_text, -1);
1307 if(*a_text == '\0' && *b_text == '\0')
1308 {
1309 dt_free(a_text);
1310 dt_free(b_text);
1311
1312 gtk_tree_model_get(model, a, P_NAME_COLUMN, &a_text, -1);
1313 gtk_tree_model_get(model, b, P_NAME_COLUMN, &b_text, -1);
1314 }
1315
1316 const int res = strcoll(a_text, b_text);
1317
1318 dt_free(a_text);
1319 dt_free(b_text);
1320
1321 return res;
1322}
1323
1325{
1326 GtkTreeStore *tree_store = GTK_TREE_STORE(gtk_tree_view_get_model((GtkTreeView *)g->data));
1327 gtk_tree_store_clear(tree_store);
1328 tree_insert_presets(tree_store);
1329}
1330
1331static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module)
1332{
1333 dt_gui_presets_show_edit_dialog(name, module, rowid, G_CALLBACK(edit_preset_response), tree, FALSE, TRUE, TRUE,
1334 GTK_WINDOW(_preferences_dialog));
1335}
1336
1337static void
1339{
1340 dt_conf_set_bool((char *)data, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
1341}
1342
1344{
1345 const char *key = gtk_widget_get_name(widget);
1346 const gboolean def = dt_confgen_get_bool(key, DT_DEFAULT);
1347 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), def);
1348}
1349
1350static gboolean
1351_gui_preferences_bool_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1352{
1353 if(event->type == GDK_2BUTTON_PRESS)
1354 {
1356 return TRUE;
1357 }
1358 return FALSE;
1359}
1360
1362{
1363 const char *key = gtk_widget_get_name(widget);
1364 const gboolean val = dt_conf_get_bool(key);
1365 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), val);
1366}
1367
1368GtkWidget *dt_gui_preferences_bool(GtkGrid *grid, const char *key, const guint col,
1369 const guint line, const gboolean swap)
1370{
1372 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1373 GtkWidget *labelev = gtk_event_box_new();
1374 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1375 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1376 GtkWidget *w = gtk_check_button_new();
1377 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), dt_conf_get_bool(key));
1378 gtk_grid_attach(GTK_GRID(grid), labelev, swap ? (col + 1) : col, line, 1, 1);
1379 gtk_grid_attach(GTK_GRID(grid), w, swap ? col : (col + 1), line, 1, 1);
1380 g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(_gui_preferences_bool_callback), (gpointer)key);
1381 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_bool_reset), (gpointer)w);
1382 return w;
1383}
1384
1385static void
1387{
1388 dt_conf_set_int((char *)data, gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
1389}
1390
1392{
1393 const char *key = gtk_widget_get_name(widget);
1394 const int def = dt_confgen_get_int(key, DT_DEFAULT);
1395 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), def);
1396}
1397
1398static gboolean
1399_gui_preferences_int_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1400{
1401 if(event->type == GDK_2BUTTON_PRESS)
1402 {
1404 return TRUE;
1405 }
1406 return FALSE;
1407}
1408
1410{
1411 const char *key = gtk_widget_get_name(widget);
1412 const int val = dt_conf_get_int(key);
1413 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), val);
1414}
1415
1416GtkWidget *dt_gui_preferences_int(GtkGrid *grid, const char *key, const guint col,
1417 const guint line)
1418{
1420 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1421 GtkWidget *labelev = gtk_event_box_new();
1422 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1423 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1424 gint min = MAX(G_MININT, dt_confgen_get_int(key, DT_MIN));
1425 gint max = MIN(G_MAXINT, dt_confgen_get_int(key, DT_MAX));
1426 GtkWidget *w = gtk_spin_button_new_with_range(min, max, 1.0);
1427 gtk_widget_set_hexpand(w, FALSE);
1428 gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 0);
1429 gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), dt_conf_get_int(key));
1430 gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1431 gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
1432 g_signal_connect(G_OBJECT(w), "value-changed", G_CALLBACK(_gui_preferences_int_callback), (gpointer)key);
1433 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_int_reset), (gpointer)w);
1434 return w;
1435}
1436
1437static void
1439{
1440 GtkTreeIter iter;
1441 if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
1442 {
1443 gchar *s = NULL;
1444 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)), &iter, 0, &s, -1);
1445 dt_conf_set_string((char *)data, s);
1446 dt_free(s);
1447 }
1448}
1449
1450void _gui_preferences_enum_set(GtkWidget *widget, const char *str)
1451{
1452 GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
1453 GtkTreeIter iter;
1454 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1455 gint i = 0;
1456 gboolean found = FALSE;
1457 while(valid)
1458 {
1459 char *value;
1460 gtk_tree_model_get(model, &iter, 0, &value, -1);
1461 if(!g_strcmp0(value, str))
1462 {
1463 dt_free(value);
1464 found = TRUE;
1465 break;
1466 }
1467 i++;
1468 dt_free(value);
1469 valid = gtk_tree_model_iter_next(model, &iter);
1470 }
1471 if(found)
1472 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), i);
1473}
1474
1476{
1477 const char *key = gtk_widget_get_name(widget);
1478 const char *str = dt_confgen_get(key, DT_DEFAULT);
1479 _gui_preferences_enum_set(widget, str);
1480}
1481
1482static gboolean
1483_gui_preferences_enum_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1484{
1485 if(event->type == GDK_2BUTTON_PRESS)
1486 {
1488 return TRUE;
1489 }
1490 return FALSE;
1491}
1492
1494{
1495 const char *key = gtk_widget_get_name(widget);
1496 char *str = dt_conf_get_string(key);
1497 _gui_preferences_enum_set(widget, str);
1498 dt_free(str);
1499}
1500
1501GtkWidget *dt_gui_preferences_enum(GtkGrid *grid, const char *key, const guint col,
1502 const guint line)
1503{
1505 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1506 GtkWidget *labelev = gtk_event_box_new();
1507 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1508 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1509
1510 GtkTreeIter iter;
1511 GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
1512 gchar *str = dt_conf_get_string(key);
1513 const char *values = dt_confgen_get(key, DT_VALUES);
1514 gint i = 0;
1515 gint pos = -1;
1516 GList *vals = dt_util_str_to_glist("][", values);
1517 for(GList *val = vals; val; val = g_list_next(val))
1518 {
1519 char *item = (char *)val->data;
1520 // remove remaining [ or ]
1521 if(item[0] == '[') item++;
1522 else if(item[strlen(item) - 1] == ']') item[strlen(item) - 1] = '\0';
1523 gtk_list_store_append(store, &iter);
1524 gtk_list_store_set(store, &iter, 0, item, 1, g_dpgettext2(NULL, "preferences", item), -1);
1525 if(pos == -1 && !g_strcmp0(str, item))
1526 {
1527 pos = i;
1528 }
1529 i++;
1530 }
1531 g_list_free_full(vals, dt_free_gpointer);
1532 vals = NULL;
1533 dt_free(str);
1534
1535 GtkWidget *w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
1536 gtk_widget_set_hexpand(w, FALSE);
1537 g_object_unref(store);
1538 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
1539 gtk_cell_renderer_set_padding(renderer, 0, 0);
1540 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), renderer, TRUE);
1541 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), renderer, "text", 1, NULL);
1542 gtk_combo_box_set_active(GTK_COMBO_BOX(w), pos);
1543
1544 gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1545 gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
1546 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_gui_preferences_enum_callback), (gpointer)key);
1547 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_enum_reset), (gpointer)w);
1548 return w;
1549}
1550
1551static void
1553{
1554 const char *str = gtk_entry_get_text(GTK_ENTRY(widget));
1555 dt_conf_set_string((char *)data, str);
1556}
1557
1559{
1560 const char *key = gtk_widget_get_name(widget);
1561 const char *str = dt_confgen_get(key, DT_DEFAULT);
1562 gtk_entry_set_text(GTK_ENTRY(widget), str);
1563}
1564
1565static gboolean
1566_gui_preferences_string_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1567{
1568 if(event->type == GDK_2BUTTON_PRESS)
1569 {
1571 return TRUE;
1572 }
1573 return FALSE;
1574}
1575
1577{
1578 const char *key = gtk_widget_get_name(widget);
1579 const char *str = dt_conf_get_string_const(key);
1580 gtk_entry_set_text(GTK_ENTRY(widget), str);
1581}
1582
1583GtkWidget *dt_gui_preferences_string(GtkGrid *grid, const char *key, const guint col,
1584 const guint line)
1585{
1587 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1588 GtkWidget *labelev = gtk_event_box_new();
1589 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1590 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1591
1592 GtkWidget *w = gtk_entry_new();
1594 const char *str = dt_conf_get_string_const(key);
1595 gtk_entry_set_text(GTK_ENTRY(w), str);
1596 gtk_widget_set_hexpand(w, TRUE);
1597
1598 gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1599 gtk_grid_attach(GTK_GRID(grid), w, col, line + 1, 1, 1);
1600 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_gui_preferences_string_callback), (gpointer)key);
1601 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_string_reset), (gpointer)w);
1602 return w;
1603}
1604
1605// clang-format off
1606// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1607// vim: shiftwidth=2 expandtab tabstop=2 cindent
1608// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1609// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint32_t container(dt_lib_module_t *self)
void dt_bauhaus_load_theme(dt_bauhaus_t *bauhaus)
Definition bauhaus.c:1290
int height
Definition bilateral.h:1
static const float const float const float min
const float max
const float top
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
char * name
int dt_presets_import_from_file(const char *preset_path)
void dt_presets_save_to_file(const int rowid, const char *preset_name, const char *filedir)
gboolean dt_presets_module_can_autoapply(const gchar *operation)
@ DT_DEFAULT
Definition conf.h:96
@ DT_MAX
Definition conf.h:98
@ DT_MIN
Definition conf.h:97
@ DT_VALUES
Definition conf.h:99
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
const char * dt_confgen_get_tooltip(const char *name)
gchar * dt_conf_get_string(const char *name)
void dt_conf_set_int(const char *name, int val)
gboolean dt_confgen_get_bool(const char *name, dt_confgen_value_kind_t kind)
int dt_conf_get_int(const char *name)
int dt_confgen_get_int(const char *name, dt_confgen_value_kind_t kind)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
const char * dt_confgen_get(const char *name, dt_confgen_value_kind_t kind)
void dt_conf_set_folder_from_file_chooser(const char *name, GtkFileChooser *chooser)
const char * dt_confgen_get_label(const char *name)
gboolean dt_conf_get_folder_to_file_chooser(const char *name, GtkFileChooser *chooser)
void dt_control_log(const char *msg,...)
Definition control.c:777
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_DEV
Definition darktable.h:739
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define PATH_MAX
Definition darktable.h:1189
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3653
#define dt_database_start_transaction(db)
Definition database.h:77
#define dt_database_release_transaction(db)
Definition database.h:78
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int32_t imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_export_metadata_t *metadata)
Definition disk.c:252
static void dt_draw_cairo_to_gdk_pixbuf(uint8_t *data, unsigned int width, unsigned int height)
Definition draw.h:464
void dtgtk_cairo_paint_lock(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_check_mark(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dt_loc_get_datadir(char *datadir, size_t bufsize)
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
void dt_gui_search_stop(GtkSearchEntry *entry, GtkWidget *widget)
Definition gtk.c:3248
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition gtk.c:2919
void dt_gui_load_theme(const char *theme)
Definition gtk.c:2239
void dt_configure_ppd_dpi(dt_gui_gtk_t *gui)
Definition gtk.c:1429
gboolean dt_gui_search_start(GtkWidget *widget, GdkEventKey *event, GtkSearchEntry *entry)
Definition gtk.c:3237
void dt_gui_refocus_parent(GtkWindow *parent)
Definition gtk.c:1936
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3473
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:479
void dt_gui_presets_show_edit_dialog(const char *name_in, const char *module_name, int rowid, GCallback final_callback, gpointer data, gboolean allow_name_change, gboolean allow_desc_change, gboolean allow_remove, GtkWindow *parent)
void dt_gui_presets_confirm_and_delete(GtkWidget *parent_dialog, const char *name, const char *module_name, int rowid)
static gboolean enable(dt_image_t *image)
Definition highlights.c:653
float iso_max
const char * maker
int iso_min
const char * model
const char * dt_l10n_get_name(const dt_l10n_language_t *language)
Definition l10n.c:448
gchar * dt_lib_get_localized_name(const gchar *plugin_name)
Definition lib.c:1439
static void swap(float *x, float *y)
Definition lightroom.c:1022
float *const restrict const size_t k
int dt_opencl_set_detected_device_enabled(const int detected, const gboolean enabled)
Definition opencl.c:339
gboolean dt_opencl_detected_device_pinned_memory(const int detected)
Definition opencl.c:371
int dt_opencl_set_detected_device_headroom(const int detected, const size_t headroom)
Definition opencl.c:417
int dt_opencl_get_detected_device_count(void)
Definition opencl.c:310
int dt_opencl_set_detected_device_pinned_memory(const int detected, const gboolean enabled)
Definition opencl.c:384
const dt_opencl_detected_device_t * dt_opencl_get_detected_device(const int detected)
Definition opencl.c:318
size_t dt_opencl_detected_device_headroom(const int detected)
Definition opencl.c:405
gboolean dt_opencl_detected_device_enabled(const int detected)
Definition opencl.c:326
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
static void _resize_dialog(GtkWidget *widget)
static void cairo_destroy_from_pixbuf(guchar *pixels, gpointer data)
static gboolean _gui_preferences_bool_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
static void tree_selection_changed(GtkTreeSelection *selection, gpointer data)
static void _opencl_device_pinned_memory_callback(GtkToggleButton *button, gpointer user_data)
static void _gui_preferences_bool_callback(GtkWidget *widget, gpointer data)
void dt_gui_preferences_enum_reset(GtkWidget *widget)
void dt_gui_preferences_string_reset(GtkWidget *widget)
static void _gui_preferences_enum_callback(GtkWidget *widget, gpointer data)
const int dt_gui_presets_exposure_value_cnt
Definition gui/presets.c:78
void dt_gui_preferences_show()
static gboolean tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
static void language_callback(GtkWidget *widget, gpointer user_data)
static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data)
static void use_sys_font_callback(GtkWidget *widget, gpointer user_data)
static void dpi_scaling_changed_callback(GtkWidget *widget, gpointer user_data)
void dt_gui_preferences_string_update(GtkWidget *widget)
static void save_usercss(GtkTextBuffer *buffer)
static void load_themes_dir(const char *basedir)
const float dt_gui_presets_aperture_value[]
Definition gui/presets.c:89
static void usercss_dialog_callback(GtkDialog *dialog, gint response_id, gpointer user_data)
const int dt_gui_presets_aperture_value_cnt
Definition gui/presets.c:87
static void _opencl_device_enabled_callback(GtkToggleButton *button, gpointer user_data)
Definition preferences.c:94
static GtkWidget * _preferences_dialog
static void import_preset(GtkButton *button, gpointer data)
static void edit_preset_response(dt_gui_presets_edit_dialog_t *g)
void dt_gui_preferences_int_update(GtkWidget *widget)
static void init_tab_presets(GtkWidget *stack)
static void _gui_preferences_int_callback(GtkWidget *widget, gpointer data)
const char * dt_gui_presets_exposure_value_str[]
Definition gui/presets.c:84
static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data)
static void export_preset(GtkButton *button, gpointer data)
static void _gui_preferences_string_callback(GtkWidget *widget, gpointer data)
static void reload_ui_last_theme(void)
static gboolean reset_language_widget(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
static void theme_callback(GtkWidget *widget, gpointer user_data)
static void init_tab_general(GtkWidget *dialog, GtkWidget *stack, dt_gui_themetweak_widgets_t *tw)
static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
void dt_gui_preferences_bool_reset(GtkWidget *widget)
void dt_gui_preferences_int_reset(GtkWidget *widget)
void _gui_preferences_enum_set(GtkWidget *widget, const char *str)
static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module)
static gboolean _gui_preferences_string_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
const char * dt_gui_presets_aperture_value_str[]
Definition gui/presets.c:91
void dt_gui_preferences_enum_update(GtkWidget *widget)
static void save_usercss_callback(GtkWidget *widget, gpointer user_data)
GtkWidget * dt_gui_preferences_int(GtkGrid *grid, const char *key, const guint col, const guint line)
const float dt_gui_presets_exposure_value[]
Definition gui/presets.c:80
static gboolean _gui_preferences_enum_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
static void font_size_changed_callback(GtkWidget *widget, gpointer user_data)
static void tree_insert_presets(GtkTreeStore *tree_model)
GtkWidget * dt_gui_preferences_string(GtkGrid *grid, const char *key, const guint col, const guint line)
#define ICON_SIZE
Definition preferences.c:78
static gboolean _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
static void load_themes(void)
static void usercss_callback(GtkWidget *widget, gpointer user_data)
GtkWidget * dt_gui_preferences_enum(GtkGrid *grid, const char *key, const guint col, const guint line)
GtkWidget * dt_gui_preferences_bool(GtkGrid *grid, const char *key, const guint col, const guint line, const gboolean swap)
@ P_N_COLUMNS
@ P_MAKER_COLUMN
@ P_ROWID_COLUMN
@ P_LENS_COLUMN
@ P_EXPOSURE_COLUMN
@ P_MODEL_COLUMN
@ P_MODULE_COLUMN
@ P_OPERATION_COLUMN
@ P_APERTURE_COLUMN
@ P_FOCAL_LENGTH_COLUMN
@ P_EDITABLE_COLUMN
@ P_ISO_COLUMN
@ P_AUTOAPPLY_COLUMN
@ P_NAME_COLUMN
static void _import_preset_from_file(const gchar *filename)
void dt_gui_preferences_bool_update(GtkWidget *widget)
static gboolean _gui_preferences_int_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
static void _opencl_device_headroom_callback(GtkSpinButton *button, gpointer user_data)
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_PREFERENCES_CHANGE
This signal is raised after preferences have been changed no parameters no return.
Definition signal.h:273
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_l10n_t * l10n
Definition darktable.h:817
struct dt_gui_gtk_t * gui
Definition darktable.h:803
const struct dt_database_t * db
Definition darktable.h:807
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
GList * themes
Definition darktable.h:857
dt_ui_t * ui
Definition gtk.h:165
char * code
Definition l10n.h:29
int selected
Definition l10n.h:37
int sys_default
Definition l10n.h:38
GList * languages
Definition l10n.h:36
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:830