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
102static void _opencl_device_pinned_memory_callback(GtkToggleButton *button, gpointer user_data)
103{
104 const int detected = GPOINTER_TO_INT(user_data);
105 const gboolean pinned_memory = gtk_toggle_button_get_active(button);
106 if(dt_opencl_set_detected_device_pinned_memory(detected, pinned_memory) == 0)
107 restart_required = TRUE;
108}
109
110static void _opencl_device_headroom_callback(GtkSpinButton *button, gpointer user_data)
111{
112 const int detected = GPOINTER_TO_INT(user_data);
113 const size_t headroom = gtk_spin_button_get_value_as_int(button);
114 if(dt_opencl_set_detected_device_headroom(detected, headroom) == 0)
115 restart_required = TRUE;
116}
117
118// Values for the accelerators/presets treeview
119
120enum
121{
137
138static void init_tab_presets(GtkWidget *stack);
139static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data);
140static void import_preset(GtkButton *button, gpointer data);
141static void export_preset(GtkButton *button, gpointer data);
142
143// Signal handlers
144static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
145 gpointer data);
146static void tree_selection_changed(GtkTreeSelection *selection, gpointer data);
147static gboolean tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data);
148static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data);
149
150static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module);
151
153
155
156static void load_themes_dir(const char *basedir)
157{
158 char *themes_dir = g_build_filename(basedir, "themes", NULL);
159 GDir *dir = g_dir_open(themes_dir, 0, NULL);
160 if(dir)
161 {
162 dt_print(DT_DEBUG_DEV, "adding themes directory: %s\n", themes_dir);
163
164 const gchar *d_name;
165 while((d_name = g_dir_read_name(dir)))
166 darktable.themes = g_list_append(darktable.themes, g_strdup(d_name));
167 g_dir_close(dir);
168 }
169 dt_free(themes_dir);
170}
171
172static void load_themes(void)
173{
174 // Clear theme list...
175 g_list_free_full(darktable.themes, dt_free_gpointer);
176 darktable.themes = NULL;
177
178 // check themes dirs
179 gchar configdir[PATH_MAX] = { 0 };
180 gchar datadir[PATH_MAX] = { 0 };
181 dt_loc_get_datadir(datadir, sizeof(datadir));
182 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
183
184 load_themes_dir(datadir);
185 load_themes_dir(configdir);
186}
187
188static void reload_ui_last_theme(void)
189{
190 const char *theme = dt_conf_get_string_const("ui_last/theme");
191 dt_gui_load_theme(theme);
193}
194
195static void theme_callback(GtkWidget *widget, gpointer user_data)
196{
197 const int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
198 gchar *theme = g_list_nth(darktable.themes, selected)->data;
199 gchar *i = g_strrstr(theme, ".");
200 if(i) *i = '\0';
201 dt_gui_load_theme(theme);
203}
204
205static void usercss_callback(GtkWidget *widget, gpointer user_data)
206{
207 dt_conf_set_bool("themes/usercss", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
209}
210
211static void font_size_changed_callback(GtkWidget *widget, gpointer user_data)
212{
213 dt_conf_set_float("font_size", gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
215}
216
217static void dpi_scaling_changed_callback(GtkWidget *widget, gpointer user_data)
218{
219 float dpi = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
220 if(dpi > 0.0) dpi = fmax(64, dpi); // else <= 0 -> use system default
221 dt_conf_set_float("screen_dpi_overwrite", dpi);
222 restart_required = TRUE;
225}
226
227static void use_sys_font_callback(GtkWidget *widget, gpointer user_data)
228{
229 dt_conf_set_bool("use_system_font", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
230 if(dt_conf_get_bool("use_system_font"))
231 gtk_widget_set_state_flags(GTK_WIDGET(user_data), GTK_STATE_FLAG_INSENSITIVE, TRUE);
232 else
233 gtk_widget_set_state_flags(GTK_WIDGET(user_data), GTK_STATE_FLAG_NORMAL, TRUE);
234
236}
237
238static void save_usercss(GtkTextBuffer *buffer)
239{
240 //get file locations
241 char usercsspath[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
242 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
243 g_snprintf(usercsspath, sizeof(usercsspath), "%s/user.css", configdir);
244
245 //get the text
246 GtkTextIter start, end;
247 gtk_text_buffer_get_start_iter(buffer, &start);
248 gtk_text_buffer_get_end_iter(buffer, &end);
249 gchar *usercsscontent = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
250
251 //write to file
252 GError *error = NULL;
253 if(!g_file_set_contents(usercsspath, usercsscontent, -1, &error))
254 {
255 fprintf(stderr, "%s: error saving css to %s: %s\n", G_STRFUNC, usercsspath, error->message);
256 g_clear_error(&error);
257 }
258 dt_free(usercsscontent);
259}
260
261static void save_usercss_callback(GtkWidget *widget, gpointer user_data)
262{
264 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->css_text_view));
265
266 save_usercss(buffer);
267
268 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tw->apply_toggle)))
269 {
270 //reload the theme
272 }
273 else
274 {
275 //toggle the apply button, which will also reload the theme
276 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tw->apply_toggle), TRUE);
277 }
278}
279
280static void usercss_dialog_callback(GtkDialog *dialog, gint response_id, gpointer user_data)
281{
282 //just save the latest css but don't reload the theme
284 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->css_text_view));
285 save_usercss(buffer);
286}
287
289
290static void language_callback(GtkWidget *widget, gpointer user_data)
291{
292 int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
293 dt_l10n_language_t *language = (dt_l10n_language_t *)g_list_nth_data(darktable.l10n->languages, selected);
294 if(darktable.l10n->sys_default == selected)
295 {
296 dt_conf_set_string("ui_last/gui_language", "");
298 }
299 else
300 {
301 dt_conf_set_string("ui_last/gui_language", language->code);
302 darktable.l10n->selected = selected;
303 }
304 restart_required = TRUE;
305}
306
307static gboolean reset_language_widget(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
308{
309 if(event->type == GDK_2BUTTON_PRESS)
310 {
311 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), darktable.l10n->sys_default);
312 return TRUE;
313 }
314 return FALSE;
315}
316
318{
319
320 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
321 GtkWidget *grid = gtk_grid_new();
322 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
323 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
324 gtk_widget_set_valign(grid, GTK_ALIGN_START);
325 int line = 0;
326
327 gtk_box_pack_start(GTK_BOX(container), grid, FALSE, FALSE, 0);
328
329 gtk_stack_add_titled(GTK_STACK(stack), container, _("general"), _("general"));
330
331 // language
332
333 GtkWidget *label = gtk_label_new(_("interface language"));
334 gtk_widget_set_halign(label, GTK_ALIGN_START);
335 GtkWidget *labelev = gtk_event_box_new();
336 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
337 gtk_container_add(GTK_CONTAINER(labelev), label);
338 GtkWidget *widget = gtk_combo_box_text_new();
339
340 for(GList *iter = darktable.l10n->languages; iter; iter = g_list_next(iter))
341 {
342 const char *name = dt_l10n_get_name(iter->data);
343 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), name);
344 }
345
346 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), darktable.l10n->selected);
347 g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(language_callback), 0);
348 gtk_widget_set_tooltip_text(labelev, _("double-click to reset to the system language"));
349 gtk_event_box_set_visible_window(GTK_EVENT_BOX(labelev), FALSE);
350 gtk_widget_set_tooltip_text(widget, _("set the language of the user interface. the system default is marked with an * (needs a restart)"));
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 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_language_widget), (gpointer)widget);
354
355 // theme
356
357 load_themes();
358
359 label = gtk_label_new(_("theme"));
360 gtk_widget_set_halign(label, GTK_ALIGN_START);
361 widget = gtk_combo_box_text_new();
362 labelev = gtk_event_box_new();
363 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
364 gtk_container_add(GTK_CONTAINER(labelev), label);
365 gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
366 gtk_grid_attach_next_to(GTK_GRID(grid), widget, labelev, GTK_POS_RIGHT, 1, 1);
367
368 // read all themes
369 char *theme_name = dt_conf_get_string("ui_last/theme");
370 int selected = 0;
371 int k = 0;
372 for(GList *iter = darktable.themes; iter; iter = g_list_next(iter))
373 {
374 gchar *name = g_strdup((gchar*)(iter->data));
375 // remove extension
376 gchar *i = g_strrstr(name, ".");
377 if(i) *i = '\0';
378 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), name);
379 if(!g_strcmp0(name, theme_name)) selected = k;
380 g_free(name);
381 k++;
382 }
383 dt_free(theme_name);
384
385 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), selected);
386
387 g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(theme_callback), 0);
388 gtk_widget_set_tooltip_text(widget, _("set the theme for the user interface"));
389
390 //Font size check and spin buttons
391 GtkWidget *usesysfont = gtk_check_button_new();
392 GtkWidget *fontsize = gtk_spin_button_new_with_range(5.0f, 30.0f, 0.2f);
393 int i = dt_conf_get_bool("font_prefs_align_right") ? gtk_widget_set_hexpand(fontsize, TRUE), 2 : 0;
394
395 //checkbox to use system font size
396 if(dt_conf_get_bool("use_system_font"))
397 gtk_widget_set_state_flags(fontsize, GTK_STATE_FLAG_INSENSITIVE, TRUE);
398 else
399 gtk_widget_set_state_flags(fontsize, GTK_STATE_FLAG_NORMAL, TRUE);
400
401 label = gtk_label_new(_("use system font size"));
402 gtk_widget_set_halign(label, GTK_ALIGN_START);
403 labelev = gtk_event_box_new();
404 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
405 gtk_container_add(GTK_CONTAINER(labelev), label);
406 gtk_grid_attach(GTK_GRID(grid), labelev, i, i?2:line++, 1, 1);
407 gtk_grid_attach_next_to(GTK_GRID(grid), usesysfont, labelev, GTK_POS_RIGHT, 1, 1);
408 gtk_widget_set_tooltip_text(usesysfont, _("use system font size"));
409 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(usesysfont), dt_conf_get_bool("use_system_font"));
410 g_signal_connect(G_OBJECT(usesysfont), "toggled", G_CALLBACK(use_sys_font_callback), (gpointer)fontsize);
411
412
413 //font size selector
414 if(dt_conf_get_float("font_size") < 5.0f || dt_conf_get_float("font_size") > 20.0f)
415 dt_conf_set_float("font_size", 12.0f);
416
417 label = gtk_label_new(_("font size in points"));
418 gtk_widget_set_halign(label, GTK_ALIGN_START);
419 labelev = gtk_event_box_new();
420 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
421 gtk_container_add(GTK_CONTAINER(labelev), label);
422 gtk_grid_attach(GTK_GRID(grid), labelev, i, i?0:line++, 1, 1);
423 gtk_grid_attach_next_to(GTK_GRID(grid), fontsize, labelev, GTK_POS_RIGHT, 1, 1);
424 gtk_widget_set_tooltip_text(fontsize, _("font size in points"));
425 gtk_spin_button_set_value(GTK_SPIN_BUTTON(fontsize), dt_conf_get_float("font_size"));
426 g_signal_connect(G_OBJECT(fontsize), "value_changed", G_CALLBACK(font_size_changed_callback), 0);
427
428 GtkWidget *screen_dpi_overwrite = gtk_spin_button_new_with_range(-1.0f, 360, 1.f);
429 label = gtk_label_new(_("GUI controls and text DPI"));
430 gtk_widget_set_halign(label, GTK_ALIGN_START);
431 labelev = gtk_event_box_new();
432 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
433 gtk_container_add(GTK_CONTAINER(labelev), label);
434 gtk_grid_attach(GTK_GRID(grid), labelev, i, i?1:line++, 1, 1);
435 gtk_grid_attach_next_to(GTK_GRID(grid), screen_dpi_overwrite, labelev, GTK_POS_RIGHT, 1, 1);
436 gtk_widget_set_tooltip_text(screen_dpi_overwrite, _("adjust the global GUI resolution to rescale controls, buttons, labels, etc.\n"
437 "increase for a magnified GUI, decrease to fit more content in window.\n"
438 "set to -1 to use the system-defined global resolution.\n"
439 "default is 96 DPI on most systems.\n"
440 "(needs a restart)."));
441 gtk_spin_button_set_value(GTK_SPIN_BUTTON(screen_dpi_overwrite), dt_conf_get_float("screen_dpi_overwrite"));
442 g_signal_connect(G_OBJECT(screen_dpi_overwrite), "value_changed", G_CALLBACK(dpi_scaling_changed_callback), 0);
443
444 //checkbox to allow user to modify theme with user.css
445 label = gtk_label_new(_("modify selected theme with CSS tweaks below"));
446 gtk_widget_set_halign(label, GTK_ALIGN_START);
447 tw->apply_toggle = gtk_check_button_new();
448 labelev = gtk_event_box_new();
449 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
450 gtk_container_add(GTK_CONTAINER(labelev), label);
451 gtk_grid_attach(GTK_GRID(grid), labelev, 0, line++, 1, 1);
452 gtk_grid_attach_next_to(GTK_GRID(grid), tw->apply_toggle, labelev, GTK_POS_RIGHT, 1, 1);
453 gtk_widget_set_tooltip_text(tw->apply_toggle, _("modify theme with CSS keyed below (saved to user.css)"));
454 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tw->apply_toggle), dt_conf_get_bool("themes/usercss"));
455 g_signal_connect(G_OBJECT(tw->apply_toggle), "toggled", G_CALLBACK(usercss_callback), 0);
456
457 //scrollable textarea with save button to allow user to directly modify user.css file
458 GtkWidget *usercssbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
459 gtk_box_pack_start(GTK_BOX(container), usercssbox, TRUE, TRUE, 0);
460 gtk_widget_set_name(usercssbox, "usercss-box");
461
462 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
463 tw->css_text_view = gtk_text_view_new_with_buffer(buffer);
465 dt_gui_textview_set_padding(GTK_TEXT_VIEW(tw->css_text_view));
466 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tw->css_text_view), GTK_WRAP_WORD);
467 dt_gui_add_class(tw->css_text_view, "dt_monospace");
468 gtk_widget_set_hexpand(tw->css_text_view, TRUE);
469 gtk_widget_set_halign(tw->css_text_view, GTK_ALIGN_FILL);
470
471 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
472 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
473 gtk_container_add(GTK_CONTAINER(scroll), tw->css_text_view);
474 gtk_box_pack_start(GTK_BOX(usercssbox), scroll, TRUE, TRUE, 0);
475
476 tw->save_button = gtk_button_new_with_label(C_("usercss", "save CSS and apply"));
477 g_signal_connect(G_OBJECT(tw->save_button), "clicked", G_CALLBACK(save_usercss_callback), tw);
478 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(usercss_dialog_callback), tw);
479 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
480 gtk_box_pack_end(GTK_BOX(hbox), tw->save_button, FALSE, TRUE, 0);
481 gtk_box_pack_start(GTK_BOX(usercssbox), hbox, FALSE, FALSE, 0);
482 gtk_widget_set_tooltip_text(tw->save_button, _("click to save and apply the CSS tweaks entered in this editor"));
483
484 //set textarea text from file or default
485 char usercsspath[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
486 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
487 g_snprintf(usercsspath, sizeof(usercsspath), "%s/user.css", configdir);
488
489 if(g_file_test(usercsspath, G_FILE_TEST_EXISTS))
490 {
491 gchar *usercsscontent = NULL;
492 //load file into buffer
493 if(g_file_get_contents(usercsspath, &usercsscontent, NULL, NULL))
494 {
495 gtk_text_buffer_set_text(buffer, usercsscontent, -1);
496 }
497 else
498 {
499 //load default text with some pointers
500 gchar *errtext = g_strconcat("/* ", _("ERROR Loading user.css"), " */", NULL);
501 gtk_text_buffer_set_text(buffer, errtext, -1);
502 dt_free(errtext);
503 }
504 dt_free(usercsscontent);
505 }
506 else
507 {
508 //load default text
509 gchar *deftext = g_strconcat("/* ", _("Enter CSS theme tweaks here"), " */\n\n", NULL);
510 gtk_text_buffer_set_text(buffer, deftext, -1);
511 dt_free(deftext);
512 }
513
514}
515
517
518#if 0
519// FIXME! this makes some systems hang forever. I don't reproduce.
520gboolean preferences_window_deleted(GtkWidget *widget, GdkEvent *event, gpointer data)
521{
522 // redraw the whole UI in case sizes have changed
523 gtk_widget_queue_resize(dt_ui_center(darktable.gui->ui));
524 gtk_widget_queue_resize(dt_ui_main_window(darktable.gui->ui));
525
526 gtk_widget_queue_draw(dt_ui_main_window(darktable.gui->ui));
527 gtk_widget_queue_draw(dt_ui_center(darktable.gui->ui));
528
529 gtk_widget_hide(widget);
530 return TRUE;
531}
532#endif
533
534static void _resize_dialog(GtkWidget *widget)
535{
536 GtkAllocation allocation;
537 gtk_widget_get_allocation(widget, &allocation);
538 dt_conf_set_int("ui_last/preferences_dialog_width", allocation.width);
539 dt_conf_set_int("ui_last/preferences_dialog_height", allocation.height);
540}
541
543{
544 GtkWindow *win = GTK_WINDOW(dt_ui_main_window(darktable.gui->ui));
545 _preferences_dialog = gtk_dialog_new_with_buttons(_("Ansel preferences"), win,
546 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
547 NULL, NULL);
548#if 0
549 // FIXME! this makes some systems hang forever. I don't reproduce.
550 g_signal_connect(G_OBJECT(_preferences_dialog), "delete-event", G_CALLBACK(preferences_window_deleted), NULL);
551#endif
552
553 gtk_window_set_default_size(GTK_WINDOW(_preferences_dialog),
554 dt_conf_get_int("ui_last/preferences_dialog_width"),
555 dt_conf_get_int("ui_last/preferences_dialog_height"));
556 g_signal_connect(G_OBJECT(_preferences_dialog), "check-resize", G_CALLBACK(_resize_dialog), NULL);
557#ifdef GDK_WINDOWING_QUARTZ
559#endif
560 gtk_window_set_position(GTK_WINDOW(_preferences_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
561 gtk_widget_set_name(_preferences_dialog, "preferences-notebook");
562
563 //grab the content area of the dialog
564 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(_preferences_dialog));
565 gtk_widget_set_name(content, "preferences-content");
566 gtk_container_set_border_width(GTK_CONTAINER(content), 0);
567
568 //place a box in the content area
569 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
570 gtk_widget_set_name(box, "preferences-box");
571 gtk_container_set_border_width(GTK_CONTAINER(box), 0);
572 gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
573
574 //create stack and sidebar and pack into the box
575 GtkWidget *stack = gtk_stack_new();
576 GtkWidget *stacksidebar = gtk_stack_sidebar_new();
577 gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(stacksidebar), GTK_STACK(stack));
578 gtk_box_pack_start(GTK_BOX(box), stacksidebar, FALSE, FALSE, 0);
579 gtk_box_pack_start(GTK_BOX(box), stack, TRUE, TRUE, 0);
580
582
583 restart_required = FALSE;
584
585 //setup tabs
586 init_tab_general(_preferences_dialog, stack, tweak_widgets);
587 init_tab_views(_preferences_dialog, stack);
588 init_tab_processing(_preferences_dialog, stack);
589
590 // `init_tab_processing()` is generated from `data/anselconfig.xml.in`, so it can only create static
591 // preferences. OpenCL devices are detected at runtime, therefore their per-device switches are added
592 // after the generated processing tab exists.
593 {
594 GtkWidget *scroll = gtk_stack_get_child_by_name(GTK_STACK(stack), _("processing"));
595 GtkWidget *viewport = GTK_IS_BIN(scroll) ? gtk_bin_get_child(GTK_BIN(scroll)) : NULL;
596 GtkWidget *grid = GTK_IS_BIN(viewport) ? gtk_bin_get_child(GTK_BIN(viewport)) : NULL;
597
598 const int detected_devices = dt_opencl_get_detected_device_count();
599 if(!IS_NULL_PTR(grid) && detected_devices > 0)
600 {
601 int line = 0;
602 int insert_line = -1;
603 GList *children = gtk_container_get_children(GTK_CONTAINER(grid));
604 // Walk over the generated preference rows to find where the dynamic OpenCL rows must be inserted.
605 // We also keep the current bottom row as a fallback when the expected anchor is not present.
606 for(GList *child = children; child; child = g_list_next(child))
607 {
608 int top = 0;
609 int height = 1;
610 gtk_container_child_get(GTK_CONTAINER(grid), GTK_WIDGET(child->data), "top-attach", &top, "height", &height,
611 NULL);
612 line = MAX(line, top + height);
613
614 if(GTK_IS_CONTAINER(child->data))
615 {
616 // Find the disk thumbnail cache row. The per-GPU OpenCL switches belong just
617 // after it, before the remaining GPU memory/runtime preferences.
618 GList *box_children = gtk_container_get_children(GTK_CONTAINER(child->data));
619 for(GList *box_child = box_children; box_child; box_child = g_list_next(box_child))
620 {
621 const char *name = gtk_widget_get_name(GTK_WIDGET(box_child->data));
622 if(!IS_NULL_PTR(name) && strcmp(name, "cache_disk_backend") == 0)
623 {
624 insert_line = top + height;
625 break;
626 }
627 }
628 g_list_free(box_children);
629 }
630 }
631 g_list_free(children);
632
633 if(insert_line < 0)
634 insert_line = line;
635 else
636 {
637 children = gtk_container_get_children(GTK_CONTAINER(grid));
638 // Shift all generated rows below the insertion point down, leaving one row
639 // for the indented box that contains all per-GPU option blocks.
640 for(GList *child = children; child; child = g_list_next(child))
641 {
642 int top = 0;
643 gtk_container_child_get(GTK_CONTAINER(grid), GTK_WIDGET(child->data), "top-attach", &top, NULL);
644 if(top >= insert_line)
645 gtk_container_child_set(GTK_CONTAINER(grid), GTK_WIDGET(child->data), "top-attach",
646 top + 1, NULL);
647 }
648 g_list_free(children);
649 line = insert_line;
650 }
651
652 GtkWidget *devices_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
653 gtk_widget_set_margin_start(devices_box, DT_PIXEL_APPLY_DPI(24));
654 gtk_widget_set_hexpand(devices_box, TRUE);
655 GtkWidget *devices_grid = gtk_grid_new();
656 gtk_grid_set_column_spacing(GTK_GRID(devices_grid), DT_GUI_BOX_SPACING);
657 gtk_widget_set_hexpand(devices_grid, TRUE);
658 gtk_box_pack_start(GTK_BOX(devices_box), devices_grid, FALSE, FALSE, 0);
659
660 // Create one block per detected GPU. The OpenCL subsystem owns the device-specific
661 // configuration keys and keeps the global OpenCL preference in sync.
662 int devices_line = 0;
663 for(int dev = 0; dev < detected_devices; dev++)
664 {
666 if(IS_NULL_PTR(device)) continue;
667
668 const char *device_name = !IS_NULL_PTR(device->name) ? device->name
669 : (!IS_NULL_PTR(device->cname) ? device->cname : "");
670 gchar *label_text = g_strdup_printf("%d: %s", device->config_id, device_name);
671 GtkWidget *device_title = gtk_label_new(label_text);
672 GtkWidget *title_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
673 gtk_box_pack_start(GTK_BOX(title_box), device_title, FALSE, FALSE, 0);
674 gtk_widget_set_margin_top(title_box, dev > 0 ? DT_PIXEL_APPLY_DPI(8) : 0);
675 gtk_widget_set_name(title_box, "pref_subsection");
676 gtk_grid_attach(GTK_GRID(devices_grid), title_box, 0, devices_line++, 3, 1);
677 dt_free(label_text);
678
679 GtkWidget *enable_label = gtk_label_new(_("Enable"));
680 gtk_widget_set_halign(enable_label, GTK_ALIGN_START);
681 gtk_widget_set_hexpand(enable_label, TRUE);
682 gtk_grid_attach(GTK_GRID(devices_grid), enable_label, 0, devices_line, 1, 1);
683 GtkWidget *enable_labdef = gtk_label_new("");
684 gtk_widget_set_name(enable_labdef, "preference_non_default");
685 gtk_grid_attach(GTK_GRID(devices_grid), enable_labdef, 1, devices_line, 1, 1);
686
687 GtkWidget *enable = gtk_check_button_new();
688 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable), dt_opencl_detected_device_enabled(dev));
689 gtk_widget_set_tooltip_text(enable, _("Enable or disable OpenCL processing for this GPU"));
690 g_signal_connect(G_OBJECT(enable), "toggled", G_CALLBACK(_opencl_device_enabled_callback),
691 GINT_TO_POINTER(dev));
692 gtk_grid_attach(GTK_GRID(devices_grid), enable, 2, devices_line++, 1, 1);
693
694 GtkWidget *pinned_memory_label = gtk_label_new(_("pinned memory"));
695 gtk_widget_set_halign(pinned_memory_label, GTK_ALIGN_START);
696 gtk_widget_set_hexpand(pinned_memory_label, TRUE);
697 gtk_grid_attach(GTK_GRID(devices_grid), pinned_memory_label, 0, devices_line, 1, 1);
698 GtkWidget *pinned_memory_labdef = gtk_label_new("");
699 gtk_widget_set_name(pinned_memory_labdef, "preference_non_default");
700 gtk_grid_attach(GTK_GRID(devices_grid), pinned_memory_labdef, 1, devices_line, 1, 1);
701
702 GtkWidget *pinned_memory = gtk_check_button_new();
703 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pinned_memory),
705 gtk_widget_set_tooltip_text(pinned_memory, _("Use pinned host memory for OpenCL transfers on this GPU"));
706 g_signal_connect(G_OBJECT(pinned_memory), "toggled", G_CALLBACK(_opencl_device_pinned_memory_callback),
707 GINT_TO_POINTER(dev));
708 gtk_grid_attach(GTK_GRID(devices_grid), pinned_memory, 2, devices_line++, 1, 1);
709
710 GtkWidget *headroom_label = gtk_label_new(_("GPU vRAM headroom (MiB)"));
711 gtk_widget_set_halign(headroom_label, GTK_ALIGN_START);
712 gtk_widget_set_hexpand(headroom_label, TRUE);
713 gtk_grid_attach(GTK_GRID(devices_grid), headroom_label, 0, devices_line, 1, 1);
714 GtkWidget *headroom_labdef = gtk_label_new("");
715 gtk_widget_set_name(headroom_labdef, "preference_non_default");
716 gtk_grid_attach(GTK_GRID(devices_grid), headroom_labdef, 1, devices_line, 1, 1);
717
718 GtkWidget *headroom = gtk_spin_button_new_with_range(0, G_MAXINT, 1);
719 gtk_widget_set_hexpand(headroom, FALSE);
720 gtk_spin_button_set_digits(GTK_SPIN_BUTTON(headroom), 0);
721 gtk_spin_button_set_value(GTK_SPIN_BUTTON(headroom), dt_opencl_detected_device_headroom(dev));
722 gtk_widget_set_tooltip_text(headroom, _("GPU memory reserved for the system and other applications"));
723 g_signal_connect(G_OBJECT(headroom), "value-changed", G_CALLBACK(_opencl_device_headroom_callback),
724 GINT_TO_POINTER(dev));
725 gtk_grid_attach(GTK_GRID(devices_grid), headroom, 2, devices_line++, 1, 1);
726 }
727
728 gtk_grid_attach(GTK_GRID(grid), devices_box, 0, line++, 3, 1);
729 }
730 }
731
732 init_tab_security(_preferences_dialog, stack);
733 init_tab_storage(_preferences_dialog, stack);
734 init_tab_misc(_preferences_dialog, stack);
735 init_tab_presets(stack);
736
737 gtk_widget_show_all(_preferences_dialog);
738 (void)gtk_dialog_run(GTK_DIALOG(_preferences_dialog));
739
740 dt_free(tweak_widgets);
741 gtk_widget_destroy(_preferences_dialog);
742
743 if(restart_required)
744 dt_control_log(_("Ansel needs to be restarted for settings to take effect"));
745
747}
748
749static void cairo_destroy_from_pixbuf(guchar *pixels, gpointer data)
750{
751 cairo_destroy((cairo_t *)data);
752}
753
754static void tree_insert_presets(GtkTreeStore *tree_model)
755{
756 GtkTreeIter iter, parent;
757 sqlite3_stmt *stmt;
758 gchar *last_module = NULL;
759
760 // Create a GdkPixbuf with a cairo drawing.
761 // lock
762 cairo_surface_t *lock_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DT_PIXEL_APPLY_DPI(ICON_SIZE),
764 cairo_t *lock_cr = cairo_create(lock_cst);
765 cairo_set_source_rgb(lock_cr, 0.7, 0.7, 0.7);
767 cairo_surface_flush(lock_cst);
768 guchar *data = cairo_image_surface_get_data(lock_cst);
770 GdkPixbuf *lock_pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
772 cairo_image_surface_get_stride(lock_cst),
774
775 // check mark
776 cairo_surface_t *check_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DT_PIXEL_APPLY_DPI(ICON_SIZE),
778 cairo_t *check_cr = cairo_create(check_cst);
779 cairo_set_source_rgb(check_cr, 0.7, 0.7, 0.7);
781 cairo_surface_flush(check_cst);
782 data = cairo_image_surface_get_data(check_cst);
784 GdkPixbuf *check_pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8,
786 cairo_image_surface_get_stride(check_cst),
787 cairo_destroy_from_pixbuf, check_cr);
788 // clang-format off
790 "SELECT rowid, name, operation, autoapply, model, maker, lens, iso_min, "
791 "iso_max, exposure_min, exposure_max, aperture_min, aperture_max, "
792 "focal_length_min, focal_length_max, writeprotect FROM data.presets ORDER BY "
793 "operation, name",
794 -1, &stmt, NULL);
795 // clang-format on
796 while(sqlite3_step(stmt) == SQLITE_ROW)
797 {
798 const gint rowid = sqlite3_column_int(stmt, 0);
799 const gchar *name = (gchar *)sqlite3_column_text(stmt, 1);
800 const gchar *operation = (gchar *)sqlite3_column_text(stmt, 2);
801 const gboolean autoapply = (sqlite3_column_int(stmt, 3) == 0 ? FALSE : TRUE);
802 const gchar *model = (gchar *)sqlite3_column_text(stmt, 4);
803 const gchar *maker = (gchar *)sqlite3_column_text(stmt, 5);
804 const gchar *lens = (gchar *)sqlite3_column_text(stmt, 6);
805 const float iso_min = sqlite3_column_double(stmt, 7);
806 const float iso_max = sqlite3_column_double(stmt, 8);
807 const float exposure_min = sqlite3_column_double(stmt, 9);
808 const float exposure_max = sqlite3_column_double(stmt, 10);
809 const float aperture_min = sqlite3_column_double(stmt, 11);
810 const float aperture_max = sqlite3_column_double(stmt, 12);
811 const int focal_length_min = sqlite3_column_double(stmt, 13);
812 const int focal_length_max = sqlite3_column_double(stmt, 14);
813 const gboolean writeprotect = (sqlite3_column_int(stmt, 15) == 0 ? FALSE : TRUE);
814
815 gchar *iso = NULL, *exposure = NULL, *aperture = NULL, *focal_length = NULL, *smaker = NULL, *smodel = NULL, *slens = NULL;
816 int min, max;
817
818 gchar *module = g_strdup(dt_iop_get_localized_name(operation));
819 if(IS_NULL_PTR(module)) module = g_strdup(dt_lib_get_localized_name(operation));
820 if(IS_NULL_PTR(module)) module = g_strdup(operation);
821
822 if(!dt_presets_module_can_autoapply(operation))
823 {
824 iso = g_strdup("");
825 exposure = g_strdup("");
826 aperture = g_strdup("");
827 focal_length = g_strdup("");
828 smaker = g_strdup("");
829 smodel = g_strdup("");
830 slens = g_strdup("");
831 }
832 else
833 {
834 smaker = g_strdup(maker);
835 smodel = g_strdup(model);
836 slens = g_strdup(lens);
837
838 if(iso_min == 0.0 && iso_max == FLT_MAX)
839 iso = g_strdup("%");
840 else
841 iso = g_strdup_printf("%" G_GSIZE_FORMAT " - %" G_GSIZE_FORMAT "", (size_t)iso_min, (size_t)iso_max);
842
843 for(min = 0; min < dt_gui_presets_exposure_value_cnt && exposure_min > dt_gui_presets_exposure_value[min]; min++)
844 ;
845 for(max = 0; max < dt_gui_presets_exposure_value_cnt && exposure_max > dt_gui_presets_exposure_value[max]; max++)
846 ;
847 if(min == 0 && max == dt_gui_presets_exposure_value_cnt - 1)
848 exposure = g_strdup("%");
849 else
850 exposure = g_strdup_printf("%s - %s", dt_gui_presets_exposure_value_str[min],
852
853 for(min = 0; min < dt_gui_presets_aperture_value_cnt && aperture_min > dt_gui_presets_aperture_value[min]; min++)
854 ;
855 for(max = 0; max < dt_gui_presets_aperture_value_cnt && aperture_max > dt_gui_presets_aperture_value[max]; max++)
856 ;
857 if(min == 0 && max == dt_gui_presets_aperture_value_cnt - 1)
858 aperture = g_strdup("%");
859 else
860 aperture = g_strdup_printf("%s - %s", dt_gui_presets_aperture_value_str[min],
862
863 if(focal_length_min == 0.0 && focal_length_max == 1000.0)
864 focal_length = g_strdup("%");
865 else
866 focal_length = g_strdup_printf("%d - %d", focal_length_min, focal_length_max);
867 }
868
869 if(g_strcmp0(last_module, operation) != 0)
870 {
871 gtk_tree_store_insert_with_values(tree_model, &iter, NULL, -1,
873 _(module), P_EDITABLE_COLUMN, NULL, P_NAME_COLUMN, "", P_MODEL_COLUMN, "",
876 dt_free(last_module);
877 last_module = g_strdup(operation);
878 parent = iter;
879 }
880
881 gtk_tree_store_insert_with_values(tree_model, &iter, &parent, -1,
882 P_ROWID_COLUMN, rowid, P_OPERATION_COLUMN, operation,
883 P_MODULE_COLUMN, "", P_EDITABLE_COLUMN, writeprotect ? lock_pixbuf : NULL,
885 P_ISO_COLUMN, iso, P_EXPOSURE_COLUMN, exposure, P_APERTURE_COLUMN, aperture,
887 autoapply ? check_pixbuf : NULL, -1);
888
889 dt_free(focal_length);
890 dt_free(aperture);
891 dt_free(exposure);
892 dt_free(iso);
893 dt_free(module);
894 dt_free(smaker);
895 dt_free(smodel);
896 dt_free(slens);
897 }
898 dt_free(last_module);
899 sqlite3_finalize(stmt);
900
901 g_object_unref(lock_pixbuf);
902 cairo_surface_destroy(lock_cst);
903 g_object_unref(check_pixbuf);
904 cairo_surface_destroy(check_cst);
905}
906
907static gboolean _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
908{
909 gchar *key_case = g_utf8_casefold(key, -1), *label = NULL;
910
911 gtk_tree_model_get(model, iter, P_NAME_COLUMN, &label, -1);
912 gchar *name_case = g_utf8_casefold(label, -1);
913 dt_free(label);
914 gtk_tree_model_get(model, iter, P_MODULE_COLUMN, &label, -1);
915 gchar *module_case = g_utf8_casefold(label, -1);
916 dt_free(label);
917
918 const gboolean different = !((name_case && strstr(name_case, key_case))
919 || (module_case && strstr(module_case, key_case)));
920
921 dt_free(name_case);
922 dt_free(module_case);
923 dt_free(key_case);
924
925 if(!different)
926 {
927 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
928 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(search_data), path);
929 gtk_tree_path_free(path);
930
931 return FALSE;
932 }
933
934 GtkTreeIter child;
935 if(gtk_tree_model_iter_children(model, &child, iter))
936 {
937 do
938 {
939 _search_func(model, column, key, &child, search_data);
940 }
941 while(gtk_tree_model_iter_next(model, &child));
942 }
943
944 return TRUE;
945}
946
947static void init_tab_presets(GtkWidget *stack)
948{
949 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
950 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
951 GtkTreeView *tree = GTK_TREE_VIEW(gtk_tree_view_new());
952 GtkTreeStore *model = gtk_tree_store_new(
953 P_N_COLUMNS, G_TYPE_INT /*rowid*/, G_TYPE_STRING /*operation*/, G_TYPE_STRING /*module*/,
954 GDK_TYPE_PIXBUF /*editable*/, G_TYPE_STRING /*name*/, G_TYPE_STRING /*model*/, G_TYPE_STRING /*maker*/,
955 G_TYPE_STRING /*lens*/, G_TYPE_STRING /*iso*/, G_TYPE_STRING /*exposure*/, G_TYPE_STRING /*aperture*/,
956 G_TYPE_STRING /*focal length*/, GDK_TYPE_PIXBUF /*auto*/);
957 GtkCellRenderer *renderer;
958 GtkTreeViewColumn *column;
959
960 // Adding the outer container
961 gtk_stack_add_titled(GTK_STACK(stack), container, _("presets"), _("presets"));
962
964
965 // Setting a custom sort functions so expandable groups rise to the top
966 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), P_MODULE_COLUMN, GTK_SORT_ASCENDING);
967 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), P_MODULE_COLUMN, compare_rows_presets, NULL, NULL);
968
969 // Setting up the cell renderers
970 renderer = gtk_cell_renderer_text_new();
971 column = gtk_tree_view_column_new_with_attributes(_("module"), renderer, "text", P_MODULE_COLUMN, NULL);
972 gtk_tree_view_append_column(tree, column);
973
974 renderer = gtk_cell_renderer_pixbuf_new();
975 column = gtk_tree_view_column_new_with_attributes("", renderer, "pixbuf", P_EDITABLE_COLUMN, NULL);
976 gtk_tree_view_append_column(tree, column);
977
978 renderer = gtk_cell_renderer_text_new();
979 column = gtk_tree_view_column_new_with_attributes(_("name"), renderer, "text", P_NAME_COLUMN, NULL);
980 gtk_tree_view_append_column(tree, column);
981
982 renderer = gtk_cell_renderer_text_new();
983 column = gtk_tree_view_column_new_with_attributes(_("model"), renderer, "text", P_MODEL_COLUMN, NULL);
984 gtk_tree_view_append_column(tree, column);
985
986 renderer = gtk_cell_renderer_text_new();
987 column = gtk_tree_view_column_new_with_attributes(_("maker"), renderer, "text", P_MAKER_COLUMN, NULL);
988 gtk_tree_view_append_column(tree, column);
989
990 renderer = gtk_cell_renderer_text_new();
991 column = gtk_tree_view_column_new_with_attributes(_("lens"), renderer, "text", P_LENS_COLUMN, NULL);
992 gtk_tree_view_append_column(tree, column);
993
994 renderer = gtk_cell_renderer_text_new();
995 column = gtk_tree_view_column_new_with_attributes(_("ISO"), renderer, "text", P_ISO_COLUMN, NULL);
996 gtk_tree_view_append_column(tree, column);
997
998 renderer = gtk_cell_renderer_text_new();
999 column = gtk_tree_view_column_new_with_attributes(_("exposure"), renderer, "text", P_EXPOSURE_COLUMN, NULL);
1000 gtk_tree_view_append_column(tree, column);
1001
1002 renderer = gtk_cell_renderer_text_new();
1003 column = gtk_tree_view_column_new_with_attributes(_("aperture"), renderer, "text", P_APERTURE_COLUMN, NULL);
1004 gtk_tree_view_append_column(tree, column);
1005
1006 renderer = gtk_cell_renderer_text_new();
1007 column = gtk_tree_view_column_new_with_attributes(_("focal length"), renderer, "text",
1008 P_FOCAL_LENGTH_COLUMN, NULL);
1009 gtk_tree_view_append_column(tree, column);
1010
1011 renderer = gtk_cell_renderer_pixbuf_new();
1012 column = gtk_tree_view_column_new_with_attributes(_("auto"), renderer, "pixbuf", P_AUTOAPPLY_COLUMN, NULL);
1013 gtk_tree_view_append_column(tree, column);
1014
1015 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1016 gtk_box_pack_start(GTK_BOX(container), scroll, TRUE, TRUE, 0);
1017
1018 // Adding the import/export buttons
1019 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1020 gtk_widget_set_name(hbox, "preset_controls");
1021
1022 GtkWidget *search_presets = gtk_search_entry_new();
1023 dt_accels_disconnect_on_text_input(search_presets);
1024
1025 gtk_box_pack_start(GTK_BOX(hbox), search_presets, FALSE, TRUE, 0);
1026 gtk_entry_set_placeholder_text(GTK_ENTRY(search_presets), _("search presets list"));
1027 gtk_widget_set_tooltip_text(GTK_WIDGET(search_presets), _("incrementally search the list of presets\npress up or down keys to cycle through matches"));
1028 g_signal_connect(G_OBJECT(search_presets), "activate", G_CALLBACK(dt_gui_search_stop), tree);
1029 g_signal_connect(G_OBJECT(search_presets), "stop-search", G_CALLBACK(dt_gui_search_stop), tree);
1030 g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK(dt_gui_search_start), search_presets);
1031 gtk_tree_view_set_search_entry(tree, GTK_ENTRY(search_presets));
1032
1033 GtkWidget *button = gtk_button_new_with_label(C_("preferences", "import..."));
1034 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1035 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(import_preset), (gpointer)model);
1036
1037 button = gtk_button_new_with_label(C_("preferences", "export..."));
1038 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1039 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(export_preset), (gpointer)model);
1040
1041 gtk_box_pack_start(GTK_BOX(container), hbox, FALSE, FALSE, 0);
1042
1043 // Attaching treeview signals
1044
1045 // row-activated either expands/collapses a row or activates editing
1046 g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(tree_row_activated_presets), NULL);
1047
1048 // A keypress may delete preset
1049 g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK(tree_key_press_presets), (gpointer)model);
1050
1051 // Setting up the search functionality
1052 gtk_tree_view_set_search_equal_func(tree, _search_func, tree, NULL);
1053
1054 // Attaching the model to the treeview
1055 gtk_tree_view_set_model(tree, GTK_TREE_MODEL(model));
1056
1057 // Adding the treeview to its containers
1058 dt_gui_add_class(scroll, "dt_recessed_scroll");
1059 gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(tree));
1060 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1061
1062 g_object_unref(G_OBJECT(model));
1063}
1064
1065// TODO: remember which sections were collapsed/expanded and where the view was scrolled to and restore that
1066// after editing is done
1067// Alternative: change edit_preset_response to not clear+refill the tree, but to update the single row
1068// which changed.
1069static void tree_row_activated_presets(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
1070 gpointer data)
1071{
1072 GtkTreeIter iter;
1073 GtkTreeModel *model = gtk_tree_view_get_model(tree);
1074
1075 gtk_tree_model_get_iter(model, &iter, path);
1076
1077 if(gtk_tree_model_iter_has_child(model, &iter))
1078 {
1079 // For branch nodes, toggle expansion on activation
1080 if(gtk_tree_view_row_expanded(tree, path))
1081 gtk_tree_view_collapse_row(tree, path);
1082 else
1083 gtk_tree_view_expand_row(tree, path, FALSE);
1084 }
1085 else
1086 {
1087 // For leaf nodes, open editing window if the preset is not writeprotected
1088 gint rowid;
1089 gchar *name, *operation;
1090 GdkPixbuf *editable;
1091 gtk_tree_model_get(model, &iter, P_ROWID_COLUMN, &rowid, P_NAME_COLUMN, &name, P_OPERATION_COLUMN,
1092 &operation, P_EDITABLE_COLUMN, &editable, -1);
1093 if(IS_NULL_PTR(editable))
1094 edit_preset(tree, rowid, name, operation);
1095 else
1096 g_object_unref(editable);
1097 dt_free(name);
1098 dt_free(operation);
1099 }
1100}
1101
1102
1103
1104static gboolean tree_key_press_presets(GtkWidget *widget, GdkEventKey *event, gpointer data)
1105{
1106
1107 GtkTreeModel *model = (GtkTreeModel *)data;
1108 GtkTreeIter iter;
1109 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1110
1111 // We can just ignore mod key presses outright
1112 if(event->is_modifier) return FALSE;
1113 guint key = dt_keys_mainpad_alternatives(event->keyval);
1114
1115 if(key == GDK_KEY_Delete || key == GDK_KEY_BackSpace)
1116 {
1117 // If a leaf node is selected, delete that preset
1118
1119 // If nothing is selected, or branch node selected, just return
1120 if(!gtk_tree_selection_get_selected(selection, &model, &iter)
1121 || gtk_tree_model_iter_has_child(model, &iter))
1122 return FALSE;
1123
1124 // For leaf nodes, open delete confirmation window if the preset is not writeprotected
1125 gint rowid;
1126 gchar *name;
1127 GdkPixbuf *editable;
1128 gtk_tree_model_get(model, &iter, P_ROWID_COLUMN, &rowid, P_NAME_COLUMN, &name,
1129 P_EDITABLE_COLUMN, &editable, -1);
1130 if(IS_NULL_PTR(editable))
1131 {
1132 sqlite3_stmt *stmt;
1133 gchar* operation = NULL;
1134
1136 "SELECT name, operation FROM data.presets WHERE rowid = ?1",
1137 -1, &stmt, NULL);
1138 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, rowid);
1139 if(sqlite3_step(stmt) == SQLITE_ROW)
1140 {
1141 operation = g_strdup( (const char*)sqlite3_column_text(stmt,1));
1142 }
1143 sqlite3_finalize(stmt);
1144
1146
1147 GtkTreeStore *tree_store = GTK_TREE_STORE(model);
1148 gtk_tree_store_clear(tree_store);
1149 tree_insert_presets(tree_store);
1150
1151 if(operation)
1152 {
1153 dt_free(operation);
1154 }
1155 }
1156 else
1157 g_object_unref(editable);
1158
1159 dt_free(name);
1160
1161 return TRUE;
1162 }
1163 else
1164 {
1165 return FALSE;
1166 }
1167}
1168
1169static void _import_preset_from_file(const gchar* filename)
1170{
1171 if(!dt_presets_import_from_file(filename))
1172 {
1173 dt_control_log(_("failed to import preset %s"), filename);
1174 }
1175}
1176
1177static void import_preset(GtkButton *button, gpointer data)
1178{
1179 GtkTreeModel *model = (GtkTreeModel *)data;
1180 GtkWindow *win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
1181
1182 // Zero value indicates import
1183 GtkFileChooserNative *chooser = gtk_file_chooser_native_new(
1184 _("select preset(s) to import"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
1185 _("_open"), _("_cancel"));
1186
1187 dt_conf_get_folder_to_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(chooser));
1188 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE);
1189
1190 GtkFileFilter *filter;
1191 filter = GTK_FILE_FILTER(gtk_file_filter_new());
1192 gtk_file_filter_add_pattern(filter, "*.dtpreset");
1193 gtk_file_filter_add_pattern(filter, "*.DTPRESET");
1194 gtk_file_filter_set_name(filter, _("Ansel preset files"));
1195 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
1196
1197 filter = GTK_FILE_FILTER(gtk_file_filter_new());
1198 gtk_file_filter_add_pattern(filter, "*");
1199 gtk_file_filter_set_name(filter, _("all files"));
1200
1201 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
1202
1203 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1204 {
1205 GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chooser));
1206 g_slist_foreach(filenames, (GFunc)_import_preset_from_file, NULL);
1207 g_slist_free_full(filenames, dt_free_gpointer);
1208
1209 GtkTreeStore *tree_store = GTK_TREE_STORE(model);
1210 gtk_tree_store_clear(tree_store);
1211 tree_insert_presets(tree_store);
1212
1213 dt_conf_set_folder_from_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(chooser));
1214 }
1215 g_object_unref(chooser);
1216}
1217
1218static void export_preset(GtkButton *button, gpointer data)
1219{
1220 GtkWindow *win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
1221
1222 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1223 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1224 _("_save"), _("_cancel"));
1225
1226 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1227
1228 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1229 {
1230 gchar *filedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1231 sqlite3_stmt *stmt;
1232
1233 // we have n+1 selects for saving presets, using single transaction for whole process saves us microlocks
1235
1237 "SELECT rowid, name, operation FROM data.presets WHERE writeprotect = 0",
1238 -1, &stmt, NULL);
1239
1240 while(sqlite3_step(stmt) == SQLITE_ROW)
1241 {
1242 const gint rowid = sqlite3_column_int(stmt, 0);
1243 const gchar *name = (gchar *)sqlite3_column_text(stmt, 1);
1244 const gchar *operation = (gchar *)sqlite3_column_text(stmt, 2);
1245 gchar* preset_name = g_strdup_printf("%s_%s", operation, name);
1246
1247 dt_presets_save_to_file(rowid, preset_name, filedir);
1248
1249 dt_free(preset_name);
1250 }
1251
1252 sqlite3_finalize(stmt);
1253
1255
1256 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1257
1258 dt_free(filedir);
1259 }
1260 g_object_unref(filechooser);
1261}
1262
1263// Custom sort function for TreeModel entries for presets list
1264static gint compare_rows_presets(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
1265{
1266 gchar *a_text;
1267 gchar *b_text;
1268
1269 gtk_tree_model_get(model, a, P_MODULE_COLUMN, &a_text, -1);
1270 gtk_tree_model_get(model, b, P_MODULE_COLUMN, &b_text, -1);
1271 if(*a_text == '\0' && *b_text == '\0')
1272 {
1273 dt_free(a_text);
1274 dt_free(b_text);
1275
1276 gtk_tree_model_get(model, a, P_NAME_COLUMN, &a_text, -1);
1277 gtk_tree_model_get(model, b, P_NAME_COLUMN, &b_text, -1);
1278 }
1279
1280 const int res = strcoll(a_text, b_text);
1281
1282 dt_free(a_text);
1283 dt_free(b_text);
1284
1285 return res;
1286}
1287
1289{
1290 GtkTreeStore *tree_store = GTK_TREE_STORE(gtk_tree_view_get_model((GtkTreeView *)g->data));
1291 gtk_tree_store_clear(tree_store);
1292 tree_insert_presets(tree_store);
1293}
1294
1295static void edit_preset(GtkTreeView *tree, const gint rowid, const gchar *name, const gchar *module)
1296{
1297 dt_gui_presets_show_edit_dialog(name, module, rowid, G_CALLBACK(edit_preset_response), tree, FALSE, TRUE, TRUE,
1298 GTK_WINDOW(_preferences_dialog));
1299}
1300
1301static void
1303{
1304 dt_conf_set_bool((char *)data, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
1305}
1306
1308{
1309 const char *key = gtk_widget_get_name(widget);
1310 const gboolean def = dt_confgen_get_bool(key, DT_DEFAULT);
1311 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), def);
1312}
1313
1314static gboolean
1315_gui_preferences_bool_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1316{
1317 if(event->type == GDK_2BUTTON_PRESS)
1318 {
1320 return TRUE;
1321 }
1322 return FALSE;
1323}
1324
1326{
1327 const char *key = gtk_widget_get_name(widget);
1328 const gboolean val = dt_conf_get_bool(key);
1329 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), val);
1330}
1331
1332GtkWidget *dt_gui_preferences_bool(GtkGrid *grid, const char *key, const guint col,
1333 const guint line, const gboolean swap)
1334{
1336 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1337 GtkWidget *labelev = gtk_event_box_new();
1338 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1339 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1340 GtkWidget *w = gtk_check_button_new();
1341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), dt_conf_get_bool(key));
1342 gtk_grid_attach(GTK_GRID(grid), labelev, swap ? (col + 1) : col, line, 1, 1);
1343 gtk_grid_attach(GTK_GRID(grid), w, swap ? col : (col + 1), line, 1, 1);
1344 g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(_gui_preferences_bool_callback), (gpointer)key);
1345 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_bool_reset), (gpointer)w);
1346 return w;
1347}
1348
1349static void
1351{
1352 dt_conf_set_int((char *)data, gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
1353}
1354
1356{
1357 const char *key = gtk_widget_get_name(widget);
1358 const int def = dt_confgen_get_int(key, DT_DEFAULT);
1359 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), def);
1360}
1361
1362static gboolean
1363_gui_preferences_int_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1364{
1365 if(event->type == GDK_2BUTTON_PRESS)
1366 {
1368 return TRUE;
1369 }
1370 return FALSE;
1371}
1372
1374{
1375 const char *key = gtk_widget_get_name(widget);
1376 const int val = dt_conf_get_int(key);
1377 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), val);
1378}
1379
1380GtkWidget *dt_gui_preferences_int(GtkGrid *grid, const char *key, const guint col,
1381 const guint line)
1382{
1384 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1385 GtkWidget *labelev = gtk_event_box_new();
1386 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1387 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1388 gint min = MAX(G_MININT, dt_confgen_get_int(key, DT_MIN));
1389 gint max = MIN(G_MAXINT, dt_confgen_get_int(key, DT_MAX));
1390 GtkWidget *w = gtk_spin_button_new_with_range(min, max, 1.0);
1391 gtk_widget_set_hexpand(w, FALSE);
1392 gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 0);
1393 gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), dt_conf_get_int(key));
1394 gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1395 gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
1396 g_signal_connect(G_OBJECT(w), "value-changed", G_CALLBACK(_gui_preferences_int_callback), (gpointer)key);
1397 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_int_reset), (gpointer)w);
1398 return w;
1399}
1400
1401static void
1403{
1404 GtkTreeIter iter;
1405 if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
1406 {
1407 gchar *s = NULL;
1408 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)), &iter, 0, &s, -1);
1409 dt_conf_set_string((char *)data, s);
1410 dt_free(s);
1411 }
1412}
1413
1414void _gui_preferences_enum_set(GtkWidget *widget, const char *str)
1415{
1416 GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
1417 GtkTreeIter iter;
1418 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1419 gint i = 0;
1420 gboolean found = FALSE;
1421 while(valid)
1422 {
1423 char *value;
1424 gtk_tree_model_get(model, &iter, 0, &value, -1);
1425 if(!g_strcmp0(value, str))
1426 {
1427 dt_free(value);
1428 found = TRUE;
1429 break;
1430 }
1431 i++;
1432 dt_free(value);
1433 valid = gtk_tree_model_iter_next(model, &iter);
1434 }
1435 if(found)
1436 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), i);
1437}
1438
1440{
1441 const char *key = gtk_widget_get_name(widget);
1442 const char *str = dt_confgen_get(key, DT_DEFAULT);
1443 _gui_preferences_enum_set(widget, str);
1444}
1445
1446static gboolean
1447_gui_preferences_enum_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1448{
1449 if(event->type == GDK_2BUTTON_PRESS)
1450 {
1452 return TRUE;
1453 }
1454 return FALSE;
1455}
1456
1458{
1459 const char *key = gtk_widget_get_name(widget);
1460 char *str = dt_conf_get_string(key);
1461 _gui_preferences_enum_set(widget, str);
1462 dt_free(str);
1463}
1464
1465GtkWidget *dt_gui_preferences_enum(GtkGrid *grid, const char *key, const guint col,
1466 const guint line)
1467{
1469 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1470 GtkWidget *labelev = gtk_event_box_new();
1471 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1472 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1473
1474 GtkTreeIter iter;
1475 GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
1476 gchar *str = dt_conf_get_string(key);
1477 const char *values = dt_confgen_get(key, DT_VALUES);
1478 gint i = 0;
1479 gint pos = -1;
1480 GList *vals = dt_util_str_to_glist("][", values);
1481 for(GList *val = vals; val; val = g_list_next(val))
1482 {
1483 char *item = (char *)val->data;
1484 // remove remaining [ or ]
1485 if(item[0] == '[') item++;
1486 else if(item[strlen(item) - 1] == ']') item[strlen(item) - 1] = '\0';
1487 gtk_list_store_append(store, &iter);
1488 gtk_list_store_set(store, &iter, 0, item, 1, g_dpgettext2(NULL, "preferences", item), -1);
1489 if(pos == -1 && !g_strcmp0(str, item))
1490 {
1491 pos = i;
1492 }
1493 i++;
1494 }
1495 g_list_free_full(vals, dt_free_gpointer);
1496 vals = NULL;
1497 dt_free(str);
1498
1499 GtkWidget *w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
1500 gtk_widget_set_hexpand(w, FALSE);
1501 g_object_unref(store);
1502 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
1503 gtk_cell_renderer_set_padding(renderer, 0, 0);
1504 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), renderer, TRUE);
1505 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), renderer, "text", 1, NULL);
1506 gtk_combo_box_set_active(GTK_COMBO_BOX(w), pos);
1507
1508 gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1509 gtk_grid_attach(GTK_GRID(grid), w, col + 1, line, 1, 1);
1510 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_gui_preferences_enum_callback), (gpointer)key);
1511 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_enum_reset), (gpointer)w);
1512 return w;
1513}
1514
1515static void
1517{
1518 const char *str = gtk_entry_get_text(GTK_ENTRY(widget));
1519 dt_conf_set_string((char *)data, str);
1520}
1521
1523{
1524 const char *key = gtk_widget_get_name(widget);
1525 const char *str = dt_confgen_get(key, DT_DEFAULT);
1526 gtk_entry_set_text(GTK_ENTRY(widget), str);
1527}
1528
1529static gboolean
1530_gui_preferences_string_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
1531{
1532 if(event->type == GDK_2BUTTON_PRESS)
1533 {
1535 return TRUE;
1536 }
1537 return FALSE;
1538}
1539
1541{
1542 const char *key = gtk_widget_get_name(widget);
1543 const char *str = dt_conf_get_string_const(key);
1544 gtk_entry_set_text(GTK_ENTRY(widget), str);
1545}
1546
1547GtkWidget *dt_gui_preferences_string(GtkGrid *grid, const char *key, const guint col,
1548 const guint line)
1549{
1551 gtk_widget_set_tooltip_text(w_label, _(dt_confgen_get_tooltip(key)));
1552 GtkWidget *labelev = gtk_event_box_new();
1553 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
1554 gtk_container_add(GTK_CONTAINER(labelev), w_label);
1555
1556 GtkWidget *w = gtk_entry_new();
1558 const char *str = dt_conf_get_string_const(key);
1559 gtk_entry_set_text(GTK_ENTRY(w), str);
1560 gtk_widget_set_hexpand(w, TRUE);
1561
1562 gtk_grid_attach(GTK_GRID(grid), labelev, col, line, 1, 1);
1563 gtk_grid_attach(GTK_GRID(grid), w, col, line + 1, 1, 1);
1564 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_gui_preferences_string_callback), (gpointer)key);
1565 g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(_gui_preferences_string_reset), (gpointer)w);
1566 return w;
1567}
1568
1569// clang-format off
1570// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1571// vim: shiftwidth=2 expandtab tabstop=2 cindent
1572// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1573// 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:1289
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:761
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_DEV
Definition darktable.h:717
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define PATH_MAX
Definition darktable.h:1062
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#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:3016
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition gtk.c:2687
void dt_gui_load_theme(const char *theme)
Definition gtk.c:2029
void dt_configure_ppd_dpi(dt_gui_gtk_t *gui)
Definition gtk.c:1348
gboolean dt_gui_search_start(GtkWidget *widget, GdkEventKey *event, GtkSearchEntry *entry)
Definition gtk.c:3005
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:3225
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:461
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)
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:337
gboolean dt_opencl_detected_device_pinned_memory(const int detected)
Definition opencl.c:369
int dt_opencl_set_detected_device_headroom(const int detected, const size_t headroom)
Definition opencl.c:410
int dt_opencl_get_detected_device_count(void)
Definition opencl.c:308
int dt_opencl_set_detected_device_pinned_memory(const int detected, const gboolean enabled)
Definition opencl.c:382
const dt_opencl_detected_device_t * dt_opencl_get_detected_device(const int detected)
Definition opencl.c:316
size_t dt_opencl_detected_device_headroom(const int detected)
Definition opencl.c:398
gboolean dt_opencl_detected_device_enabled(const int detected)
Definition opencl.c:324
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)
static void _import_preset_from_file(const gchar *filename)
void dt_gui_preferences_bool_update(GtkWidget *widget)
@ 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 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:347
@ 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:789
struct dt_gui_gtk_t * gui
Definition darktable.h:775
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
GList * themes
Definition darktable.h:829
dt_ui_t * ui
Definition gtk.h:164
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