Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
application.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-2026 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 "darktable.h"
75#include "common/paths.h" // DT_PATH_MAX
76#include "metadata/colorlabels.h" // DT_COLORLABELS_*
77#include "gui/screen_metrics.h"
81#include "common/l10n.h"
83#include "common/utility.h"
84#include "gui/guides.h"
85#include "gui/import.h"
86#include "widgets/expander.h"
87
88#include "common/collection.h"
89#include "common/selection.h"
90#include "gui/application.h"
92#include "gui/common/film_gui.h"
98#include "gui/splash.h"
99#include "gui/closing.h"
100
101#include "common/conf.h"
102#include "control/control.h"
103#include "control/jobs.h"
104#include "control/signal.h"
105#include "gui/presets.h"
106#include "views/view.h"
107
108#include <gdk/gdkkeysyms.h>
109#ifdef GDK_WINDOWING_WAYLAND
110#include <gdk/gdkwayland.h>
111#endif
112#ifdef _WIN32
113#include <gdk/gdkwin32.h>
114#endif
115#include <gtk/gtk.h>
116#include <math.h>
117#include <stdlib.h>
118#include <stdio.h>
119#include <string.h>
120#include "widgets/container.h"
121#include "widgets/widget_style.h"
122#ifdef MAC_INTEGRATION
123#include <gtkosxapplication.h>
124#endif
125#ifdef GDK_WINDOWING_QUARTZ
126#include "osx/osx.h"
127#endif
128
129/*
130 * NEW UI API
131 */
132
133/* generic callback for redraw widget signals */
134
135/* widgets/ carries its own colour-label indices so it needs no application header. This is
136 * the only place both enums are visible, so this is where they are pinned together. */
137_Static_assert((int)DT_WIDGET_COLORLABEL_RED == (int)DT_COLORLABELS_RED
140 "widget colour-label indices drifted from dt_colorlabels_enum");
141
142static void _update_display_profile(void);
143static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget);
144/* callback for redraw log signals */
145static void _ui_log_redraw_callback(gpointer instance, GtkWidget *widget);
146static void _ui_toast_redraw_callback(gpointer instance, GtkWidget *widget);
147
148// set class function to add CSS classes with just a simple line call
149
150
151
152/* ------------------------------------------------------------------------------------------
153 * Widget-callback suppression depth (see darktable.h for the rationale and API).
154 * ------------------------------------------------------------------------------------------ */
155/* Sub-handle accessors for the GUI singleton (declared in gui/application.h). The orchestrator
156 * binds darktable.gui via dt_gui_get_global(); these narrow it to the parts callers
157 * actually want, so they stop walking the application struct. */
159{
160 return dt_gui_get_global()->ui;
161}
162
164{
165 return dt_gui_get_global()->accels;
166}
167
172
177
178
179
180
181
182/*
183 * OLD UI API
184 */
185static void _init_widgets(dt_gui_gtk_t *gui);
186static gboolean _configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data);
187
188
189
190
191static gboolean _draw(GtkWidget *da, cairo_t *cr, gpointer user_data)
192{
193 /* cr is GTK's double buffer, clipped to what was invalidated; the centre paints into it */
195 return TRUE;
196}
197
198
199static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
200{
202
203 int delta_y;
205 {
206 return dt_view_manager_scrolled(darktable.view_manager, event->x, event->y,
207 delta_y < 0,
208 event->state, delta_y);
209 }
210
211 return FALSE;
212}
213
214
216{
221 int width, height;
223 dt_conf_set_int("ui_last/window_width", width);
224 dt_conf_set_int("ui_last/window_height", height);
225
226 gboolean save_window_position = TRUE;
227#ifdef GDK_WINDOWING_WAYLAND
228 GdkDisplay *display = gtk_widget_get_display(widget);
229 if(GDK_IS_WAYLAND_DISPLAY(display))
231#endif
232
234 {
238 {
240 if(!IS_NULL_PTR(monitor))
241 {
243 int monitor_index = -1;
244 for(int i = 0; i < n_monitors; i++)
245 {
247 {
249 break;
250 }
251 }
252 if(monitor_index >= 0)
253 dt_conf_set_int("ui_last/window_monitor", monitor_index);
254 }
255 }
256
258 {
259 int x, y;
260 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
261 dt_conf_set_int("ui_last/window_x", x);
262 dt_conf_set_int("ui_last/window_y", y);
263 }
264 }
265
267
268 return 0;
269}
270
271
273{
275 dt_gui_add_class(win, "dt_gui_quit");
276 gtk_window_set_title(GTK_WINDOW(win), _("Closing Ansel..."));
277
279
280 // Write out windows dimension
282
283 // hide main window
285}
286
287gboolean dt_gui_quit_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
288{
290 return TRUE;
291}
292
298
299#ifdef MAC_INTEGRATION
300#ifdef GTK_TYPE_OSX_APPLICATION
301static gboolean _osx_quit_callback(GtkOSXApplication *OSXapp, gpointer user_data)
302#else
303static gboolean _osx_quit_callback(GtkosxApplication *OSXapp, gpointer user_data)
304#endif
305{
310 break;
313 windows = NULL;
314 return TRUE;
315}
316
317#ifdef GTK_TYPE_OSX_APPLICATION
318static gboolean _osx_openfile_callback(GtkOSXApplication *OSXapp, gchar *path, gpointer user_data)
319#else
320static gboolean _osx_openfile_callback(GtkosxApplication *OSXapp, gchar *path, gpointer user_data)
321#endif
322{
323 return dt_load_from_string(path, TRUE, NULL) > 0;
324}
325#endif
326
327static gboolean _configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
328{
329 static int oldw = 0;
330 static int oldh = 0;
331 if(oldw != event->width || oldh != event->height)
332 {
333 // maybe we are on another screen now with > 50% of the area
335 }
336 oldw = event->width;
337 oldh = event->height;
338
339#ifndef GDK_WINDOWING_QUARTZ
340 dt_configure_ppd_dpi((dt_gui_gtk_t *) user_data);
341#endif
342
343 return dt_control_configure(da, event, user_data);
344}
345
346static gboolean _window_configure(GtkWidget *da, GdkEvent *event, gpointer user_data)
347{
348 static int oldx = 0;
349 static int oldy = 0;
350 if(oldx != event->configure.x || oldy != event->configure.y)
351 {
352 // maybe we are on another screen now with > 50% of the area
354 oldx = event->configure.x;
355 oldy = event->configure.y;
356 }
357 return FALSE;
358}
359
361{
362 gboolean valid;
363 double x;
364 double y;
365 guint32 time_ms;
368 double pressure;
369 gboolean have_tilt;
370 double tilt_x;
371 double tilt_y;
373
375
376static inline double _clamp01d(const double value)
377{
378 return MIN(1.0, MAX(0.0, value));
379}
380
381static const gdouble *_event_axes(const GdkEvent *event)
382{
383 if(IS_NULL_PTR(event)) return NULL;
384 switch(event->type)
385 {
387 return ((const GdkEventMotion *)event)->axes;
388 case GDK_BUTTON_PRESS:
392 return ((const GdkEventButton *)event)->axes;
393 default:
394 return NULL;
395 }
396}
397
399 const GdkAxisUse axis, double *value, gboolean *from_source_map)
400{
402 if(IS_NULL_PTR(value)) return FALSE;
403
404 const gdouble *axes = _event_axes(event);
405 if(source_device && axes)
406 {
407 double source_value = 0.0;
409 {
412 return TRUE;
413 }
414 }
415
416 return gdk_event_get_axis((GdkEvent *)event, axis, value);
417}
418
420 const GdkAxisUse axis, double *value)
421{
425 {
427 if(!display || !gdk_display_device_is_grabbed(display, device)) return FALSE;
428 }
429
431 if(n_axes <= 0) return FALSE;
432
433 double *axes = g_newa(double, n_axes);
434 memset(axes, 0, sizeof(double) * n_axes);
435 GdkModifierType modifiers = 0;
438}
439
440static gboolean _sample_tablet_state_from_devices(const GdkEvent *event,
441 double *pressure, gboolean *have_pressure,
442 double *tilt_x, double *tilt_y, gboolean *have_tilt,
443 const char **picked_device_name)
444{
445 if(pressure) *pressure = 0.0;
446 if(have_pressure) *have_pressure = FALSE;
447 if(tilt_x) *tilt_x = 0.0;
448 if(tilt_y) *tilt_y = 0.0;
449 if(have_tilt) *have_tilt = FALSE;
451
452 if(IS_NULL_PTR(darktable.gui)) return FALSE;
455 if(IS_NULL_PTR(window)) return FALSE;
456
457 int best_score = -1;
458 double best_p = 0.0;
459 gboolean best_have_p = FALSE;
460 double best_tx = 0.0;
461 double best_ty = 0.0;
462 gboolean best_have_t = FALSE;
463 const char *best_name = NULL;
464
467
468 for(GList *l = runtime_devices; l; l = g_list_next(l))
469 {
470 GdkDevice *device = (GdkDevice *)l->data;
471 if(IS_NULL_PTR(device)) continue;
473 if(source == GDK_SOURCE_KEYBOARD) continue;
475 {
477 /* gdk_device_get_state() requires slave devices to be grabbed.
478 * Skip non-grabbed slaves to avoid GTK/GDK criticals. */
479 if(!display || !gdk_display_device_is_grabbed(display, device)) continue;
480 }
481
483 const gboolean supports_pressure = (axis_flags & GDK_AXIS_FLAG_PRESSURE) != 0;
484 const gboolean supports_x_tilt = (axis_flags & GDK_AXIS_FLAG_XTILT) != 0;
485 const gboolean supports_y_tilt = (axis_flags & GDK_AXIS_FLAG_YTILT) != 0;
486 const gboolean pen_like = (source == GDK_SOURCE_PEN || source == GDK_SOURCE_ERASER || source == GDK_SOURCE_CURSOR);
488
490 if(n_axes <= 0) continue;
491 double *axes = g_newa(double, n_axes);
492 memset(axes, 0, sizeof(double) * n_axes);
493 GdkModifierType modifiers = 0;
495
496 double p = 0.0;
497 gboolean have_p = FALSE;
498 double tx = 0.0;
499 double ty = 0.0;
500 gboolean have_tx = FALSE;
501 gboolean have_ty = FALSE;
502
503 for(int i = 0; i < n_axes; i++)
504 {
507 {
508 p = axes[i];
509 have_p = TRUE;
510 }
511 else if(use == GDK_AXIS_XTILT)
512 {
513 tx = axes[i];
514 have_tx = TRUE;
515 }
516 else if(use == GDK_AXIS_YTILT)
517 {
518 ty = axes[i];
519 have_ty = TRUE;
520 }
521 }
522
523 const gboolean have_t = have_tx || have_ty;
524 const int score = (have_p ? 2 : 0) + (have_t ? 2 : 0) + (p > 1e-4 ? 4 : 0)
525 + ((hypot(tx, ty) > 1e-4) ? 3 : 0) + (pen_like ? 1 : 0);
526 if(score <= best_score) continue;
527
529 best_p = p;
531 best_tx = tx;
532 best_ty = ty;
535 }
537 {
540 }
541
542 if(best_score < 0) return FALSE;
543 if(pressure) *pressure = best_p;
544 if(have_pressure) *have_pressure = best_have_p;
545 if(tilt_x) *tilt_x = best_tx;
546 if(tilt_y) *tilt_y = best_ty;
547 if(have_tilt) *have_tilt = best_have_t;
549 return TRUE;
550}
551
552static dt_control_pointer_input_t _extract_pointer_input(const GdkEvent *event, const double x, const double y,
553 const guint32 time_ms, const gboolean reset_kinematics,
554 const char *tag)
555{
556 dt_control_pointer_input_t input = { 0 };
557 input.x = x;
558 input.y = y;
559 input.time_ms = time_ms;
565 const gboolean supports_pressure = (axis_flags & GDK_AXIS_FLAG_PRESSURE) != 0;
566 const gboolean supports_x_tilt = (axis_flags & GDK_AXIS_FLAG_XTILT) != 0;
567 const gboolean supports_y_tilt = (axis_flags & GDK_AXIS_FLAG_YTILT) != 0;
568 gboolean read_pressure = FALSE;
569 gboolean read_x_tilt = FALSE;
570 gboolean read_y_tilt = FALSE;
571 gboolean map_pressure_source = FALSE;
572 gboolean map_xtilt_source = FALSE;
573 gboolean map_ytilt_source = FALSE;
574 gboolean state_pressure_source = FALSE;
575 gboolean state_pressure_event = FALSE;
576 gboolean state_xtilt_source = FALSE;
577 gboolean state_ytilt_source = FALSE;
578 gboolean fallback_pressure = FALSE;
579 gboolean fallback_tilt = FALSE;
580 const char *fallback_device_name = NULL;
582 const int tool_type = tool ? (int)gdk_device_tool_get_tool_type(tool) : -1;
583 const gboolean tool_is_stylus
590
591 {
592 double pressure = 0.0;
594 {
596 input.pressure = _clamp01d(pressure);
597 input.has_pressure = TRUE;
600 }
602 {
604 input.has_pressure = TRUE;
605 }
606 else if(!is_tablet_like)
607 {
609 }
610 }
611
612 if(input.has_pressure && input.pressure <= 0.0 && window)
613 {
614 double p_state = 0.0;
616 {
617 input.pressure = _clamp01d(p_state);
619 }
620 else if(event_device
622 {
623 input.pressure = _clamp01d(p_state);
625 }
626 }
627
628 {
629 double x_tilt = 0.0;
630 double y_tilt = 0.0;
631 const gboolean has_x_tilt
633 const gboolean has_y_tilt
638 {
639 input.tilt_x = CLAMP(x_tilt, -1.0, 1.0);
640 input.tilt_y = CLAMP(y_tilt, -1.0, 1.0);
641 input.has_tilt = TRUE;
645 }
647 {
650 input.has_tilt = TRUE;
651 }
652 else if(!is_tablet_like)
653 {
655 }
656 }
657
658 if(!input.has_tilt && window)
659 {
660 double tx_state = 0.0, ty_state = 0.0;
663 if(has_tx || has_ty)
664 {
665 input.tilt_x = CLAMP(tx_state, -1.0, 1.0);
666 input.tilt_y = CLAMP(ty_state, -1.0, 1.0);
667 input.has_tilt = TRUE;
673 }
674 }
675
676 if(input.has_tilt)
677 input.tilt = _clamp01d(hypot(input.tilt_x, input.tilt_y));
678 else
679 input.tilt = 0.0;
680
681 if(!input.has_pressure || !input.has_tilt)
682 {
683 double fb_pressure = 0.0;
684 gboolean fb_have_pressure = FALSE;
685 double fb_tilt_x = 0.0;
686 double fb_tilt_y = 0.0;
687 gboolean fb_have_tilt = FALSE;
691 {
692 if(!input.has_pressure && fb_have_pressure)
693 {
695 input.has_pressure = TRUE;
697 }
698 if(!input.has_tilt && fb_have_tilt)
699 {
700 input.tilt_x = CLAMP(fb_tilt_x, -1.0, 1.0);
701 input.tilt_y = CLAMP(fb_tilt_y, -1.0, 1.0);
702 input.tilt = _clamp01d(hypot(input.tilt_x, input.tilt_y));
703 input.has_tilt = TRUE;
705 }
706 }
707 }
708
709 if(input.has_pressure || input.has_tilt) is_tablet_like = TRUE;
710
712 {
718 input.acceleration = 0.0;
719 return input;
720 }
721
723 {
724 const double dt_s = MAX((double)(time_ms - _tablet_motion_state.time_ms), 1.0) * 1e-3;
725 const double dx = x - _tablet_motion_state.x;
726 const double dy = y - _tablet_motion_state.y;
727 const double speed_px_s = hypot(dx, dy) / dt_s;
728 const double accel_px_s2 = fabs(speed_px_s - _tablet_motion_state.speed_px_s) / dt_s;
729 /* Normalize acceleration for stylus mapping. 25000 px/s² keeps a useful
730 * dynamic range while clipping extreme event jitter. */
731 input.acceleration = _clamp01d(accel_px_s2 / 25000.0);
732 _tablet_motion_state.speed_px_s = speed_px_s;
733 }
734 else
735 {
736 input.acceleration = 0.0;
738 }
739
744
746 "[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",
747 tag ? tag : "event",
748 device ? gdk_device_get_name(device) : "<none>",
751 (int)source,
752 is_tablet_like ? 1 : 0,
753 tool_type,
754 supports_pressure ? 1 : 0,
755 supports_x_tilt ? 1 : 0,
756 supports_y_tilt ? 1 : 0,
757 read_pressure ? 1 : 0,
758 read_x_tilt ? 1 : 0,
759 read_y_tilt ? 1 : 0,
760 map_pressure_source ? 1 : 0,
761 map_xtilt_source ? 1 : 0,
762 map_ytilt_source ? 1 : 0,
763 state_pressure_source ? 1 : 0,
764 state_pressure_event ? 1 : 0,
765 state_xtilt_source ? 1 : 0,
766 state_ytilt_source ? 1 : 0,
767 fallback_pressure ? 1 : 0,
768 fallback_tilt ? 1 : 0,
770 input.has_pressure ? input.pressure : -1.0,
771 input.has_tilt ? input.tilt_x : 0.0,
772 input.has_tilt ? input.tilt_y : 0.0,
773 input.has_tilt ? input.tilt : 0.0,
774 input.acceleration,
775 x, y,
776 time_ms,
777 reset_kinematics ? 1 : 0);
778
779 return input;
780}
781
782/* The three pointer handlers below hand the modifier state on WHOLE. Masking it to the low
783 * four bits keeps SHIFT, LOCK, CONTROL and MOD1 -- every modifier that matters on X11 and
784 * Win32, and none of the one that matters on Quartz: a physical Cmd is reported there as
785 * GDK_MOD2_MASK (0x10), on button and motion events exactly as on key events, and that is the
786 * bit DT_PRIMARY_MASK resolves to and every shortcut is matched against. Dropping it makes
787 * every primary+click and primary+drag gesture in the application unreachable on macOS --
788 * inserting a mask node, constraining a shape, the colour picker's area gestures -- while the
789 * same code works on the other platforms, since CONTROL survives the mask there.
790 *
791 * Nothing downstream reads these bits raw: every consumer goes through dt_modifier_is() /
792 * dt_modifiers_include() (widgets/widget_settings.h), which mask with
793 * gtk_accelerator_get_default_mod_mask() -- the button bits a drag adds are not in it, and
794 * neither is GDK_MOD2_MASK on X11, where that bit is NumLock rather than a modifier anyone
795 * presses on purpose. _scrolled() above already passes the whole state for the same reason. */
796static gboolean _button_pressed(GtkWidget *w, GdkEventButton *event, gpointer user_data)
797{
799
800 /* Reset Gtk focus */
803
804 const dt_control_pointer_input_t input = _extract_pointer_input((const GdkEvent *)event, event->x, event->y,
805 event->time, TRUE, "button-press");
807 const double pressure = input.has_pressure ? input.pressure : 1.0;
808 dt_control_button_pressed(event->x, event->y, pressure, event->button, event->type, event->state);
809 return FALSE;
810}
811
812static gboolean _button_released(GtkWidget *w, GdkEventButton *event, gpointer user_data)
813{
815 const dt_control_pointer_input_t input = _extract_pointer_input((const GdkEvent *)event, event->x, event->y,
816 event->time, FALSE, "button-release");
818 dt_control_button_released(event->x, event->y, event->button, event->state);
819
820 return TRUE;
821}
822
823static gboolean _mouse_moved(GtkWidget *w, GdkEventMotion *event, gpointer user_data)
824{
826
827 const dt_control_pointer_input_t input = _extract_pointer_input((const GdkEvent *)event, event->x, event->y,
828 event->time, FALSE, "motion");
830 dt_control_mouse_moved(event->x, event->y, input.has_pressure ? input.pressure : 1.0, event->state);
831 return FALSE;
832}
833
834#ifdef _WIN32
835/* Arbitrary stable subclass identifier encoded as ASCII "ASNN".
836 * It only needs to stay unique within this process for SetWindowSubclass(). */
837#define DT_WIN32_CURSOR_SUBCLASS_CENTER ((UINT_PTR)0x41534e4e)
838
841{
842 /* On Win32, DefSubclassProc() answers WM_SETCURSOR for the drawing area in center view by
843 * restoring the window-class default arrow on every mouse move. The center
844 * view already selected the proper cursor through GDK, so swallow the
845 * client-area reset and keep the current cursor unchanged until the view
846 * requests another explicit cursor change. */
848 return TRUE;
849
850 return DefSubclassProc(hwnd, message, w_param, l_param);
851}
852
860
868#endif
869
870static gboolean _key_pressed(GtkWidget *w, GdkEventKey *event)
871{
874 return TRUE;
875}
876
877/* Keyboard trace for "-d input", installed by dt_gui_gtk_init() on that flag alone.
878 *
879 * A keystroke goes to whichever toplevel holds the focus, and each of them handles keys on
880 * its own: dt_accels_dispatch() sees the main window's, a dialog or a standalone panel sees
881 * its own, and a focused text entry swallows what it consumes before either. There is no
882 * single handler the whole program's keys pass through, so an emission hook takes them at
883 * the signal instead -- it runs ahead of every handler and whatever they return.
884 *
885 * The hook is on "event", NOT on "key-press-event". GtkWidget emits the generic signal
886 * first and only emits the specific one if nothing handled it, and the keys worth tracing
887 * are precisely the handled ones: dt_accels_dispatch() is connected to "event" on the main
888 * window and returns TRUE for every keystroke that fires a shortcut, so a hook on
889 * "key-press-event" prints every key the program ignores and none of the ones it acts on.
890 *
891 * One keystroke reaches the hook several times: gtk_propagate_event() hands it to the
892 * toplevel, which walks the focus chain until a widget handles it. It is the same GdkEvent
893 * throughout, so the first emission is printed and the repeats are skipped. Identity is the
894 * tuple the windowing system filled in, which two distinct keystrokes cannot share --
895 * auto-repeat does reuse a timestamp for the release and the press it pairs with, but those
896 * differ by type.
897 */
898static gboolean _log_key_event(GSignalInvocationHint *hint, guint n_params, const GValue *params, gpointer data)
899{
901 static guint32 last_time = 0;
902 static guint last_keyval = 0;
903 static guint last_state = 0;
904 static guint16 last_keycode = 0;
905
906 const GdkEventKey *event = (const GdkEventKey *)g_value_get_boxed(&params[1]);
907 if(IS_NULL_PTR(event) || (event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE)) return TRUE;
908
909 if(event->type == last_type && event->time == last_time && event->keyval == last_keyval
910 && event->state == last_state && event->hardware_keycode == last_keycode)
911 return TRUE;
912
913 last_type = event->type;
914 last_time = event->time;
915 last_keyval = event->keyval;
916 last_state = event->state;
917 last_keycode = event->hardware_keycode;
918
919 // `state` holds the modifiers as they were BEFORE this event, so a modifier key's own
920 // press carries none of its own bit while its release carries it -- the same keyval either
921 // way. That is GDK's contract, not a decoding accident, and reading the trace requires it,
922 // which is why the primary modifier is named from the KEY below and not from the state.
923 GdkModifierType mods = (GdkModifierType)(event->state & gtk_accelerator_get_default_mod_mask());
924
925 // The primary modifier is spelled by the application, not by GTK's per-platform naming
926 // table: DT_PRIMARY_MASK is the bit every shortcut is registered and matched against, and on
927 // Quartz one physical Cmd also sets GDK's virtual GDK_META_MASK duplicate next to it -- which
928 // gtk_accelerator_name() renders as a SECOND modifier ("<Primary><Mod2>"), spelling one
929 // keystroke as two. Both bits come out here and the token is printed once, the same way
930 // _accels_keys_decode() (widgets/accelerators.c) and dt_modifier_is() drop that duplicate
931 // before matching. The raw state is printed as well, so nothing is hidden.
932 gboolean primary_mod = (mods & DT_PRIMARY_MASK) != 0;
933 mods = (GdkModifierType)(mods & ~DT_PRIMARY_MASK);
934#ifdef GDK_WINDOWING_QUARTZ
935 primary_mod = primary_mod || (mods & GDK_META_MASK) != 0;
936 mods = (GdkModifierType)(mods & ~GDK_META_MASK);
937 // Cmd, which GDK reports as the Meta keysym on this backend.
938 const gboolean primary_key = event->keyval == GDK_KEY_Meta_L || event->keyval == GDK_KEY_Meta_R;
939#else
940 const gboolean primary_key = event->keyval == GDK_KEY_Control_L || event->keyval == GDK_KEY_Control_R;
941#endif
942
943 // The key that IS the primary modifier is announced as such -- with the physical key it
944 // stands for, since that differs per platform -- and then carries no separate modifier
945 // token of its own, which would name it twice on the release.
946 gchar *accel = gtk_accelerator_name(event->keyval, mods);
948 // Every toplevel is a GtkWindow, so the type alone cannot say whether a key reached the main
949 // window or a panel holding the keyboard; the title can.
950 const gchar *title = GTK_IS_WINDOW(widget) ? gtk_window_get_title(GTK_WINDOW(widget)) : NULL;
951 dt_print(DT_DEBUG_INPUT, "[input] key %s: %s%s%s%s (keyval 0x%x, keycode %u, state 0x%x) on %s%s%s%s\n",
952 (event->type == GDK_KEY_PRESS) ? "pressed" : "released",
953 (primary_mod && !primary_key) ? "<Primary>" : "", primary_key ? "<Primary> (" : "",
954 IS_NULL_PTR(accel) ? "<unnamed>" : accel, primary_key ? ")" : "",
955 event->keyval, (unsigned int)event->hardware_keycode, event->state,
956 IS_NULL_PTR(widget) ? "<none>" : G_OBJECT_TYPE_NAME(widget), IS_NULL_PTR(title) ? "" : " \"",
957 IS_NULL_PTR(title) ? "" : title, IS_NULL_PTR(title) ? "" : "\"");
958 dt_free(accel);
959
960 return TRUE;
961}
962
963static gboolean _center_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
964{
966 return TRUE;
967}
968
969static gboolean _center_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
970{
972 return TRUE;
973}
974
975static const char* _get_source_name(int pos)
976{
977 static const gchar *SOURCE_NAMES[]
978 = { "GDK_SOURCE_MOUSE", "GDK_SOURCE_PEN", "GDK_SOURCE_ERASER", "GDK_SOURCE_CURSOR",
979 "GDK_SOURCE_KEYBOARD", "GDK_SOURCE_TOUCHSCREEN", "GDK_SOURCE_TOUCHPAD", "GDK_SOURCE_TRACKPOINT",
980 "GDK_SOURCE_TABLET_PAD" };
981 if(pos >= G_N_ELEMENTS(SOURCE_NAMES)) return "<UNKNOWN>";
982 return SOURCE_NAMES[pos];
983}
984
985static const char* _get_mode_name(int pos)
986{
987 static const gchar *MODE_NAMES[] = { "GDK_MODE_DISABLED", "GDK_MODE_SCREEN", "GDK_MODE_WINDOW" };
988 if(pos >= G_N_ELEMENTS(MODE_NAMES)) return "<UNKNOWN>";
989 return MODE_NAMES[pos];
990}
991
992static const char* _get_axis_name(int pos)
993{
994 static const gchar *AXIS_NAMES[]
995 = { "GDK_AXIS_IGNORE", "GDK_AXIS_X", "GDK_AXIS_Y", "GDK_AXIS_PRESSURE",
996 "GDK_AXIS_XTILT", "GDK_AXIS_YTILT", "GDK_AXIS_WHEEL", "GDK_AXIS_DISTANCE",
997 "GDK_AXIS_ROTATION", "GDK_AXIS_SLIDER", "GDK_AXIS_LAST" };
998 if(pos >= G_N_ELEMENTS(AXIS_NAMES)) return "<UNKNOWN>";
999 return AXIS_NAMES[pos];
1000}
1001
1002
1003/* ---- Host hooks for the shortcut system (widgets/accelerators.c) --------------------- */
1004#define DT_ACCEL_SEARCH_RECENT_KEY "plugins/accel_search/recent_entries"
1005
1007{
1008 return dt_gui_main_window();
1009}
1010
1020
1021/* dt_control_change_cursor() and dt_toast_log() are macros, so the widget hooks need real
1022 * functions to point at. */
1023// The host owns the preferences; widgets only know the key they were given.
1024static gboolean _widget_stored_int(const char *key, int *value)
1025{
1026 if(!dt_conf_key_exists(key)) return FALSE;
1028 return TRUE;
1029}
1030
1031static void _widget_store_int(const char *key, int value)
1032{
1034}
1035
1036static gboolean _widget_stored_bool(const char *key)
1037{
1038 return dt_conf_get_bool(key);
1039}
1040
1041static void _widget_store_bool(const char *key, gboolean value)
1042{
1044}
1045
1046// The display profile can only be probed once the control loop is up: before that there is no
1047// realised window to read an X atom or a colord property from. common/colorspaces.c does not
1048// know what a control loop is, so the check lives here, with the three call sites it guards.
1054
1055// Relay a display-profile change onto the signal bus. colorspaces/ raises nothing itself.
1060
1065
1066static void _widget_cursor(GdkCursorType cursor)
1067{
1069}
1070
1071static void _widget_message(const char *message)
1072{
1073 dt_toast_log("%s", message);
1074}
1075
1076static gint _accels_top_offset(void)
1077{
1080 if(IS_NULL_PTR(top_panel) || !gtk_widget_get_visible(top_panel)) return 0;
1081 return gtk_widget_get_allocated_height(top_panel);
1082}
1083
1084static gchar *_accels_recent_get(int index)
1085{
1086 gchar *key = g_strdup_printf("%s/%d", DT_ACCEL_SEARCH_RECENT_KEY, index);
1088 dt_free(key);
1089 return value;
1090}
1091
1092static void _accels_recent_set(int index, const char *value)
1093{
1094 gchar *key = g_strdup_printf("%s/%d", DT_ACCEL_SEARCH_RECENT_KEY, index);
1096 dt_free(key);
1097}
1098
1099/* common/ reports startup progress; opening the splash on the first message is display
1100 * state, so it lives here rather than in whatever subsystem happens to be slow. */
1101static void _gui_startup_progress(const char *message)
1102{
1103 // dt_gui_splash_init() already no-ops once the splash exists, so no "have I opened it?"
1104 // flag is needed here -- which is the same flag common/opencl.c used to carry.
1106 dt_gui_splash_updatef("%s", message);
1107}
1108
1109/* GUI-thread half of _gui_refresh_thumbnail(). Ends in gtk_widget_queue_draw(), so it must
1110 * not run anywhere else. */
1111static void _refresh_thumbnail_widgets(int32_t imgid, gboolean refresh_filmstrip)
1112{
1113 struct dt_ui_t *ui = dt_gui_get_ui();
1114 if(IS_NULL_PTR(ui)) return;
1115
1117
1118 // Best-effort: refreshing the filmstrip spawns an export thread that competes with the
1119 // realtime darkroom main preview, so darkroom write paths ask for it to be skipped.
1120 if(refresh_filmstrip)
1122}
1123
1124/* What crosses the thread boundary: two values, and deliberately nothing else. A request
1125 * carrying a dt_thumbnail_t* or a GtkWidget* would need that object refcounted, because the
1126 * GUI thread can destroy it between the worker posting the request and the main loop running
1127 * it -- the hazard libs/backgroundjobs.c already pays a refcount for. An image id cannot go
1128 * stale; the widget lookup happens on the GUI thread, where the answer is valid or absent. */
1134
1135static gboolean _refresh_thumbnail_on_gui_thread(gpointer user_data)
1136{
1137 const _thumbnail_refresh_request_t *request = (const _thumbnail_refresh_request_t *)user_data;
1139 return G_SOURCE_REMOVE;
1140}
1141
1142/* common/ announces that an image's thumbnail is stale; this turns that into the two
1143 * widget refreshes. Registered at the end of dt_gui_gtk_init(), so headless runs never
1144 * have a handler and the notification is a no-op there.
1145 *
1146 * The announcement arrives on whatever thread changed the image, and most of them are not
1147 * this one: dt_image_history_changed() is reached from the flip/rotate job, from the import
1148 * job and from style application, all of which run in the job queue. The refresh ends in
1149 * gtk_widget_queue_draw(), which walks the widget hierarchy and edits the toplevel's
1150 * invalidation region -- structures the GUI thread is reading at the same time. That is
1151 * Sentry 142561119: an access violation inside GTK's own memmove, on the job thread, with
1152 * dt_control_work() still on the stack underneath it.
1153 *
1154 * So the work is marshalled onto the main loop unless we are already on it. Running inline
1155 * when we are keeps the ordering every existing GUI-thread caller already relies on. */
1156static void _gui_refresh_thumbnail(int32_t imgid, gboolean refresh_filmstrip)
1157{
1159 {
1160 _refresh_thumbnail_widgets(imgid, refresh_filmstrip);
1161 return;
1162 }
1163
1165 request->imgid = imgid;
1166 request->refresh_filmstrip = refresh_filmstrip;
1168}
1169
1171{
1172 /* lets zero mem */
1173 memset(gui, 0, sizeof(dt_gui_gtk_t));
1174
1175 // The collection and the selection are lighttable state -- which images the grid shows,
1176 // which of them are picked -- so the GUI creates them, not dt_init(): a GUI-less process
1177 // (ansel-cli) must not pay for the user's collection query and selection reload just to
1178 // export one file. Both modules treat "never created" as a no-op at their own boundary.
1179 // Created before any widget below, so everything the GUI builds can already read them.
1182 dt_guides_init(); // darkroom overlay registry: same argument, nothing to overlay without a GUI
1183
1184 // GUI-side handler for backend import events (an inverted dependency): registered here
1185 // rather than in dt_init() -- the handler drives GTK widgets, and the registration site
1186 // in import_jobs.c NULL-checks before calling, so a GUI-less process simply runs with
1187 // the slot empty. dt_dev_history_gui_init() is its darkroom sibling and registers from
1188 // views/darkroom.c gui_init(), the layer entitled to see develop/.
1190
1192
1193 // force gtk3 to use normal scroll bars instead of the popup thing. they get in the way of controls
1194 // the alternative would be to gtk_scrolled_window_set_overlay_scrolling(..., FALSE); every single widget
1195 // that might have scroll bars
1196 g_setenv("GTK_OVERLAY_SCROLLING", "0", 0);
1197
1198 // same for ubuntus overlay-scrollbar-gtk3
1199 g_setenv("LIBOVERLAY_SCROLLBAR", "0", 0);
1200
1201 // unset gtk rc from kde:
1202 char path[DT_PATH_MAX] = { 0 }, datadir[DT_PATH_MAX] = { 0 }, sharedir[DT_PATH_MAX] = { 0 }, configdir[DT_PATH_MAX] = { 0 };
1203 dt_loc_get_datadir(datadir, sizeof(datadir));
1204 dt_loc_get_sharedir(sharedir, sizeof(sharedir));
1205 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
1206
1207 const char *css_theme = dt_conf_get_string_const("ui_last/theme");
1208 if(css_theme)
1209 g_strlcpy(gui->gtkrc, css_theme, sizeof(gui->gtkrc));
1210 else
1211 g_snprintf(gui->gtkrc, sizeof(gui->gtkrc), "ansel");
1212
1213#ifdef MAC_INTEGRATION
1214#ifdef GTK_TYPE_OSX_APPLICATION
1217 OSXApp, GTK_MENU_SHELL(gtk_menu_bar_new())); // needed for default entries to show up
1218#else
1221 OSXApp, GTK_MENU_SHELL(gtk_menu_bar_new())); // needed for default entries to show up
1222#endif
1223 g_signal_connect(G_OBJECT(OSXApp), "NSApplicationBlockTermination", G_CALLBACK(_osx_quit_callback), NULL);
1225#endif
1226
1227 GtkWidget *widget;
1228 gui->ui = g_malloc0(sizeof(dt_ui_t));
1229 gui->center_tooltip = 0;
1230 gui->culling_mode = FALSE;
1231 gui->presets_popup_menu = NULL;
1232 gui->last_preset = NULL;
1233 gui->export_popup.window = NULL;
1234 gui->export_popup.module = NULL;
1235 gui->styles_popup.window = NULL;
1236 gui->styles_popup.module = NULL;
1237
1238 // smooth scrolling must be enabled to handle trackpad/touch events
1240
1241 // Emulates the same feature as Gtk focus but for scrolling events
1242 // The GtkWidget capturing scrolling events will write its address in this pointer
1244
1245 // Init global accels. We localize the config because accels pathes use translated GUI labels.
1246 // User switching between languages may loose their custom shortcuts if we didn't localize them.
1247 // NOTE: needs to be inited before widgets, more specifically before the global menu
1249 gchar *keyboardrc_path = g_build_filename(configdir, keyboardrc, NULL);
1250
1251 GtkAccelFlags flags = 0;
1252 if(dt_conf_get_bool("accels/mask")) flags |= GTK_ACCEL_MASK;
1256
1257 // Load the user's saved shortcuts right away, before _init_widgets() below builds the
1258 // main window and enters the default view. Views call dt_accels_connect_accels() from
1259 // their own enter() (see e.g. views/lighttable.c), which happens well before darktable.c's
1260 // own later, single dt_accels_connect_accels() call -- without this earlier load, that
1261 // first connect reconciles every shortcut against an empty accel map (key=0, mods=0) and
1262 // permanently records it as a deliberate "user cleared this shortcut" override, before the
1263 // real saved value has had a chance to be read at all. This must be the ONLY call to
1264 // dt_accels_load_user_config(): calling it again anywhere later would re-read the
1265 // still-on-disk (unsaved until exit) file and clobber any normalization applied to the
1266 // live GtkAccelMap since this first load.
1268
1269 // Initializing widgets
1270 _init_widgets(gui);
1271
1272 //init overlay colors
1274
1275 dt_concat_path_file(path, datadir, "icons");
1277 dt_concat_path_file(path, sharedir, "icons");
1279
1281 widget = center;
1282
1285 gtk_widget_grab_focus(widget);
1287 g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(_configure), gui);
1288 g_signal_connect(G_OBJECT(widget), "draw", G_CALLBACK(_draw), NULL);
1289 g_signal_connect(G_OBJECT(widget), "motion-notify-event", G_CALLBACK(_mouse_moved), NULL);
1290 g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(_key_pressed), NULL);
1291 g_signal_connect(G_OBJECT(widget), "leave-notify-event", G_CALLBACK(_center_leave), NULL);
1292 g_signal_connect(G_OBJECT(widget), "enter-notify-event", G_CALLBACK(_center_enter), NULL);
1293 g_signal_connect(G_OBJECT(widget), "button-press-event", G_CALLBACK(_button_pressed), NULL);
1294 g_signal_connect(G_OBJECT(widget), "button-release-event", G_CALLBACK(_button_released), NULL);
1295 g_signal_connect(G_OBJECT(widget), "scroll-event", G_CALLBACK(_scrolled), NULL);
1296#ifdef _WIN32
1299 if(gtk_widget_get_realized(widget))
1300 _center_realize(widget, NULL);
1301#endif
1302
1304
1306 // update the profile when the window is moved. resize is already handled in configure()
1307 widget = dt_ui_main_window(darktable.gui->ui);
1308 g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(_window_configure), NULL);
1309
1311
1312 // load theme
1314
1315 // let's try to support pressure sensitive input devices like tablets for mask drawing
1316 dt_print(DT_DEBUG_INPUT, "[input device] Input devices found:\n\n");
1317
1318 GList *input_devices
1320 const int manager_slave_count = 0;
1321 const int manager_floating_count = 0;
1324 dt_print(DT_DEBUG_INPUT, "[input device] seat capabilities bitmask: %u\n",
1326 dt_print(DT_DEBUG_INPUT, "[input device] stylus-capable devices reported by seat: %d\n", g_list_length(stylus_devices));
1327 dt_print(DT_DEBUG_INPUT, "[input device] manager fallback devices: slave=%d floating=%d merged_total=%d\n",
1329 for(GList *l = stylus_devices; !IS_NULL_PTR(l); l = g_list_next(l))
1330 {
1331 GdkDevice *device = (GdkDevice *)l->data;
1332 if(IS_NULL_PTR(device)) continue;
1333 dt_print(DT_DEBUG_INPUT, " [tablet seat] %s source=%s axes_flags=%u n_axes=%d\n",
1336 }
1337 if(stylus_devices)
1338 {
1341 }
1342 for(GList *l = input_devices; !IS_NULL_PTR(l); l = g_list_next(l))
1343 {
1344 GdkDevice *device = (GdkDevice *)l->data;
1345 if(IS_NULL_PTR(device)) continue;
1347 const gint n_axes = (source == GDK_SOURCE_KEYBOARD ? 0 : gdk_device_get_n_axes(device));
1348
1349 // force-enable everything we find in screen mode.
1350 // TODO: make that an user param ?
1352
1353 dt_print(DT_DEBUG_INPUT, "%s (%s), source: %s, mode: %s, %d axes, %d keys\n", gdk_device_get_name(device),
1354 (source != GDK_SOURCE_KEYBOARD) && gdk_device_get_has_cursor(device) ? "with cursor" : "no cursor",
1355 _get_source_name(source),
1358
1359 for(int i = 0; i < n_axes; i++)
1360 {
1362 }
1363 dt_print(DT_DEBUG_INPUT, "\n");
1364 }
1365 if(input_devices)
1366 {
1367 g_list_free(input_devices);
1368 input_devices = NULL;
1369 }
1370
1371 // The keystroke trace costs an emission hook on every event every widget receives --
1372 // motion included -- so it is installed only when the channel it prints on is on. Debug
1373 // flags are parsed from the command line before this runs and never change afterwards.
1375 {
1377 }
1378
1379 // Gtk seems to capture some reserved shortcuts (Tab). We need to bypass it entirely
1380 // by hacking all events.
1383
1384 // finally set the cursor to be the default.
1385 // for some reason this is needed on some systems to pick up the correctly themed cursor
1388
1389 // Tell common/ how to repaint a stale thumbnail. The backend announces staleness and
1390 // knows nothing about thumbtables; this is the only place the two are connected.
1391 // Tell the widget layer which thread owns widget state, and how the user wants scrolling.
1393 dt_widget_set_scroll_reversed(dt_conf_get_bool("scroll/reverse_x"), dt_conf_get_bool("scroll/reverse_y"));
1394
1409
1417
1418 return 0;
1419}
1420
1422{
1424 GtkAllocation allocation;
1425 gtk_widget_get_allocation(widget, &allocation);
1426
1427 /* Pre-configure the views so a draw arriving before the first configure-event has a valid
1428 * size to work with.
1429 *
1430 * The size handed over must be THE SAME size the configure-event handler will report for the
1431 * same widget, and it was not: this passed the allocation minus twice an 8 px `tabborder',
1432 * while dt_control_configure() passes the event's width and height untouched. Two callers,
1433 * one widget, two answers 16 px apart -- so the view was configured at one size here and at
1434 * another as soon as GTK delivered an allocation, and a view that recomputes on a size change
1435 * (the darkroom pipeline does) paid for both.
1436 *
1437 * The border was not drawn by anything: `tabborder' was written on the line above and read on
1438 * the line below, and nowhere else in the program. It is gone. */
1439 dt_view_manager_configure(darktable.view_manager, allocation.width, allocation.height);
1440#ifdef MAC_INTEGRATION
1441#ifdef GTK_TYPE_OSX_APPLICATION
1443#else
1445#endif
1446#endif
1447#ifdef GDK_WINDOWING_QUARTZ
1449#endif
1450 /* start the event loop */
1451 gtk_main();
1452
1453 dt_cleanup();
1454}
1455
1456
1458{
1459 GtkWidget *widget = gui->ui->main_window;
1460
1461 gui->ppd = dt_get_system_gui_ppd(widget);
1462 dt_widget_set_ppd(gui->ppd);
1465
1466 // get the screen resolution
1467 const float screen_dpi_overwrite = dt_conf_get_float("screen_dpi_overwrite");
1468 if(screen_dpi_overwrite > 0.0)
1469 {
1472 dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to %f dpi as specified in "
1473 "the configuration file\n",
1475 }
1476 else
1477 {
1478#ifdef GDK_WINDOWING_QUARTZ
1479 dt_osx_autoset_dpi(widget);
1480#endif
1482 if(gui->dpi < 0.0)
1483 {
1484 gui->dpi = 96.0;
1486 dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to the default 96 dpi\n");
1487 }
1488 else
1489 dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to %f dpi\n", gui->dpi);
1490 }
1491 gui->dpi_factor
1492 = gui->dpi / 96;
1493 dt_screen_set_dpi(gui->dpi); // the raw resolution, for whoever reports it rather than scales by it
1494 dt_widget_set_dpi_factor(gui->dpi_factor); // according to man xrandr and the docs of gdk_screen_set_resolution 96 is the default
1495
1496 // em depends on the screen DPI (point -> px), so refresh it here too.
1498}
1499
1500// Last DT_GUI_BOX_SPACING value actually applied to containers. Used to retarget exactly
1501// the containers that carry the standard spacing when em changes, leaving deliberate 0-spacing
1502// and custom-spacing containers untouched. Seeded with the pre-em reference (10px).
1503static gint _last_box_spacing = 10;
1504
1505typedef struct _spacing_ctx_t { gint old_s, new_s; } _spacing_ctx_t;
1506
1507// Recursively retarget GtkBox/GtkGrid/GtkFlowBox children whose spacing still equals the
1508// previously-applied standard spacing. Setting spacing inside gtk_container_foreach() doesn't
1509// mutate the child list, so the walk is safe.
1510static void _refresh_container_spacing(GtkWidget *w, gpointer user_data)
1511{
1512 const _spacing_ctx_t *c = (const _spacing_ctx_t *)user_data;
1513
1514 if(GTK_IS_BOX(w))
1515 {
1516 if(gtk_box_get_spacing(GTK_BOX(w)) == c->old_s) gtk_box_set_spacing(GTK_BOX(w), c->new_s);
1517 }
1518 else if(GTK_IS_FLOW_BOX(w))
1519 {
1520 if((gint)gtk_flow_box_get_row_spacing(GTK_FLOW_BOX(w)) == c->old_s)
1522 if((gint)gtk_flow_box_get_column_spacing(GTK_FLOW_BOX(w)) == c->old_s)
1524 }
1525 else if(GTK_IS_GRID(w))
1526 {
1527 if((gint)gtk_grid_get_row_spacing(GTK_GRID(w)) == c->old_s)
1528 gtk_grid_set_row_spacing(GTK_GRID(w), c->new_s);
1529 if((gint)gtk_grid_get_column_spacing(GTK_GRID(w)) == c->old_s)
1531 }
1532
1533 if(GTK_IS_CONTAINER(w))
1535}
1536
1537// Propagate a new DT_GUI_BOX_SPACING to already-built containers across every toplevel, so a
1538// runtime font/DPI change updates the inner gutters live (gtk_*_set_spacing bakes the value into
1539// the widget at creation time, so reloading the CSS alone is not enough).
1541{
1542 const gint new_s = DT_GUI_BOX_SPACING;
1543 if(new_s == _last_box_spacing) return;
1544
1545 _spacing_ctx_t c = { _last_box_spacing, new_s };
1546 GList *toplevels = gtk_window_list_toplevels(); // list owned by us, elements not reffed
1547 for(GList *l = toplevels; l; l = l->next)
1550
1551 _last_box_spacing = new_s;
1552}
1553
1555{
1556 dt_gui_gtk_t *gui = darktable.gui;
1557 if(!gui || !gui->ui || !gui->ui->main_window) return;
1558
1560 PangoFontDescription *desc = NULL;
1562 if(!desc) return;
1563
1564 const gint size = pango_font_description_get_size(desc);
1565 if(size > 0)
1566 {
1568 // already device-independent px
1569 gui->em = (double)size / PANGO_SCALE;
1570 else
1571 // points -> px at the screen DPI, matching how GTK renders point-sized fonts
1572 gui->em = (double)size / PANGO_SCALE * gui->dpi / 72.0;
1573
1575 }
1577
1578 // The new em may change DT_GUI_BOX_SPACING; push it to existing containers so the change is live.
1580}
1581
1583{
1584 if(IS_NULL_PTR(layout) || !darktable.gui) return;
1585 // Cairo-drawn text is laid out in points; the screen DPI converts those to device-independent px,
1586 // matching how GTK renders the rest of the UI. Centralized here so call sites never hand-write the DPI.
1588}
1589
1590
1591static gboolean _focus_in_out_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1592{
1594 return FALSE;
1595}
1596
1597static gboolean _ui_log_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1598{
1599 gtk_widget_hide(GTK_WIDGET(user_data));
1600 return TRUE;
1601}
1602
1603static gboolean _ui_toast_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1604{
1605 gtk_widget_hide(GTK_WIDGET(user_data));
1606 return TRUE;
1607}
1608
1610{
1612 GtkWidget *widget;
1613
1614 // Creating the main window
1616 gtk_widget_set_name(gui->ui->main_window , "main_window");
1617 gtk_window_set_role(GTK_WINDOW(gui->ui->main_window ), "main-app");
1620
1621 // Init the titlebar ASAP because we replace the desktop titlebar & decoration with ours
1622 dt_ui_init_titlebar(gui->ui);
1623
1625
1627
1628 // NOTE: allowing full-screen on startup shits the bed with MacOS
1629 if(dt_conf_get_bool("ui_last/maximized"))
1630 {
1631 gboolean restore_window_position = TRUE;
1632#ifdef GDK_WINDOWING_WAYLAND
1634 if(GDK_IS_WAYLAND_DISPLAY(display))
1636#endif
1637
1639 {
1642
1644 {
1645 if(dt_conf_key_exists("ui_last/window_monitor"))
1646 {
1647 const int monitor_index = dt_conf_get_int("ui_last/window_monitor");
1650 }
1651
1653 && dt_conf_key_exists("ui_last/window_x")
1654 && dt_conf_key_exists("ui_last/window_y"))
1655 {
1656 const int x = dt_conf_get_int("ui_last/window_x");
1657 const int y = dt_conf_get_int("ui_last/window_y");
1659 }
1660
1661 if(IS_NULL_PTR(monitor))
1665 }
1666
1667 if(!IS_NULL_PTR(monitor))
1668 {
1669 GdkRectangle workarea = { 0 };
1672 }
1673 }
1674
1676 }
1677 else
1678 {
1679 int width = dt_conf_get_int("ui_last/window_width");
1680 int height = dt_conf_get_int("ui_last/window_height");
1682
1683 gboolean restore_window_position = TRUE;
1684#ifdef GDK_WINDOWING_WAYLAND
1686 if(GDK_IS_WAYLAND_DISPLAY(display))
1688#endif
1689
1691 && dt_conf_key_exists("ui_last/window_x")
1692 && dt_conf_key_exists("ui_last/window_y"))
1693 {
1694 const int x = dt_conf_get_int("ui_last/window_x");
1695 const int y = dt_conf_get_int("ui_last/window_y");
1696
1697 int clamped_x = x;
1698 int clamped_y = y;
1701
1703 {
1704 if(dt_conf_key_exists("ui_last/window_monitor"))
1705 {
1706 const int monitor_index = dt_conf_get_int("ui_last/window_monitor");
1709 }
1710
1711 if(IS_NULL_PTR(monitor))
1713 if(IS_NULL_PTR(monitor))
1717 }
1718
1719 if(!IS_NULL_PTR(monitor))
1720 {
1721 GdkRectangle workarea = { 0 };
1723
1724 const int max_x = workarea.x + MAX(0, workarea.width - width);
1725 const int max_y = workarea.y + MAX(0, workarea.height - height);
1726 clamped_x = CLAMP(x, workarea.x, max_x);
1727 clamped_y = CLAMP(y, workarea.y, max_y);
1728 }
1729
1731 }
1732 }
1733
1735
1740
1741 container = gui->ui->main_window;
1742
1743 // Adding the outermost vbox
1746 gtk_widget_show(widget);
1747
1748 /* connect to signal redraw all */
1751
1752 container = widget;
1753
1754 // Initializing the main table
1756
1757 /* the log message */
1763 dt_gui_add_class(darktable.gui->ui->log_msg, "dt_messages");
1768 //gtk_overlay_reorder_overlay(GTK_OVERLAY(darktable.gui->ui->center_base), eb, -1);
1769
1770 /* the toast message */
1776 g_signal_connect(G_OBJECT(eb), "scroll-event", G_CALLBACK(_scrolled), NULL);
1778
1784
1785 dt_gui_add_class(darktable.gui->ui->toast_msg, "dt_messages");
1790 //gtk_overlay_reorder_overlay(GTK_OVERLAY(darktable.gui->ui->center_base), eb, -1);
1791
1792 /* update log message label */
1795
1796 /* update toast message label */
1799
1800
1801 // Showing everything
1803
1806}
1807
1817
1819{
1821 gtk_container_foreach(GTK_CONTAINER(ui->containers[c]), callback, (gpointer)ui->containers[c]);
1822}
1823
1828
1843
1844/* this is called as a signal handler, the signal raising logic asserts the gdk lock. */
1845static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
1846{
1847 gtk_widget_queue_draw(widget);
1848}
1849
1850static void _ui_log_redraw_callback(gpointer instance, GtkWidget *widget)
1851{
1852 // draw log message, if any
1853 dt_control_t *const control = dt_control_get_global();
1855 if(!GTK_IS_LABEL(widget))
1856 {
1858 return;
1859 }
1860 if(dt_control_get_global()->log_ack != dt_control_get_global()->log_pos)
1861 {
1862 if(strcmp(dt_control_get_global()->log_message[dt_control_get_global()->log_ack], gtk_label_get_text(GTK_LABEL(widget))))
1864 gtk_widget_show(widget);
1865 }
1866 else
1867 {
1868 gtk_widget_hide(widget);
1869 }
1871}
1872
1873static void _ui_toast_redraw_callback(gpointer instance, GtkWidget *widget)
1874{
1875 // draw toast message, if any
1876 dt_control_t *const control = dt_control_get_global();
1878 if(!GTK_IS_LABEL(widget))
1879 {
1881 return;
1882 }
1883 if(dt_control_get_global()->toast_ack != dt_control_get_global()->toast_pos)
1884 {
1885 if(strcmp(dt_control_get_global()->toast_message[dt_control_get_global()->toast_ack], gtk_label_get_text(GTK_LABEL(widget))))
1886 gtk_label_set_markup(GTK_LABEL(widget), dt_control_get_global()->toast_message[dt_control_get_global()->toast_ack]);
1887 if(!gtk_widget_get_visible(widget))
1888 {
1891 gtk_widget_show(widget);
1892 }
1893 }
1894 else
1895 {
1896 if(gtk_widget_get_visible(widget)) gtk_widget_hide(widget);
1897 }
1899}
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911// TODO: should that go to another place than gtk.c?
1912void dt_gui_add_help_link(GtkWidget *widget, char *link)
1913{
1914 g_object_set_data_full(G_OBJECT(widget), "dt-help-url", link, g_free);
1916}
1917
1918// load a CSS theme
1919void dt_gui_load_theme(const char *theme)
1920{
1921 char theme_css[DT_PATH_MAX] = { 0 };
1922 g_snprintf(theme_css, sizeof(theme_css), "%s.css", theme);
1923
1924 if(!dt_conf_key_exists("use_system_font"))
1925 dt_conf_set_bool("use_system_font", TRUE);
1926
1927 //set font size
1928 if(dt_conf_get_bool("use_system_font"))
1930 else
1931 {
1932 //font name can only use period as decimal separator
1933 //but printf format strings use comma for some locales, so replace comma with period
1934 gchar *font_size = g_strdup_printf(_("%.1f"), dt_conf_get_float("font_size"));
1936 gchar *font_name = g_strdup_printf(_("Sans %s"), font_size_updated);
1941 }
1942
1943 gchar *path, *usercsspath;
1944 char datadir[DT_PATH_MAX] = { 0 }, configdir[DT_PATH_MAX] = { 0 };
1945 dt_loc_get_datadir(datadir, sizeof(datadir));
1946 dt_loc_get_user_config_dir(configdir, sizeof(configdir));
1947
1948 // user dir theme
1949 path = g_build_filename(configdir, "themes", theme_css, NULL);
1951 {
1952 // dt dir theme
1953 dt_free(path);
1954 path = g_build_filename(datadir, "themes", theme_css, NULL);
1956 {
1957 // fallback to default theme
1958 dt_free(path);
1959 path = g_build_filename(datadir, "themes", "ansel.css", NULL);
1960 dt_conf_set_string("ui_last/theme", "ansel");
1961 }
1962 else
1963 dt_conf_set_string("ui_last/theme", theme);
1964 }
1965 else
1966 dt_conf_set_string("ui_last/theme", theme);
1967
1968 GError *error = NULL;
1969
1973
1974 usercsspath = g_build_filename(configdir, "user.css", NULL);
1975
1976 gchar *path_uri = g_filename_to_uri(path, NULL, &error);
1978 fprintf(stderr, "%s: could not convert path %s to URI. Error: %s\n", G_STRFUNC, path, error->message);
1979
1982 fprintf(stderr, "%s: could not convert path %s to URI. Error: %s\n", G_STRFUNC, usercsspath, error->message);
1983
1984 gchar *themecss = NULL;
1986 {
1987 themecss = g_strjoin(NULL, "@import url('", path_uri,
1988 "'); @import url('", usercsspath_uri, "');", NULL);
1989 }
1990 else
1991 {
1992 themecss = g_strjoin(NULL, "@import url('", path_uri, "');", NULL);
1993 }
1994
1997 dt_free(path);
1999
2000 if(dt_conf_get_bool("ui/hide_tooltips"))
2001 {
2002 gchar *newcss = g_strjoin(NULL, themecss, " tooltip {opacity: 0; background: transparent;}", NULL);
2004 themecss = newcss;
2005 }
2006
2008 {
2009 fprintf(stderr, "%s: error parsing combined CSS %s: %s\n", G_STRFUNC, themecss, error->message);
2011 }
2012
2014
2016
2017 // setup the colors
2018
2019 GdkRGBA *c = dt_widget_colors();
2020 GtkWidget *main_window = dt_ui_main_window(darktable.gui->ui);
2022
2023 c[DT_GUI_COLOR_BG] = (GdkRGBA){ 0.1333, 0.1333, 0.1333, 1.0 };
2024
2025 struct color_init
2026 {
2027 const char *name;
2028 GdkRGBA default_col;
2029 } init[DT_GUI_COLOR_LAST] = {
2030 [DT_GUI_COLOR_DARKROOM_BG] = { "darkroom_bg_color", { .2, .2, .2, 1.0 } },
2031 [DT_GUI_COLOR_DARKROOM_PREVIEW_BG] = { "darkroom_preview_bg_color", { .1, .1, .1, 1.0 } },
2032 [DT_GUI_COLOR_LIGHTTABLE_BG] = { "lighttable_bg_color", { .2, .2, .2, 1.0 } },
2033 [DT_GUI_COLOR_LIGHTTABLE_PREVIEW_BG] = { "lighttable_preview_bg_color", { .1, .1, .1, 1.0 } },
2034 [DT_GUI_COLOR_LIGHTTABLE_FONT] = { "lighttable_bg_font_color", { .7, .7, .7, 1.0 } },
2035 [DT_GUI_COLOR_PRINT_BG] = { "print_bg_color", { .2, .2, .2, 1.0 } },
2036 [DT_GUI_COLOR_BRUSH_CURSOR] = { "brush_cursor", { 1., 1., 1., 0.9 } },
2037 [DT_GUI_COLOR_BRUSH_TRACE] = { "brush_trace", { 0., 0., 0., 0.8 } },
2038 [DT_GUI_COLOR_BUTTON_FG] = { "button_fg", { 0.7, 0.7, 0.7, 0.55 } },
2039 [DT_GUI_COLOR_THUMBNAIL_BG] = { "thumbnail_bg_color", { 0.4, 0.4, 0.4, 1.0 } },
2040 [DT_GUI_COLOR_THUMBNAIL_SELECTED_BG] = { "thumbnail_selected_bg_color", { 0.8, 0.8, 0.8, 1.0 } },
2041 [DT_GUI_COLOR_THUMBNAIL_HOVER_BG] = { "thumbnail_hover_bg_color", { 0.65, 0.65, 0.65, 1.0 } },
2042 [DT_GUI_COLOR_THUMBNAIL_OUTLINE] = { "thumbnail_outline_color", { 0.2, 0.2, 0.2, 1.0 } },
2043 [DT_GUI_COLOR_THUMBNAIL_SELECTED_OUTLINE] = { "thumbnail_selected_outline_color", { 0.4, 0.4, 0.4, 1.0 } },
2044 [DT_GUI_COLOR_THUMBNAIL_HOVER_OUTLINE] = { "thumbnail_hover_outline_color", { 0.6, 0.6, 0.6, 1.0 } },
2045 [DT_GUI_COLOR_THUMBNAIL_FONT] = { "thumbnail_font_color", { 0.425, 0.425, 0.425, 1.0 } },
2046 [DT_GUI_COLOR_THUMBNAIL_SELECTED_FONT] = { "thumbnail_selected_font_color", { 0.5, 0.5, 0.5, 1.0 } },
2047 [DT_GUI_COLOR_THUMBNAIL_HOVER_FONT] = { "thumbnail_hover_font_color", { 0.7, 0.7, 0.7, 1.0 } },
2048 [DT_GUI_COLOR_THUMBNAIL_BORDER] = { "thumbnail_border_color", { 0.1, 0.1, 0.1, 1.0 } },
2049 [DT_GUI_COLOR_THUMBNAIL_SELECTED_BORDER] = { "thumbnail_selected_border_color", { 0.9, 0.9, 0.9, 1.0 } },
2050 [DT_GUI_COLOR_FILMSTRIP_BG] = { "filmstrip_bg_color", { 0.2, 0.2, 0.2, 1.0 } },
2051 [DT_GUI_COLOR_PREVIEW_HOVER_BORDER] = { "preview_hover_border_color", { 0.9, 0.9, 0.9, 1.0 } },
2052 [DT_GUI_COLOR_LOG_BG] = { "log_bg_color", { 0.1, 0.1, 0.1, 1.0 } },
2053 [DT_GUI_COLOR_LOG_FG] = { "log_fg_color", { 0.6, 0.6, 0.6, 1.0 } },
2054 [DT_GUI_COLOR_MAP_COUNT_SAME_LOC] = { "map_count_same_loc_color", { 1.0, 1.0, 1.0, 1.0 } },
2055 [DT_GUI_COLOR_MAP_COUNT_DIFF_LOC] = { "map_count_diff_loc_color", { 1.0, 0.85, 0.0, 1.0 } },
2056 [DT_GUI_COLOR_MAP_COUNT_BG] = { "map_count_bg_color", { 0.0, 0.0, 0.0, 1.0 } },
2057 [DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH] = { "map_count_circle_color_h", { 1.0, 1.0, 0.8, 1.0 } },
2058 [DT_GUI_COLOR_MAP_LOC_SHAPE_LOW] = { "map_count_circle_color_l", { 0.0, 0.0, 0.0, 1.0 } },
2059 [DT_GUI_COLOR_MAP_LOC_SHAPE_DEF] = { "map_count_circle_color_d", { 1.0, 0.0, 0.0, 1.0 } },
2060 [DT_GUI_COLOR_WARNING] = { "warning_color", { 1.0, 0.647, 0.0, 1.0 } },
2061 };
2062
2063 // starting from 1 as DT_GUI_COLOR_BG is not part of this table
2064 for(int i = 1; i < DT_GUI_COLOR_LAST; i++)
2065 {
2067 {
2068 c[i] = init[i].default_col;
2069 }
2070 }
2071
2073
2074 // The active theme/font may change the root font size, so refresh the cached em
2075 // that drives DT_GUI_BOX_SPACING.
2077}
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131// draw rounded rectangle
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2151{
2152 // Refocus window, useful if we just closed a popup/modal/transient
2154
2155 // Desperate measure to refocus the window
2159
2160 // dt_view_manager_name() returns the translated display name (e.g. "Table lumineuse"), not the
2161 // internal id: compare against module_name, which is stable and untranslated.
2163 if(!IS_NULL_PTR(current_view) && !strcmp(current_view->module_name, "lighttable"))
2164 {
2166 }
2167 else
2168 {
2170 }
2171
2172 // Be sure to re-enable accelerators
2174}
2175
2176// clang-format off
2177// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2178// vim: shiftwidth=2 expandtab tabstop=2 cindent
2179// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2180// clang-format on
void dt_accels_set_top_offset_handler(dt_accels_top_offset_handler_t handler)
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)
void dt_accels_set_refocus_handler(dt_accels_refocus_handler_t handler)
void dt_accels_set_recent_handlers(dt_accels_recent_get_handler_t get, dt_accels_recent_set_handler_t set)
void dt_accels_set_global(dt_accels_t *accels)
void dt_accels_load_user_config(dt_accels_t *accels)
Loads keyboardrc.lang from config dir. This needs to run after we inited the accel map from widgets c...
static void dt_accels_disable(dt_accels_t *accels, gboolean state)
#define DT_PRIMARY_MASK
static const char * _get_source_name(int pos)
static gboolean _widget_stored_int(const char *key, int *value)
static double _clamp01d(const double value)
static void _accels_recent_set(int index, const char *value)
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)
static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
static gint _accels_top_offset(void)
GtkWidget * dt_gui_main_window(void)
static gboolean _button_released(GtkWidget *w, GdkEventButton *event, gpointer user_data)
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)
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
gboolean dt_gui_quit_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
static gboolean _center_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
static const gdouble * _event_axes(const GdkEvent *event)
static gint _widget_natural_width(GtkWidget *widget)
static const char * _get_mode_name(int pos)
static void _center_unrealize(GtkWidget *widget, gpointer user_data)
static void _widget_store_bool(const char *key, gboolean value)
static void _widget_message(const char *message)
static gboolean _key_pressed(GtkWidget *w, GdkEventKey *event)
static gboolean _center_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
struct dt_accels_t * dt_gui_get_accels(void)
static gboolean _ui_toast_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
static gboolean _button_pressed(GtkWidget *w, GdkEventButton *event, gpointer user_data)
static dt_tablet_motion_state_t _tablet_motion_state
static gchar * _accels_recent_get(int index)
static gboolean _draw(GtkWidget *da, cairo_t *cr, gpointer user_data)
static gboolean _ui_log_button_press_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
static void _notify_profile_changed(void)
void dt_ui_container_destroy_children(dt_ui_t *ui, const dt_ui_container_t c)
destroy all child widgets from container
static gboolean _log_key_event(GSignalInvocationHint *hint, guint n_params, const GValue *params, gpointer data)
struct dt_ui_t * dt_gui_get_ui(void)
static gboolean _widget_stored_bool(const char *key)
static LRESULT CALLBACK _center_win32_cursor_proc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param, UINT_PTR subclass_id, DWORD_PTR ref_data)
void dt_gui_load_theme(const char *theme)
static void _widget_cursor(GdkCursorType cursor)
int dt_gui_gtk_write_config()
void dt_gui_store_last_preset(const char *name)
void dt_configure_ppd_dpi(dt_gui_gtk_t *gui)
GtkWidget * dt_gui_center_widget(void)
static gboolean _get_axis_value_for_source(const GdkEvent *event, GdkDevice *source_device, const GdkAxisUse axis, double *value, gboolean *from_source_map)
static const char * _get_axis_name(int pos)
void dt_gui_gtk_quit()
static gboolean _refresh_thumbnail_on_gui_thread(gpointer user_data)
static void _refresh_thumbnail_widgets(int32_t imgid, gboolean refresh_filmstrip)
static void _init_widgets(dt_gui_gtk_t *gui)
static void _update_display_profile(void)
static gboolean _mouse_moved(GtkWidget *w, GdkEventMotion *event, gpointer user_data)
#define DT_ACCEL_SEARCH_RECENT_KEY
void dt_gui_add_help_link(GtkWidget *widget, char *link)
#define DT_WIN32_CURSOR_SUBCLASS_CENTER
void dt_ui_notify_user()
draw user's attention
static void _notebook_page_changed(gpointer owner)
static gboolean _focus_in_out_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
static void _ui_log_redraw_callback(gpointer instance, GtkWidget *widget)
int dt_gui_gtk_init(dt_gui_gtk_t *gui)
static void _widget_store_int(const char *key, int value)
void dt_gui_refocus_center()
static void _gui_refresh_thumbnail(int32_t imgid, gboolean refresh_filmstrip)
static void _refresh_all_container_spacing(void)
static gint _last_box_spacing
void dt_gui_set_pango_resolution(PangoLayout *layout)
static gboolean _configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
static void _ui_toast_redraw_callback(gpointer instance, GtkWidget *widget)
void dt_gui_gtk_run(dt_gui_gtk_t *gui)
static GtkWidget * _widget_root_window(void)
static void _refresh_container_spacing(GtkWidget *w, gpointer user_data)
static gboolean _window_configure(GtkWidget *da, GdkEvent *event, gpointer user_data)
static void _center_realize(GtkWidget *widget, gpointer user_data)
static void _gui_startup_progress(const char *message)
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
static gboolean _sample_axis_from_device_state(GdkWindow *window, GdkDevice *device, const GdkAxisUse axis, double *value)
void dt_gui_update_em(void)
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void init(dt_imageio_module_format_t *self)
Definition avif.c:157
uint32_t container(dt_lib_module_t *self)
void dt_gui_closing_quit(void)
Definition closing.c:487
void dt_gui_closing_wait(void)
Definition closing.c:492
void dt_collection_init_global(void)
Definition collection.c:143
void dt_collection_gui_register_handlers(void)
@ DT_COLORLABELS_PURPLE
Definition colorlabels.h:46
@ DT_COLORLABELS_LAST
Definition colorlabels.h:47
@ DT_COLORLABELS_RED
Definition colorlabels.h:42
static const float x
void dt_colorspaces_set_profile_changed_handler(dt_colorspaces_profile_changed_handler_t handler)
Register the one callback fired when the display profile changes.
void dt_colorspaces_set_display_profile(const dt_colorspaces_color_profile_type_t profile_type, GtkWidget *widget)
Refresh the cached display profile from the monitor showing widget (X atom, colord,...
The colour-profile module's API: which profiles exist, and how to apply one.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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)
Does key have a value?
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
void dt_gui_container_destroy_children(GtkContainer *container)
Definition container.c:80
void dt_control_set_shutdown_wait_handler(dt_control_shutdown_wait_t handler)
Definition control.c:470
void dt_control_key_pressed(GdkEventKey *event)
Definition control.c:682
void dt_control_button_pressed(double x, double y, double pressure, int which, int type, uint32_t state)
Definition control.c:787
void dt_toast_log(const char *msg,...)
Definition control.c:886
void dt_control_mouse_leave()
Definition control.c:667
gboolean dt_control_configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
Definition control.c:530
void dt_control_button_released(double x, double y, int which, uint32_t state)
Definition control.c:687
void dt_control_set_pointer_input(const dt_control_pointer_input_t *input)
Definition control.c:77
int dt_control_running()
Definition control.c:442
void dt_control_mouse_moved(double x, double y, double pressure, int which)
Definition control.c:677
void dt_control_mouse_enter()
Definition control.c:672
void dt_control_expose(cairo_t *cr, const int width, const int height)
Definition control.c:609
#define dt_control_change_cursor(cursor)
Definition control.h:121
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
int dt_load_from_string(const gchar *input, gboolean open_image_in_dr, gboolean *single_image)
Definition darktable.c:389
void dt_cleanup()
Definition darktable.c:1864
darktable_t darktable
Definition darktable.c:213
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
GtkWidget * window
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
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)
void dt_film_gui_register_handlers(void)
Definition film_gui.c:116
void dt_folder_survey_gui_register_handlers()
void dt_gui_presets_init()
void dt_guides_init(void)
Definition guides.c:594
void dt_guides_set_overlay_colors()
Definition guides.c:680
void dt_history_merge_gui_register_handlers(void)
void dt_gui_import_init_handlers(void)
Register the GUI-side import handlers (the discarded-files recap dialog).
Definition import.c:1791
const char * dt_l10n_get_current_lang(dt_l10n_t *l10n)
Definition l10n.c:457
struct dt_l10n_t * dt_l10n_get_global(void)
Definition darktable.c:636
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
@ DT_DEBUG_INPUT
Definition logging.h:64
@ DT_DEBUG_CONTROL
Definition logging.h:52
@ DT_DEBUG_MASKS
Definition logging.h:62
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
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:289
void dt_osx_autoset_dpi(GtkWidget *widget)
Definition osx.mm:55
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
void dt_concat_path_file(char destination[4096], const char path[4096], const char *const file)
Append a constant filename to a variable, stack-based, fixed-sized, directory, and add a / in-between...
Definition darktable.c:2674
const char * name
Definition pdf.h:90
@ DT_COLORSPACE_DISPLAY
The monitor profile – the one list entry that mutates after init.
void dt_screen_set_dpi(double dpi)
void dt_selection_init_global(void)
Definition selection.c:85
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:390
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_CONTROL_REDRAW_ALL
This signal is raised when dt_control_queue_redraw() is called. no param, no returned value.
Definition signal.h:73
@ DT_SIGNAL_CONTROL_TOAST_REDRAW
This signal is raised when dt_control_toast_redraw() is called. no param, no returned value.
Definition signal.h:292
@ DT_SIGNAL_CONTROL_NOTEBOOK_TAB_CHANGED
This signal is raised when a GtkNotebook created/registered via dt_ui_notebook_set_picker_owner() swi...
Definition signal.h:306
@ DT_SIGNAL_CONTROL_PROFILE_CHANGED
This signal is raised when the screen profile has changed no param, no returned value.
Definition signal.h:240
@ DT_SIGNAL_CONTROL_LOG_REDRAW
This signal is raised when dt_control_log_redraw() is called. no param, no returned value.
Definition signal.h:287
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
void dt_gui_splash_init(void)
Definition splash.c:512
void dt_gui_splash_updatef(const char *format,...)
Definition splash.c:687
void dt_gui_splash_set_transient_for(GtkWidget *parent)
Definition splash.c:82
void dt_startup_progress_set_handler(dt_startup_progress_handler_t handler)
struct dt_gui_gtk_t * gui
Definition darktable.h:190
struct dt_view_manager_t * view_manager
Definition darktable.h:187
dt_pthread_mutex_t log_mutex
Definition control.h:232
dt_pthread_mutex_t toast_mutex
Definition control.h:239
struct dt_gui_gtk_t::@35 export_popup
int32_t center_tooltip
dt_accels_t * accels
GtkMenu * presets_popup_menu
char gtkrc[DT_PATH_MAX]
double dpi_factor
dt_ui_t * ui
dt_pthread_mutex_t mutex
gboolean culling_mode
struct dt_gui_gtk_t::@36 styles_popup
GtkWidget * window
cairo_filter_t filter_image
char * last_preset
GtkWidget * grid
Definition thumbtable.h:111
dt_thumbtable_t * thumbtable_lighttable
GtkWidget * containers[DT_UI_CONTAINER_SIZE]
GtkWidget * main_window
GtkWidget * center_base
GtkWidget * log_msg
dt_thumbtable_t * thumbtable_filmstrip
GtkWidget * panels[DT_UI_PANEL_SIZE]
GtkWidget * toast_msg
char module_name[64]
Definition view.h:155
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 dt_thumbnail_notify_set_handler(dt_thumbnail_refresh_handler_t handler)
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
#define dt_thumbtable_refresh_thumbnail(table, imgid, reinit)
Definition thumbtable.h:301
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:163
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
Definition view.c:633
int dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state, int delta_y)
Definition view.c:645
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
void dt_widget_set_ppd(double ppd)
void dt_widget_set_cursor_handler(dt_widget_cursor_handler_t handler)
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
void dt_widget_set_debug_overlays(gboolean enabled)
void dt_widget_set_dpi_factor(double factor)
void dt_widget_set_image_filter(cairo_filter_t filter)
void dt_widget_set_scroll_reversed(gboolean reverse_x, gboolean reverse_y)
GdkRGBA * dt_widget_colors(void)
void dt_gui_freeze_reset(void)
void dt_widget_set_theme_loaded(gboolean loaded)
void dt_widget_set_natural_width_handler(dt_widget_natural_width_handler_t handler)
gboolean dt_widget_on_gui_thread(void)
Is the calling thread the one that owns widget state?
void dt_widget_set_root_window_handler(dt_widget_root_window_handler_t handler)
void dt_widget_set_em_size(double em)
void dt_widget_set_gui_thread(pthread_t thread)
void dt_widget_set_notebook_page_handler(dt_widget_notebook_page_handler_t handler)
void dt_widget_set_refocus_handler(dt_widget_refocus_handler_t handler)
GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
double dt_get_system_gui_ppd(GtkWidget *widget)
void dt_widget_set_storage_handlers(dt_widget_stored_int_getter_t get_int, dt_widget_stored_int_setter_t set_int, dt_widget_stored_bool_getter_t get_bool, dt_widget_stored_bool_setter_t set_bool)
void dt_widget_set_message_handler(dt_widget_message_handler_t handler)
void dt_widget_set_mouse_radius(float radius, float clamped)
void dt_widget_set_scroll_mask(GdkEventMask mask)
@ DT_GUI_COLOR_BG
@ DT_GUI_COLOR_DARKROOM_PREVIEW_BG
@ DT_GUI_COLOR_LIGHTTABLE_FONT
@ DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH
@ DT_GUI_COLOR_LOG_FG
@ DT_GUI_COLOR_BRUSH_CURSOR
@ DT_GUI_COLOR_LAST
@ DT_GUI_COLOR_THUMBNAIL_FONT
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_BG
@ DT_GUI_COLOR_MAP_COUNT_BG
@ DT_GUI_COLOR_LOG_BG
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_BORDER
@ DT_GUI_COLOR_THUMBNAIL_OUTLINE
@ DT_GUI_COLOR_MAP_LOC_SHAPE_LOW
@ DT_GUI_COLOR_LIGHTTABLE_BG
@ DT_GUI_COLOR_BUTTON_FG
@ DT_GUI_COLOR_DARKROOM_BG
@ DT_GUI_COLOR_THUMBNAIL_BORDER
@ DT_GUI_COLOR_LIGHTTABLE_PREVIEW_BG
@ DT_GUI_COLOR_WARNING
@ DT_GUI_COLOR_MAP_COUNT_SAME_LOC
@ DT_GUI_COLOR_MAP_COUNT_DIFF_LOC
@ DT_GUI_COLOR_THUMBNAIL_BG
@ DT_GUI_COLOR_PREVIEW_HOVER_BORDER
@ DT_GUI_COLOR_FILMSTRIP_BG
@ DT_GUI_COLOR_BRUSH_TRACE
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_FONT
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_OUTLINE
@ DT_GUI_COLOR_THUMBNAIL_HOVER_BG
@ DT_GUI_COLOR_PRINT_BG
@ DT_GUI_COLOR_MAP_LOC_SHAPE_DEF
@ DT_GUI_COLOR_THUMBNAIL_HOVER_FONT
@ DT_GUI_COLOR_THUMBNAIL_HOVER_OUTLINE
@ DT_WIDGET_COLORLABEL_RED
@ DT_WIDGET_COLORLABEL_PURPLE
@ DT_WIDGET_COLORLABEL_COUNT
#define DT_UI_SCALE_DEVICE(value)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
GtkWidget * dt_ui_toast_msg(dt_ui_t *ui)
GtkWidget * dt_ui_center(dt_ui_t *ui)
void dt_ui_cleanup_titlebar(dt_ui_t *ui)
void dt_ui_init_titlebar(dt_ui_t *ui)
int dt_ui_panel_get_size(dt_ui_t *ui, const dt_ui_panel_t p)
gboolean dt_ui_panel_ancestor(dt_ui_t *ui, const dt_ui_panel_t p, GtkWidget *w)
GtkWidget * dt_ui_log_msg(dt_ui_t *ui)
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
void dt_ui_init_main_table(GtkWidget *parent, dt_ui_t *ui)
GtkWidget * dt_ui_center_base(dt_ui_t *ui)
dt_ui_container_t
@ DT_UI_PANEL_TOP
@ DT_UI_PANEL_LEFT
@ DT_UI_PANEL_RIGHT