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