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