Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
gtk.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2014, 2016 johannes hanika.
4 Copyright (C) 2010 Alexandre Prokoudine.
5 Copyright (C) 2010-2014 Henrik Andersson.
6 Copyright (C) 2010, 2013-2014, 2016 Pascal de Bruijn.
7 Copyright (C) 2010 Richard Hughes.
8 Copyright (C) 2010 Stuart Henderson.
9 Copyright (C) 2010-2019 Tobias Ellinghaus.
10 Copyright (C) 2011 Antony Dovgal.
11 Copyright (C) 2011-2012, 2015 Jérémy Rosen.
12 Copyright (C) 2011 Moritz Lipp.
13 Copyright (C) 2011 Olivier Tribout.
14 Copyright (C) 2011 Robert Bieber.
15 Copyright (C) 2011-2013 Simon Spannagel.
16 Copyright (C) 2012, 2014, 2019-2022 Aldric Renaudin.
17 Copyright (C) 2012-2017, 2019-2020 parafin.
18 Copyright (C) 2012 Richard Wonka.
19 Copyright (C) 2013 Jochem Kossen.
20 Copyright (C) 2013, 2015, 2017-2022 Pascal Obry.
21 Copyright (C) 2013-2016, 2019-2020 Roman Lebedev.
22 Copyright (C) 2014 Mikhail Trishchenkov.
23 Copyright (C) 2015 Edouard Gomez.
24 Copyright (C) 2015 Pedro Côrte-Real.
25 Copyright (C) 2015, 2019 Ulrich Pegelow.
26 Copyright (C) 2016-2017 Peter Budai.
27 Copyright (C) 2017-2018, 2021 Dan Torop.
28 Copyright (C) 2017-2018 Matthieu Moy.
29 Copyright (C) 2017-2018 Rikard Öxler.
30 Copyright (C) 2018-2019 Edgardo Hoszowski.
31 Copyright (C) 2018 Mario Lueder.
32 Copyright (C) 2019-2020, 2022-2023, 2025-2026 Aurélien PIERRE.
33 Copyright (C) 2019 emeikei.
34 Copyright (C) 2019 Heiko Bauke.
35 Copyright (C) 2019 jakubfi.
36 Copyright (C) 2019 vacaboja.
37 Copyright (C) 2020 Bill Ferguson.
38 Copyright (C) 2020-2022 Chris Elston.
39 Copyright (C) 2020-2021 David-Tillmann Schaefer.
40 Copyright (C) 2020-2022 Diederik Ter Rahe.
41 Copyright (C) 2020 Hanno Schwalm.
42 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
43 Copyright (C) 2020-2021 Hubert Kowalski.
44 Copyright (C) 2020 Marco.
45 Copyright (C) 2020, 2022 Miloš Komarčević.
46 Copyright (C) 2020-2021 Philippe Weyland.
47 Copyright (C) 2020 quovadit.
48 Copyright (C) 2020-2021 Ralf Brown.
49 Copyright (C) 2021 darkelectron.
50 Copyright (C) 2021 lhietal.
51 Copyright (C) 2021 luzpaz.
52 Copyright (C) 2022 Martin Bařinka.
53 Copyright (C) 2022 Nicolas Auffray.
54 Copyright (C) 2022 Roman Neuhauser.
55 Copyright (C) 2022 Victor Forsiuk.
56 Copyright (C) 2023 Luca Zulberti.
57 Copyright (C) 2023 Maurizio Paglia.
58 Copyright (C) 2025 Alynx Zhou.
59 Copyright (C) 2025 Guillaume Stutin.
60
61 darktable is free software: you can redistribute it and/or modify
62 it under the terms of the GNU General Public License as published by
63 the Free Software Foundation, either version 3 of the License, or
64 (at your option) any later version.
65
66 darktable is distributed in the hope that it will be useful,
67 but WITHOUT ANY WARRANTY; without even the implied warranty of
68 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
69 GNU General Public License for more details.
70
71 You should have received a copy of the GNU General Public License
72 along with darktable. If not, see <http://www.gnu.org/licenses/>.
73*/
74#include "common/darktable.h"
75#include "common/collection.h"
76#include "common/colorspaces.h"
77#include "common/l10n.h"
79#include "common/ratings.h"
80#include "common/image.h"
81#include "common/image_cache.h"
82#include "gui/guides.h"
83#include "bauhaus/bauhaus.h"
84#include "develop/develop.h"
85#include "develop/imageop.h"
86#include "dtgtk/button.h"
87#include "dtgtk/expander.h"
88#include "dtgtk/sidepanel.h"
89
90#include "gui/gtk.h"
91#include "gui/splash.h"
92
93#include "common/styles.h"
94#include "control/conf.h"
95#include "control/control.h"
96#include "control/jobs.h"
97#include "control/signal.h"
98#include "gui/presets.h"
99#include "views/view.h"
100
101#include <gdk/gdkkeysyms.h>
102#ifdef GDK_WINDOWING_WAYLAND
103#include <gdk/gdkwayland.h>
104#endif
105#ifdef _WIN32
106#include <gdk/gdkwin32.h>
107#endif
108#include <gtk/gtk.h>
109#include <math.h>
110#include <stdlib.h>
111#include <stdio.h>
112#include <string.h>
113#include <unistd.h>
114#ifdef MAC_INTEGRATION
115#include <gtkosxapplication.h>
116#endif
117#ifdef GDK_WINDOWING_QUARTZ
118#include "osx/osx.h"
119#endif
120#include <pthread.h>
121
122/*
123 * NEW UI API
124 */
125
126/* generic callback for redraw widget signals */
127static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget);
128/* callback for redraw log signals */
129static void _ui_log_redraw_callback(gpointer instance, GtkWidget *widget);
130static void _ui_toast_redraw_callback(gpointer instance, GtkWidget *widget);
131
132// set class function to add CSS classes with just a simple line call
133void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
134{
135 GtkStyleContext *context = gtk_widget_get_style_context(widget);
136 if(!gtk_style_context_has_class(context, class_name))
137 {
138 gtk_style_context_add_class(context, class_name);
139 gtk_widget_queue_draw(widget);
140 }
141}
142
143void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
144{
145 GtkStyleContext *context = gtk_widget_get_style_context(widget);
146 if(gtk_style_context_has_class(context, class_name))
147 {
148 gtk_style_context_remove_class(context, class_name);
149 gtk_widget_queue_draw(widget);
150 }
151}
152
153/*
154 * OLD UI API
155 */
156static void _init_widgets(dt_gui_gtk_t *gui);
157static gboolean _configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data);
158
159gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y)
160{
161 // avoid double counting real and emulated events when receiving smooth scrolls
162 if(gdk_event_get_pointer_emulated((GdkEvent*)event)) return FALSE;
163
164 gboolean handled = FALSE;
165 switch(event->direction)
166 {
167 // is one-unit cardinal, e.g. from a mouse scroll wheel
168 case GDK_SCROLL_LEFT:
169 if(delta_x)
170 {
171 *delta_x = dt_conf_get_bool("scroll/reverse_x") ? 1.0 : -1.0;
172 if(delta_y) *delta_y = 0.0;
173 handled = TRUE;
174 }
175 break;
176 case GDK_SCROLL_RIGHT:
177 if(delta_x)
178 {
179 *delta_x = dt_conf_get_bool("scroll/reverse_x") ? -1.0 : 1.0;
180 if(delta_y) *delta_y = 0.0;
181 handled = TRUE;
182 }
183 break;
184 case GDK_SCROLL_UP:
185 if(delta_y)
186 {
187 if(delta_x) *delta_x = 0.0;
188 *delta_y = dt_conf_get_bool("scroll/reverse_y") ? 1.0 : -1.0;
189 handled = TRUE;
190 }
191 break;
192 case GDK_SCROLL_DOWN:
193 if(delta_y)
194 {
195 if(delta_x) *delta_x = 0.0;
196 *delta_y = dt_conf_get_bool("scroll/reverse_y") ? -1.0 : 1.0;
197 handled = TRUE;
198 }
199 break;
200 // is trackpad (or touch) scroll
201 case GDK_SCROLL_SMOOTH:
202 if((delta_x && event->delta_x != 0) || (delta_y && event->delta_y != 0))
203 {
204#ifdef GDK_WINDOWING_QUARTZ // on macOS deltas need to be scaled
205 if(delta_x) *delta_x = dt_conf_get_bool("scroll/reverse_x") ? -event->delta_x / 50. : event->delta_x / 50.;
206 if(delta_y) *delta_y = dt_conf_get_bool("scroll/reverse_y") ? -event->delta_y / 50. : event->delta_y / 50.;
207#else
208 if(delta_x) *delta_x = dt_conf_get_bool("scroll/reverse_x") ? -event->delta_x : event->delta_x;
209 if(delta_y) *delta_y = dt_conf_get_bool("scroll/reverse_y") ? -event->delta_y : event->delta_y;
210#endif
211 handled = TRUE;
212 }
213 default:
214 break;
215 }
216 return handled;
217}
218
219gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
220{
221 // avoid double counting real and emulated events when receiving smooth scrolls
222 if(gdk_event_get_pointer_emulated((GdkEvent*)event)) return FALSE;
223
224 // accumulates scrolling regardless of source or the widget being scrolled
225 static gdouble acc_x = 0.0, acc_y = 0.0;
226
227 gboolean handled = FALSE;
228
229 switch(event->direction)
230 {
231 // is one-unit cardinal, e.g. from a mouse scroll wheel
232 case GDK_SCROLL_LEFT:
233 if(delta_x)
234 {
235 *delta_x = dt_conf_get_bool("scroll/reverse_x") ? 1 : -1;
236 if(delta_y) *delta_y = 0;
237 handled = TRUE;
238 }
239 break;
240 case GDK_SCROLL_RIGHT:
241 if(delta_x)
242 {
243 *delta_x = dt_conf_get_bool("scroll/reverse_x") ? -1 : 1;
244 if(delta_y) *delta_y = 0;
245 handled = TRUE;
246 }
247 break;
248 case GDK_SCROLL_UP:
249 if(delta_y)
250 {
251 if(delta_x) *delta_x = 0;
252 *delta_y = dt_conf_get_bool("scroll/reverse_y") ? 1 : -1;
253 handled = TRUE;
254 }
255 break;
256 case GDK_SCROLL_DOWN:
257 if(delta_y)
258 {
259 if(delta_x) *delta_x = 0;
260 *delta_y = dt_conf_get_bool("scroll/reverse_y") ? -1 : 1;
261 handled = TRUE;
262 }
263 break;
264 // is trackpad (or touch) scroll
265 case GDK_SCROLL_SMOOTH:
266 // stop events reset accumulated delta
267 if(event->is_stop)
268 {
269 acc_x = acc_y = 0.0;
270 break;
271 }
272 // accumulate trackpad/touch scrolls until they make a unit
273 // scroll, and only then tell caller that there is a scroll to
274 // handle
275#ifdef GDK_WINDOWING_QUARTZ // on macOS deltas need to be scaled
276 acc_x += dt_conf_get_bool("scroll/reverse_x") ? -event->delta_x / 50. : event->delta_x / 50.;
277 acc_y += dt_conf_get_bool("scroll/reverse_y") ? -event->delta_y / 50. : event->delta_y / 50.;
278#else
279 acc_x += dt_conf_get_bool("scroll/reverse_x") ? -event->delta_x : event->delta_x;
280 acc_y += dt_conf_get_bool("scroll/reverse_y") ? -event->delta_y : event->delta_y;
281#endif
282 const gdouble amt_x = trunc(acc_x);
283 const gdouble amt_y = trunc(acc_y);
284 if(amt_x != 0 || amt_y != 0)
285 {
286 acc_x -= amt_x;
287 acc_y -= amt_y;
288 if((delta_x && amt_x != 0) || (delta_y && amt_y != 0))
289 {
290 if(delta_x) *delta_x = (int)amt_x;
291 if(delta_y) *delta_y = (int)amt_y;
292 handled = TRUE;
293 }
294 }
295 break;
296 default:
297 break;
298 }
299 return handled;
300}
301
302gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
303{
304 gdouble delta_x, delta_y;
305 if(dt_gui_get_scroll_deltas(event, &delta_x, &delta_y))
306 {
307 *delta = delta_x + delta_y;
308 return TRUE;
309 }
310 return FALSE;
311}
312
313gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
314{
315 int delta_x, delta_y;
316 if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y))
317 {
318 *delta = delta_x + delta_y;
319 return TRUE;
320 }
321 return FALSE;
322}
323
324
325static gboolean _draw(GtkWidget *da, cairo_t *cr, gpointer user_data)
326{
327 dt_control_expose(NULL);
329 {
330 cairo_set_source_surface(cr, darktable.gui->surface, 0, 0);
331 cairo_paint(cr);
332 }
333 return TRUE;
334}
335
336#ifdef _DEBUG
337void dt_gtk_widget_queue_draw_ext(GtkWidget *widget, const char *name, const char *file, const int line)
338{
339 if(!GTK_IS_WIDGET(widget))
340 {
341 dt_print(DT_DEBUG_GTK, "gtk_widget_queue_draw(%s) called with a non-WIDGET or NULL widget at %s:%d (widget=%p)\n",
342 name, file, line, widget);
343 return;
344 }
345 else
346 dt_print(DT_DEBUG_GTK, "queueing redraw for `%s` (`%s`) at %s:%d\n",
347 name, gtk_widget_get_name(widget), file, line);
348
349
350 (gtk_widget_queue_draw)(widget);
351}
352
353void dt_gtk_toggle_button_set_active_ext(GtkToggleButton *toggle_button, const char *name, const gboolean active,
354 const char *file, const int line)
355{
356 if(!GTK_IS_TOGGLE_BUTTON(toggle_button))
357 {
358 dt_print(DT_DEBUG_GTK, "gtk_toggle_button_set_active(%s) called with a non-TOGGLE_BUTTON or NULL widget at %s:%d (toggle_button=%p)\n",
359 name, file, line, toggle_button);
360 return;
361 }
362 else
363 dt_print(DT_DEBUG_GTK, "setting toggle button `%s` (`%s`) to %s at %s:%d\n", name, gtk_widget_get_name(GTK_WIDGET(toggle_button)),
364 active ? "active" : "inactive", file, line);
365
366 (gtk_toggle_button_set_active)(toggle_button, active);
367}
368#endif
369
370static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
371{
372 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
373
374 int delta_y;
375 if(dt_gui_get_scroll_unit_delta(event, &delta_y))
376 {
377 return dt_view_manager_scrolled(darktable.view_manager, event->x, event->y,
378 delta_y < 0,
379 event->state, delta_y);
380 }
381
382 return FALSE;
383}
384
385
387{
390 const GdkWindowState window_state = gdk_window_get_state(gtk_widget_get_window(widget));
391 dt_conf_set_bool("ui_last/maximized", (window_state & GDK_WINDOW_STATE_MAXIMIZED));
392 int width, height;
393 gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
394 dt_conf_set_int("ui_last/window_width", width);
395 dt_conf_set_int("ui_last/window_height", height);
396
397 gboolean save_window_position = TRUE;
398#ifdef GDK_WINDOWING_WAYLAND
399 GdkDisplay *display = gtk_widget_get_display(widget);
400 if(GDK_IS_WAYLAND_DISPLAY(display))
401 save_window_position = FALSE;
402#endif
403
404 if(save_window_position)
405 {
406 GdkWindow *gdk_window = gtk_widget_get_window(widget);
407 GdkDisplay *window_display = gtk_widget_get_display(widget);
408 if(!IS_NULL_PTR(gdk_window) && !IS_NULL_PTR(window_display))
409 {
410 GdkMonitor *monitor = gdk_display_get_monitor_at_window(window_display, gdk_window);
411 if(!IS_NULL_PTR(monitor))
412 {
413 const int n_monitors = gdk_display_get_n_monitors(window_display);
414 int monitor_index = -1;
415 for(int i = 0; i < n_monitors; i++)
416 {
417 if(gdk_display_get_monitor(window_display, i) == monitor)
418 {
419 monitor_index = i;
420 break;
421 }
422 }
423 if(monitor_index >= 0)
424 dt_conf_set_int("ui_last/window_monitor", monitor_index);
425 }
426 }
427
428 if(!(window_state & GDK_WINDOW_STATE_MAXIMIZED))
429 {
430 int x, y;
431 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
432 dt_conf_set_int("ui_last/window_x", x);
433 dt_conf_set_int("ui_last/window_y", y);
434 }
435 }
436
438
439 return 0;
440}
441
443{
444 const GdkRGBA bc = darktable.gui->colors[color];
445 cairo_set_source_rgb(cr, bc.red, bc.green, bc.blue);
446}
447
448void dt_gui_gtk_set_source_rgba(cairo_t *cr, dt_gui_color_t color, float opacity_coef)
449{
450 GdkRGBA bc = darktable.gui->colors[color];
451 cairo_set_source_rgba(cr, bc.red, bc.green, bc.blue, bc.alpha * opacity_coef);
452}
453
455{
457 dt_gui_add_class(win, "dt_gui_quit");
458 gtk_window_set_title(GTK_WINDOW(win), _("closing Ansel..."));
459
461
462 // Write out windows dimension
464
465 // hide main window
466 gtk_widget_hide(dt_ui_main_window(darktable.gui->ui));
467}
468
469gboolean dt_gui_quit_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
470{
472 return TRUE;
473}
474
476{
478 darktable.gui->last_preset = g_strdup(name);
479}
480
481#ifdef MAC_INTEGRATION
482#ifdef GTK_TYPE_OSX_APPLICATION
483static gboolean _osx_quit_callback(GtkOSXApplication *OSXapp, gpointer user_data)
484#else
485static gboolean _osx_quit_callback(GtkosxApplication *OSXapp, gpointer user_data)
486#endif
487{
488 GList *windows, *window;
489 windows = gtk_window_list_toplevels();
490 for(window = windows; !IS_NULL_PTR(window); window = g_list_next(window))
491 if(gtk_window_get_modal(GTK_WINDOW(window->data)) && gtk_widget_get_visible(GTK_WIDGET(window->data)))
492 break;
493 if(IS_NULL_PTR(window)) dt_control_quit();
494 g_list_free(windows);
495 windows = NULL;
496 return TRUE;
497}
498
499#ifdef GTK_TYPE_OSX_APPLICATION
500static gboolean _osx_openfile_callback(GtkOSXApplication *OSXapp, gchar *path, gpointer user_data)
501#else
502static gboolean _osx_openfile_callback(GtkosxApplication *OSXapp, gchar *path, gpointer user_data)
503#endif
504{
505 return dt_load_from_string(path, TRUE, NULL) > 0;
506}
507#endif
508
509static gboolean _configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
510{
511 static int oldw = 0;
512 static int oldh = 0;
513 // make our selves a properly sized pixmap if our window has been resized
514 if(oldw != event->width || oldh != event->height)
515 {
516 // create our new pixmap with the correct size.
517 cairo_surface_t *tmpsurface
518 = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, event->width, event->height);
519 // copy the contents of the old pixmap to the new pixmap. This keeps ugly uninitialized
520 // pixmaps from being painted upon resize
521 // int minw = oldw, minh = oldh;
522 // if(event->width < minw) minw = event->width;
523 // if(event->height < minh) minh = event->height;
524
525 cairo_t *cr = cairo_create(tmpsurface);
526 cairo_set_source_surface(cr, darktable.gui->surface, 0, 0);
527 cairo_paint(cr);
528 cairo_destroy(cr);
529
530 // we're done with our old pixmap, so we can get rid of it and replace it with our properly-sized one.
531 cairo_surface_destroy(darktable.gui->surface);
532 darktable.gui->surface = tmpsurface;
534 DT_COLORSPACE_DISPLAY); // maybe we are on another screen now with > 50% of the area
535 }
536 oldw = event->width;
537 oldh = event->height;
538
539#ifndef GDK_WINDOWING_QUARTZ
540 dt_configure_ppd_dpi((dt_gui_gtk_t *) user_data);
541#endif
542
543 return dt_control_configure(da, event, user_data);
544}
545
546static gboolean _window_configure(GtkWidget *da, GdkEvent *event, gpointer user_data)
547{
548 static int oldx = 0;
549 static int oldy = 0;
550 if(oldx != event->configure.x || oldy != event->configure.y)
551 {
553 DT_COLORSPACE_DISPLAY); // maybe we are on another screen now with > 50% of the area
554 oldx = event->configure.x;
555 oldy = event->configure.y;
556 }
557 return FALSE;
558}
559
561{
562 gboolean valid;
563 double x;
564 double y;
565 guint32 time_ms;
568 double pressure;
569 gboolean have_tilt;
570 double tilt_x;
571 double tilt_y;
573
575
576static inline double _clamp01d(const double value)
577{
578 return MIN(1.0, MAX(0.0, value));
579}
580
581static const gdouble *_event_axes(const GdkEvent *event)
582{
583 if(IS_NULL_PTR(event)) return NULL;
584 switch(event->type)
585 {
586 case GDK_MOTION_NOTIFY:
587 return ((const GdkEventMotion *)event)->axes;
588 case GDK_BUTTON_PRESS:
589 case GDK_2BUTTON_PRESS:
590 case GDK_3BUTTON_PRESS:
591 case GDK_BUTTON_RELEASE:
592 return ((const GdkEventButton *)event)->axes;
593 default:
594 return NULL;
595 }
596}
597
598static gboolean _get_axis_value_for_source(const GdkEvent *event, GdkDevice *source_device,
599 const GdkAxisUse axis, double *value, gboolean *from_source_map)
600{
601 if(from_source_map) *from_source_map = FALSE;
602 if(IS_NULL_PTR(value)) return FALSE;
603
604 const gdouble *axes = _event_axes(event);
605 if(source_device && axes)
606 {
607 double source_value = 0.0;
608 if(gdk_device_get_axis(source_device, (gdouble *)axes, axis, &source_value))
609 {
610 *value = source_value;
611 if(from_source_map) *from_source_map = TRUE;
612 return TRUE;
613 }
614 }
615
616 return gdk_event_get_axis((GdkEvent *)event, axis, value);
617}
618
619static gboolean _sample_axis_from_device_state(GdkWindow *window, GdkDevice *device,
620 const GdkAxisUse axis, double *value)
621{
622 if(IS_NULL_PTR(window) || IS_NULL_PTR(device) || IS_NULL_PTR(value)) return FALSE;
623 if(gdk_device_get_source(device) == GDK_SOURCE_KEYBOARD) return FALSE;
624 if(gdk_device_get_device_type(device) == GDK_DEVICE_TYPE_SLAVE)
625 {
626 GdkDisplay *display = gdk_device_get_display(device);
627 if(!display || !gdk_display_device_is_grabbed(display, device)) return FALSE;
628 }
629
630 const int n_axes = gdk_device_get_n_axes(device);
631 if(n_axes <= 0) return FALSE;
632
633 double *axes = g_newa(double, n_axes);
634 memset(axes, 0, sizeof(double) * n_axes);
635 GdkModifierType modifiers = 0;
636 gdk_device_get_state(device, window, axes, &modifiers);
637 return gdk_device_get_axis(device, axes, axis, value);
638}
639
640static gboolean _sample_tablet_state_from_devices(const GdkEvent *event,
641 double *pressure, gboolean *have_pressure,
642 double *tilt_x, double *tilt_y, gboolean *have_tilt,
643 const char **picked_device_name)
644{
645 if(pressure) *pressure = 0.0;
646 if(have_pressure) *have_pressure = FALSE;
647 if(tilt_x) *tilt_x = 0.0;
648 if(tilt_y) *tilt_y = 0.0;
649 if(have_tilt) *have_tilt = FALSE;
650 if(picked_device_name) *picked_device_name = NULL;
651
652 if(IS_NULL_PTR(darktable.gui)) return FALSE;
653 GdkWindow *window = gdk_event_get_window((GdkEvent *)event);
654 if(IS_NULL_PTR(window)) window = gtk_widget_get_window(dt_ui_center(darktable.gui->ui));
655 if(IS_NULL_PTR(window)) return FALSE;
656
657 int best_score = -1;
658 double best_p = 0.0;
659 gboolean best_have_p = FALSE;
660 double best_tx = 0.0;
661 double best_ty = 0.0;
662 gboolean best_have_t = FALSE;
663 const char *best_name = NULL;
664
665 GdkSeat *seat = gdk_display_get_default() ? gdk_display_get_default_seat(gdk_display_get_default()) : NULL;
666 GList *runtime_devices = seat ? gdk_seat_get_slaves(seat, GDK_SEAT_CAPABILITY_ALL_POINTING) : NULL;
667
668 for(GList *l = runtime_devices; l; l = g_list_next(l))
669 {
670 GdkDevice *device = (GdkDevice *)l->data;
671 if(IS_NULL_PTR(device)) continue;
672 const GdkInputSource source = gdk_device_get_source(device);
673 if(source == GDK_SOURCE_KEYBOARD) continue;
674 if(gdk_device_get_device_type(device) == GDK_DEVICE_TYPE_SLAVE)
675 {
676 GdkDisplay *display = gdk_device_get_display(device);
677 /* gdk_device_get_state() requires slave devices to be grabbed.
678 * Skip non-grabbed slaves to avoid GTK/GDK criticals. */
679 if(!display || !gdk_display_device_is_grabbed(display, device)) continue;
680 }
681
682 const GdkAxisFlags axis_flags = gdk_device_get_axes(device);
683 const gboolean supports_pressure = (axis_flags & GDK_AXIS_FLAG_PRESSURE) != 0;
684 const gboolean supports_x_tilt = (axis_flags & GDK_AXIS_FLAG_XTILT) != 0;
685 const gboolean supports_y_tilt = (axis_flags & GDK_AXIS_FLAG_YTILT) != 0;
686 const gboolean pen_like = (source == GDK_SOURCE_PEN || source == GDK_SOURCE_ERASER || source == GDK_SOURCE_CURSOR);
687 if(!supports_pressure && !supports_x_tilt && !supports_y_tilt && !pen_like) continue;
688
689 const int n_axes = gdk_device_get_n_axes(device);
690 if(n_axes <= 0) continue;
691 double *axes = g_newa(double, n_axes);
692 memset(axes, 0, sizeof(double) * n_axes);
693 GdkModifierType modifiers = 0;
694 gdk_device_get_state(device, window, axes, &modifiers);
695
696 double p = 0.0;
697 gboolean have_p = FALSE;
698 double tx = 0.0;
699 double ty = 0.0;
700 gboolean have_tx = FALSE;
701 gboolean have_ty = FALSE;
702
703 for(int i = 0; i < n_axes; i++)
704 {
705 const GdkAxisUse use = gdk_device_get_axis_use(device, i);
706 if(use == GDK_AXIS_PRESSURE)
707 {
708 p = axes[i];
709 have_p = TRUE;
710 }
711 else if(use == GDK_AXIS_XTILT)
712 {
713 tx = axes[i];
714 have_tx = TRUE;
715 }
716 else if(use == GDK_AXIS_YTILT)
717 {
718 ty = axes[i];
719 have_ty = TRUE;
720 }
721 }
722
723 const gboolean have_t = have_tx || have_ty;
724 const int score = (have_p ? 2 : 0) + (have_t ? 2 : 0) + (p > 1e-4 ? 4 : 0)
725 + ((hypot(tx, ty) > 1e-4) ? 3 : 0) + (pen_like ? 1 : 0);
726 if(score <= best_score) continue;
727
728 best_score = score;
729 best_p = p;
730 best_have_p = have_p;
731 best_tx = tx;
732 best_ty = ty;
733 best_have_t = have_t;
734 best_name = gdk_device_get_name(device);
735 }
736 if(runtime_devices)
737 {
738 g_list_free(runtime_devices);
739 runtime_devices = NULL;
740 }
741
742 if(best_score < 0) return FALSE;
743 if(pressure) *pressure = best_p;
744 if(have_pressure) *have_pressure = best_have_p;
745 if(tilt_x) *tilt_x = best_tx;
746 if(tilt_y) *tilt_y = best_ty;
747 if(have_tilt) *have_tilt = best_have_t;
748 if(picked_device_name) *picked_device_name = best_name;
749 return TRUE;
750}
751
752static dt_control_pointer_input_t _extract_pointer_input(const GdkEvent *event, const double x, const double y,
753 const guint32 time_ms, const gboolean reset_kinematics,
754 const char *tag)
755{
756 dt_control_pointer_input_t input = { 0 };
757 input.x = x;
758 input.y = y;
759 input.time_ms = time_ms;
760 GdkDevice *source_device = gdk_event_get_source_device((GdkEvent *)event);
761 GdkDevice *event_device = gdk_event_get_device((GdkEvent *)event);
762 GdkDevice *device = source_device ? source_device : event_device;
763 const GdkInputSource source = device ? gdk_device_get_source(device) : GDK_SOURCE_MOUSE;
764 const GdkAxisFlags axis_flags = device ? gdk_device_get_axes(device) : 0;
765 const gboolean supports_pressure = (axis_flags & GDK_AXIS_FLAG_PRESSURE) != 0;
766 const gboolean supports_x_tilt = (axis_flags & GDK_AXIS_FLAG_XTILT) != 0;
767 const gboolean supports_y_tilt = (axis_flags & GDK_AXIS_FLAG_YTILT) != 0;
768 gboolean read_pressure = FALSE;
769 gboolean read_x_tilt = FALSE;
770 gboolean read_y_tilt = FALSE;
771 gboolean map_pressure_source = FALSE;
772 gboolean map_xtilt_source = FALSE;
773 gboolean map_ytilt_source = FALSE;
774 gboolean state_pressure_source = FALSE;
775 gboolean state_pressure_event = FALSE;
776 gboolean state_xtilt_source = FALSE;
777 gboolean state_ytilt_source = FALSE;
778 gboolean fallback_pressure = FALSE;
779 gboolean fallback_tilt = FALSE;
780 const char *fallback_device_name = NULL;
781 GdkDeviceTool *tool = gdk_event_get_device_tool((GdkEvent *)event);
782 const int tool_type = tool ? (int)gdk_device_tool_get_tool_type(tool) : -1;
783 const gboolean tool_is_stylus
784 = tool && (tool_type == GDK_DEVICE_TOOL_TYPE_PEN || tool_type == GDK_DEVICE_TOOL_TYPE_ERASER
785 || tool_type == GDK_DEVICE_TOOL_TYPE_BRUSH || tool_type == GDK_DEVICE_TOOL_TYPE_PENCIL
786 || tool_type == GDK_DEVICE_TOOL_TYPE_AIRBRUSH);
787 gboolean is_tablet_like = supports_pressure || supports_x_tilt || supports_y_tilt || tool_is_stylus;
788 GdkWindow *window = gdk_event_get_window((GdkEvent *)event);
789 if(IS_NULL_PTR(window) && darktable.gui) window = gtk_widget_get_window(dt_ui_center(darktable.gui->ui));
790
791 {
792 double pressure = 0.0;
793 if(_get_axis_value_for_source(event, source_device, GDK_AXIS_PRESSURE, &pressure, &map_pressure_source))
794 {
795 read_pressure = TRUE;
796 input.pressure = _clamp01d(pressure);
797 input.has_pressure = TRUE;
800 }
801 else if(is_tablet_like && _tablet_motion_state.have_pressure)
802 {
804 input.has_pressure = TRUE;
805 }
806 else if(!is_tablet_like)
807 {
809 }
810 }
811
812 if(input.has_pressure && input.pressure <= 0.0 && window)
813 {
814 double p_state = 0.0;
815 if(source_device && _sample_axis_from_device_state(window, source_device, GDK_AXIS_PRESSURE, &p_state))
816 {
817 input.pressure = _clamp01d(p_state);
818 state_pressure_source = TRUE;
819 }
820 else if(event_device
821 && _sample_axis_from_device_state(window, event_device, GDK_AXIS_PRESSURE, &p_state))
822 {
823 input.pressure = _clamp01d(p_state);
824 state_pressure_event = TRUE;
825 }
826 }
827
828 {
829 double x_tilt = 0.0;
830 double y_tilt = 0.0;
831 const gboolean has_x_tilt
832 = _get_axis_value_for_source(event, source_device, GDK_AXIS_XTILT, &x_tilt, &map_xtilt_source);
833 const gboolean has_y_tilt
834 = _get_axis_value_for_source(event, source_device, GDK_AXIS_YTILT, &y_tilt, &map_ytilt_source);
835 read_x_tilt = has_x_tilt;
836 read_y_tilt = has_y_tilt;
837 if(has_x_tilt || has_y_tilt)
838 {
839 input.tilt_x = CLAMP(x_tilt, -1.0, 1.0);
840 input.tilt_y = CLAMP(y_tilt, -1.0, 1.0);
841 input.has_tilt = TRUE;
845 }
846 else if(is_tablet_like && _tablet_motion_state.have_tilt)
847 {
850 input.has_tilt = TRUE;
851 }
852 else if(!is_tablet_like)
853 {
855 }
856 }
857
858 if(!input.has_tilt && window)
859 {
860 double tx_state = 0.0, ty_state = 0.0;
861 const gboolean has_tx = source_device && _sample_axis_from_device_state(window, source_device, GDK_AXIS_XTILT, &tx_state);
862 const gboolean has_ty = source_device && _sample_axis_from_device_state(window, source_device, GDK_AXIS_YTILT, &ty_state);
863 if(has_tx || has_ty)
864 {
865 input.tilt_x = CLAMP(tx_state, -1.0, 1.0);
866 input.tilt_y = CLAMP(ty_state, -1.0, 1.0);
867 input.has_tilt = TRUE;
871 state_xtilt_source = has_tx;
872 state_ytilt_source = has_ty;
873 }
874 }
875
876 if(input.has_tilt)
877 input.tilt = _clamp01d(hypot(input.tilt_x, input.tilt_y));
878 else
879 input.tilt = 0.0;
880
881 if(!input.has_pressure || !input.has_tilt)
882 {
883 double fb_pressure = 0.0;
884 gboolean fb_have_pressure = FALSE;
885 double fb_tilt_x = 0.0;
886 double fb_tilt_y = 0.0;
887 gboolean fb_have_tilt = FALSE;
888 if(_sample_tablet_state_from_devices(event, &fb_pressure, &fb_have_pressure,
889 &fb_tilt_x, &fb_tilt_y, &fb_have_tilt,
890 &fallback_device_name))
891 {
892 if(!input.has_pressure && fb_have_pressure)
893 {
894 input.pressure = _clamp01d(fb_pressure);
895 input.has_pressure = TRUE;
896 fallback_pressure = TRUE;
897 }
898 if(!input.has_tilt && fb_have_tilt)
899 {
900 input.tilt_x = CLAMP(fb_tilt_x, -1.0, 1.0);
901 input.tilt_y = CLAMP(fb_tilt_y, -1.0, 1.0);
902 input.tilt = _clamp01d(hypot(input.tilt_x, input.tilt_y));
903 input.has_tilt = TRUE;
904 fallback_tilt = TRUE;
905 }
906 }
907 }
908
909 if(input.has_pressure || input.has_tilt) is_tablet_like = TRUE;
910
911 if(reset_kinematics)
912 {
918 input.acceleration = 0.0;
919 return input;
920 }
921
923 {
924 const double dt_s = MAX((double)(time_ms - _tablet_motion_state.time_ms), 1.0) * 1e-3;
925 const double dx = x - _tablet_motion_state.x;
926 const double dy = y - _tablet_motion_state.y;
927 const double speed_px_s = hypot(dx, dy) / dt_s;
928 const double accel_px_s2 = fabs(speed_px_s - _tablet_motion_state.speed_px_s) / dt_s;
929 /* Normalize acceleration for stylus mapping. 25000 px/s² keeps a useful
930 * dynamic range while clipping extreme event jitter. */
931 input.acceleration = _clamp01d(accel_px_s2 / 25000.0);
932 _tablet_motion_state.speed_px_s = speed_px_s;
933 }
934 else
935 {
936 input.acceleration = 0.0;
938 }
939
944
946 "[tablet] %s dev='%s' src_dev='%s' evt_dev='%s' src=%d tablet=%d tool=%d supports[p=%d xt=%d yt=%d] read[p=%d xt=%d yt=%d] map_src[p=%d xt=%d yt=%d] state[p_src=%d p_evt=%d xt_src=%d yt_src=%d] fallback[p=%d t=%d dev='%s'] values[p=%.4f tx=%.4f ty=%.4f t=%.4f a=%.4f] xy=(%.1f, %.1f) t_ms=%u reset=%d\n",
947 tag ? tag : "event",
948 device ? gdk_device_get_name(device) : "<none>",
949 source_device ? gdk_device_get_name(source_device) : "<none>",
950 event_device ? gdk_device_get_name(event_device) : "<none>",
951 (int)source,
952 is_tablet_like ? 1 : 0,
953 tool_type,
954 supports_pressure ? 1 : 0,
955 supports_x_tilt ? 1 : 0,
956 supports_y_tilt ? 1 : 0,
957 read_pressure ? 1 : 0,
958 read_x_tilt ? 1 : 0,
959 read_y_tilt ? 1 : 0,
960 map_pressure_source ? 1 : 0,
961 map_xtilt_source ? 1 : 0,
962 map_ytilt_source ? 1 : 0,
963 state_pressure_source ? 1 : 0,
964 state_pressure_event ? 1 : 0,
965 state_xtilt_source ? 1 : 0,
966 state_ytilt_source ? 1 : 0,
967 fallback_pressure ? 1 : 0,
968 fallback_tilt ? 1 : 0,
969 fallback_device_name ? fallback_device_name : "<none>",
970 input.has_pressure ? input.pressure : -1.0,
971 input.has_tilt ? input.tilt_x : 0.0,
972 input.has_tilt ? input.tilt_y : 0.0,
973 input.has_tilt ? input.tilt : 0.0,
974 input.acceleration,
975 x, y,
976 time_ms,
977 reset_kinematics ? 1 : 0);
978
979 return input;
980}
981
982static gboolean _button_pressed(GtkWidget *w, GdkEventButton *event, gpointer user_data)
983{
984 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
985
986 /* Reset Gtk focus */
988 gtk_widget_grab_focus(w);
989
990 const dt_control_pointer_input_t input = _extract_pointer_input((const GdkEvent *)event, event->x, event->y,
991 event->time, TRUE, "button-press");
993 const double pressure = input.has_pressure ? input.pressure : 1.0;
994 dt_control_button_pressed(event->x, event->y, pressure, event->button, event->type, event->state & 0xf);
995 return FALSE;
996}
997
998static gboolean _button_released(GtkWidget *w, GdkEventButton *event, gpointer user_data)
999{
1000 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
1001 const dt_control_pointer_input_t input = _extract_pointer_input((const GdkEvent *)event, event->x, event->y,
1002 event->time, FALSE, "button-release");
1004 dt_control_button_released(event->x, event->y, event->button, event->state & 0xf);
1005
1006 return TRUE;
1007}
1008
1009static gboolean _mouse_moved(GtkWidget *w, GdkEventMotion *event, gpointer user_data)
1010{
1011 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
1012
1013 const dt_control_pointer_input_t input = _extract_pointer_input((const GdkEvent *)event, event->x, event->y,
1014 event->time, FALSE, "motion");
1016 dt_control_mouse_moved(event->x, event->y, input.has_pressure ? input.pressure : 1.0, event->state & 0xf);
1017 return FALSE;
1018}
1019
1020#ifdef _WIN32
1021/* Arbitrary stable subclass identifier encoded as ASCII "ASNN".
1022 * It only needs to stay unique within this process for SetWindowSubclass(). */
1023#define DT_WIN32_CURSOR_SUBCLASS_CENTER ((UINT_PTR)0x41534e4e)
1024
1025static LRESULT CALLBACK _center_win32_cursor_proc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param,
1026 UINT_PTR subclass_id, DWORD_PTR ref_data)
1027{
1028 /* On Win32, DefSubclassProc() answers WM_SETCURSOR for the drawing area in center view by
1029 * restoring the window-class default arrow on every mouse move. The center
1030 * view already selected the proper cursor through GDK, so swallow the
1031 * client-area reset and keep the current cursor unchanged until the view
1032 * requests another explicit cursor change. */
1033 if(subclass_id == DT_WIN32_CURSOR_SUBCLASS_CENTER && message == WM_SETCURSOR && LOWORD(l_param) == HTCLIENT)
1034 return TRUE;
1035
1036 return DefSubclassProc(hwnd, message, w_param, l_param);
1037}
1038
1039static void _center_realize(GtkWidget *widget, gpointer user_data)
1040{
1041 GdkWindow *center_window = gtk_widget_get_window(widget);
1042 HWND center_hwnd = center_window ? (HWND)gdk_win32_window_get_handle(center_window) : NULL;
1043 if(!IS_NULL_PTR(center_hwnd))
1044 SetWindowSubclass(center_hwnd, _center_win32_cursor_proc, DT_WIN32_CURSOR_SUBCLASS_CENTER, (DWORD_PTR)widget);
1045}
1046
1047static void _center_unrealize(GtkWidget *widget, gpointer user_data)
1048{
1049 GdkWindow *center_window = gtk_widget_get_window(widget);
1050 HWND center_hwnd = center_window ? (HWND)gdk_win32_window_get_handle(center_window) : NULL;
1051 if(!IS_NULL_PTR(center_hwnd))
1052 RemoveWindowSubclass(center_hwnd, _center_win32_cursor_proc, DT_WIN32_CURSOR_SUBCLASS_CENTER);
1053}
1054#endif
1055
1056static gboolean _key_pressed(GtkWidget *w, GdkEventKey *event)
1057{
1058 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
1060 return TRUE;
1061}
1062
1063static gboolean _center_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1064{
1066 return TRUE;
1067}
1068
1069static gboolean _center_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1070{
1072 return TRUE;
1073}
1074
1075static const char* _get_source_name(int pos)
1076{
1077 static const gchar *SOURCE_NAMES[]
1078 = { "GDK_SOURCE_MOUSE", "GDK_SOURCE_PEN", "GDK_SOURCE_ERASER", "GDK_SOURCE_CURSOR",
1079 "GDK_SOURCE_KEYBOARD", "GDK_SOURCE_TOUCHSCREEN", "GDK_SOURCE_TOUCHPAD", "GDK_SOURCE_TRACKPOINT",
1080 "GDK_SOURCE_TABLET_PAD" };
1081 if(pos >= G_N_ELEMENTS(SOURCE_NAMES)) return "<UNKNOWN>";
1082 return SOURCE_NAMES[pos];
1083}
1084
1085static const char* _get_mode_name(int pos)
1086{
1087 static const gchar *MODE_NAMES[] = { "GDK_MODE_DISABLED", "GDK_MODE_SCREEN", "GDK_MODE_WINDOW" };
1088 if(pos >= G_N_ELEMENTS(MODE_NAMES)) return "<UNKNOWN>";
1089 return MODE_NAMES[pos];
1090}
1091
1092static const char* _get_axis_name(int pos)
1093{
1094 static const gchar *AXIS_NAMES[]
1095 = { "GDK_AXIS_IGNORE", "GDK_AXIS_X", "GDK_AXIS_Y", "GDK_AXIS_PRESSURE",
1096 "GDK_AXIS_XTILT", "GDK_AXIS_YTILT", "GDK_AXIS_WHEEL", "GDK_AXIS_DISTANCE",
1097 "GDK_AXIS_ROTATION", "GDK_AXIS_SLIDER", "GDK_AXIS_LAST" };
1098 if(pos >= G_N_ELEMENTS(AXIS_NAMES)) return "<UNKNOWN>";
1099 return AXIS_NAMES[pos];
1100}
1101
1103{
1104 /* lets zero mem */
1105 memset(gui, 0, sizeof(dt_gui_gtk_t));
1106
1107 dt_pthread_mutex_init(&gui->mutex, NULL);
1108
1109 // force gtk3 to use normal scroll bars instead of the popup thing. they get in the way of controls
1110 // the alternative would be to gtk_scrolled_window_set_overlay_scrolling(..., FALSE); every single widget
1111 // that might have scroll bars
1112 g_setenv("GTK_OVERLAY_SCROLLING", "0", 0);
1113
1114 // same for ubuntus overlay-scrollbar-gtk3
1115 g_setenv("LIBOVERLAY_SCROLLBAR", "0", 0);
1116
1117 // unset gtk rc from kde:
1118 char path[PATH_MAX] = { 0 }, datadir[PATH_MAX] = { 0 }, sharedir[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
1119 dt_loc_get_datadir(datadir, sizeof(datadir));
1120 dt_loc_get_sharedir(sharedir, sizeof(sharedir));
1121 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
1122
1123 const char *css_theme = dt_conf_get_string_const("ui_last/theme");
1124 if(css_theme)
1125 g_strlcpy(gui->gtkrc, css_theme, sizeof(gui->gtkrc));
1126 else
1127 g_snprintf(gui->gtkrc, sizeof(gui->gtkrc), "ansel");
1128
1129#ifdef MAC_INTEGRATION
1130#ifdef GTK_TYPE_OSX_APPLICATION
1131 GtkOSXApplication *OSXApp = g_object_new(GTK_TYPE_OSX_APPLICATION, NULL);
1132 gtk_osxapplication_set_menu_bar(
1133 OSXApp, GTK_MENU_SHELL(gtk_menu_bar_new())); // needed for default entries to show up
1134#else
1135 GtkosxApplication *OSXApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
1136 gtkosx_application_set_menu_bar(
1137 OSXApp, GTK_MENU_SHELL(gtk_menu_bar_new())); // needed for default entries to show up
1138#endif
1139 g_signal_connect(G_OBJECT(OSXApp), "NSApplicationBlockTermination", G_CALLBACK(_osx_quit_callback), NULL);
1140 g_signal_connect(G_OBJECT(OSXApp), "NSApplicationOpenFile", G_CALLBACK(_osx_openfile_callback), NULL);
1141#endif
1142
1143 GtkWidget *widget;
1144 gui->ui = g_malloc0(sizeof(dt_ui_t));
1145 gui->surface = NULL;
1146 gui->center_tooltip = 0;
1147 gui->culling_mode = FALSE;
1148 gui->selection_stacked = FALSE;
1149 gui->presets_popup_menu = NULL;
1150 gui->last_preset = NULL;
1151 gui->export_popup.window = NULL;
1152 gui->export_popup.module = NULL;
1153 gui->styles_popup.window = NULL;
1154 gui->styles_popup.module = NULL;
1155
1156 // smooth scrolling must be enabled to handle trackpad/touch events
1157 gui->scroll_mask = GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK;
1158
1159 // Emulates the same feature as Gtk focus but for scrolling events
1160 // The GtkWidget capturing scrolling events will write its address in this pointer
1161 gui->has_scroll_focus = NULL;
1162
1163 // Init global accels. We localize the config because accels pathes use translated GUI labels.
1164 // User switching between languages may loose their custom shortcuts if we didn't localize them.
1165 // NOTE: needs to be inited before widgets, more specifically before the global menu
1166 gchar *keyboardrc = g_strdup_printf("keyboardrc.%s", dt_l10n_get_current_lang(darktable.l10n));
1167 gchar *keyboardrc_path = g_build_filename(configdir, keyboardrc, NULL);
1168
1169 GtkAccelFlags flags = 0;
1170 if(dt_conf_get_bool("accels/mask")) flags |= GTK_ACCEL_MASK;
1171 gui->accels = dt_accels_init(keyboardrc_path, flags);
1172 dt_free(keyboardrc);
1173 dt_free(keyboardrc_path);
1174
1175 // Initializing widgets
1176 _init_widgets(gui);
1177
1178 //init overlay colors
1180
1181 dt_concat_path_file(path, datadir, "icons");
1182 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path);
1183 dt_concat_path_file(path, sharedir, "icons");
1184 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path);
1185
1187 widget = center;
1188
1189 gtk_widget_set_can_focus(widget, TRUE);
1190 gtk_widget_set_visible(widget, TRUE);
1191 gtk_widget_grab_focus(widget);
1192 gtk_widget_add_events(widget, GDK_PROXIMITY_IN_MASK | GDK_PROXIMITY_OUT_MASK | GDK_TABLET_PAD_MASK);
1193 g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(_configure), gui);
1194 g_signal_connect(G_OBJECT(widget), "draw", G_CALLBACK(_draw), NULL);
1195 g_signal_connect(G_OBJECT(widget), "motion-notify-event", G_CALLBACK(_mouse_moved), NULL);
1196 g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(_key_pressed), NULL);
1197 g_signal_connect(G_OBJECT(widget), "leave-notify-event", G_CALLBACK(_center_leave), NULL);
1198 g_signal_connect(G_OBJECT(widget), "enter-notify-event", G_CALLBACK(_center_enter), NULL);
1199 g_signal_connect(G_OBJECT(widget), "button-press-event", G_CALLBACK(_button_pressed), NULL);
1200 g_signal_connect(G_OBJECT(widget), "button-release-event", G_CALLBACK(_button_released), NULL);
1201 g_signal_connect(G_OBJECT(widget), "scroll-event", G_CALLBACK(_scrolled), NULL);
1202#ifdef _WIN32
1203 g_signal_connect(G_OBJECT(widget), "realize", G_CALLBACK(_center_realize), NULL);
1204 g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(_center_unrealize), NULL);
1205 if(gtk_widget_get_realized(widget))
1206 _center_realize(widget, NULL);
1207#endif
1208
1210
1212 // update the profile when the window is moved. resize is already handled in configure()
1213 widget = dt_ui_main_window(darktable.gui->ui);
1214 g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(_window_configure), NULL);
1215
1216 darktable.gui->reset = 0;
1217
1218 // load theme
1220
1221 // let's try to support pressure sensitive input devices like tablets for mask drawing
1222 dt_print(DT_DEBUG_INPUT, "[input device] Input devices found:\n\n");
1223
1224 GList *input_devices
1225 = gdk_seat_get_slaves(gdk_display_get_default_seat(gdk_display_get_default()), GDK_SEAT_CAPABILITY_ALL);
1226 const int manager_slave_count = 0;
1227 const int manager_floating_count = 0;
1228 GList *stylus_devices
1229 = gdk_seat_get_slaves(gdk_display_get_default_seat(gdk_display_get_default()), GDK_SEAT_CAPABILITY_TABLET_STYLUS);
1230 dt_print(DT_DEBUG_INPUT, "[input device] seat capabilities bitmask: %u\n",
1231 (unsigned int)gdk_seat_get_capabilities(gdk_display_get_default_seat(gdk_display_get_default())));
1232 dt_print(DT_DEBUG_INPUT, "[input device] stylus-capable devices reported by seat: %d\n", g_list_length(stylus_devices));
1233 dt_print(DT_DEBUG_INPUT, "[input device] manager fallback devices: slave=%d floating=%d merged_total=%d\n",
1234 manager_slave_count, manager_floating_count, g_list_length(input_devices));
1235 for(GList *l = stylus_devices; !IS_NULL_PTR(l); l = g_list_next(l))
1236 {
1237 GdkDevice *device = (GdkDevice *)l->data;
1238 if(IS_NULL_PTR(device)) continue;
1239 dt_print(DT_DEBUG_INPUT, " [tablet seat] %s source=%s axes_flags=%u n_axes=%d\n",
1240 gdk_device_get_name(device), _get_source_name(gdk_device_get_source(device)),
1241 (unsigned int)gdk_device_get_axes(device), gdk_device_get_n_axes(device));
1242 }
1243 if(stylus_devices)
1244 {
1245 g_list_free(stylus_devices);
1246 stylus_devices = NULL;
1247 }
1248 for(GList *l = input_devices; !IS_NULL_PTR(l); l = g_list_next(l))
1249 {
1250 GdkDevice *device = (GdkDevice *)l->data;
1251 if(IS_NULL_PTR(device)) continue;
1252 const GdkInputSource source = gdk_device_get_source(device);
1253 const gint n_axes = (source == GDK_SOURCE_KEYBOARD ? 0 : gdk_device_get_n_axes(device));
1254
1255 // force-enable everything we find in screen mode.
1256 // TODO: make that an user param ?
1257 gdk_device_set_mode(device, GDK_MODE_SCREEN);
1258
1259 dt_print(DT_DEBUG_INPUT, "%s (%s), source: %s, mode: %s, %d axes, %d keys\n", gdk_device_get_name(device),
1260 (source != GDK_SOURCE_KEYBOARD) && gdk_device_get_has_cursor(device) ? "with cursor" : "no cursor",
1261 _get_source_name(source),
1262 _get_mode_name(gdk_device_get_mode(device)), n_axes,
1263 source != GDK_SOURCE_KEYBOARD ? gdk_device_get_n_keys(device) : 0);
1264
1265 for(int i = 0; i < n_axes; i++)
1266 {
1267 dt_print(DT_DEBUG_INPUT, " %s\n", _get_axis_name(gdk_device_get_axis_use(device, i)));
1268 }
1269 dt_print(DT_DEBUG_INPUT, "\n");
1270 }
1271 if(input_devices)
1272 {
1273 g_list_free(input_devices);
1274 input_devices = NULL;
1275 }
1276
1277 // Gtk seems to capture some reserved shortcuts (Tab). We need to bypass it entirely
1278 // by hacking all events.
1279 gtk_widget_add_events(dt_ui_main_window(gui->ui), gui->scroll_mask);
1280 g_signal_connect(G_OBJECT(dt_ui_main_window(gui->ui)), "event", G_CALLBACK(dt_accels_dispatch), gui->accels);
1281
1282 // finally set the cursor to be the default.
1283 // for some reason this is needed on some systems to pick up the correctly themed cursor
1284 dt_control_change_cursor(GDK_LEFT_PTR);
1286
1287 return 0;
1288}
1289
1291{
1293 GtkAllocation allocation;
1294 gtk_widget_get_allocation(widget, &allocation);
1295
1296 if(darktable.gui->surface)
1297 {
1298 cairo_surface_destroy(darktable.gui->surface);
1299 darktable.gui->surface = NULL;
1300 }
1301
1303 = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1304 // need to pre-configure views to avoid crash caused by draw coming before configure-event
1306 const int tb = darktable.control->tabborder;
1307 dt_view_manager_configure(darktable.view_manager, allocation.width - 2 * tb, allocation.height - 2 * tb);
1308#ifdef MAC_INTEGRATION
1309#ifdef GTK_TYPE_OSX_APPLICATION
1310 gtk_osxapplication_ready(g_object_new(GTK_TYPE_OSX_APPLICATION, NULL));
1311#else
1312 gtkosx_application_ready(g_object_new(GTKOSX_TYPE_APPLICATION, NULL));
1313#endif
1314#endif
1315#ifdef GDK_WINDOWING_QUARTZ
1317#endif
1318 /* start the event loop */
1319 gtk_main();
1320
1321 if (darktable.gui->surface)
1322 {
1323 cairo_surface_destroy(darktable.gui->surface);
1324 darktable.gui->surface = NULL;
1325 }
1326 dt_cleanup();
1327}
1328
1329// refactored function to read current ppd, because gtk for osx has been unreliable
1330// we use the specific function here. Anyway, if nothing meaningful is found we default back to 1.0
1332{
1333 double res = 0.0f;
1334#ifdef GDK_WINDOWING_QUARTZ
1335 res = dt_osx_get_ppd();
1336#else
1337 res = gtk_widget_get_scale_factor(widget);
1338#endif
1339 if((res < 1.0f) || (res > 4.0f))
1340 {
1341 dt_print(DT_DEBUG_CONTROL, "[dt_get_system_gui_ppd] can't detect system ppd\n");
1342 return 1.0f;
1343 }
1344 dt_print(DT_DEBUG_CONTROL, "[dt_get_system_gui_ppd] system ppd is %f\n", res);
1345 return res;
1346}
1347
1349{
1350 GtkWidget *widget = gui->ui->main_window;
1351
1352 gui->ppd = dt_get_system_gui_ppd(widget);
1353 gui->filter_image = CAIRO_FILTER_GOOD;
1354
1355 // get the screen resolution
1356 const float screen_dpi_overwrite = dt_conf_get_float("screen_dpi_overwrite");
1357 if(screen_dpi_overwrite > 0.0)
1358 {
1359 gui->dpi = screen_dpi_overwrite;
1360 gdk_screen_set_resolution(gtk_widget_get_screen(widget), screen_dpi_overwrite);
1361 dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to %f dpi as specified in "
1362 "the configuration file\n",
1363 screen_dpi_overwrite);
1364 }
1365 else
1366 {
1367#ifdef GDK_WINDOWING_QUARTZ
1368 dt_osx_autoset_dpi(widget);
1369#endif
1370 gui->dpi = gdk_screen_get_resolution(gtk_widget_get_screen(widget));
1371 if(gui->dpi < 0.0)
1372 {
1373 gui->dpi = 96.0;
1374 gdk_screen_set_resolution(gtk_widget_get_screen(widget), 96.0);
1375 dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to the default 96 dpi\n");
1376 }
1377 else
1378 dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to %f dpi\n", gui->dpi);
1379 }
1380 gui->dpi_factor
1381 = gui->dpi / 96; // according to man xrandr and the docs of gdk_screen_set_resolution 96 is the default
1382
1383 // em depends on the screen DPI (point -> px), so refresh it here too.
1385}
1386
1387// Last DT_GUI_BOX_SPACING value actually applied to containers. Used to retarget exactly
1388// the containers that carry the standard spacing when em changes, leaving deliberate 0-spacing
1389// and custom-spacing containers untouched. Seeded with the pre-em reference (10px).
1390static gint _last_box_spacing = 10;
1391
1392typedef struct _spacing_ctx_t { gint old_s, new_s; } _spacing_ctx_t;
1393
1394// Recursively retarget GtkBox/GtkGrid/GtkFlowBox children whose spacing still equals the
1395// previously-applied standard spacing. Setting spacing inside gtk_container_foreach() doesn't
1396// mutate the child list, so the walk is safe.
1397static void _refresh_container_spacing(GtkWidget *w, gpointer user_data)
1398{
1399 const _spacing_ctx_t *c = (const _spacing_ctx_t *)user_data;
1400
1401 if(GTK_IS_BOX(w))
1402 {
1403 if(gtk_box_get_spacing(GTK_BOX(w)) == c->old_s) gtk_box_set_spacing(GTK_BOX(w), c->new_s);
1404 }
1405 else if(GTK_IS_FLOW_BOX(w))
1406 {
1407 if((gint)gtk_flow_box_get_row_spacing(GTK_FLOW_BOX(w)) == c->old_s)
1408 gtk_flow_box_set_row_spacing(GTK_FLOW_BOX(w), c->new_s);
1409 if((gint)gtk_flow_box_get_column_spacing(GTK_FLOW_BOX(w)) == c->old_s)
1410 gtk_flow_box_set_column_spacing(GTK_FLOW_BOX(w), c->new_s);
1411 }
1412 else if(GTK_IS_GRID(w))
1413 {
1414 if((gint)gtk_grid_get_row_spacing(GTK_GRID(w)) == c->old_s)
1415 gtk_grid_set_row_spacing(GTK_GRID(w), c->new_s);
1416 if((gint)gtk_grid_get_column_spacing(GTK_GRID(w)) == c->old_s)
1417 gtk_grid_set_column_spacing(GTK_GRID(w), c->new_s);
1418 }
1419
1420 if(GTK_IS_CONTAINER(w))
1421 gtk_container_foreach(GTK_CONTAINER(w), _refresh_container_spacing, user_data);
1422}
1423
1424// Propagate a new DT_GUI_BOX_SPACING to already-built containers across every toplevel, so a
1425// runtime font/DPI change updates the inner gutters live (gtk_*_set_spacing bakes the value into
1426// the widget at creation time, so reloading the CSS alone is not enough).
1428{
1429 const gint new_s = DT_GUI_BOX_SPACING;
1430 if(new_s == _last_box_spacing) return;
1431
1432 _spacing_ctx_t c = { _last_box_spacing, new_s };
1433 GList *toplevels = gtk_window_list_toplevels(); // list owned by us, elements not reffed
1434 for(GList *l = toplevels; l; l = l->next)
1435 _refresh_container_spacing(GTK_WIDGET(l->data), &c);
1436 g_list_free(toplevels);
1437
1438 _last_box_spacing = new_s;
1439}
1440
1442{
1443 dt_gui_gtk_t *gui = darktable.gui;
1444 if(!gui || !gui->ui || !gui->ui->main_window) return;
1445
1446 GtkStyleContext *ctx = gtk_widget_get_style_context(gui->ui->main_window);
1447 PangoFontDescription *desc = NULL;
1448 gtk_style_context_get(ctx, gtk_style_context_get_state(ctx), GTK_STYLE_PROPERTY_FONT, &desc, NULL);
1449 if(!desc) return;
1450
1451 const gint size = pango_font_description_get_size(desc);
1452 if(size > 0)
1453 {
1454 if(pango_font_description_get_size_is_absolute(desc))
1455 // already device-independent px
1456 gui->em = (double)size / PANGO_SCALE;
1457 else
1458 // points -> px at the screen DPI, matching how GTK renders point-sized fonts
1459 gui->em = (double)size / PANGO_SCALE * gui->dpi / 72.0;
1460 }
1461 pango_font_description_free(desc);
1462
1463 // The new em may change DT_GUI_BOX_SPACING; push it to existing containers so the change is live.
1465}
1466
1467void dt_gui_set_pango_resolution(PangoLayout *layout)
1468{
1469 if(IS_NULL_PTR(layout) || !darktable.gui) return;
1470 // Cairo-drawn text is laid out in points; the screen DPI converts those to device-independent px,
1471 // matching how GTK renders the rest of the UI. Centralized here so call sites never hand-write the DPI.
1472 pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
1473}
1474
1476{
1477 if(IS_NULL_PTR(cr)) return;
1478
1479 // Source GTK's resolved text-rendering options (anti-aliasing, hinting, subpixel order,
1480 // hint-metrics/kerning), which GTK populates from GtkSettings/Xft/fontconfig. The widget's
1481 // Pango context is the same source native widgets use; fall back to the main window, then to the
1482 // screen defaults, so an off-screen/scratch Cairo surface never silently reverts to Cairo's
1483 // AA-on defaults (which would make our cairo-drawn text look unlike the rest of the UI).
1484 const cairo_font_options_t *fo = NULL;
1485
1486 if(widget)
1487 {
1488 PangoContext *pc = gtk_widget_get_pango_context(widget);
1489 if(pc) fo = pango_cairo_context_get_font_options(pc);
1490 }
1491 if(!fo && darktable.gui && darktable.gui->ui && darktable.gui->ui->main_window)
1492 {
1493 PangoContext *pc = gtk_widget_get_pango_context(darktable.gui->ui->main_window);
1494 if(pc) fo = pango_cairo_context_get_font_options(pc);
1495 }
1496 if(!fo)
1497 {
1498 GdkScreen *screen = gdk_screen_get_default();
1499 if(screen) fo = gdk_screen_get_font_options(screen);
1500 }
1501
1502 // cairo_set_font_options() copies internally, so the const pointer's lifetime is not a concern.
1503 if(fo) cairo_set_font_options(cr, fo);
1504}
1505
1506static gboolean _focus_in_out_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1507{
1508 gtk_window_set_urgency_hint(GTK_WINDOW(widget), FALSE);
1509 return FALSE;
1510}
1511
1512static gboolean _ui_log_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1513{
1514 gtk_widget_hide(GTK_WIDGET(user_data));
1515 return TRUE;
1516}
1517
1518static gboolean _ui_toast_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1519{
1520 gtk_widget_hide(GTK_WIDGET(user_data));
1521 return TRUE;
1522}
1523
1525{
1527 GtkWidget *widget;
1528
1529 // Creating the main window
1530 gui->ui->main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1531 gtk_widget_set_name(gui->ui->main_window , "main_window");
1532 gtk_window_set_role(GTK_WINDOW(gui->ui->main_window ), "main-app");
1533 gtk_window_set_icon_name(GTK_WINDOW(gui->ui->main_window ), "ansel");
1534 gtk_window_set_title(GTK_WINDOW(gui->ui->main_window ), "Ansel");
1535
1536 // Init the titlebar ASAP because we replace the desktop titlebar & decoration with ours
1537 dt_ui_init_titlebar(gui->ui);
1538
1540
1541 gtk_window_set_default_size(GTK_WINDOW(gui->ui->main_window), DT_PIXEL_APPLY_DPI(1200), DT_PIXEL_APPLY_DPI(800));
1542
1543 // NOTE: allowing full-screen on startup shits the bed with MacOS
1544 if(dt_conf_get_bool("ui_last/maximized"))
1545 {
1546 gboolean restore_window_position = TRUE;
1547#ifdef GDK_WINDOWING_WAYLAND
1548 GdkDisplay *display = gtk_widget_get_display(gui->ui->main_window);
1549 if(GDK_IS_WAYLAND_DISPLAY(display))
1550 restore_window_position = FALSE;
1551#endif
1552
1553 if(restore_window_position)
1554 {
1555 GdkDisplay *window_display = gtk_widget_get_display(gui->ui->main_window);
1556 GdkMonitor *monitor = NULL;
1557
1558 if(!IS_NULL_PTR(window_display))
1559 {
1560 if(dt_conf_key_exists("ui_last/window_monitor"))
1561 {
1562 const int monitor_index = dt_conf_get_int("ui_last/window_monitor");
1563 if(monitor_index >= 0 && monitor_index < gdk_display_get_n_monitors(window_display))
1564 monitor = gdk_display_get_monitor(window_display, monitor_index);
1565 }
1566
1567 if(IS_NULL_PTR(monitor)
1568 && dt_conf_key_exists("ui_last/window_x")
1569 && dt_conf_key_exists("ui_last/window_y"))
1570 {
1571 const int x = dt_conf_get_int("ui_last/window_x");
1572 const int y = dt_conf_get_int("ui_last/window_y");
1573 monitor = gdk_display_get_monitor_at_point(window_display, x, y);
1574 }
1575
1576 if(IS_NULL_PTR(monitor))
1577 monitor = gdk_display_get_primary_monitor(window_display);
1578 if(IS_NULL_PTR(monitor) && gdk_display_get_n_monitors(window_display) > 0)
1579 monitor = gdk_display_get_monitor(window_display, 0);
1580 }
1581
1582 if(!IS_NULL_PTR(monitor))
1583 {
1584 GdkRectangle workarea = { 0 };
1585 gdk_monitor_get_workarea(monitor, &workarea);
1586 gtk_window_move(GTK_WINDOW(gui->ui->main_window), workarea.x, workarea.y);
1587 }
1588 }
1589
1590 gtk_window_maximize(GTK_WINDOW(gui->ui->main_window));
1591 }
1592 else
1593 {
1594 int width = dt_conf_get_int("ui_last/window_width");
1595 int height = dt_conf_get_int("ui_last/window_height");
1596 gtk_window_resize(GTK_WINDOW(gui->ui->main_window), width, height);
1597
1598 gboolean restore_window_position = TRUE;
1599#ifdef GDK_WINDOWING_WAYLAND
1600 GdkDisplay *display = gtk_widget_get_display(gui->ui->main_window);
1601 if(GDK_IS_WAYLAND_DISPLAY(display))
1602 restore_window_position = FALSE;
1603#endif
1604
1605 if(restore_window_position
1606 && dt_conf_key_exists("ui_last/window_x")
1607 && dt_conf_key_exists("ui_last/window_y"))
1608 {
1609 const int x = dt_conf_get_int("ui_last/window_x");
1610 const int y = dt_conf_get_int("ui_last/window_y");
1611
1612 int clamped_x = x;
1613 int clamped_y = y;
1614 GdkDisplay *window_display = gtk_widget_get_display(gui->ui->main_window);
1615 GdkMonitor *monitor = NULL;
1616
1617 if(!IS_NULL_PTR(window_display))
1618 {
1619 if(dt_conf_key_exists("ui_last/window_monitor"))
1620 {
1621 const int monitor_index = dt_conf_get_int("ui_last/window_monitor");
1622 if(monitor_index >= 0 && monitor_index < gdk_display_get_n_monitors(window_display))
1623 monitor = gdk_display_get_monitor(window_display, monitor_index);
1624 }
1625
1626 if(IS_NULL_PTR(monitor))
1627 monitor = gdk_display_get_monitor_at_point(window_display, x + width / 2, y + height / 2);
1628 if(IS_NULL_PTR(monitor))
1629 monitor = gdk_display_get_primary_monitor(window_display);
1630 if(IS_NULL_PTR(monitor) && gdk_display_get_n_monitors(window_display) > 0)
1631 monitor = gdk_display_get_monitor(window_display, 0);
1632 }
1633
1634 if(!IS_NULL_PTR(monitor))
1635 {
1636 GdkRectangle workarea = { 0 };
1637 gdk_monitor_get_workarea(monitor, &workarea);
1638
1639 const int max_x = workarea.x + MAX(0, workarea.width - width);
1640 const int max_y = workarea.y + MAX(0, workarea.height - height);
1641 clamped_x = CLAMP(x, workarea.x, max_x);
1642 clamped_y = CLAMP(y, workarea.y, max_y);
1643 }
1644
1645 gtk_window_move(GTK_WINDOW(gui->ui->main_window), clamped_x, clamped_y);
1646 }
1647 }
1648
1650
1651 g_signal_connect(G_OBJECT(gui->ui->main_window ), "delete_event", G_CALLBACK(dt_gui_quit_callback), NULL);
1652 g_signal_connect(G_OBJECT(gui->ui->main_window ), "focus-in-event", G_CALLBACK(_focus_in_out_event), NULL);
1653 g_signal_connect(G_OBJECT(gui->ui->main_window ), "focus-out-event", G_CALLBACK(_focus_in_out_event), NULL);
1654 g_signal_connect_after(G_OBJECT(gui->ui->main_window ), "key-press-event", G_CALLBACK(_key_pressed), NULL);
1655
1656 container = gui->ui->main_window;
1657
1658 // Adding the outermost vbox
1659 widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1660 gtk_container_add(GTK_CONTAINER(container), widget);
1661 gtk_widget_show(widget);
1662
1663 /* connect to signal redraw all */
1665 G_CALLBACK(_ui_widget_redraw_callback), gui->ui->main_window);
1666
1667 container = widget;
1668
1669 // Initializing the main table
1671
1672 /* the log message */
1673 GtkWidget *eb = gtk_event_box_new();
1674 darktable.gui->ui->log_msg = gtk_label_new("");
1675 g_signal_connect(G_OBJECT(eb), "button-press-event", G_CALLBACK(_ui_log_button_press_event),
1677 gtk_label_set_ellipsize(GTK_LABEL(darktable.gui->ui->log_msg), PANGO_ELLIPSIZE_MIDDLE);
1678 dt_gui_add_class(darktable.gui->ui->log_msg, "dt_messages");
1679 gtk_container_add(GTK_CONTAINER(eb), darktable.gui->ui->log_msg);
1680 gtk_widget_set_valign(eb, GTK_ALIGN_CENTER);
1681 gtk_widget_set_halign(eb, GTK_ALIGN_CENTER);
1682 gtk_overlay_add_overlay(GTK_OVERLAY(darktable.gui->ui->center_base), eb);
1683 //gtk_overlay_reorder_overlay(GTK_OVERLAY(darktable.gui->ui->center_base), eb, -1);
1684
1685 /* the toast message */
1686 eb = gtk_event_box_new();
1687 darktable.gui->ui->toast_msg = gtk_label_new("");
1688 g_signal_connect(G_OBJECT(eb), "button-press-event", G_CALLBACK(_ui_toast_button_press_event),
1690 gtk_widget_set_events(eb, GDK_BUTTON_PRESS_MASK | darktable.gui->scroll_mask);
1691 g_signal_connect(G_OBJECT(eb), "scroll-event", G_CALLBACK(_scrolled), NULL);
1692 gtk_label_set_ellipsize(GTK_LABEL(darktable.gui->ui->toast_msg), PANGO_ELLIPSIZE_MIDDLE);
1693
1694 PangoAttrList *attrlist = pango_attr_list_new();
1695 PangoAttribute *attr = pango_attr_font_features_new("tnum");
1696 pango_attr_list_insert(attrlist, attr);
1697 gtk_label_set_attributes(GTK_LABEL(darktable.gui->ui->toast_msg), attrlist);
1698 pango_attr_list_unref(attrlist);
1699
1700 dt_gui_add_class(darktable.gui->ui->toast_msg, "dt_messages");
1701 gtk_container_add(GTK_CONTAINER(eb), darktable.gui->ui->toast_msg);
1702 gtk_widget_set_valign(eb, GTK_ALIGN_START);
1703 gtk_widget_set_halign(eb, GTK_ALIGN_CENTER);
1704 gtk_overlay_add_overlay(GTK_OVERLAY(darktable.gui->ui->center_base), eb);
1705 //gtk_overlay_reorder_overlay(GTK_OVERLAY(darktable.gui->ui->center_base), eb, -1);
1706
1707 /* update log message label */
1710
1711 /* update toast message label */
1714
1715
1716 // Showing everything
1717 gtk_widget_show_all(dt_ui_main_window(gui->ui));
1718
1719 gtk_widget_set_visible(dt_ui_log_msg(gui->ui), FALSE);
1720 gtk_widget_set_visible(dt_ui_toast_msg(gui->ui), FALSE);
1721}
1722
1724{
1725 g_return_if_fail(GTK_IS_CONTAINER(ui->containers[c]));
1726
1727 if(GTK_WIDGET(ui->containers[c]) != gtk_widget_get_parent(w)) return;
1728
1729 gtk_container_set_focus_child(GTK_CONTAINER(ui->containers[c]), w);
1730 gtk_widget_queue_draw(ui->containers[c]);
1731}
1732
1733void dt_ui_container_foreach(dt_ui_t *ui, const dt_ui_container_t c, GtkCallback callback)
1734{
1735 g_return_if_fail(GTK_IS_CONTAINER(ui->containers[c]));
1736 gtk_container_foreach(GTK_CONTAINER(ui->containers[c]), callback, (gpointer)ui->containers[c]);
1737}
1738
1743
1745{
1746 if(darktable.gui && !gtk_window_is_active(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui))))
1747 {
1748 gtk_window_set_urgency_hint(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), TRUE);
1749#ifdef MAC_INTEGRATION
1750#ifdef GTK_TYPE_OSX_APPLICATION
1751 gtk_osxapplication_attention_request(g_object_new(GTK_TYPE_OSX_APPLICATION, NULL), INFO_REQUEST);
1752#else
1753 gtkosx_application_attention_request(g_object_new(GTKOSX_TYPE_APPLICATION, NULL), INFO_REQUEST);
1754#endif
1755#endif
1756 }
1757}
1758
1759/* this is called as a signal handler, the signal raising logic asserts the gdk lock. */
1760static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
1761{
1762 gtk_widget_queue_draw(widget);
1763}
1764
1765static void _ui_log_redraw_callback(gpointer instance, GtkWidget *widget)
1766{
1767 // draw log message, if any
1769 if(!GTK_IS_LABEL(widget))
1770 {
1772 return;
1773 }
1775 {
1776 if(strcmp(darktable.control->log_message[darktable.control->log_ack], gtk_label_get_text(GTK_LABEL(widget))))
1777 gtk_label_set_markup(GTK_LABEL(widget), darktable.control->log_message[darktable.control->log_ack]);
1778 gtk_widget_show(widget);
1779 }
1780 else
1781 {
1782 gtk_widget_hide(widget);
1783 }
1785}
1786
1787static void _ui_toast_redraw_callback(gpointer instance, GtkWidget *widget)
1788{
1789 // draw toast message, if any
1791 if(!GTK_IS_LABEL(widget))
1792 {
1794 return;
1795 }
1797 {
1798 if(strcmp(darktable.control->toast_message[darktable.control->toast_ack], gtk_label_get_text(GTK_LABEL(widget))))
1799 gtk_label_set_markup(GTK_LABEL(widget), darktable.control->toast_message[darktable.control->toast_ack]);
1800 if(!gtk_widget_get_visible(widget))
1801 {
1802 const int h = gtk_widget_get_allocated_height(dt_ui_center_base(darktable.gui->ui));
1803 gtk_widget_set_margin_bottom(gtk_widget_get_parent(widget), 0.15 * h - DT_PIXEL_APPLY_DPI(10));
1804 gtk_widget_show(widget);
1805 }
1806 }
1807 else
1808 {
1809 if(gtk_widget_get_visible(widget)) gtk_widget_hide(widget);
1810 }
1812}
1813
1814void dt_ellipsize_combo(GtkComboBox *cbox)
1815{
1816 GList *renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(cbox));
1817 for(const GList *it = renderers; it; it = g_list_next(it))
1818 {
1819 GtkCellRendererText *tr = GTK_CELL_RENDERER_TEXT(it->data);
1820 g_object_set(G_OBJECT(tr), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
1821 }
1822 g_list_free(renderers);
1823 renderers = NULL;
1824}
1825
1832
1833static void _gtk_main_quit_safe(GtkWidget *widget, gpointer data)
1834{
1835 (void)widget;
1836 (void)data;
1837 if(gtk_main_level() > 0) gtk_main_quit();
1838}
1839
1840static void _yes_no_button_handler(GtkButton *button, gpointer data)
1841{
1842 result_t *result = (result_t *)data;
1843
1844 if((void *)button == (void *)result->button_yes)
1845 result->result = RESULT_YES;
1846 else if((void *)button == (void *)result->button_no)
1847 result->result = RESULT_NO;
1848
1849 if(result->entry)
1850 result->entry_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(result->entry)));
1851 gtk_widget_destroy(result->window);
1852 _gtk_main_quit_safe(NULL, NULL);
1853}
1854
1855gboolean dt_gui_show_standalone_yes_no_dialog(const char *title, const char *markup, const char *no_text,
1856 const char *yes_text)
1857{
1858 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1859#ifdef GDK_WINDOWING_QUARTZ
1861#endif
1862
1863 // themes not yet loaded, no CSS add some manual padding
1864 const int padding = darktable.themes ? 0 : 5;
1865
1866 gtk_window_set_icon_name(GTK_WINDOW(window), "ansel");
1867 gtk_window_set_title(GTK_WINDOW(window), title);
1868 g_signal_connect(window, "destroy", G_CALLBACK(_gtk_main_quit_safe), NULL);
1869
1870 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
1871
1874 {
1875 GtkWidget *main_window = dt_ui_main_window(darktable.gui->ui);
1876 if(GTK_IS_WINDOW(main_window))
1877 {
1878 GtkWindow *win = GTK_WINDOW(main_window);
1879 gtk_window_set_transient_for(GTK_WINDOW(window), win);
1880 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
1881 if(gtk_widget_get_visible(GTK_WIDGET(win)))
1882 {
1883 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT);
1884 }
1885 }
1886 }
1887
1888 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1889 gtk_container_add(GTK_CONTAINER(window), vbox);
1890
1891 GtkWidget *mhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1892 gtk_box_pack_start(GTK_BOX(vbox), mhbox, TRUE, TRUE, padding);
1893
1894 if(padding)
1895 {
1896 gtk_box_pack_start(GTK_BOX(mhbox),
1897 gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING), TRUE, TRUE, padding);
1898 }
1899
1900 GtkWidget *label = gtk_label_new(NULL);
1901 gtk_label_set_markup(GTK_LABEL(label), markup);
1902 gtk_box_pack_start(GTK_BOX(mhbox), label, TRUE, TRUE, padding);
1903
1904 if(padding)
1905 {
1906 gtk_box_pack_start(GTK_BOX(mhbox),
1907 gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING), TRUE, TRUE, padding);
1908 }
1909
1910 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1911 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1912
1913 result_t result = {.result = RESULT_NONE, .window = window};
1914
1915 GtkWidget *button;
1916
1917 if(no_text)
1918 {
1919 button = gtk_button_new_with_label(no_text);
1920 result.button_no = button;
1921 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_yes_no_button_handler), &result);
1922 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1923 }
1924
1925 if(yes_text)
1926 {
1927 button = gtk_button_new_with_label(yes_text);
1928 result.button_yes = button;
1929 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_yes_no_button_handler), &result);
1930 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1931 }
1932
1933 gtk_widget_show_all(window);
1934 gtk_main();
1935
1936 return result.result == RESULT_YES;
1937}
1938
1939char *dt_gui_show_standalone_string_dialog(const char *title, const char *markup, const char *placeholder,
1940 const char *no_text, const char *yes_text)
1941{
1942 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1943#ifdef GDK_WINDOWING_QUARTZ
1945#endif
1946
1947 gtk_window_set_icon_name(GTK_WINDOW(window), "ansel");
1948 gtk_window_set_title(GTK_WINDOW(window), title);
1949 g_signal_connect(window, "destroy", G_CALLBACK(_gtk_main_quit_safe), NULL);
1950
1951 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
1952
1955 {
1956 GtkWidget *main_window = dt_ui_main_window(darktable.gui->ui);
1957 if(GTK_IS_WINDOW(main_window))
1958 {
1959 GtkWindow *win = GTK_WINDOW(main_window);
1960 gtk_window_set_transient_for(GTK_WINDOW(window), win);
1961 if(gtk_widget_get_visible(GTK_WIDGET(win)))
1962 {
1963 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT);
1964 }
1965 }
1966 }
1967
1968 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1969 gtk_widget_set_margin_start(vbox, 10);
1970 gtk_widget_set_margin_end(vbox, 10);
1971 gtk_widget_set_margin_top(vbox, 7);
1972 gtk_widget_set_margin_bottom(vbox, 5);
1973 gtk_container_add(GTK_CONTAINER(window), vbox);
1974
1975 GtkWidget *label = gtk_label_new(NULL);
1976 gtk_label_set_markup(GTK_LABEL(label), markup);
1977 gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
1978
1979 GtkWidget *entry = gtk_entry_new();
1981
1982 g_object_ref(entry);
1983 if(placeholder)
1984 gtk_entry_set_placeholder_text(GTK_ENTRY(entry), placeholder);
1985 gtk_box_pack_start(GTK_BOX(vbox), entry, TRUE, TRUE, 0);
1986
1987 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1988 gtk_widget_set_margin_top(hbox, 10);
1989 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1990
1991 result_t result = {.result = RESULT_NONE, .window = window, .entry = entry};
1992
1993 GtkWidget *button;
1994
1995 if(no_text)
1996 {
1997 button = gtk_button_new_with_label(no_text);
1998 result.button_no = button;
1999 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_yes_no_button_handler), &result);
2000 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
2001 }
2002
2003 if(yes_text)
2004 {
2005 button = gtk_button_new_with_label(yes_text);
2006 result.button_yes = button;
2007 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_yes_no_button_handler), &result);
2008 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
2009 }
2010
2011 gtk_widget_show_all(window);
2012 gtk_main();
2013
2014 if(result.result == RESULT_YES)
2015 return result.entry_text;
2016
2017 dt_free(result.entry_text);
2018 return NULL;
2019}
2020
2021// TODO: should that go to another place than gtk.c?
2022void dt_gui_add_help_link(GtkWidget *widget, char *link)
2023{
2024 g_object_set_data_full(G_OBJECT(widget), "dt-help-url", link, g_free);
2025 gtk_widget_add_events(widget, GDK_BUTTON_PRESS_MASK);
2026}
2027
2028// load a CSS theme
2029void dt_gui_load_theme(const char *theme)
2030{
2031 char theme_css[PATH_MAX] = { 0 };
2032 g_snprintf(theme_css, sizeof(theme_css), "%s.css", theme);
2033
2034 if(!dt_conf_key_exists("use_system_font"))
2035 dt_conf_set_bool("use_system_font", TRUE);
2036
2037 //set font size
2038 if(dt_conf_get_bool("use_system_font"))
2039 gtk_settings_reset_property(gtk_settings_get_default(), "gtk-font-name");
2040 else
2041 {
2042 //font name can only use period as decimal separator
2043 //but printf format strings use comma for some locales, so replace comma with period
2044 gchar *font_size = g_strdup_printf(_("%.1f"), dt_conf_get_float("font_size"));
2045 gchar *font_size_updated = dt_util_str_replace(font_size, ",", ".");
2046 gchar *font_name = g_strdup_printf(_("Sans %s"), font_size_updated);
2047 g_object_set(gtk_settings_get_default(), "gtk-font-name", font_name, NULL);
2048 dt_free(font_size_updated);
2049 dt_free(font_size);
2050 dt_free(font_name);
2051 }
2052
2053 gchar *path, *usercsspath;
2054 char datadir[PATH_MAX] = { 0 }, configdir[PATH_MAX] = { 0 };
2055 dt_loc_get_datadir(datadir, sizeof(datadir));
2056 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
2057
2058 // user dir theme
2059 path = g_build_filename(configdir, "themes", theme_css, NULL);
2060 if(!g_file_test(path, G_FILE_TEST_EXISTS))
2061 {
2062 // dt dir theme
2063 dt_free(path);
2064 path = g_build_filename(datadir, "themes", theme_css, NULL);
2065 if(!g_file_test(path, G_FILE_TEST_EXISTS))
2066 {
2067 // fallback to default theme
2068 dt_free(path);
2069 path = g_build_filename(datadir, "themes", "ansel.css", NULL);
2070 dt_conf_set_string("ui_last/theme", "ansel");
2071 }
2072 else
2073 dt_conf_set_string("ui_last/theme", theme);
2074 }
2075 else
2076 dt_conf_set_string("ui_last/theme", theme);
2077
2078 GError *error = NULL;
2079
2080 GtkStyleProvider *themes_style_provider = GTK_STYLE_PROVIDER(gtk_css_provider_new());
2081 gtk_style_context_add_provider_for_screen
2082 (gdk_screen_get_default(), themes_style_provider, GTK_STYLE_PROVIDER_PRIORITY_USER + 1);
2083
2084 usercsspath = g_build_filename(configdir, "user.css", NULL);
2085
2086 gchar *path_uri = g_filename_to_uri(path, NULL, &error);
2087 if(IS_NULL_PTR(path_uri))
2088 fprintf(stderr, "%s: could not convert path %s to URI. Error: %s\n", G_STRFUNC, path, error->message);
2089
2090 gchar *usercsspath_uri = g_filename_to_uri(usercsspath, NULL, &error);
2091 if(IS_NULL_PTR(usercsspath_uri))
2092 fprintf(stderr, "%s: could not convert path %s to URI. Error: %s\n", G_STRFUNC, usercsspath, error->message);
2093
2094 gchar *themecss = NULL;
2095 if(dt_conf_get_bool("themes/usercss") && g_file_test(usercsspath, G_FILE_TEST_EXISTS))
2096 {
2097 themecss = g_strjoin(NULL, "@import url('", path_uri,
2098 "'); @import url('", usercsspath_uri, "');", NULL);
2099 }
2100 else
2101 {
2102 themecss = g_strjoin(NULL, "@import url('", path_uri, "');", NULL);
2103 }
2104
2105 dt_free(path_uri);
2106 dt_free(usercsspath_uri);
2107 dt_free(path);
2108 dt_free(usercsspath);
2109
2110 if(dt_conf_get_bool("ui/hide_tooltips"))
2111 {
2112 gchar *newcss = g_strjoin(NULL, themecss, " tooltip {opacity: 0; background: transparent;}", NULL);
2113 dt_free(themecss);
2114 themecss = newcss;
2115 }
2116
2117 if(!gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(themes_style_provider), themecss, -1, &error))
2118 {
2119 fprintf(stderr, "%s: error parsing combined CSS %s: %s\n", G_STRFUNC, themecss, error->message);
2120 g_clear_error(&error);
2121 }
2122
2123 dt_free(themecss);
2124
2125 g_object_unref(themes_style_provider);
2126
2127 // setup the colors
2128
2129 GdkRGBA *c = darktable.gui->colors;
2130 GtkWidget *main_window = dt_ui_main_window(darktable.gui->ui);
2131 GtkStyleContext *ctx = gtk_widget_get_style_context(main_window);
2132
2133 c[DT_GUI_COLOR_BG] = (GdkRGBA){ 0.1333, 0.1333, 0.1333, 1.0 };
2134
2135 struct color_init
2136 {
2137 const char *name;
2138 GdkRGBA default_col;
2139 } init[DT_GUI_COLOR_LAST] = {
2140 [DT_GUI_COLOR_DARKROOM_BG] = { "darkroom_bg_color", { .2, .2, .2, 1.0 } },
2141 [DT_GUI_COLOR_DARKROOM_PREVIEW_BG] = { "darkroom_preview_bg_color", { .1, .1, .1, 1.0 } },
2142 [DT_GUI_COLOR_LIGHTTABLE_BG] = { "lighttable_bg_color", { .2, .2, .2, 1.0 } },
2143 [DT_GUI_COLOR_LIGHTTABLE_PREVIEW_BG] = { "lighttable_preview_bg_color", { .1, .1, .1, 1.0 } },
2144 [DT_GUI_COLOR_LIGHTTABLE_FONT] = { "lighttable_bg_font_color", { .7, .7, .7, 1.0 } },
2145 [DT_GUI_COLOR_PRINT_BG] = { "print_bg_color", { .2, .2, .2, 1.0 } },
2146 [DT_GUI_COLOR_BRUSH_CURSOR] = { "brush_cursor", { 1., 1., 1., 0.9 } },
2147 [DT_GUI_COLOR_BRUSH_TRACE] = { "brush_trace", { 0., 0., 0., 0.8 } },
2148 [DT_GUI_COLOR_BUTTON_FG] = { "button_fg", { 0.7, 0.7, 0.7, 0.55 } },
2149 [DT_GUI_COLOR_THUMBNAIL_BG] = { "thumbnail_bg_color", { 0.4, 0.4, 0.4, 1.0 } },
2150 [DT_GUI_COLOR_THUMBNAIL_SELECTED_BG] = { "thumbnail_selected_bg_color", { 0.8, 0.8, 0.8, 1.0 } },
2151 [DT_GUI_COLOR_THUMBNAIL_HOVER_BG] = { "thumbnail_hover_bg_color", { 0.65, 0.65, 0.65, 1.0 } },
2152 [DT_GUI_COLOR_THUMBNAIL_OUTLINE] = { "thumbnail_outline_color", { 0.2, 0.2, 0.2, 1.0 } },
2153 [DT_GUI_COLOR_THUMBNAIL_SELECTED_OUTLINE] = { "thumbnail_selected_outline_color", { 0.4, 0.4, 0.4, 1.0 } },
2154 [DT_GUI_COLOR_THUMBNAIL_HOVER_OUTLINE] = { "thumbnail_hover_outline_color", { 0.6, 0.6, 0.6, 1.0 } },
2155 [DT_GUI_COLOR_THUMBNAIL_FONT] = { "thumbnail_font_color", { 0.425, 0.425, 0.425, 1.0 } },
2156 [DT_GUI_COLOR_THUMBNAIL_SELECTED_FONT] = { "thumbnail_selected_font_color", { 0.5, 0.5, 0.5, 1.0 } },
2157 [DT_GUI_COLOR_THUMBNAIL_HOVER_FONT] = { "thumbnail_hover_font_color", { 0.7, 0.7, 0.7, 1.0 } },
2158 [DT_GUI_COLOR_THUMBNAIL_BORDER] = { "thumbnail_border_color", { 0.1, 0.1, 0.1, 1.0 } },
2159 [DT_GUI_COLOR_THUMBNAIL_SELECTED_BORDER] = { "thumbnail_selected_border_color", { 0.9, 0.9, 0.9, 1.0 } },
2160 [DT_GUI_COLOR_FILMSTRIP_BG] = { "filmstrip_bg_color", { 0.2, 0.2, 0.2, 1.0 } },
2161 [DT_GUI_COLOR_PREVIEW_HOVER_BORDER] = { "preview_hover_border_color", { 0.9, 0.9, 0.9, 1.0 } },
2162 [DT_GUI_COLOR_LOG_BG] = { "log_bg_color", { 0.1, 0.1, 0.1, 1.0 } },
2163 [DT_GUI_COLOR_LOG_FG] = { "log_fg_color", { 0.6, 0.6, 0.6, 1.0 } },
2164 [DT_GUI_COLOR_MAP_COUNT_SAME_LOC] = { "map_count_same_loc_color", { 1.0, 1.0, 1.0, 1.0 } },
2165 [DT_GUI_COLOR_MAP_COUNT_DIFF_LOC] = { "map_count_diff_loc_color", { 1.0, 0.85, 0.0, 1.0 } },
2166 [DT_GUI_COLOR_MAP_COUNT_BG] = { "map_count_bg_color", { 0.0, 0.0, 0.0, 1.0 } },
2167 [DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH] = { "map_count_circle_color_h", { 1.0, 1.0, 0.8, 1.0 } },
2168 [DT_GUI_COLOR_MAP_LOC_SHAPE_LOW] = { "map_count_circle_color_l", { 0.0, 0.0, 0.0, 1.0 } },
2169 [DT_GUI_COLOR_MAP_LOC_SHAPE_DEF] = { "map_count_circle_color_d", { 1.0, 0.0, 0.0, 1.0 } },
2170 };
2171
2172 // starting from 1 as DT_GUI_COLOR_BG is not part of this table
2173 for(int i = 1; i < DT_GUI_COLOR_LAST; i++)
2174 {
2175 if(!gtk_style_context_lookup_color(ctx, init[i].name, &c[i]))
2176 {
2177 c[i] = init[i].default_col;
2178 }
2179 }
2180
2181 // The active theme/font may change the root font size, so refresh the cached em
2182 // that drives DT_GUI_BOX_SPACING.
2184}
2185
2186GdkModifierType dt_key_modifier_state()
2187{
2188 guint state = 0;
2189 GdkWindow *window = gtk_widget_get_window(dt_ui_main_window(darktable.gui->ui));
2190 gdk_device_get_state(gdk_seat_get_pointer(gdk_display_get_default_seat(gdk_window_get_display(window))), window, NULL, &state);
2191 return state;
2192
2193/* FIXME double check correct way of doing this (merge conflict with Input System NG 20210319)
2194 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
2195 return gdk_keymap_get_modifier_state(keymap) & gdk_keymap_get_modifier_mask(keymap, GDK_MODIFIER_INTENT_DEFAULT_MOD_MASK);
2196*/
2197}
2198
2199static void _notebook_size_callback(GtkNotebook *notebook, GdkRectangle *allocation, gpointer *data)
2200{
2201 const int n = gtk_notebook_get_n_pages(notebook);
2202 g_return_if_fail(n > 0);
2203
2204 GtkRequestedSize *sizes = g_malloc_n(n, sizeof(GtkRequestedSize));
2205
2206 for(int i = 0; i < n; i++)
2207 {
2208 sizes[i].data = gtk_notebook_get_tab_label(notebook, gtk_notebook_get_nth_page(notebook, i));
2209 sizes[i].minimum_size = 0;
2210 GtkRequisition natural_size;
2211 gtk_widget_get_preferred_size(sizes[i].data, NULL, &natural_size);
2212 sizes[i].natural_size = natural_size.width;
2213 }
2214
2215 GtkAllocation first, last;
2216 gtk_widget_get_allocation(sizes[0].data, &first);
2217 gtk_widget_get_allocation(sizes[n - 1].data, &last);
2218
2219 const gint total_space = last.x + last.width - first.x; // ignore tab padding; CSS sets padding for label
2220
2221 if(total_space > 0)
2222 {
2223 gtk_distribute_natural_allocation(total_space, n, sizes);
2224
2225 for(int i = 0; i < n; i++)
2226 gtk_widget_set_size_request(sizes[i].data, sizes[i].minimum_size, -1);
2227
2228 gtk_widget_size_allocate(GTK_WIDGET(notebook), allocation);
2229
2230 for(int i = 0; i < n; i++)
2231 gtk_widget_set_size_request(sizes[i].data, -1, -1);
2232 }
2233
2234 dt_free(sizes);
2235}
2236
2237// GTK_STATE_FLAG_PRELIGHT does not seem to get set on the label on hover so
2238// state-flags-changed cannot update darktable.control->element for shortcut mapping
2239static gboolean _notebook_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
2240{
2241 GtkAllocation notebook_alloc, label_alloc;
2242 gtk_widget_get_allocation(widget, &notebook_alloc);
2243
2244 GtkNotebook *notebook = GTK_NOTEBOOK(widget);
2245 const int n = gtk_notebook_get_n_pages(notebook);
2246 for(int i = 0; i < n; i++)
2247 {
2248 gtk_widget_get_allocation(gtk_notebook_get_tab_label(notebook, gtk_notebook_get_nth_page(notebook, i)), &label_alloc);
2249 }
2250
2251 return FALSE;
2252}
2253
2255{
2256 return GTK_NOTEBOOK(gtk_notebook_new());
2257}
2258
2259GtkWidget *dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
2260{
2261 gchar *text_cpy = g_strdup(_(text));
2262 dt_capitalize_label(text_cpy);
2263 GtkWidget *label = gtk_label_new(text_cpy);
2264 dt_free(text_cpy);
2265 GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2266 if(strlen(text) > 2)
2267 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
2268 gtk_widget_set_tooltip_text(label, tooltip ? tooltip : _(text));
2269 gtk_widget_set_has_tooltip(GTK_WIDGET(notebook), FALSE);
2270
2271 gint page_num = gtk_notebook_append_page(notebook, page, label);
2272 gtk_container_child_set(GTK_CONTAINER(notebook), page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2273 if(page_num == 1 &&
2274 !g_signal_handler_find(G_OBJECT(notebook), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _notebook_size_callback, NULL))
2275 {
2276 g_signal_connect(G_OBJECT(notebook), "size-allocate", G_CALLBACK(_notebook_size_callback), NULL);
2277 g_signal_connect(G_OBJECT(notebook), "motion-notify-event", G_CALLBACK(_notebook_motion_notify_callback), NULL);
2278 }
2279
2280 return page;
2281}
2282
2284{
2285 gint height = DT_PIXEL_APPLY_DPI(10);
2286
2287 if(GTK_IS_TREE_VIEW(w))
2288 {
2289 gint row_height = 0;
2290
2291 const gint num_columns = gtk_tree_view_get_n_columns(GTK_TREE_VIEW(w));
2292 for(int c = 0; c < num_columns; c++)
2293 {
2294 gint cell_height = 0;
2295 gtk_tree_view_column_cell_get_size(gtk_tree_view_get_column(GTK_TREE_VIEW(w), c),
2296 NULL, NULL, NULL, NULL, &cell_height);
2297 if(cell_height > row_height) row_height = cell_height;
2298 }
2299 GValue separation = { G_TYPE_INT };
2300 gtk_widget_style_get_property(w, "vertical-separator", &separation);
2301
2302 if(row_height > 0) height = row_height + g_value_get_int(&separation);
2303 }
2304 else if(GTK_IS_TEXT_VIEW(w))
2305 {
2306 PangoLayout *layout = gtk_widget_create_pango_layout(w, "X");
2307 pango_layout_get_pixel_size(layout, NULL, &height);
2308 g_object_unref(layout);
2309 }
2310 else
2311 {
2312 GtkWidget *child = dt_gui_container_first_child(GTK_CONTAINER(w));
2313 if(child)
2314 {
2315 height = gtk_widget_get_allocated_height(child);
2316 }
2317 }
2318
2319 return height;
2320}
2321
2322static const char *const DT_GUI_WIDGET_AUTO_HEIGHT_KEY = "dt-gui-widget-auto-height";
2323
2324// find the scrolled window parent of a treeview, if any
2326{
2327 if(!GTK_IS_WIDGET(w)) return NULL;
2328
2329 GtkWidget *parent = w;
2330 while(parent)
2331 {
2332 if(GTK_IS_SCROLLED_WINDOW(parent)) break;
2333 parent = gtk_widget_get_parent(parent);
2334 }
2335
2336 return GTK_IS_SCROLLED_WINDOW(parent) ? parent : NULL;
2337}
2338
2339// Counts only visible items (those whose parents are expanded)
2340static int _treeview_count_visible_rows(GtkTreeView *treeview, GtkTreeModel *model, GtkTreeIter *parent)
2341{
2342 if(!GTK_IS_TREE_MODEL(model)) return 0;
2343
2344 GtkTreeIter iter;
2345 gboolean valid = parent ? gtk_tree_model_iter_children(model, &iter, parent)
2346 : gtk_tree_model_get_iter_first(model, &iter);
2347 int count = 0;
2348
2349 while(valid)
2350 {
2351 count++;
2352
2353 // If this item is expanded, recursively count its visible children
2354 if(gtk_tree_model_iter_has_child(model, &iter))
2355 {
2356 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
2357 if(path)
2358 {
2359 if(gtk_tree_view_row_expanded(treeview, path))
2360 {
2361 count += _treeview_count_visible_rows(treeview, model, &iter);
2362 }
2363 gtk_tree_path_free(path);
2364 }
2365 }
2366
2367 valid = gtk_tree_model_iter_next(model, &iter);
2368 }
2369
2370 return count;
2371}
2372
2374{
2375 if(!GTK_IS_TEXT_VIEW(textview)) return 0;
2376
2377 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
2378 if(!GTK_IS_TEXT_BUFFER(buffer)) return 0;
2379
2380 // For text views, use the number of logical lines in the buffer.
2381 return MAX(0, gtk_text_buffer_get_line_count(buffer));
2382}
2383
2385{
2386 if(IS_NULL_PTR(state)) return;
2387
2388 GtkTreeModel *model = state->model;
2389 if(model)
2390 {
2391 if(state->model_row_inserted) g_signal_handler_disconnect(model, state->model_row_inserted);
2392 if(state->model_row_deleted) g_signal_handler_disconnect(model, state->model_row_deleted);
2393 if(state->model_row_changed) g_signal_handler_disconnect(model, state->model_row_changed);
2394 if(state->model_rows_reordered) g_signal_handler_disconnect(model, state->model_rows_reordered);
2395 g_object_remove_weak_pointer(G_OBJECT(model), (gpointer *)&state->model);
2396 }
2397
2398 if(GTK_IS_TREE_VIEW(treeview))
2399 {
2400 if(state->model_row_expanded) g_signal_handler_disconnect(treeview, state->model_row_expanded);
2401 if(state->model_row_collapsed) g_signal_handler_disconnect(treeview, state->model_row_collapsed);
2402 }
2403
2404 state->model = NULL;
2405 state->model_row_inserted = 0;
2406 state->model_row_deleted = 0;
2407 state->model_row_changed = 0;
2408 state->model_rows_reordered = 0;
2409 state->model_row_expanded = 0;
2410 state->model_row_collapsed = 0;
2411}
2412
2414{
2415 if(IS_NULL_PTR(state)) return;
2416
2417 GtkTextBuffer *buffer = state->buffer;
2418 if(buffer)
2419 {
2420 if(state->buffer_changed) g_signal_handler_disconnect(buffer, state->buffer_changed);
2421 g_object_remove_weak_pointer(G_OBJECT(buffer), (gpointer *)&state->buffer);
2422 }
2423
2424 state->buffer = NULL;
2425 state->buffer_changed = 0;
2426}
2427
2436{
2438 return win ? gtk_widget_get_allocated_height(win) : DT_PIXEL_APPLY_DPI(1000);
2439}
2440
2451{
2452 dt_gui_widget_auto_height_t *state = g_object_get_data(G_OBJECT(w), DT_GUI_WIDGET_AUTO_HEIGHT_KEY);
2453 if(IS_NULL_PTR(state)) return;
2454
2456 if(!GTK_IS_SCROLLED_WINDOW(sw)) return;
2457
2458 const gint max_height = _resizable_scroll_max_height();
2459 const gint min_size = MAX(1, state->min_size);
2460 const gboolean has_conf = state->config_str && dt_conf_key_exists(state->config_str);
2461
2462 gint height;
2463 gint increment = 0;
2464
2465 if(state->mode == DT_UI_RESIZE_STATIC)
2466 {
2467 // Fixed height: the user's persisted size, or min_size as the default. Independent of content,
2468 // so a content refresh (e.g. on hovering another thumbnail) never shifts the layout.
2469 height = CLAMP(has_conf ? dt_conf_get_int(state->config_str) : min_size, min_size, max_height);
2470 }
2471 else
2472 {
2473 // Dynamic: fit to content, capped by the user's persisted height (or the window ceiling).
2474 const gboolean row_based = GTK_IS_TREE_VIEW(w) || GTK_IS_TEXT_VIEW(w);
2475 increment = row_based ? _get_container_row_heigth(w) : 0;
2476
2477 gint content = 0;
2478 if(GTK_IS_TREE_VIEW(w))
2479 {
2480 const int rows = _treeview_count_visible_rows(GTK_TREE_VIEW(w), gtk_tree_view_get_model(GTK_TREE_VIEW(w)), NULL);
2481 content = MAX(1, rows) * increment;
2482 }
2483 else if(GTK_IS_TEXT_VIEW(w))
2484 {
2485 const int rows = _textview_count_visible_rows(w);
2486 content = MAX(1, rows) * increment;
2487 }
2488 else
2489 {
2490 gtk_widget_get_preferred_height(w, NULL, &content);
2491 }
2492
2493 const gint cap = has_conf ? CLAMP(dt_conf_get_int(state->config_str), min_size, max_height) : max_height;
2494 height = CLAMP(MIN(content, cap), min_size, max_height);
2495
2496 // snap to whole rows for lists/textviews to avoid clipped half-rows
2497 if(increment > 0)
2498 {
2499 height += increment - 1;
2500 height -= height % increment;
2501 }
2502 }
2503 state->last_height = height;
2504
2505 GtkBorder padding;
2506 gtk_style_context_get_padding(gtk_widget_get_style_context(sw),
2507 gtk_widget_get_state_flags(sw), &padding);
2508
2509 gint old_height = 0;
2510 gtk_widget_get_size_request(sw, NULL, &old_height);
2511 const gint new_height = height + padding.top + padding.bottom + (GTK_IS_TEXT_VIEW(w) ? 2 : 0);
2512 if(new_height != old_height)
2513 gtk_widget_set_size_request(sw, -1, new_height);
2514}
2515
2516static void _widget_auto_update(GtkWidget *widget)
2517{
2519}
2520
2521static gboolean _resizable_scroll_draw(GtkWidget *w, cairo_t *cr, gpointer user_data)
2522{
2524 return FALSE;
2525}
2526
2527static void _resizable_scroll_realize(GtkWidget *w, gpointer user_data)
2528{
2530}
2531
2532// Drag handle accessors: bare (pre-padding) target height, kept consistent with the sizing rule.
2533static int _resizable_scroll_handle_get_size(gpointer user_data)
2534{
2535 GtkWidget *w = GTK_WIDGET(user_data);
2536 const dt_gui_widget_auto_height_t *state = g_object_get_data(G_OBJECT(w), DT_GUI_WIDGET_AUTO_HEIGHT_KEY);
2537 return state ? state->last_height : 0;
2538}
2539
2540static int _resizable_scroll_handle_resize(int requested_size, gboolean finished, gpointer user_data)
2541{
2542 GtkWidget *w = GTK_WIDGET(user_data);
2543 dt_gui_widget_auto_height_t *state = g_object_get_data(G_OBJECT(w), DT_GUI_WIDGET_AUTO_HEIGHT_KEY);
2544 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->config_str)) return requested_size;
2545
2546 const gint value = CLAMP(requested_size, MAX(1, state->min_size), _resizable_scroll_max_height());
2547 dt_conf_set_int(state->config_str, value);
2549 return state->last_height;
2550}
2551
2552static void _widget_auto_model_row_inserted(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
2553 gpointer user_data)
2554{
2555 (void)model;
2556 (void)path;
2557 (void)iter;
2558 _widget_auto_update(GTK_WIDGET(user_data));
2559}
2560
2561static void _widget_auto_model_row_deleted(GtkTreeModel *model, GtkTreePath *path, gpointer user_data)
2562{
2563 (void)model;
2564 (void)path;
2565 _widget_auto_update(GTK_WIDGET(user_data));
2566}
2567
2568static void _widget_auto_model_row_changed(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
2569 gpointer user_data)
2570{
2571 (void)model;
2572 (void)path;
2573 (void)iter;
2574 _widget_auto_update(GTK_WIDGET(user_data));
2575}
2576
2577static void _widget_auto_model_rows_reordered(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
2578 gint *new_order, gpointer user_data)
2579{
2580 (void)model;
2581 (void)path;
2582 (void)iter;
2583 (void)new_order;
2584 _widget_auto_update(GTK_WIDGET(user_data));
2585}
2586
2587static void _widget_auto_model_row_expanded(GtkTreeView *tree_view, GtkTreeIter *expanded_iter, GtkTreePath *path,
2588 gpointer user_data)
2589{
2590 (void)tree_view;
2591 (void)expanded_iter;
2592 (void)path;
2593 _widget_auto_update(GTK_WIDGET(user_data));
2594}
2595
2596static void _widget_auto_model_row_collapsed(GtkTreeView *tree_view, GtkTreeIter *collapsed_iter, GtkTreePath *path,
2597 gpointer user_data)
2598{
2599 (void)tree_view;
2600 (void)collapsed_iter;
2601 (void)path;
2602
2603 // Recalculate tree view height after loading the data
2604 _widget_auto_update(GTK_WIDGET(user_data));
2605}
2606
2607static void _widget_auto_text_buffer_changed(GtkTextBuffer *buffer, gpointer user_data)
2608{
2609 (void)buffer;
2610 _widget_auto_update(GTK_WIDGET(user_data));
2611}
2612
2614{
2615 if(!GTK_IS_TREE_VIEW(treeview)) return;
2616
2617 dt_gui_widget_auto_height_t *state = g_object_get_data(G_OBJECT(treeview), DT_GUI_WIDGET_AUTO_HEIGHT_KEY);
2618 if(IS_NULL_PTR(state)) return;
2619
2620 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2621 if(model == state->model) return;
2622
2624 if(!GTK_IS_TREE_MODEL(model)) return;
2625
2626 state->model = model;
2627 g_object_add_weak_pointer(G_OBJECT(model), (gpointer *)&state->model);
2628 state->model_row_inserted = g_signal_connect(model, "row-inserted",
2629 G_CALLBACK(_widget_auto_model_row_inserted), treeview);
2630 state->model_row_deleted = g_signal_connect(model, "row-deleted",
2631 G_CALLBACK(_widget_auto_model_row_deleted), treeview);
2632 state->model_row_changed = g_signal_connect(model, "row-changed",
2633 G_CALLBACK(_widget_auto_model_row_changed), treeview);
2634 state->model_rows_reordered = g_signal_connect(model, "rows-reordered",
2635 G_CALLBACK(_widget_auto_model_rows_reordered), treeview);
2636 state->model_row_expanded = g_signal_connect(treeview, "row-expanded",
2637 G_CALLBACK(_widget_auto_model_row_expanded), treeview);
2638 state->model_row_collapsed = g_signal_connect(treeview, "row-collapsed",
2639 G_CALLBACK(_widget_auto_model_row_collapsed), treeview);
2640}
2641
2643{
2644 if(!GTK_IS_TEXT_VIEW(textview)) return;
2645
2646 dt_gui_widget_auto_height_t *state = g_object_get_data(G_OBJECT(textview), DT_GUI_WIDGET_AUTO_HEIGHT_KEY);
2647 if(IS_NULL_PTR(state)) return;
2648
2649 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
2650 if(buffer == state->buffer) return;
2651
2653 if(!GTK_IS_TEXT_BUFFER(buffer)) return;
2654
2655 state->buffer = buffer;
2656 g_object_add_weak_pointer(G_OBJECT(buffer), (gpointer *)&state->buffer);
2657 state->buffer_changed = g_signal_connect(buffer, "changed",
2658 G_CALLBACK(_widget_auto_text_buffer_changed), textview);
2659}
2660
2661static void _widget_auto_on_model_changed(GObject *treeview, GParamSpec *pspec, gpointer user_data)
2662{
2663 (void)pspec;
2664 (void)user_data;
2665 _widget_auto_connect_model(GTK_WIDGET(treeview));
2666 _widget_auto_update(GTK_WIDGET(treeview));
2667}
2668
2669static void _widget_auto_on_buffer_changed(GObject *textview, GParamSpec *pspec, gpointer user_data)
2670{
2671 (void)pspec;
2672 (void)user_data;
2673 _widget_auto_connect_buffer(GTK_WIDGET(textview));
2674 _widget_auto_update(GTK_WIDGET(textview));
2675}
2676
2677static void _widget_auto_height_free(gpointer data)
2678{
2680 if(IS_NULL_PTR(state)) return;
2683 g_free(state->config_str);
2684 dt_free(state);
2685}
2686
2687void dt_gui_textview_set_padding(GtkTextView *textview)
2688{
2689 if(!GTK_IS_TEXT_VIEW(textview)) return;
2690
2691 gtk_text_view_set_left_margin(textview, DT_PIXEL_APPLY_DPI(4));
2692 gtk_text_view_set_right_margin(textview, DT_PIXEL_APPLY_DPI(4));
2693 gtk_text_view_set_top_margin(textview, DT_PIXEL_APPLY_DPI(2));
2694 gtk_text_view_set_bottom_margin(textview, DT_PIXEL_APPLY_DPI(2));
2695}
2696
2713GtkWidget *dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
2714{
2715 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
2716 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
2717 if(GTK_IS_TREE_VIEW(w)) dt_gui_add_class(sw, "dt_recessed_scroll");
2718 gtk_container_add(GTK_CONTAINER(sw), w);
2719
2720 // Per-widget sizing state, freed with w.
2721 dt_gui_widget_auto_height_t *state = calloc(1, sizeof(*state));
2722 state->config_str = g_strdup(config_str);
2723 state->min_size = MAX(1, DT_PIXEL_APPLY_DPI(min_size));
2724 state->mode = mode;
2725 g_object_set_data_full(G_OBJECT(w), DT_GUI_WIDGET_AUTO_HEIGHT_KEY, state, _widget_auto_height_free);
2726
2727 if(mode == DT_UI_RESIZE_DYNAMIC)
2728 {
2729 // Sizing triggers. Lists/textviews recompute from their model/buffer change signals (cheap and
2730 // exact) plus a one-shot on realize, when row heights become accurate -- we deliberately avoid a
2731 // per-draw recount, which would re-walk a large model on every redraw. Generic content has no
2732 // such signals, so it recomputes on draw using its (GTK-cached) preferred height.
2733 if(GTK_IS_TREE_VIEW(w))
2734 {
2735 g_signal_connect(w, "notify::model", G_CALLBACK(_widget_auto_on_model_changed), NULL);
2736 g_signal_connect(w, "realize", G_CALLBACK(_resizable_scroll_realize), NULL);
2737 }
2738 else if(GTK_IS_TEXT_VIEW(w))
2739 {
2740 g_signal_connect(w, "notify::buffer", G_CALLBACK(_widget_auto_on_buffer_changed), NULL);
2741 g_signal_connect(w, "realize", G_CALLBACK(_resizable_scroll_realize), NULL);
2742 }
2743 else
2744 {
2745 g_signal_connect(G_OBJECT(w), "draw", G_CALLBACK(_resizable_scroll_draw), NULL);
2746 }
2749 }
2750 else
2751 {
2752 // Static: height is content-independent, so we only size once after realization (and on drag).
2753 g_signal_connect(w, "realize", G_CALLBACK(_resizable_scroll_realize), NULL);
2754 }
2755
2756 // Drag grip floating on the scrolled window's bottom edge (overlay): it takes no layout space,
2757 // so the wrapper leaves no margin-like gap and stays aligned with neighbouring widgets. The grip
2758 // is centered on the bottom border via CSS and is invisible until hovered.
2759 GtkWidget *handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_VERTICAL, FALSE,
2760 _("Drag to resize"),
2763
2764 GtkWidget *over = gtk_overlay_new();
2765 gtk_container_add(GTK_CONTAINER(over), sw);
2766 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
2767
2769 return over;
2770}
2771
2776{
2777 if(!GTK_IS_CONTAINER(wrapper)) return NULL;
2778 GList *children = gtk_container_get_children(GTK_CONTAINER(wrapper));
2779 GtkWidget *sw = NULL;
2780 for(GList *l = children; l; l = g_list_next(l))
2781 {
2782 if(GTK_IS_SCROLLED_WINDOW(l->data))
2783 {
2784 sw = GTK_WIDGET(l->data);
2785 break;
2786 }
2787 }
2788 g_list_free(children);
2789 return sw;
2790}
2791
2792// ---- Resizable drawing area (the histogram-scope paradigm, made reusable) -------------------
2793// A fixed-pixel-height GtkDrawingArea (or any widget sized by height-request) made vertically
2794// resizable by the shared grip primitive, with the height persisted to config. Unlike the
2795// scroll-wrap helper above, the content draws itself and is not scrolled; we only manage its
2796// height-request, so the drawing code keeps reading the live allocation as usual.
2797
2798static const char *const DT_UI_RESIZABLE_AREA_KEY = "dt-ui-resizable-area";
2799
2801{
2802 char *config_str; // conf key persisting the user-chosen height (px); owned
2803 int min_height; // minimum height floor, device pixels
2804 int last_height; // last applied height, shared with the drag handle
2806
2807static void _resizable_area_free(gpointer data)
2808{
2810 if(IS_NULL_PTR(state)) return;
2811 g_free(state->config_str);
2812 dt_free(state);
2813}
2814
2815static int _resizable_area_get_size(gpointer user_data)
2816{
2817 GtkWidget *area = GTK_WIDGET(user_data);
2818 const dt_ui_resizable_area_t *state = g_object_get_data(G_OBJECT(area), DT_UI_RESIZABLE_AREA_KEY);
2819 if(state) return state->last_height;
2820 return gtk_widget_get_allocated_height(area);
2821}
2822
2823static int _resizable_area_resize(int requested_size, gboolean finished, gpointer user_data)
2824{
2825 GtkWidget *area = GTK_WIDGET(user_data);
2826 dt_ui_resizable_area_t *state = g_object_get_data(G_OBJECT(area), DT_UI_RESIZABLE_AREA_KEY);
2827 if(IS_NULL_PTR(state)) return requested_size;
2828
2829 const int height = CLAMP(requested_size, MAX(1, state->min_height), _resizable_scroll_max_height());
2830 state->last_height = height;
2831 gtk_widget_set_size_request(area, -1, height);
2832 if(finished && state->config_str) dt_conf_set_int(state->config_str, height);
2833 return height;
2834}
2835
2836GtkWidget *dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
2837{
2838 dt_ui_resizable_area_t *state = calloc(1, sizeof(*state));
2839 state->config_str = g_strdup(config_str);
2840 state->min_height = MAX(1, DT_PIXEL_APPLY_DPI(min_height));
2841
2842 int height = (config_str && dt_conf_key_exists(config_str)) ? dt_conf_get_int(config_str)
2843 : DT_PIXEL_APPLY_DPI(default_height);
2844 height = CLAMP(height, state->min_height, _resizable_scroll_max_height());
2845 state->last_height = height;
2846 g_object_set_data_full(G_OBJECT(area), DT_UI_RESIZABLE_AREA_KEY, state, _resizable_area_free);
2847 gtk_widget_set_size_request(area, -1, height);
2848
2849 // Drag grip floating on the area's bottom edge (an overlay, not a packed sibling), so it takes
2850 // no layout space -- the area stays flush with neighbouring widgets, no margin-like gap. It sits
2851 // over the graph's bottom inset/axis margin (graphs reserve one), invisible until hovered.
2852 GtkWidget *handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_VERTICAL, FALSE,
2853 _("Drag to resize"),
2855
2856 GtkWidget *over = gtk_overlay_new();
2857 gtk_container_add(GTK_CONTAINER(over), area);
2858 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
2859 return over;
2860}
2861
2863{
2864 g_return_val_if_fail(GTK_IS_CONTAINER(container), FALSE);
2865 GList *children = gtk_container_get_children(container);
2866 gboolean has_children = !IS_NULL_PTR(children);
2867 g_list_free(children);
2868 children = NULL;
2869 return has_children;
2870}
2871
2873{
2874 g_return_val_if_fail(GTK_IS_CONTAINER(container), FALSE);
2875 GList *children = gtk_container_get_children(container);
2876 int num_children = g_list_length(children);
2877 g_list_free(children);
2878 children = NULL;
2879 return num_children;
2880}
2881
2883{
2884 g_return_val_if_fail(GTK_IS_CONTAINER(container), NULL);
2885 GList *children = gtk_container_get_children(container);
2886 GtkWidget *child = children ? (GtkWidget*)children->data : NULL;
2887 g_list_free(children);
2888 children = NULL;
2889 return child;
2890}
2891
2893{
2894 g_return_val_if_fail(GTK_IS_CONTAINER(container), NULL);
2895 GList *children = gtk_container_get_children(container);
2896 GtkWidget *child = (GtkWidget*)g_list_nth_data(children, which);
2897 g_list_free(children);
2898 children = NULL;
2899 return child;
2900}
2901
2902static void _remove_child(GtkWidget *widget, gpointer data)
2903{
2904 gtk_container_remove((GtkContainer*)data, widget);
2905}
2906
2908{
2909 g_return_if_fail(GTK_IS_CONTAINER(container));
2910 gtk_container_foreach(container, _remove_child, container);
2911}
2912
2913static void _delete_child(GtkWidget *widget, gpointer data)
2914{
2915 (void)data; // avoid unreferenced-parameter warning
2916 gtk_widget_destroy(widget);
2917}
2918
2920{
2921 g_return_if_fail(GTK_IS_CONTAINER(container));
2922 gtk_container_foreach(container, _delete_child, NULL);
2923}
2924
2926{
2927 if(IS_NULL_PTR(widget)) return NULL;
2928
2929 GtkWidget *relative = widget;
2930
2931 // Wayland only accepts the top-most enclosing popup as transient parent.
2932 for(GtkWidget *parent = gtk_widget_get_parent(widget); parent; parent = gtk_widget_get_parent(parent))
2933 if(GTK_IS_POPOVER(parent)) relative = parent;
2934
2935 if(rect)
2936 {
2937 rect->x = 0;
2938 rect->y = 0;
2939 rect->width = MAX(gtk_widget_get_allocated_width(widget), 1);
2940 rect->height = MAX(gtk_widget_get_allocated_height(widget), 1);
2941
2942 if(relative != widget
2943 && !gtk_widget_translate_coordinates(widget, relative, 0, 0, &rect->x, &rect->y))
2944 {
2945 rect->x = 0;
2946 rect->y = 0;
2947 }
2948 }
2949
2950 return relative;
2951}
2952
2953void dt_gui_menu_popup(GtkMenu *menu, GtkWidget *button, GdkGravity widget_anchor, GdkGravity menu_anchor)
2954{
2955 gtk_widget_show_all(GTK_WIDGET(menu));
2956
2957 GdkEvent *event = gtk_get_current_event();
2958 if(button)
2959 {
2960 GdkRectangle rect = { 0 };
2961 GtkWidget *relative = dt_gui_get_popup_relative_widget(button, &rect);
2962
2963 if(relative && relative != button && gtk_widget_get_window(relative))
2964 gtk_menu_popup_at_rect(menu, gtk_widget_get_window(relative), &rect, widget_anchor, menu_anchor, event);
2965 else
2966 gtk_menu_popup_at_widget(menu, button, widget_anchor, menu_anchor, event);
2967 }
2968 else
2969 {
2970 if(IS_NULL_PTR(event))
2971 {
2972 event = gdk_event_new(GDK_BUTTON_PRESS);
2973 event->button.device = gdk_seat_get_pointer(gdk_display_get_default_seat(gdk_display_get_default()));
2974 event->button.window = gtk_widget_get_window(GTK_WIDGET(darktable.gui->ui->main_window));
2975 g_object_ref(event->button.window);
2976 }
2977
2978 gtk_menu_popup_at_pointer(menu, event);
2979 }
2980 gdk_event_free(event);
2981}
2982
2983static void _popover_set_relative_to_topmost_parent(GtkPopover *popover, GtkWidget *button)
2984{
2985 GdkRectangle rect = { 0 };
2986 GtkWidget *relative = dt_gui_get_popup_relative_widget(button, &rect);
2987 gtk_popover_set_relative_to(popover, relative ? relative : button);
2988 gtk_popover_set_pointing_to(popover, &rect);
2989}
2990
2991// draw rounded rectangle
2992void dt_gui_draw_rounded_rectangle(cairo_t *cr, float width, float height, float x, float y)
2993{
2994 const float radius = height / 5.0f;
2995 const float degrees = M_PI / 180.0;
2996 cairo_new_sub_path(cr);
2997 cairo_arc(cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees);
2998 cairo_arc(cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees);
2999 cairo_arc(cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees);
3000 cairo_arc(cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
3001 cairo_close_path(cr);
3002 cairo_fill(cr);
3003}
3004
3005gboolean dt_gui_search_start(GtkWidget *widget, GdkEventKey *event, GtkSearchEntry *entry)
3006{
3007 if(gtk_search_entry_handle_event(entry, (GdkEvent *)event))
3008 {
3009 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
3010 return TRUE;
3011 }
3012
3013 return FALSE;
3014}
3015
3016void dt_gui_search_stop(GtkSearchEntry *entry, GtkWidget *widget)
3017{
3018 gtk_widget_grab_focus(widget);
3019
3020 gtk_entry_set_text(GTK_ENTRY(entry), "");
3021
3022 if(GTK_IS_TREE_VIEW(widget))
3023 {
3024 GtkTreePath *path = NULL;
3025 gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, NULL);
3026 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), path);
3027 gtk_tree_path_free(path);
3028 }
3029}
3030
3032{
3033 if(active)
3034 {
3035 // We don't apply the GTK_STATE_SELECTED flag to the container here because it would
3036 // be inherited by all children, which would mess up the state of checkboxes and togglebuttons.
3037 dt_gui_add_class(GTK_WIDGET(cs->expander), "active");
3038 }
3039 else
3040 {
3041 gtk_widget_set_state_flags(GTK_WIDGET(cs->expander), GTK_STATE_FLAG_NORMAL, TRUE);
3042 dt_gui_remove_class(GTK_WIDGET(cs->expander), "active");
3043 }
3044}
3045
3046static void _collapsible_container_show(GtkWidget *widget, gpointer user_data)
3047{
3048 /* Called whenever the container receives a "show" event, including from gtk_widget_show_all().
3049 * If the toggle is not active the section should remain collapsed, so we re-hide the container
3050 * immediately. By the time this fires, show_all has already recursed into the children and
3051 * set their visible flags, so a later expand will find all children ready to display. */
3053 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cs->toggle)))
3054 gtk_widget_hide(widget);
3055}
3056
3057static void _coeffs_button_changed(GtkDarktableToggleButton *widget, gpointer user_data)
3058{
3060
3061 const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cs->toggle));
3064 (active ? CPF_DIRECTION_DOWN : CPF_DIRECTION_LEFT), NULL);
3065 dt_conf_set_bool(cs->confname, active);
3066 _collapsible_set_states(cs, active);
3067}
3068
3069static void _coeffs_expander_click(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
3070{
3071 if(e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) return;
3072
3074
3075 const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cs->toggle));
3076 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cs->toggle), !active);
3077 _collapsible_set_states(cs, !active);
3078}
3079
3081{
3082 const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cs->toggle));
3084 (active ? CPF_DIRECTION_DOWN : CPF_DIRECTION_LEFT), NULL);
3086
3087 if(active)
3088 gtk_widget_show(GTK_WIDGET(cs->container));
3089 else
3090 gtk_widget_hide(GTK_WIDGET(cs->container));
3091
3092 _collapsible_set_states(cs, active);
3093}
3094
3096{
3097 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cs->toggle), FALSE);
3098 gtk_widget_hide(GTK_WIDGET(cs->container));
3100}
3101
3103 const char *confname, const char *label,
3104 GtkBox *parent, GtkPackType pack)
3105{
3106 const gboolean expanded = dt_conf_get_bool(confname);
3107
3108 cs->confname = confname;
3109 cs->parent = parent;
3110
3111 // collapsible section header
3112 GtkWidget *destdisp_head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3113 GtkWidget *header_evb = gtk_event_box_new();
3114 cs->label = dt_ui_section_label_new(label);
3115 dt_gui_add_class(destdisp_head, "dt_section_expander");
3116 gtk_container_add(GTK_CONTAINER(header_evb), cs->label);
3117
3119 (expanded ? CPF_DIRECTION_DOWN : CPF_DIRECTION_LEFT), NULL);
3120 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cs->toggle), expanded);
3121 dt_gui_add_class(cs->toggle, "dt_ignore_fg_state");
3122
3123 cs->container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3124 gtk_widget_set_name(GTK_WIDGET(cs->container), "collapsible");
3125 gtk_box_pack_start(GTK_BOX(destdisp_head), header_evb, TRUE, TRUE, 0);
3126 gtk_box_pack_start(GTK_BOX(destdisp_head), cs->toggle, FALSE, FALSE, 0);
3127
3128 cs->expander = dtgtk_expander_new(destdisp_head, GTK_WIDGET(cs->container));
3129 /* gtk_widget_show_all() on the parent (called by lib modules after gui_init) recurses into
3130 * the container and would override the collapsed state set by dtgtk_expander_set_expanded.
3131 * Connect to "show" so we can re-apply the correct visibility right after show_all touches it. */
3132 g_signal_connect(G_OBJECT(cs->container), "show",
3133 G_CALLBACK(_collapsible_container_show), (gpointer)cs);
3134 // Pack at the requested side so callers control ordering at insertion time.
3135 if(pack == GTK_PACK_START)
3136 gtk_box_pack_start(GTK_BOX(cs->parent), cs->expander, FALSE, FALSE, 0);
3137 else
3138 gtk_box_pack_end(GTK_BOX(cs->parent), cs->expander, FALSE, FALSE, 0);
3140 gtk_widget_set_name(cs->expander, "collapse-block");
3141
3142 g_signal_connect(G_OBJECT(cs->toggle), "toggled",
3143 G_CALLBACK(_coeffs_button_changed), (gpointer)cs);
3144
3145 g_signal_connect(G_OBJECT(header_evb), "button-release-event",
3146 G_CALLBACK(_coeffs_expander_click),
3147 (gpointer)cs);
3148}
3149
3150void dt_capitalize_label(gchar *text)
3151{
3152 if(text)
3153 {
3154 const char *underscore = "_";
3155
3156 // Deal with strings beginning with Mnemonics
3157 if(text[0] == underscore[0])
3158 text[1] = g_unichar_toupper(text[1]);
3159 else
3160 text[0] = g_unichar_toupper(text[0]);
3161 }
3162}
3163
3164GtkBox * attach_popover(GtkWidget *widget, const char *icon, GtkWidget *content)
3165{
3166 // Create the wrapping box and add the original widget to it
3167 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3168 gtk_box_pack_start(GTK_BOX(box), widget, FALSE, FALSE, 0);
3169
3170 // Create the info icon button that will trigger the popover
3171 GtkWidget *button = gtk_menu_button_new();
3172 GtkWidget *image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON);
3173 gtk_button_set_image(GTK_BUTTON(button), image);
3174 gtk_widget_set_hexpand(button, FALSE);
3175 gtk_widget_set_vexpand(button, FALSE);
3176 gtk_widget_set_size_request(button, DT_PIXEL_APPLY_DPI(16), DT_PIXEL_APPLY_DPI(16));
3177 gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
3178
3179 // Create the content of the popover
3180 GtkWidget *popover_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3181 gtk_box_pack_start(GTK_BOX(popover_box), content, FALSE, FALSE, 0);
3182
3183 // Wrap the content into a popover and attach it to the button
3184 GtkWidget *popover = gtk_popover_new(button);
3185 gtk_container_add(GTK_CONTAINER(popover), popover_box);
3186 gtk_popover_set_modal(GTK_POPOVER(popover), FALSE);
3187 g_signal_connect(G_OBJECT(popover), "show", G_CALLBACK(_popover_set_relative_to_topmost_parent), button);
3188 gtk_menu_button_set_popover(GTK_MENU_BUTTON(button), popover);
3189 gtk_widget_show_all(popover_box);
3190
3191 return GTK_BOX(box);
3192}
3193
3194GtkBox * attach_help_popover(GtkWidget *widget, const char *label)
3195{
3196 // Create the content of the popover
3197 GtkWidget *popover_label = gtk_label_new(label);
3198 gtk_label_set_line_wrap(GTK_LABEL(popover_label), TRUE);
3199 gtk_label_set_max_width_chars(GTK_LABEL(popover_label), 60);
3200 return attach_popover(widget, "help-about", popover_label);
3201}
3202
3203static gboolean _text_entry_focus_in_event(GtkWidget *self, GdkEventFocus event, gpointer user_data)
3204{
3206 return FALSE;
3207}
3208
3209static gboolean _text_entry_focus_out_event(GtkWidget *self, GdkEventFocus event, gpointer user_data)
3210{
3212 return FALSE;
3213}
3214
3215static gboolean _text_entry_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
3216{
3217 if(event->keyval == GDK_KEY_Escape)
3218 {
3220 return TRUE;
3221 }
3222 return FALSE;
3223}
3224
3226{
3227 gtk_widget_add_events(widget, GDK_FOCUS_CHANGE_MASK);
3228 g_signal_connect(G_OBJECT(widget), "focus-in-event", G_CALLBACK(_text_entry_focus_in_event), NULL);
3229 g_signal_connect(G_OBJECT(widget), "focus-out-event", G_CALLBACK(_text_entry_focus_out_event), NULL);
3230 g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(_text_entry_key_pressed), NULL);
3231}
3232
3233
3235{
3236 // Refocus window, useful if we just closed a popup/modal/transient
3237 gtk_window_present_with_time(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), GDK_CURRENT_TIME);
3238
3239 // Desperate measure to refocus the window
3240 gtk_grab_add(dt_ui_main_window(darktable.gui->ui));
3241 gtk_grab_remove(dt_ui_main_window(darktable.gui->ui));
3242 gtk_widget_grab_focus(dt_ui_main_window(darktable.gui->ui));
3243
3244 const char *current_view = dt_view_manager_name(darktable.view_manager);
3245 if(g_strcmp0(current_view, "lighttable"))
3246 {
3247 gtk_widget_grab_focus(darktable.gui->ui->thumbtable_lighttable->grid);
3248 }
3249 else
3250 {
3251 gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
3252 }
3253
3254 // Be sure to re-enable accelerators
3256}
3257
3258// clang-format off
3259// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3260// vim: shiftwidth=2 expandtab tabstop=2 cindent
3261// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3262// clang-format on
gboolean dt_accels_dispatch(GtkWidget *w, GdkEvent *event, gpointer user_data)
Force our listener for all key strokes to bypass reserved Gtk keys.
dt_accels_t * dt_accels_init(char *config_file, GtkAccelFlags flags)
static void dt_accels_disable(dt_accels_t *accels, gboolean state)
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void init(dt_imageio_module_format_t *self)
Definition avif.c:151
uint32_t container(dt_lib_module_t *self)
GtkWidget * dt_bauhaus_resize_handle_new(GtkOrientation orientation, gboolean invert, const char *tooltip, dt_bauhaus_resize_handle_get_size_f get_size, dt_bauhaus_resize_handle_resize_f resize, gpointer user_data)
Create a themed handle widget driving one-dimensional resize gestures.
Definition bauhaus.c:1179
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_colorspaces_set_display_profile(const dt_colorspaces_color_profile_type_t profile_type)
@ DT_COLORSPACE_DISPLAY
Definition colorspaces.h:91
const float delta
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * name
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
int dt_conf_key_exists(const char *key)
float dt_conf_get_float(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
void dt_control_key_pressed(GdkEventKey *event)
Definition control.c:619
void dt_control_button_pressed(double x, double y, double pressure, int which, int type, uint32_t state)
Definition control.c:706
void * dt_control_expose(void *voidptr)
Definition control.c:534
void dt_control_quit()
Definition control.c:433
void dt_control_mouse_leave()
Definition control.c:604
gboolean dt_control_configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
Definition control.c:490
void dt_control_button_released(double x, double y, int which, uint32_t state)
Definition control.c:624
void dt_control_set_pointer_input(const dt_control_pointer_input_t *input)
Definition control.c:83
void dt_control_mouse_moved(double x, double y, double pressure, int which)
Definition control.c:614
void dt_control_mouse_enter()
Definition control.c:609
#define dt_control_change_cursor(cursor)
Definition control.h:116
int dt_load_from_string(const gchar *input, gboolean open_image_in_dr, gboolean *single_image)
Definition darktable.c:328
void dt_cleanup()
Definition darktable.c:1311
void dt_concat_path_file(char destination[PATH_MAX], const char path[PATH_MAX], const char *const file)
Definition darktable.c:1889
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_INPUT
Definition darktable.h:729
@ DT_DEBUG_GTK
Definition darktable.h:718
@ DT_DEBUG_CONTROL
Definition darktable.h:716
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define PATH_MAX
Definition darktable.h:1062
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
void dtgtk_cairo_paint_solid_arrow(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
@ CPF_DIRECTION_LEFT
Definition dtgtk/paint.h:63
@ CPF_DIRECTION_DOWN
Definition dtgtk/paint.h:62
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
void dtgtk_expander_set_expanded(GtkDarktableExpander *expander, gboolean expanded)
Definition expander.c:70
GtkWidget * dtgtk_expander_new(GtkWidget *header, GtkWidget *body)
Definition expander.c:101
#define DTGTK_EXPANDER(obj)
Definition expander.h:30
void dt_loc_get_sharedir(char *sharedir, size_t bufsize)
void dt_loc_get_datadir(char *datadir, size_t bufsize)
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
static const char * _get_source_name(int pos)
Definition gtk.c:1075
gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y)
Definition gtk.c:159
static double _clamp01d(const double value)
Definition gtk.c:576
static void _widget_auto_model_rows_reordered(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gint *new_order, gpointer user_data)
Definition gtk.c:2577
static gboolean _sample_tablet_state_from_devices(const GdkEvent *event, double *pressure, gboolean *have_pressure, double *tilt_x, double *tilt_y, gboolean *have_tilt, const char **picked_device_name)
Definition gtk.c:640
static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
Definition gtk.c:1760
GtkBox * attach_help_popover(GtkWidget *widget, const char *label)
Definition gtk.c:3194
static gboolean _button_released(GtkWidget *w, GdkEventButton *event, gpointer user_data)
Definition gtk.c:998
void dt_gui_menu_popup(GtkMenu *menu, GtkWidget *button, GdkGravity widget_anchor, GdkGravity menu_anchor)
Definition gtk.c:2953
static dt_control_pointer_input_t _extract_pointer_input(const GdkEvent *event, const double x, const double y, const guint32 time_ms, const gboolean reset_kinematics, const char *tag)
Definition gtk.c:752
static gboolean _text_entry_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
Definition gtk.c:3215
void dt_gui_cairo_set_font_options(cairo_t *cr, GtkWidget *widget)
Definition gtk.c:1475
void dt_ui_container_foreach(dt_ui_t *ui, const dt_ui_container_t c, GtkCallback callback)
calls a callback on all children widgets from container
Definition gtk.c:1733
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
Definition gtk.c:302
void dt_gui_container_remove_children(GtkContainer *container)
Definition gtk.c:2907
static int _resizable_scroll_handle_resize(int requested_size, gboolean finished, gpointer user_data)
Definition gtk.c:2540
gboolean dt_gui_quit_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition gtk.c:469
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
Definition gtk.c:219
static void _widget_auto_on_buffer_changed(GObject *textview, GParamSpec *pspec, gpointer user_data)
Definition gtk.c:2669
static int _textview_count_visible_rows(GtkWidget *textview)
Definition gtk.c:2373
static gboolean _center_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition gtk.c:1063
static int _resizable_area_resize(int requested_size, gboolean finished, gpointer user_data)
Definition gtk.c:2823
static const gdouble * _event_axes(const GdkEvent *event)
Definition gtk.c:581
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
Definition gtk.c:313
static void _collapsible_container_show(GtkWidget *widget, gpointer user_data)
Definition gtk.c:3046
void dt_gui_search_stop(GtkSearchEntry *entry, GtkWidget *widget)
Definition gtk.c:3016
static void _widget_auto_connect_model(GtkWidget *treeview)
Definition gtk.c:2613
void dt_gui_hide_collapsible_section(dt_gui_collapsible_section_t *cs)
Definition gtk.c:3095
static void _widget_auto_model_row_expanded(GtkTreeView *tree_view, GtkTreeIter *expanded_iter, GtkTreePath *path, gpointer user_data)
Definition gtk.c:2587
static void _delete_child(GtkWidget *widget, gpointer data)
Definition gtk.c:2913
static const char * _get_mode_name(int pos)
Definition gtk.c:1085
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:143
static void _widget_auto_model_row_changed(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
Definition gtk.c:2568
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
Definition gtk.c:2836
static void _remove_child(GtkWidget *widget, gpointer data)
Definition gtk.c:2902
void dt_gui_new_collapsible_section(dt_gui_collapsible_section_t *cs, const char *confname, const char *label, GtkBox *parent, GtkPackType pack)
Create a collapsible section and pack it into the parent box.
Definition gtk.c:3102
static int _resizable_area_get_size(gpointer user_data)
Definition gtk.c:2815
static gboolean _key_pressed(GtkWidget *w, GdkEventKey *event)
Definition gtk.c:1056
static void _gtk_main_quit_safe(GtkWidget *widget, gpointer data)
Definition gtk.c:1833
static gint _get_container_row_heigth(GtkWidget *w)
Definition gtk.c:2283
static gboolean _notebook_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition gtk.c:2239
static void _widget_auto_connect_buffer(GtkWidget *textview)
Definition gtk.c:2642
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2259
static gboolean _center_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition gtk.c:1069
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2254
static gboolean _ui_toast_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition gtk.c:1518
static gboolean _button_pressed(GtkWidget *w, GdkEventButton *event, gpointer user_data)
Definition gtk.c:982
static dt_tablet_motion_state_t _tablet_motion_state
Definition gtk.c:574
static void _resizable_scroll_realize(GtkWidget *w, gpointer user_data)
Definition gtk.c:2527
static gboolean _draw(GtkWidget *da, cairo_t *cr, gpointer user_data)
Definition gtk.c:325
static gboolean _ui_log_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition gtk.c:1512
static void _coeffs_button_changed(GtkDarktableToggleButton *widget, gpointer user_data)
Definition gtk.c:3057
void dt_ui_container_destroy_children(dt_ui_t *ui, const dt_ui_container_t c)
destroy all child widgets from container
Definition gtk.c:1739
char * dt_gui_show_standalone_string_dialog(const char *title, const char *markup, const char *placeholder, const char *no_text, const char *yes_text)
Definition gtk.c:1939
static void _widget_auto_height_free(gpointer data)
Definition gtk.c:2677
static void _widget_auto_model_row_collapsed(GtkTreeView *tree_view, GtkTreeIter *collapsed_iter, GtkTreePath *path, gpointer user_data)
Definition gtk.c:2596
static int _resizable_scroll_handle_get_size(gpointer user_data)
Definition gtk.c:2533
static void _collapsible_set_states(dt_gui_collapsible_section_t *cs, gboolean active)
Definition gtk.c:3031
static void _widget_auto_model_row_deleted(GtkTreeModel *model, GtkTreePath *path, gpointer user_data)
Definition gtk.c:2561
int dt_gui_container_num_children(GtkContainer *container)
Definition gtk.c:2872
static void _widget_auto_disconnect_buffer(dt_gui_widget_auto_height_t *state)
Definition gtk.c:2413
void dt_gui_gtk_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
Definition gtk.c:442
void dt_gui_draw_rounded_rectangle(cairo_t *cr, float width, float height, float x, float y)
Definition gtk.c:2992
GtkWidget * dt_gui_container_nth_child(GtkContainer *container, int which)
Definition gtk.c:2892
static GtkWidget * _search_parent_scrolled_window(GtkWidget *w)
Definition gtk.c:2325
static void _coeffs_expander_click(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
Definition gtk.c:3069
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
Definition gtk.c:3080
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition gtk.c:2687
void dt_gui_load_theme(const char *theme)
Definition gtk.c:2029
GtkWidget * dt_ui_scroll_wrap_get_scrolled_window(GtkWidget *wrapper)
Return the inner scrolled window of a dt_ui_scroll_wrap() wrapper, or NULL.
Definition gtk.c:2775
static void _widget_auto_text_buffer_changed(GtkTextBuffer *buffer, gpointer user_data)
Definition gtk.c:2607
void dt_capitalize_label(gchar *text)
Definition gtk.c:3150
int dt_gui_gtk_write_config()
Definition gtk.c:386
static void _yes_no_button_handler(GtkButton *button, gpointer data)
Definition gtk.c:1840
void dt_gui_store_last_preset(const char *name)
Definition gtk.c:475
void dt_ellipsize_combo(GtkComboBox *cbox)
Definition gtk.c:1814
GdkModifierType dt_key_modifier_state()
Definition gtk.c:2186
void dt_configure_ppd_dpi(dt_gui_gtk_t *gui)
Definition gtk.c:1348
static gint _resizable_scroll_max_height(void)
Window-height ceiling shared by the auto-size rule and the drag handle.
Definition gtk.c:2435
static void _resizable_area_free(gpointer data)
Definition gtk.c:2807
static gboolean _get_axis_value_for_source(const GdkEvent *event, GdkDevice *source_device, const GdkAxisUse axis, double *value, gboolean *from_source_map)
Definition gtk.c:598
static const char * _get_axis_name(int pos)
Definition gtk.c:1092
void dt_gui_gtk_quit()
Definition gtk.c:454
static void _init_widgets(dt_gui_gtk_t *gui)
Definition gtk.c:1524
gboolean dt_gui_search_start(GtkWidget *widget, GdkEventKey *event, GtkSearchEntry *entry)
Definition gtk.c:3005
static const char *const DT_GUI_WIDGET_AUTO_HEIGHT_KEY
Definition gtk.c:2322
static void _notebook_size_callback(GtkNotebook *notebook, GdkRectangle *allocation, gpointer *data)
Definition gtk.c:2199
void dt_gui_gtk_set_source_rgba(cairo_t *cr, dt_gui_color_t color, float opacity_coef)
Definition gtk.c:448
double dt_get_system_gui_ppd(GtkWidget *widget)
Definition gtk.c:1331
static gboolean _mouse_moved(GtkWidget *w, GdkEventMotion *event, gpointer user_data)
Definition gtk.c:1009
void dt_gui_container_destroy_children(GtkContainer *container)
Definition gtk.c:2919
static void _widget_auto_model_row_inserted(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
Definition gtk.c:2552
void dt_gui_add_help_link(GtkWidget *widget, char *link)
Definition gtk.c:2022
void dt_ui_notify_user()
draw user's attention
Definition gtk.c:1744
gboolean dt_gui_show_standalone_yes_no_dialog(const char *title, const char *markup, const char *no_text, const char *yes_text)
Definition gtk.c:1855
static gboolean _focus_in_out_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition gtk.c:1506
static void _ui_log_redraw_callback(gpointer instance, GtkWidget *widget)
Definition gtk.c:1765
GtkWidget * dt_gui_get_popup_relative_widget(GtkWidget *widget, GdkRectangle *rect)
Resolve the widget used as parent for nested popups on Wayland.
Definition gtk.c:2925
static void _widget_auto_on_model_changed(GObject *treeview, GParamSpec *pspec, gpointer user_data)
Definition gtk.c:2661
int dt_gui_gtk_init(dt_gui_gtk_t *gui)
Definition gtk.c:1102
static void _resizable_scroll_apply(GtkWidget *w)
The single sizing rule for every dt_ui_scroll_wrap area.
Definition gtk.c:2450
static void _widget_auto_update(GtkWidget *widget)
Definition gtk.c:2516
gboolean dt_gui_container_has_children(GtkContainer *container)
Definition gtk.c:2862
void dt_gui_refocus_center()
Definition gtk.c:3234
static const char *const DT_UI_RESIZABLE_AREA_KEY
Definition gtk.c:2798
static void _refresh_all_container_spacing(void)
Definition gtk.c:1427
static gint _last_box_spacing
Definition gtk.c:1390
void dt_gui_set_pango_resolution(PangoLayout *layout)
Definition gtk.c:1467
GtkBox * attach_popover(GtkWidget *widget, const char *icon, GtkWidget *content)
Definition gtk.c:3164
static void _popover_set_relative_to_topmost_parent(GtkPopover *popover, GtkWidget *button)
Definition gtk.c:2983
static gboolean _configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
Definition gtk.c:509
static int _treeview_count_visible_rows(GtkTreeView *treeview, GtkTreeModel *model, GtkTreeIter *parent)
Definition gtk.c:2340
static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition gtk.c:370
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2713
static void _ui_toast_redraw_callback(gpointer instance, GtkWidget *widget)
Definition gtk.c:1787
void dt_gui_gtk_run(dt_gui_gtk_t *gui)
Definition gtk.c:1290
static gboolean _text_entry_focus_out_event(GtkWidget *self, GdkEventFocus event, gpointer user_data)
Definition gtk.c:3209
GtkWidget * dt_gui_container_first_child(GtkContainer *container)
Definition gtk.c:2882
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3225
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
static gboolean _text_entry_focus_in_event(GtkWidget *self, GdkEventFocus event, gpointer user_data)
Definition gtk.c:3203
static void _refresh_container_spacing(GtkWidget *w, gpointer user_data)
Definition gtk.c:1397
static gboolean _window_configure(GtkWidget *da, GdkEvent *event, gpointer user_data)
Definition gtk.c:546
void dt_ui_container_focus_widget(dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w)
gives a widget focus in the container
Definition gtk.c:1723
static gboolean _sample_axis_from_device_state(GdkWindow *window, GdkDevice *device, const GdkAxisUse axis, double *value)
Definition gtk.c:619
void dt_gui_update_em(void)
Definition gtk.c:1441
static void _widget_auto_disconnect_model(dt_gui_widget_auto_height_t *state, GtkWidget *treeview)
Definition gtk.c:2384
static gboolean _resizable_scroll_draw(GtkWidget *w, cairo_t *cr, gpointer user_data)
Definition gtk.c:2521
GtkWidget * dt_ui_toast_msg(dt_ui_t *ui)
get the toast message widget
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
dt_gui_color_t
Definition gtk.h:126
@ DT_GUI_COLOR_BG
Definition gtk.h:127
@ DT_GUI_COLOR_DARKROOM_PREVIEW_BG
Definition gtk.h:129
@ DT_GUI_COLOR_LIGHTTABLE_FONT
Definition gtk.h:132
@ DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH
Definition gtk.h:155
@ DT_GUI_COLOR_LOG_FG
Definition gtk.h:151
@ DT_GUI_COLOR_BRUSH_CURSOR
Definition gtk.h:134
@ DT_GUI_COLOR_LAST
Definition gtk.h:158
@ DT_GUI_COLOR_THUMBNAIL_FONT
Definition gtk.h:143
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_BG
Definition gtk.h:138
@ DT_GUI_COLOR_MAP_COUNT_BG
Definition gtk.h:154
@ DT_GUI_COLOR_LOG_BG
Definition gtk.h:150
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_BORDER
Definition gtk.h:147
@ DT_GUI_COLOR_THUMBNAIL_OUTLINE
Definition gtk.h:140
@ DT_GUI_COLOR_MAP_LOC_SHAPE_LOW
Definition gtk.h:156
@ DT_GUI_COLOR_LIGHTTABLE_BG
Definition gtk.h:130
@ DT_GUI_COLOR_BUTTON_FG
Definition gtk.h:136
@ DT_GUI_COLOR_DARKROOM_BG
Definition gtk.h:128
@ DT_GUI_COLOR_THUMBNAIL_BORDER
Definition gtk.h:146
@ DT_GUI_COLOR_LIGHTTABLE_PREVIEW_BG
Definition gtk.h:131
@ DT_GUI_COLOR_MAP_COUNT_SAME_LOC
Definition gtk.h:152
@ DT_GUI_COLOR_MAP_COUNT_DIFF_LOC
Definition gtk.h:153
@ DT_GUI_COLOR_THUMBNAIL_BG
Definition gtk.h:137
@ DT_GUI_COLOR_PREVIEW_HOVER_BORDER
Definition gtk.h:149
@ DT_GUI_COLOR_FILMSTRIP_BG
Definition gtk.h:148
@ DT_GUI_COLOR_BRUSH_TRACE
Definition gtk.h:135
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_FONT
Definition gtk.h:144
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_OUTLINE
Definition gtk.h:141
@ DT_GUI_COLOR_THUMBNAIL_HOVER_BG
Definition gtk.h:139
@ DT_GUI_COLOR_PRINT_BG
Definition gtk.h:133
@ DT_GUI_COLOR_MAP_LOC_SHAPE_DEF
Definition gtk.h:157
@ DT_GUI_COLOR_THUMBNAIL_HOVER_FONT
Definition gtk.h:145
@ DT_GUI_COLOR_THUMBNAIL_HOVER_OUTLINE
Definition gtk.h:142
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
GtkWidget * dt_ui_log_msg(dt_ui_t *ui)
get the log message widget
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:451
#define DT_UI_SCALE_DEVICE(value)
Definition gtk.h:86
dt_ui_resize_mode_t
Definition gtk.h:257
@ DT_UI_RESIZE_STATIC
Definition gtk.h:264
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
GtkWidget * dt_ui_center_base(dt_ui_t *ui)
void dt_gui_presets_init()
void dt_guides_set_overlay_colors()
Definition guides.c:666
const char * tooltip
Definition image.h:251
const char * model
static const float x
const char * dt_l10n_get_current_lang(dt_l10n_t *l10n)
Definition l10n.c:455
#define M_PI
Definition math.h:45
size_t size
Definition mipmap_cache.c:3
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
void dt_osx_focus_window()
Definition osx.mm:301
void dt_osx_autoset_dpi(GtkWidget *widget)
Definition osx.mm:54
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
float dt_osx_get_ppd()
Definition osx.mm:73
@ DT_SIGNAL_CONTROL_REDRAW_ALL
This signal is raised when dt_control_queue_redraw() is called. no param, no returned value.
Definition signal.h:69
@ DT_SIGNAL_CONTROL_TOAST_REDRAW
This signal is raised when dt_control_toast_redraw() is called. no param, no returned value.
Definition signal.h:288
@ DT_SIGNAL_CONTROL_LOG_REDRAW
This signal is raised when dt_control_log_redraw() is called. no param, no returned value.
Definition signal.h:283
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
void dt_gui_splash_set_transient_for(GtkWidget *parent)
Definition splash.c:79
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const char * confname
Definition gtk.h:249
GtkWidget * toggle
Definition gtk.h:250
GtkWidget * expander
Definition gtk.h:251
GtkWidget * label
Definition gtk.h:253
gint new_s
Definition gtk.c:1392
gint old_s
Definition gtk.c:1392
struct dt_l10n_t * l10n
Definition darktable.h:789
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
GList * themes
Definition darktable.h:829
struct dt_view_manager_t * view_manager
Definition darktable.h:772
struct dt_control_t * control
Definition darktable.h:773
double tabborder
Definition control.h:213
int toast_ack
Definition control.h:243
char toast_message[10][300]
Definition control.h:244
dt_pthread_mutex_t log_mutex
Definition control.h:240
int toast_pos
Definition control.h:243
dt_pthread_mutex_t toast_mutex
Definition control.h:247
char log_message[10][1000]
Definition control.h:237
double dpi
Definition gtk.h:200
double ppd
Definition gtk.h:200
gint scroll_mask
Definition gtk.h:224
int32_t center_tooltip
Definition gtk.h:175
cairo_surface_t * surface
Definition gtk.h:168
int32_t reset
Definition gtk.h:172
dt_accels_t * accels
Definition gtk.h:194
struct dt_gui_gtk_t::@47 mouse
float effect_radius
Definition gtk.h:210
GtkMenu * presets_popup_menu
Definition gtk.h:169
GtkWidget * has_scroll_focus
Definition gtk.h:228
double dpi_factor
Definition gtk.h:200
dt_ui_t * ui
Definition gtk.h:164
dt_pthread_mutex_t mutex
Definition gtk.h:243
gboolean culling_mode
Definition gtk.h:187
struct dt_gui_gtk_t::@48 export_popup
struct dt_gui_gtk_t::@49 styles_popup
gboolean selection_stacked
Definition gtk.h:191
char gtkrc[PATH_MAX]
Definition gtk.h:219
GtkWidget * window
Definition gtk.h:235
cairo_filter_t filter_image
Definition gtk.h:230
char * last_preset
Definition gtk.h:170
double em
Definition gtk.h:205
GdkRGBA colors[DT_GUI_COLOR_LAST]
Definition gtk.h:173
gboolean have_pressure
Definition gtk.c:567
GtkWidget * grid
Definition thumbtable.h:100
dt_thumbtable_t * thumbtable_lighttable
GtkWidget * containers[DT_UI_CONTAINER_SIZE]
GtkWidget * main_window
GtkWidget * center_base
GtkWidget * log_msg
GtkWidget * toast_msg
double x
double width
double y
GtkWidget * button_yes
Definition gtk.c:1830
char * entry_text
Definition gtk.c:1829
GtkWidget * entry
Definition gtk.c:1830
GtkWidget * button_no
Definition gtk.c:1830
enum result_t::@51 result
@ RESULT_NO
Definition gtk.c:1828
@ RESULT_YES
Definition gtk.c:1828
@ RESULT_NONE
Definition gtk.c:1828
GtkWidget * window
Definition gtk.c:1830
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:136
const char * dt_view_manager_name(dt_view_manager_t *vm)
Definition view.c:436
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
Definition view.c:628
int dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state, int delta_y)
Definition view.c:640
void dt_ui_cleanup_titlebar(dt_ui_t *ui)
void dt_ui_init_titlebar(dt_ui_t *ui)
void dt_ui_init_main_table(GtkWidget *parent, dt_ui_t *ui)
dt_ui_container_t