Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
widget_settings.c
Go to the documentation of this file.
1/*
2 * This file is part of darktable,
3 * Copyright (C) 2016 johannes hanika.
4 * Copyright (C) 2016, 2020 Tobias Ellinghaus.
5 * Copyright (C) 2020 Pascal Obry.
6 * Copyright (C) 2021 Sakari Kapanen.
7 * Copyright (C) 2022 Martin Baƙinka.
8 *
9 * darktable is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * darktable is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
21 */
22
24
25
26
27#include <stdint.h>
28#include <math.h>
29#include <pthread.h>
30#include <stdarg.h>
31#include <stdio.h>
32
33#ifdef GDK_WINDOWING_QUARTZ
34#include "osx/osx.h" // dt_osx_get_ppd(), the only reliable ppd source on quartz
35#endif
36
37// Toolkit state, set by the application during GUI init and read everywhere after.
38// Single-threaded by construction: all of it is touched from the GUI thread only.
41static cairo_filter_t _image_filter = CAIRO_FILTER_GOOD;
42
47
52
57
59{
60 _scroll_focus = widget;
61}
62
63cairo_filter_t dt_widget_image_filter(void)
64{
65 return _image_filter;
66}
67
68void dt_widget_set_image_filter(cairo_filter_t filter)
69{
70 _image_filter = filter;
71}
72
73// Widget-update suppression depth. Was a field of dt_gui_gtk_t; it is toolkit state, and a
74// widget should not need the application global to know whether to ignore its own callback.
75static int32_t _widget_suppress_depth = 0;
76
77// Which thread owns widget state, and whether it has been registered at all.
78static pthread_t _gui_thread;
79static gboolean _gui_thread_set = FALSE;
80
81// Scroll axis inversion, a user preference the application supplies.
82static gboolean _reverse_x = FALSE, _reverse_y = FALSE;
83
84// Toolkit metrics. Defaults are the 96-DPI, 16px-font reference: correct for standalone
85// dialogs that run after gtk_init() but before the application has resolved the real values.
86//
87// The store is here rather than in gui/, even though the GUI is what resolves the values,
88// because every widget reads them on every draw: putting it in gui/ would make widgets/
89// depend on gui/, and that dependency direction is what makes this module portable at all.
90// gui/screen_metrics.c forwards to these under the dt_screen_* names for the handful of
91// readers below layer 4 (the crash reporter, telemetry) and for the cairo helpers.
92static double _dpi = 96.0;
93static double _dpi_factor = 1.0;
94static double _ppd = 1.0;
95static double _em = 16.0;
96static gboolean _metrics_probed = FALSE;
97
98double dt_widget_dpi(void) { return _dpi; }
99void dt_widget_set_dpi(double dpi) { if(dpi > 0.0) { _dpi = dpi; _metrics_probed = TRUE; } }
100
102
107
112
117
122
124{
126}
127
132
133void dt_widget_message(const char *message)
134{
135 if(_message_handler && message) _message_handler(message);
136}
137
142
143void dt_widget_set_cursor(GdkCursorType cursor)
144{
146}
147
148double dt_widget_dpi_factor(void) { return _dpi_factor; }
150
151double dt_widget_ppd(void) { return _ppd; }
152void dt_widget_set_ppd(double ppd) { if(ppd > 0.0) { _ppd = ppd; _metrics_probed = TRUE; } }
153
154double dt_widget_em_size(void) { return _em; }
155void dt_widget_set_em_size(double em) { if(em > 0.0) _em = em; }
156
157void dt_widget_set_gui_thread(pthread_t thread)
158{
159 _gui_thread = thread;
161}
162
168
169static inline gboolean _on_gui_thread(void)
170{
172}
173
174// Colour-label palette, supplied by the application's theme.
175static GdkRGBA _colorlabels[16];
176static int _colorlabels_count = 0;
177
178const GdkRGBA *dt_widget_colorlabel(int index)
179{
181 return &_colorlabels[index];
182}
183
184void dt_widget_set_colorlabels(const GdkRGBA *labels, int count)
185{
186 if(labels == NULL || count <= 0) { _colorlabels_count = 0; return; }
187 if(count > (int)(sizeof(_colorlabels) / sizeof(_colorlabels[0])))
188 count = (int)(sizeof(_colorlabels) / sizeof(_colorlabels[0]));
189 for(int i = 0; i < count; i++) _colorlabels[i] = labels[i];
190 _colorlabels_count = count;
191}
192
194{
195 return _widget_suppress_depth > 0;
196}
197
198void dt_gui_freeze_begin_(const char *file, int line)
199{
200 // Only the GUI thread owns widget state. Off-thread callers (notably worker-thread
201 // reload_defaults during thumbnail/export, which has no widgets to suppress) must not touch
202 // the shared depth, or concurrent non-atomic ++/-- drift it and break suppression for the
203 // GUI thread. For them this is a deliberate no-op.
204 if(!_on_gui_thread()) return;
205 // MAX(.,0) heals any pre-existing negative drift so the depth is always genuinely suppressing.
207 (void)file;
208 (void)line;
209}
210
211void dt_gui_freeze_end_(const char *file, int line)
212{
213 if(!_on_gui_thread()) return;
215 {
216 // A bare end with nothing to match: an unbalanced freeze bracket exists. Surface it (with
217 // the offending site) instead of letting the counter go negative and silently disable
218 // suppression for the rest of the session.
219 fprintf(stderr, "[dt_gui_freeze] unbalanced end at %s:%d (depth was %d); "
220 "look for a freeze begin without a matching end.\n",
221 file, line, _widget_suppress_depth);
223 return;
224 }
226}
227
229{
231}
232
234{
235 // avoid double counting real and emulated events when receiving smooth scrolls
237
238 // accumulates scrolling regardless of source or the widget being scrolled
239 static gdouble acc_x = 0.0, acc_y = 0.0;
240
241 gboolean handled = FALSE;
242
243 switch(event->direction)
244 {
245 // is one-unit cardinal, e.g. from a mouse scroll wheel
246 case GDK_SCROLL_LEFT:
247 if(delta_x)
248 {
249 *delta_x = _reverse_x ? 1 : -1;
250 if(delta_y) *delta_y = 0;
251 handled = TRUE;
252 }
253 break;
254 case GDK_SCROLL_RIGHT:
255 if(delta_x)
256 {
257 *delta_x = _reverse_x ? -1 : 1;
258 if(delta_y) *delta_y = 0;
259 handled = TRUE;
260 }
261 break;
262 case GDK_SCROLL_UP:
263 if(delta_y)
264 {
265 if(delta_x) *delta_x = 0;
266 *delta_y = _reverse_y ? 1 : -1;
267 handled = TRUE;
268 }
269 break;
270 case GDK_SCROLL_DOWN:
271 if(delta_y)
272 {
273 if(delta_x) *delta_x = 0;
274 *delta_y = _reverse_y ? -1 : 1;
275 handled = TRUE;
276 }
277 break;
278 // is trackpad (or touch) scroll
280 // stop events reset accumulated delta
281 if(event->is_stop)
282 {
283 acc_x = acc_y = 0.0;
284 break;
285 }
286 // accumulate trackpad/touch scrolls until they make a unit
287 // scroll, and only then tell caller that there is a scroll to
288 // handle
289#ifdef GDK_WINDOWING_QUARTZ // on macOS deltas need to be scaled
290 acc_x += _reverse_x ? -event->delta_x / 50. : event->delta_x / 50.;
291 acc_y += _reverse_y ? -event->delta_y / 50. : event->delta_y / 50.;
292#else
293 acc_x += _reverse_x ? -event->delta_x : event->delta_x;
294 acc_y += _reverse_y ? -event->delta_y : event->delta_y;
295#endif
296 const gdouble amt_x = trunc(acc_x);
297 const gdouble amt_y = trunc(acc_y);
298 if(amt_x != 0 || amt_y != 0)
299 {
300 acc_x -= amt_x;
301 acc_y -= amt_y;
302 if((delta_x && amt_x != 0) || (delta_y && amt_y != 0))
303 {
304 if(delta_x) *delta_x = (int)amt_x;
305 if(delta_y) *delta_y = (int)amt_y;
306 handled = TRUE;
307 }
308 }
309 break;
310 default:
311 break;
312 }
313 return handled;
314}
315
317{
318 int delta_x, delta_y;
320 {
321 *delta = delta_x + delta_y;
322 return TRUE;
323 }
324 return FALSE;
325}
326#ifdef _DEBUG
327void dt_widget_log(const char *format, ...)
328{
329 va_list ap;
330 va_start(ap, format);
331 vfprintf(stdout, format, ap);
332 va_end(ap);
333 // Unflushed diagnostics are lost when the process does not exit cleanly, which is exactly
334 // the run you were debugging.
335 fflush(stdout);
336}
337#endif
338
339static gboolean _debug_overlays = FALSE;
340
342{
343 return _debug_overlays;
344}
345
346void dt_widget_set_debug_overlays(gboolean enabled)
347{
348 _debug_overlays = enabled;
349}
350
355
366
367gboolean dt_widget_stored_int(const char *key, int *value)
368{
369 if(!key || !_stored_int_getter) return FALSE;
371}
372
373void dt_widget_store_int(const char *key, int value)
374{
376}
377
378gboolean dt_widget_stored_bool(const char *key)
379{
380 if(!key || !_stored_bool_getter) return FALSE;
381 return _stored_bool_getter(key);
382}
383
384void dt_widget_store_bool(const char *key, gboolean value)
385{
387}
388
389static gint _min_panel_width = 350;
390
392{
393 return _min_panel_width;
394}
395
400
401static gboolean _theme_loaded = FALSE;
402
404{
405 return _theme_loaded;
406}
407
408void dt_widget_set_theme_loaded(gboolean loaded)
409{
410 _theme_loaded = loaded;
411}
412
415
420
425
430
435
436
437/* ------------------------------------------------------------------------------------------
438 * Theme palette, overlay tint and mouse radius.
439 *
440 * All three used to be fields of dt_gui_gtk_t, which meant a widget had to reach the
441 * application global to find out what colour to paint with or how close a click counts as a
442 * hit. They are toolkit state: the application resolves them (from the CSS theme, from user
443 * preferences, from the darkroom zoom) and pushes them here, and widgets read them.
444 * ------------------------------------------------------------------------------------------ */
445static GdkRGBA _colors[DT_GUI_COLOR_LAST] = { { 0.0, 0.0, 0.0, 1.0 } };
446static dt_widget_overlay_color_t _overlay = { 1.0, 1.0, 1.0, 0.5 };
447static float _mouse_radius = 15.f;
448static float _mouse_radius_clamped = 15.f;
449
450GdkRGBA *dt_widget_colors(void)
451{
452 return _colors;
453}
454
456{
457 const GdkRGBA bc = _colors[color];
458 cairo_set_source_rgb(cr, bc.red, bc.green, bc.blue);
459}
460
462{
463 const GdkRGBA bc = _colors[color];
464 cairo_set_source_rgba(cr, bc.red, bc.green, bc.blue, bc.alpha * opacity_coef);
465}
466
471
472void dt_widget_set_overlay_color(double red, double green, double blue, double contrast)
473{
474 _overlay.red = red;
475 _overlay.green = green;
476 _overlay.blue = blue;
477 _overlay.contrast = contrast;
478}
479
481{
482 return _mouse_radius;
483}
484
489
490void dt_widget_set_mouse_radius(float radius, float clamped)
491{
492 _mouse_radius = radius;
494}
495
496
497/* Scroll deltas as fractions. Same event arithmetic as the discrete-unit pair below, minus
498 * the accumulation: callers of this form want the raw smooth-scroll amount. */
499gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y)
500{
501 // avoid double counting real and emulated events when receiving smooth scrolls
502 if(gdk_event_get_pointer_emulated((GdkEvent *)event)) return FALSE;
503
504 gboolean handled = FALSE;
505 switch(event->direction)
506 {
507 // is one-unit cardinal, e.g. from a mouse scroll wheel
508 case GDK_SCROLL_LEFT:
509 if(delta_x)
510 {
511 *delta_x = _reverse_x ? 1.0 : -1.0;
512 if(delta_y) *delta_y = 0.0;
513 handled = TRUE;
514 }
515 break;
516 case GDK_SCROLL_RIGHT:
517 if(delta_x)
518 {
519 *delta_x = _reverse_x ? -1.0 : 1.0;
520 if(delta_y) *delta_y = 0.0;
521 handled = TRUE;
522 }
523 break;
524 case GDK_SCROLL_UP:
525 if(delta_y)
526 {
527 if(delta_x) *delta_x = 0.0;
528 *delta_y = _reverse_y ? 1.0 : -1.0;
529 handled = TRUE;
530 }
531 break;
532 case GDK_SCROLL_DOWN:
533 if(delta_y)
534 {
535 if(delta_x) *delta_x = 0.0;
536 *delta_y = _reverse_y ? -1.0 : 1.0;
537 handled = TRUE;
538 }
539 break;
540 // is trackpad (or touch) scroll
542 if((delta_x && event->delta_x != 0) || (delta_y && event->delta_y != 0))
543 {
544#ifdef GDK_WINDOWING_QUARTZ // on macOS deltas need to be scaled
545 if(delta_x) *delta_x = _reverse_x ? -event->delta_x / 50. : event->delta_x / 50.;
546 if(delta_y) *delta_y = _reverse_y ? -event->delta_y / 50. : event->delta_y / 50.;
547#else
548 if(delta_x) *delta_x = _reverse_x ? -event->delta_x : event->delta_x;
549 if(delta_y) *delta_y = _reverse_y ? -event->delta_y : event->delta_y;
550#endif
551 handled = TRUE;
552 }
553 break;
554 default:
555 break;
556 }
557 return handled;
558}
559
560gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
561{
562 gdouble delta_x, delta_y;
564 {
565 *delta = delta_x + delta_y;
566 return TRUE;
567 }
568 return FALSE;
569}
570
571
572#ifdef _DEBUG
573void dt_gtk_widget_queue_draw_ext(GtkWidget *widget, const char *name, const char *file, const int line)
574{
575 if(!GTK_IS_WIDGET(widget))
576 {
577 dt_widget_log("gtk_widget_queue_draw(%s) called with a non-WIDGET or NULL widget at %s:%d (widget=%p)\n",
578 name, file, line, widget);
579 return;
580 }
581 else
582 dt_widget_log("queueing redraw for `%s` (`%s`) at %s:%d\n",
583 name, gtk_widget_get_name(widget), file, line);
584
585 (gtk_widget_queue_draw)(widget);
586}
587
588void dt_gtk_toggle_button_set_active_ext(GtkToggleButton *toggle_button, const char *name, const gboolean active,
589 const char *file, const int line)
590{
592 {
593 dt_widget_log("gtk_toggle_button_set_active(%s) called with a non-TOGGLE_BUTTON or NULL widget at %s:%d (toggle_button=%p)\n",
594 name, file, line, toggle_button);
595 return;
596 }
597 else
598 dt_widget_log("setting toggle button `%s` (`%s`) to %s at %s:%d\n",
599 name, gtk_widget_get_name(GTK_WIDGET(toggle_button)), active ? "active" : "inactive", file, line);
600
602}
603#endif
604
605// refactored function to read current ppd, because gtk for osx has been unreliable
606// we use the specific function here. Anyway, if nothing meaningful is found we default back to 1.0
608{
609 double res = 0.0f;
610#ifdef GDK_WINDOWING_QUARTZ
612#else
614#endif
615 if((res < 1.0f) || (res > 4.0f))
616 {
617 dt_widget_log("[dt_get_system_gui_ppd] can't detect system ppd\n");
618 return 1.0f;
619 }
620 dt_widget_log("[dt_get_system_gui_ppd] system ppd is %f\n", res);
621 return res;
622}
623
624GdkModifierType dt_key_modifier_state()
625{
626 guint state = 0;
629 return state;
630
631/* FIXME double check correct way of doing this (merge conflict with Input System NG 20210319)
632 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
633 return gdk_keymap_get_modifier_state(keymap) & gdk_keymap_get_modifier_mask(keymap, GDK_MODIFIER_INTENT_DEFAULT_MOD_MASK);
634*/
635}
636
637// clang-format off
638// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
639// vim: shiftwidth=2 expandtab tabstop=2 cindent
640// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
641// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float delta
GdkRGBA color[]
Definition geotagging.c:539
char * key
uint32_t width
Definition mipmap_cache.c:0
float dt_osx_get_ppd()
Definition osx.mm:74
const float factor
Definition pdf.h:91
const char * name
Definition pdf.h:90
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
GtkWidget * window
#define MAX(a, b)
Definition thinplate.c:29
void dt_widget_set_ppd(double ppd)
gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y)
GtkWidget * dt_widget_root_window(void)
float dt_widget_mouse_radius(void)
double dt_widget_ppd(void)
static dt_widget_stored_int_setter_t _stored_int_setter
static dt_widget_stored_bool_setter_t _stored_bool_setter
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
static GtkWidget * _scroll_focus
void dt_widget_set_colorlabels(const GdkRGBA *labels, int count)
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)
static dt_widget_message_handler_t _message_handler
void dt_widget_set_image_filter(cairo_filter_t filter)
static pthread_t _gui_thread
void dt_widget_set_scroll_reversed(gboolean reverse_x, gboolean reverse_y)
void dt_widget_set_source_rgba(cairo_t *cr, dt_gui_color_t color, float opacity_coef)
gboolean dt_widget_stored_int(const char *key, int *value)
GdkRGBA * dt_widget_colors(void)
void dt_gui_freeze_reset(void)
static GdkRGBA _colorlabels[16]
void dt_widget_set_theme_loaded(gboolean loaded)
static int32_t _widget_suppress_depth
static dt_widget_stored_int_getter_t _stored_int_getter
gboolean dt_widget_metrics_probed(void)
GtkWidget * dt_widget_scroll_focus(void)
void dt_widget_set_natural_width_handler(dt_widget_natural_width_handler_t handler)
void dt_widget_store_bool(const char *key, gboolean value)
static double _dpi
static dt_widget_cursor_handler_t _cursor_handler
static dt_widget_stored_bool_getter_t _stored_bool_getter
void dt_gui_freeze_end_(const char *file, int line)
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)
static dt_widget_natural_width_handler_t _natural_width_handler
void dt_widget_set_dpi(double dpi)
void dt_widget_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
static gint _min_panel_width
GdkModifierType dt_key_modifier_state()
static gboolean _debug_overlays
static dt_widget_root_window_handler_t _root_window_handler
static dt_widget_refocus_handler_t _refocus_handler
static float _mouse_radius_clamped
gboolean dt_widget_debug_overlays(void)
void dt_widget_set_notebook_page_handler(dt_widget_notebook_page_handler_t handler)
void dt_widget_set_overlay_color(double red, double green, double blue, double contrast)
static dt_widget_notebook_page_handler_t _notebook_page_handler
static cairo_filter_t _image_filter
gint dt_widget_min_panel_width(void)
void dt_widget_set_refocus_handler(dt_widget_refocus_handler_t handler)
const GdkRGBA * dt_widget_colorlabel(int index)
static gboolean _gui_thread_set
GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
double dt_get_system_gui_ppd(GtkWidget *widget)
static dt_widget_overlay_color_t _overlay
static gboolean _metrics_probed
static gboolean _on_gui_thread(void)
static GdkRGBA _colors[DT_GUI_COLOR_LAST]
float dt_widget_mouse_radius_clamped(void)
static gboolean _reverse_x
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)
static float _mouse_radius
static GdkEventMask _scroll_mask
gboolean dt_widget_stored_bool(const char *key)
void dt_widget_set_min_panel_width(gint width)
void dt_widget_set_message_handler(dt_widget_message_handler_t handler)
static int _colorlabels_count
double dt_widget_dpi_factor(void)
void dt_widget_store_int(const char *key, int value)
void dt_widget_refocus(void)
static double _ppd
gboolean dt_gui_widgets_suppressed(void)
cairo_filter_t dt_widget_image_filter(void)
void dt_widget_notebook_page_changed(gpointer owner)
void dt_widget_set_mouse_radius(float radius, float clamped)
static double _em
const dt_widget_overlay_color_t * dt_widget_overlay_color(void)
void dt_widget_set_scroll_mask(GdkEventMask mask)
static gboolean _theme_loaded
gint dt_widget_natural_width(GtkWidget *widget)
static double _dpi_factor
double dt_widget_dpi(void)
double dt_widget_em_size(void)
void dt_widget_message(const char *message)
void dt_widget_set_cursor(GdkCursorType cursor)
void dt_gui_freeze_begin_(const char *file, int line)
gboolean dt_widget_theme_loaded(void)
static gboolean _reverse_y
void(* dt_widget_refocus_handler_t)(void)
gboolean(* dt_widget_stored_int_getter_t)(const char *key, int *value)
gboolean(* dt_widget_stored_bool_getter_t)(const char *key)
dt_gui_color_t
@ DT_GUI_COLOR_LAST
gint(* dt_widget_natural_width_handler_t)(GtkWidget *widget)
void(* dt_widget_stored_int_setter_t)(const char *key, int value)
void(* dt_widget_cursor_handler_t)(GdkCursorType cursor)
void(* dt_widget_notebook_page_handler_t)(gpointer owner)
#define dt_widget_log(...)
GtkWidget *(* dt_widget_root_window_handler_t)(void)
void(* dt_widget_stored_bool_setter_t)(const char *key, gboolean value)
void(* dt_widget_message_handler_t)(const char *message)