Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
control.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2016 johannes hanika.
4 Copyright (C) 2010 Brian Teague.
5 Copyright (C) 2010, 2015 Bruce Guenter.
6 Copyright (C) 2010-2013 Henrik Andersson.
7 Copyright (C) 2010 Pascal de Bruijn.
8 Copyright (C) 2010 Stuart Henderson.
9 Copyright (C) 2010-2019 Tobias Ellinghaus.
10 Copyright (C) 2010 Wyatt Olson.
11 Copyright (C) 2011 Antony Dovgal.
12 Copyright (C) 2011 Robert Bieber.
13 Copyright (C) 2011 Rostyslav Pidgornyi.
14 Copyright (C) 2011-2013, 2016 Ulrich Pegelow.
15 Copyright (C) 2012-2014, 2019-2020 Aldric Renaudin.
16 Copyright (C) 2012 James C. McPherson.
17 Copyright (C) 2012-2013 José Carlos García Sogo.
18 Copyright (C) 2012 Michal Babej.
19 Copyright (C) 2012 Michal Fabik.
20 Copyright (C) 2012-2015 parafin.
21 Copyright (C) 2012 Richard Wonka.
22 Copyright (C) 2012-2013 Simon Spannagel.
23 Copyright (C) 2013-2014 Jérémy Rosen.
24 Copyright (C) 2013, 2018-2022 Pascal Obry.
25 Copyright (C) 2013 Pierre Le Magourou.
26 Copyright (C) 2013-2016 Roman Lebedev.
27 Copyright (C) 2016 Asma.
28 Copyright (C) 2016-2017 Peter Budai.
29 Copyright (C) 2018 Andreas Schneider.
30 Copyright (C) 2018-2019 Edgardo Hoszowski.
31 Copyright (C) 2018 rawfiner.
32 Copyright (C) 2018 Rikard Öxler.
33 Copyright (C) 2019, 2022-2023, 2025-2026 Aurélien PIERRE.
34 Copyright (C) 2020 Chris Elston.
35 Copyright (C) 2020-2022 Diederik Ter Rahe.
36 Copyright (C) 2020 Hanno Schwalm.
37 Copyright (C) 2020 Hubert Kowalski.
38 Copyright (C) 2022 Martin Bařinka.
39 Copyright (C) 2024-2026 Guillaume Stutin.
40
41 darktable is free software: you can redistribute it and/or modify
42 it under the terms of the GNU General Public License as published by
43 the Free Software Foundation, either version 3 of the License, or
44 (at your option) any later version.
45
46 darktable is distributed in the hope that it will be useful,
47 but WITHOUT ANY WARRANTY; without even the implied warranty of
48 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49 GNU General Public License for more details.
50
51 You should have received a copy of the GNU General Public License
52 along with darktable. If not, see <http://www.gnu.org/licenses/>.
53*/
54#ifdef HAVE_CONFIG_H
55#include "config.h"
56#endif
57#include "widgets/bauhaus.h"
58#include "darktable.h"
59#include "common/logging.h"
60#include "common/selection.h"
61#include "control/control.h"
62#include "develop/develop.h"
63
64#include "gui/application.h"
65#include "views/view.h"
66
67#include <assert.h>
68#include <gdk/gdkkeysyms.h>
69#include <glib/gstdio.h>
70#include <stdlib.h>
71#include <string.h>
72#include "gui/screen_metrics.h"
73#include "control/signal.h"
74
76
78{
79 if(IS_NULL_PTR(input)) return;
80 _pointer_input = *input;
81}
82
84{
85 if(IS_NULL_PTR(input)) return;
86 *input = _pointer_input;
87}
88
90{
91 // same thread as init
92 s->gui_thread = pthread_self();
93 s->cursor.shape = GDK_LEFT_PTR;
94 s->cursor.current_shape = GDK_LEFT_PTR;
95 s->cursor.shape_str = NULL;
96 s->cursor.current_shape_str = NULL;
97 s->cursor.hide = FALSE;
98 // s->last_expose_time = dt_get_wtime();
99 s->log_pos = s->log_ack = 0;
100 s->log_busy = 0;
102 dt_pthread_mutex_init(&(s->log_mutex), NULL);
103
104 s->toast_pos = s->toast_ack = 0;
105 s->toast_busy = 0;
108
109 pthread_cond_init(&s->cond, NULL);
116
117 // start threads
119
120 s->mouse_over_id = -1;
121 s->keyboard_over_id = -1;
122 s->cursor.lock = FALSE;
123}
124
138static const char *_get_cursor_name(dt_cursor_t cursor)
139{
140 static const struct
141 {
142 dt_cursor_t shape;
143 const char *name;
144 } cursor_names[] = {
145 { GDK_X_CURSOR, "GDK_X_CURSOR" },
146 { GDK_ARROW, "GDK_ARROW" },
147 { GDK_BASED_ARROW_DOWN, "GDK_BASED_ARROW_DOWN" },
148 { GDK_BASED_ARROW_UP, "GDK_BASED_ARROW_UP" },
149 { GDK_BOAT, "GDK_BOAT" },
150 { GDK_BOGOSITY, "GDK_BOGOSITY" },
151 { GDK_BOTTOM_LEFT_CORNER, "GDK_BOTTOM_LEFT_CORNER" },
152 { GDK_BOTTOM_RIGHT_CORNER, "GDK_BOTTOM_RIGHT_CORNER" },
153 { GDK_BOTTOM_SIDE, "GDK_BOTTOM_SIDE" },
154 { GDK_BOTTOM_TEE, "GDK_BOTTOM_TEE" },
155 { GDK_BOX_SPIRAL, "GDK_BOX_SPIRAL" },
156 { GDK_CENTER_PTR, "GDK_CENTER_PTR" },
157 { GDK_CIRCLE, "GDK_CIRCLE" },
158 { GDK_CLOCK, "GDK_CLOCK" },
159 { GDK_COFFEE_MUG, "GDK_COFFEE_MUG" },
160 { GDK_CROSS, "GDK_CROSS" },
161 { GDK_CROSS_REVERSE, "GDK_CROSS_REVERSE" },
162 { GDK_CROSSHAIR, "GDK_CROSSHAIR" },
163 { GDK_DIAMOND_CROSS, "GDK_DIAMOND_CROSS" },
164 { GDK_DOT, "GDK_DOT" },
165 { GDK_DOTBOX, "GDK_DOTBOX" },
166 { GDK_DOUBLE_ARROW, "GDK_DOUBLE_ARROW" },
167 { GDK_DRAFT_LARGE, "GDK_DRAFT_LARGE" },
168 { GDK_DRAFT_SMALL, "GDK_DRAFT_SMALL" },
169 { GDK_DRAPED_BOX, "GDK_DRAPED_BOX" },
170 { GDK_EXCHANGE, "GDK_EXCHANGE" },
171 { GDK_FLEUR, "GDK_FLEUR" },
172 { GDK_GOBBLER, "GDK_GOBBLER" },
173 { GDK_GUMBY, "GDK_GUMBY" },
174 { GDK_HAND1, "GDK_HAND1" },
175 { GDK_HAND2, "GDK_HAND2" },
176 { GDK_HEART, "GDK_HEART" },
177 { GDK_ICON, "GDK_ICON" },
178 { GDK_IRON_CROSS, "GDK_IRON_CROSS" },
179 { GDK_LEFT_PTR, "GDK_LEFT_PTR" },
180 { GDK_LEFT_SIDE, "GDK_LEFT_SIDE" },
181 { GDK_LEFT_TEE, "GDK_LEFT_TEE" },
182 { GDK_LEFTBUTTON, "GDK_LEFTBUTTON" },
183 { GDK_LL_ANGLE, "GDK_LL_ANGLE" },
184 { GDK_LR_ANGLE, "GDK_LR_ANGLE" },
185 { GDK_MAN, "GDK_MAN" },
186 { GDK_MIDDLEBUTTON, "GDK_MIDDLEBUTTON" },
187 { GDK_MOUSE, "GDK_MOUSE" },
188 { GDK_PENCIL, "GDK_PENCIL" },
189 { GDK_PIRATE, "GDK_PIRATE" },
190 { GDK_PLUS, "GDK_PLUS" },
191 { GDK_QUESTION_ARROW, "GDK_QUESTION_ARROW" },
192 { GDK_RIGHT_PTR, "GDK_RIGHT_PTR" },
193 { GDK_RIGHT_SIDE, "GDK_RIGHT_SIDE" },
194 { GDK_RIGHT_TEE, "GDK_RIGHT_TEE" },
195 { GDK_RIGHTBUTTON, "GDK_RIGHTBUTTON" },
196 { GDK_RTL_LOGO, "GDK_RTL_LOGO" },
197 { GDK_SAILBOAT, "GDK_SAILBOAT" },
198 { GDK_SB_DOWN_ARROW, "GDK_SB_DOWN_ARROW" },
199 { GDK_SB_H_DOUBLE_ARROW, "GDK_SB_H_DOUBLE_ARROW" },
200 { GDK_SB_LEFT_ARROW, "GDK_SB_LEFT_ARROW" },
201 { GDK_SB_RIGHT_ARROW, "GDK_SB_RIGHT_ARROW" },
202 { GDK_SB_UP_ARROW, "GDK_SB_UP_ARROW" },
203 { GDK_SB_V_DOUBLE_ARROW, "GDK_SB_V_DOUBLE_ARROW" },
204 { GDK_SHUTTLE, "GDK_SHUTTLE" },
205 { GDK_SIZING, "GDK_SIZING" },
206 { GDK_SPIDER, "GDK_SPIDER" },
207 { GDK_SPRAYCAN, "GDK_SPRAYCAN" },
208 { GDK_STAR, "GDK_STAR" },
209 { GDK_TARGET, "GDK_TARGET" },
210 { GDK_TCROSS, "GDK_TCROSS" },
211 { GDK_TOP_LEFT_ARROW, "GDK_TOP_LEFT_ARROW" },
212 { GDK_TOP_LEFT_CORNER, "GDK_TOP_LEFT_CORNER" },
213 { GDK_TOP_RIGHT_CORNER, "GDK_TOP_RIGHT_CORNER" },
214 { GDK_TOP_SIDE, "GDK_TOP_SIDE" },
215 { GDK_TOP_TEE, "GDK_TOP_TEE" },
216 { GDK_TREK, "GDK_TREK" },
217 { GDK_UL_ANGLE, "GDK_UL_ANGLE" },
218 { GDK_UMBRELLA, "GDK_UMBRELLA" },
219 { GDK_UR_ANGLE, "GDK_UR_ANGLE" },
220 { GDK_WATCH, "GDK_WATCH" },
221 { GDK_XTERM, "GDK_XTERM" },
222 { GDK_LAST_CURSOR, "GDK_LAST_CURSOR" },
223 { GDK_BLANK_CURSOR, "GDK_BLANK_CURSOR" },
224 { GDK_CURSOR_IS_PIXMAP, "GDK_CURSOR_IS_PIXMAP" },
225 };
226
227 for(size_t i = 0; i < sizeof(cursor_names) / sizeof(cursor_names[0]); i++)
228 if(cursor_names[i].shape == cursor) return cursor_names[i].name;
229
230 return "GDK_CURSOR_UNKNOWN";
231}
232
237
242
243static void _control_set_cursor_on_widget(GtkWidget *widget, GdkCursor *cursor)
244{
245 if(IS_NULL_PTR(widget)) return;
246
247 GdkWindow *window = gtk_widget_get_window(widget);
248 if(!IS_NULL_PTR(window)) gdk_window_set_cursor(window, cursor);
249}
250
251static void _control_apply_cursor(GdkCursor *cursor)
252{
253 GtkWidget *main_window = dt_gui_main_window();
254 GtkWidget *center_base = dt_ui_center_base(dt_gui_get_ui());
256
257 _control_set_cursor_on_widget(main_window, cursor);
258
259 if(gtk_widget_get_window(center_base) != gtk_widget_get_window(main_window))
260 _control_set_cursor_on_widget(center_base, cursor);
261
262 if(gtk_widget_get_window(center) != gtk_widget_get_window(center_base)
263 && gtk_widget_get_window(center) != gtk_widget_get_window(main_window))
264 _control_set_cursor_on_widget(center, cursor);
265}
266
267static void _control_store_current_cursor(const dt_cursor_t shape, const char *shape_str)
268{
269 gchar *current_shape_str = g_strdup(shape_str);
270
273 darktable.control->cursor.current_shape_str = current_shape_str;
274}
275
283void dt_control_change_cursor_by_name(const char *curs_str)
284{
285 if(IS_NULL_PTR(curs_str)) return;
286
288 {
289 const gboolean hide = darktable.control->cursor.hide;
290 const gboolean current_is_named_cursor =
292 && g_strcmp0(darktable.control->cursor.current_shape_str, curs_str) == 0;
293
294 // "progress" is a special cursor that should not overwrite the queued cursor.
295 if(g_strcmp0(curs_str, "progress") != 0)
297
298 if(hide)
299 {
301 && darktable.control->cursor.current_shape == GDK_BLANK_CURSOR)
302 return;
303 }
304 else if(current_is_named_cursor)
305 return;
306
307 // We choose the GTK constructor here because named cursors are pixmaps in
308 // GTK's cache, while the hidden cursor is one of the standard shapes.
309 const dt_cursor_t chosen_shape = hide ? GDK_BLANK_CURSOR : GDK_CURSOR_IS_PIXMAP;
310 GdkCursor *cursor_shape = hide
311 ? gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR)
312 : gdk_cursor_new_from_name(gdk_display_get_default(), curs_str);
313
314 if(IS_NULL_PTR(cursor_shape)) return;
315 _control_apply_cursor(cursor_shape);
316 _control_store_current_cursor(chosen_shape, hide ? NULL : curs_str);
317
319 dt_print(DT_DEBUG_CONTROL, "Changing cursor to `%s`\n", hide ? "GDK_BLANK_CURSOR" : curs_str);
320
321 g_object_unref(cursor_shape);
322 }
323}
324
336{
337 if(IS_NULL_PTR(curs_str)) return;
339 GdkDisplay *display = gdk_display_get_default();
340 if(!IS_NULL_PTR(display)) gdk_display_flush(display);
341}
342
344{
345 //fprintf(stderr, "Committing cursor \n");
346 if(darktable.control->log_busy > 0) return;
347
350 else
352}
353
354void dt_control_change_cursor_EXT(dt_cursor_t cursor, const char *file, int line)
355{
356 const gboolean hide = darktable.control->cursor.hide;
357
358 // GDK_CURSOR_IS_PIXMAP is returned by GTK for named cursors and custom pixmaps.
359 // It is not a cursor shape that can be constructed with gdk_cursor_new_for_display().
360 const dt_cursor_t requested_shape = cursor == GDK_CURSOR_IS_PIXMAP ? GDK_LEFT_PTR : cursor;
361 const dt_cursor_t chosen_shape = hide ? GDK_BLANK_CURSOR : requested_shape;
362
363 // Keep the requested cursor queued even if the visible cursor stays blank.
364 dt_control_queue_cursor_EXT(requested_shape, file, line);
365
367 && darktable.control->cursor.current_shape == chosen_shape)
368 return;
369
371 {
372 GdkCursor *cursor_shape = gdk_cursor_new_for_display(gdk_display_get_default(), chosen_shape);
373 if(IS_NULL_PTR(cursor_shape)) return;
374 _control_apply_cursor(cursor_shape);
375 g_object_unref(cursor_shape);
376 _control_store_current_cursor(chosen_shape, NULL);
377
380 "Changing cursor to `%s`, requested from %s:%d\n",
381 hide ? "GDK_BLANK_CURSOR" : _get_cursor_name(requested_shape), file, line);
382 }
383}
384
385void dt_control_queue_cursor_EXT(dt_cursor_t cursor, const char *file, int line)
386{
387 const dt_cursor_t requested_shape = cursor == GDK_CURSOR_IS_PIXMAP ? GDK_LEFT_PTR : cursor;
388
389 if(darktable.control->cursor.shape == requested_shape
391 return;
392
394 dt_print(DT_DEBUG_CONTROL, "Queue cursor to `%s`, requested from %s:%d\n", _get_cursor_name(requested_shape), file, line);
395
398 darktable.control->cursor.shape = requested_shape;
399}
400
407void dt_control_queue_cursor_by_name(const char *curs_str)
408{
409 if(IS_NULL_PTR(curs_str)) return;
410 // "progress" is a special cursor that should not overwrite the queued cursor.
411 // Use dt_change_cursor_by_name() to set it directly without queuing.
412 if(g_strcmp0(curs_str, "progress") == 0)
413 return;
414
415 if(g_strcmp0(darktable.control->cursor.shape_str, curs_str) == 0) return;
416
417 GdkCursor *cursor = gdk_cursor_new_from_name(gdk_display_get_default(), curs_str);
418 if(IS_NULL_PTR(cursor)) return;
419 g_object_unref(cursor);
420
422 darktable.control->cursor.shape_str = g_strdup(curs_str);
423}
424
425void dt_control_set_cursor_visible_EXT(gboolean visible, const char *file, int line)
426{
428 dt_print(DT_DEBUG_CONTROL, "%s cursor, requested from %s:%d\n", visible ? "Show" : "Hide", file, line);
429 darktable.control->cursor.hide = !visible;
430}
431
436
441
443{
444 // FIXME: when shutdown, run_mutex is not inited anymore!
447 int running = s->running;
449 return running;
450}
451
453{
455 // thread safe quit, 1st pass:
461
462 if(gtk_main_level() > 0) gtk_main_quit();
463}
464
466{
469 s->running = 0;
472 pthread_cond_broadcast(&s->cond);
473
474 /* then wait for kick_on_workers_thread */
475 pthread_join(s->kick_on_workers_thread, NULL);
476
477 int k;
478 for(k = 0; k < s->num_threads; k++)
479 // pthread_kill(s->thread[k], 9);
480 pthread_join(s->thread[k], NULL);
481 for(k = 0; k < DT_CTL_WORKER_RESERVED; k++)
482 // pthread_kill(s->thread_res[k], 9);
483 pthread_join(s->thread_res[k], NULL);
484
485 // Workers are joined: nothing else touches the queues now. Dispose every still-queued job here,
486 // while the plug-in .so files that own their callbacks (state_changed_cb, params_destroy) are
487 // still loaded. dt_control_cleanup() -> dt_control_jobs_cleanup() runs much later, after
488 // dt_lib_cleanup() has dlclose()'d those modules, so cancelling a leftover job there jumps into
489 // an unmapped callback and crashes (e.g. a pending textnotes-load job -> SIGSEGV).
491}
492
494{
495 // A vacuum used to be contemplated here. It belongs to whoever owns the connection, and
496 // src/database now does it -- VACUUM + ANALYZE on both schemas, on the maintenance policy
497 // the user configured. Nothing for the control layer to decide.
499 g_free(s->cursor.shape_str);
500 g_free(s->cursor.current_shape_str);
508}
509
510
511// ================================================================================
512// gui functions:
513// ================================================================================
514
515gboolean dt_control_configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
516{
517 // re-configure all components:
518 dt_view_manager_configure(darktable.view_manager, event->width, event->height);
519 return TRUE;
520}
521
522static GdkRGBA lookup_color(GtkStyleContext *context, const char *name)
523{
524 GdkRGBA color, fallback = {1.0, 0.0, 0.0, 1.0};
525 if(!gtk_style_context_lookup_color (context, name, &color))
526 color = fallback;
527 return color;
528}
529
530void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
531{
532 PangoRectangle ink;
533 PangoLayout *layout;
534 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
535 const float fontsize = DT_PIXEL_APPLY_DPI(14);
536 pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE);
537 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
538 layout = pango_cairo_create_layout(cr);
539 pango_layout_set_font_description(layout, desc);
540 // A COPY: a worker thread frees this string on the next dt_set_main_message(), which the
541 // pipeline issues once per module per frame while the darkroom renders.
542 char *const message = dt_get_main_message_copy();
543 pango_layout_set_text(layout, !IS_NULL_PTR(message) ? message : _("Working..."), -1);
544 dt_free(message);
545 pango_layout_get_pixel_extents(layout, &ink, NULL);
546 if(ink.width > width * 0.98)
547 {
548 pango_layout_set_text(layout, "...", -1);
549 pango_layout_get_pixel_extents(layout, &ink, NULL);
550 }
551 const float xc = width / 2.0, yc = height * 0.85 - DT_PIXEL_APPLY_DPI(30), wd = ink.width * .5f;
552 cairo_move_to(cr, xc - wd, yc + 1. / 3. * fontsize - fontsize);
553 pango_cairo_layout_path(cr, layout);
554 cairo_set_line_width(cr, 2.0);
556 cairo_stroke_preserve(cr);
558 cairo_fill(cr);
559 pango_font_description_free(desc);
560 g_object_unref(layout);
561}
562
563gboolean dt_control_button_down(int which)
564{
565 // The mask is device state, so any of our windows answers the same thing; the centre widget
566 // is the one every caller is dragging over.
568 GdkWindow *window = IS_NULL_PTR(center) ? NULL : gtk_widget_get_window(center);
569 GdkDisplay *display = IS_NULL_PTR(window) ? NULL : gdk_window_get_display(window);
570 GdkSeat *seat = IS_NULL_PTR(display) ? NULL : gdk_display_get_default_seat(display);
571 GdkDevice *pointer = IS_NULL_PTR(seat) ? NULL : gdk_seat_get_pointer(seat);
572 if(IS_NULL_PTR(window) || IS_NULL_PTR(pointer)) return FALSE;
573
574 GdkModifierType mask = 0;
575 gdk_window_get_device_position(window, pointer, NULL, NULL, &mask);
576
577 switch(which)
578 {
579 case 1: return (mask & GDK_BUTTON1_MASK) != 0;
580 case 2: return (mask & GDK_BUTTON2_MASK) != 0;
581 case 3: return (mask & GDK_BUTTON3_MASK) != 0;
582 default: return FALSE;
583 }
584}
585
586/* The centre used to be painted three times per frame: into a full-window ARGB surface
587 * allocated and freed on every expose, from there into a persistent pixmap, and from the pixmap
588 * into GTK's own double buffer -- with the pixmap's whole area filled first. Measured at a
589 * 2560x1440 window on a 2x screen (a 59 MB buffer): 29.5 ms to allocate and first-touch the
590 * throwaway surface, 5.9 ms per full fill, 5.8 ms per full blit, around 59 ms a frame before a
591 * single overlay was drawn, against the 16.7 ms a 60 fps frame allows. GTK's cr is already a
592 * double buffer, and it arrives clipped to the region that was invalidated, so painting into it
593 * directly costs one pass over that region and nothing over the rest of the window. */
594void dt_control_expose(cairo_t *cr, const int width, const int height)
595{
596 if(IS_NULL_PTR(cr) || width <= 0 || height <= 0) return;
597 int pointerx, pointery;
599 gdk_window_get_device_position(gtk_widget_get_window(widget),
600 gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget))),
601 &pointerx, &pointery, NULL);
602
605
606 GtkStyleContext *context = gtk_widget_get_style_context(widget);
607
608 // look up some colors once
609 GdkRGBA bg_color = lookup_color(context, "bg_color");
610
611 cairo_save(cr);
612 cairo_rectangle(cr, 0, 0, width, height);
613 cairo_clip(cr);
614 cairo_new_path(cr);
615 /* the background, only for a view that does not paint every pixel itself: the darkroom
616 * composes its own, and a fill under it was a full pass over the window for nothing */
618 {
619 gdk_cairo_set_source_rgba(cr, &bg_color);
620 cairo_paint(cr);
621 }
622 // draw view
623 dt_view_manager_expose(darktable.view_manager, cr, width, height, pointerx, pointery);
624 cairo_restore(cr);
625
626 // draw busy indicator
628
629 if(darktable.control->log_busy > 0)
630 {
632 // force set the cursor to arrow with busy indicator
634 }
635 else // Apply cursor change
637
639
640 // Draw progress bar (guard during early startup)
642 {
643 const float progress_h = DT_PIXEL_APPLY_DPI(5);
644 cairo_rectangle(cr, 0, height - progress_h,
646 progress_h);
647 cairo_set_source_rgba(cr, 0., 0., 0., 0.33);
648 cairo_fill(cr);
649 }
650}
651
656
661
662void dt_control_mouse_moved(double x, double y, double pressure, int which)
663{
665}
666
667void dt_control_key_pressed(GdkEventKey *event)
668{
670}
671
672void dt_control_button_released(double x, double y, int which, uint32_t state)
673{
674
676}
677
679{
682 gtk_widget_set_tooltip_text(widget, "");
683}
684
685static gboolean _dt_ctl_switch_mode_to(gpointer user_data)
686{
687 const char *mode = (const char*)user_data;
690 return FALSE;
691}
692
693static gboolean _dt_ctl_switch_mode_to_by_view(gpointer user_data)
694{
695 const dt_view_t *view = (const dt_view_t*)user_data;
698 return FALSE;
699}
700
701void dt_ctl_switch_mode_to(const char *mode)
702{
704 if(current_view && !strcmp(mode, current_view->module_name))
705 {
706 // if we are not in lighttable, we switch back to that view
707 if(strcmp(current_view->module_name, "lighttable")) dt_ctl_switch_mode_to("lighttable");
708 return;
709 }
710
711 g_main_context_invoke(NULL, _dt_ctl_switch_mode_to, (gpointer)mode);
712}
713
715{
717 g_main_context_invoke(NULL, _dt_ctl_switch_mode_to_by_view, (gpointer)view);
718}
719
720static gboolean _dt_ctl_open_image_in_darkroom(gpointer user_data)
721{
722 const int32_t imgid = GPOINTER_TO_INT(user_data);
724
725 // Round-trip through the lighttable, the same way the darkroom changes image from the
726 // filmstrip (_dev_change_image() in views/darkroom.c): everything is torn down and re-inited
727 // through the regular enter()/leave() path instead of a partial in-place reload. Skipped when
728 // the lighttable is already the current view -- there is no darkroom state to tear down then,
729 // and re-entering it would needlessly rebuild the grid and the whole panel layout.
731 if(IS_NULL_PTR(current_view) || g_strcmp0(current_view->module_name, "lighttable"))
733
734 // Publish the target AFTER leaving the darkroom, and from the GUI thread: the darkroom's
735 // leave() restores the selection to the image it was editing, and any pointer motion over the
736 // grid rewrites the mouse-over id. Both would silently overwrite a target published earlier
737 // from a worker thread, and the darkroom would re-open the previous image instead.
740
742 return FALSE;
743}
744
745void dt_ctl_open_image_in_darkroom(const int32_t imgid)
746{
747 g_main_context_invoke(NULL, _dt_ctl_open_image_in_darkroom, GINT_TO_POINTER(imgid));
748}
749
760
771
772void dt_control_button_pressed(double x, double y, double pressure, int which, int type, uint32_t state)
773{
776 // adding pressure to this data structure is not needed right now. should the need ever arise: here is the
777 // place to do it :)
778 //const float wd = darktable.control->width;
779 const float ht = darktable.control->height;
780
781 // ack log message:
783 const float /*xc = wd/4.0-20,*/ yc = ht * 0.85 + 10;
785 if(which == 1 /*&& x > xc - 10 && x < xc + 10*/ && y > yc - 10 && y < yc + 10)
786 {
788 {
789 g_source_remove(darktable.control->log_message_timeout_id);
791 }
794 return;
795 }
797
798 // ack toast message:
801 if(which == 1 /*&& x > xc - 10 && x < xc + 10*/ && y > yc - 10 && y < yc + 10)
802 {
804 {
807 }
810 return;
811 }
813
815}
816
817static gboolean _redraw_center(gpointer user_data)
818{
821 return FALSE; // don't call this again
822}
823
824void dt_control_log(const char *msg, ...)
825{
827 va_list ap;
828 va_start(ap, msg);
829 char *escaped_msg = g_markup_vprintf_escaped(msg, ap);
830 const int msglen = strlen(escaped_msg);
832 dt_free(escaped_msg);
833 va_end(ap);
835 g_source_remove(darktable.control->log_message_timeout_id);
838
840 = g_timeout_add(DT_CTL_LOG_TIMEOUT + 1000 * (msglen / 40),
843 // redraw center later in gui thread:
844 g_idle_add(_redraw_center, 0);
845}
846
847static void _toast_log(const gboolean markup, const char *msg, va_list ap)
848{
850
851 // if we don't want markup, we escape <>&... so they are not interpreted later
852 if(markup)
854 else
855 {
856 char *escaped_msg = g_markup_vprintf_escaped(msg, ap);
858 dt_free(escaped_msg);
859 }
860
867 // redraw center later in gui thread:
868 g_idle_add(_redraw_center, 0);
869}
870
871void dt_toast_log(const char *msg, ...)
872{
873 va_list ap;
874 va_start(ap, msg);
875 _toast_log(FALSE, msg, ap);
876 va_end(ap);
877}
878
879void dt_toast_markup_log(const char *msg, ...)
880{
881 va_list ap;
882 va_start(ap, msg);
883 _toast_log(TRUE, msg, ap);
884 va_end(ap);
885}
886
894
902
910
918
923
928
933
938
943
948
949static int _widget_queue_draw(void *widget_data)
950{
951 dt_control_redraw_widget_t *const redraw_widget = (dt_control_redraw_widget_t *)widget_data;
952 GtkWidget *const widget = GTK_WIDGET(g_weak_ref_get(&redraw_widget->widget_ref));
953 if(!IS_NULL_PTR(widget))
954 {
955 gtk_widget_queue_draw(widget);
956 g_object_unref(widget);
957 }
958
959 return FALSE;
960}
961
962static void _widget_queue_draw_cleanup(void *widget_data)
963{
964 dt_control_redraw_widget_t *const redraw_widget = (dt_control_redraw_widget_t *)widget_data;
965 g_weak_ref_clear(&redraw_widget->widget_ref);
966 dt_free(redraw_widget);
967}
968
970{
972 {
973 dt_control_redraw_widget_t *const redraw_widget = calloc(1, sizeof(dt_control_redraw_widget_t));
974 if(IS_NULL_PTR(redraw_widget)) return;
975
976 g_weak_ref_init(&redraw_widget->widget_ref, G_OBJECT(widget));
977 g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, _widget_queue_draw, redraw_widget, _widget_queue_draw_cleanup);
978 }
979}
980
981void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
982{
984}
985
993
995{
998 {
1000
1001 // If we reset mouse_over_id to -1, aka "none" signal,
1002 // reset also the keyboard_over_id, in a "loose focus" way,
1003 // to keep only the selection for common/act_on.h
1007 }
1008 else
1010}
1011
1019
1026
1027// clang-format off
1028// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1029// vim: shiftwidth=2 expandtab tabstop=2 cindent
1030// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1031// clang-format on
GtkWidget * dt_gui_main_window(void)
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
void dt_gui_gtk_quit()
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:701
static int _widget_queue_draw(void *widget_data)
Definition control.c:949
void dt_control_set_cursor_visible_EXT(gboolean visible, const char *file, int line)
Set whether the cursor should be visible or not.
Definition control.c:425
void dt_control_key_pressed(GdkEventKey *event)
Definition control.c:667
static void _dt_ctl_switch_mode_prepare()
Definition control.c:678
void dt_control_init(dt_control_t *s)
Definition control.c:89
static void _control_set_cursor_on_widget(GtkWidget *widget, GdkCursor *cursor)
Definition control.c:243
int32_t dt_control_get_mouse_over_id()
Definition control.c:986
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:530
void dt_control_mouse_is_dragging(gboolean state)
Definition control.c:432
void dt_control_button_pressed(double x, double y, double pressure, int which, int type, uint32_t state)
Definition control.c:772
void dt_toast_log(const char *msg,...)
Definition control.c:871
void dt_ctl_switch_mode_to_by_view(const dt_view_t *view)
Definition control.c:714
void dt_control_queue_cursor_EXT(dt_cursor_t cursor, const char *file, int line)
Definition control.c:385
void dt_control_get_pointer_input(dt_control_pointer_input_t *input)
Definition control.c:83
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:994
static gboolean _redraw_center(gpointer user_data)
Definition control.c:817
void dt_control_forbid_change_cursor()
Definition control.c:233
void dt_toast_markup_log(const char *msg,...)
Definition control.c:879
void dt_control_log(const char *msg,...)
Definition control.c:824
static GdkRGBA lookup_color(GtkStyleContext *context, const char *name)
Definition control.c:522
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
static gboolean _dt_ctl_switch_mode_to_by_view(gpointer user_data)
Definition control.c:693
void dt_control_quit()
Definition control.c:452
void dt_control_mouse_leave()
Definition control.c:652
void dt_control_toast_busy_enter()
Definition control.c:895
void dt_control_shutdown(dt_control_t *s)
Definition control.c:465
void dt_control_log_busy_leave()
Definition control.c:903
void dt_control_mouse_is_painting(gboolean state)
Definition control.c:437
gboolean dt_control_configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
Definition control.c:515
void dt_control_change_cursor_by_name_and_flush(const char *curs_str)
Apply a named cursor immediatelly and flush display updates for immediate feedback.
Definition control.c:335
void dt_control_cleanup(dt_control_t *s)
Definition control.c:493
static const char * _get_cursor_name(dt_cursor_t cursor)
Human-readable name of a GDK cursor shape, for debug output.
Definition control.c:138
void dt_control_button_released(double x, double y, int which, uint32_t state)
Definition control.c:672
void dt_control_commit_cursor()
Definition control.c:343
void dt_control_set_pointer_input(const dt_control_pointer_input_t *input)
Definition control.c:77
static void _control_store_current_cursor(const dt_cursor_t shape, const char *shape_str)
Definition control.c:267
void dt_control_change_cursor_by_name(const char *curs_str)
Apply a GTK named cursor without changing the queued cursor.
Definition control.c:283
void dt_control_allow_change_cursor()
Definition control.c:238
int dt_control_running()
Definition control.c:442
static void _control_apply_cursor(GdkCursor *cursor)
Definition control.c:251
void dt_ctl_open_image_in_darkroom(const int32_t imgid)
open one specific image in the darkroom, from any view and any thread.
Definition control.c:745
void dt_control_log_busy_enter()
Definition control.c:887
static dt_control_pointer_input_t _pointer_input
Definition control.c:75
void dt_control_mouse_moved(double x, double y, double pressure, int which)
Definition control.c:662
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:969
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
int32_t dt_control_get_keyboard_over_id()
Definition control.c:1012
void dt_control_toast_redraw()
Request a redraw of the toast message label.
Definition control.c:939
void dt_control_mouse_enter()
Definition control.c:657
void dt_control_change_cursor_EXT(dt_cursor_t cursor, const char *file, int line)
Definition control.c:354
void dt_control_log_redraw()
Request a redraw of the log message label.
Definition control.c:934
static gboolean _dt_ctl_toast_message_timeout_callback(gpointer data)
Definition control.c:761
void dt_control_navigation_redraw()
Request a redraw of the navigation module's widget.
Definition control.c:929
void dt_control_expose(cairo_t *cr, const int width, const int height)
Definition control.c:594
static void _widget_queue_draw_cleanup(void *widget_data)
Definition control.c:962
void dt_control_toast_busy_leave()
Definition control.c:911
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:407
static void _toast_log(const gboolean markup, const char *msg, va_list ap)
Definition control.c:847
static gboolean _dt_ctl_open_image_in_darkroom(gpointer user_data)
Definition control.c:720
static gboolean _dt_ctl_log_message_timeout_callback(gpointer data)
Definition control.c:750
gboolean dt_control_button_down(int which)
Is mouse button which currently held down?
Definition control.c:563
static gboolean _dt_ctl_switch_mode_to(gpointer user_data)
Definition control.c:685
void dt_control_queue_redraw()
Request a redraw of the whole workspace.
Definition control.c:919
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:1020
#define DT_CTL_LOG_TIMEOUT
Definition control.h:195
#define DT_CTL_TOAST_SIZE
Definition control.h:196
#define DT_CTL_TOAST_MSG_SIZE
Definition control.h:197
char * dt_get_main_message_copy(void)
Definition darktable.c:583
#define dt_control_change_cursor(cursor)
Definition control.h:121
#define DT_CTL_LOG_SIZE
Definition control.h:193
#define DT_CTL_LOG_MSG_SIZE
Definition control.h:194
#define DT_CTL_TOAST_TIMEOUT
Definition control.h:198
GdkCursorType dt_cursor_t
Definition control.h:71
darktable_t darktable
Definition darktable.c:213
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
GtkWidget * window
GtkWidget * view
the tree view
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_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
GdkRGBA color[]
Definition geotagging.c:541
void dt_control_jobs_drain(dt_control_t *control)
Definition jobs.c:675
void dt_control_jobs_cleanup(dt_control_t *control)
Definition jobs.c:698
void dt_control_jobs_init(dt_control_t *control)
Definition jobs.c:642
#define DT_CTL_WORKER_RESERVED
Definition jobs.h:38
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_VERBOSE
Definition logging.h:78
@ DT_DEBUG_CONTROL
Definition logging.h:52
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.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
const char * name
Definition pdf.h:90
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:297
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_CONTROL_REDRAW_ALL
This signal is raised when dt_control_queue_redraw() is called. no param, no returned value.
Definition signal.h:72
@ DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
Definition signal.h:62
@ DT_SIGNAL_CONTROL_TOAST_REDRAW
This signal is raised when dt_control_toast_redraw() is called. no param, no returned value.
Definition signal.h:291
@ DT_SIGNAL_CONTROL_REDRAW_CENTER
This signal is raised when dt_control_queue_redraw_center() is called. no param, no returned value.
Definition signal.h:77
@ DT_SIGNAL_CONTROL_LOG_REDRAW
This signal is raised when dt_control_log_redraw() is called. no param, no returned value.
Definition signal.h:286
@ DT_SIGNAL_CONTROL_NAVIGATION_REDRAW
This signal is raised when dt_control_navigation_redraw() is called. no param, no returned value.
Definition signal.h:281
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
struct dt_develop_t * develop
Definition darktable.h:185
struct dt_view_manager_t * view_manager
Definition darktable.h:187
struct dt_control_t * control
Definition darktable.h:188
pthread_t * thread
Definition control.h:251
dt_pthread_mutex_t mutex
Definition control.h:268
dt_pthread_mutex_t run_mutex
Definition control.h:248
int32_t height
Definition control.h:207
int32_t num_threads
Definition control.h:250
int32_t running
Definition control.h:246
dt_cursor_t shape
Definition control.h:219
double button_y
Definition control.h:209
int toast_ack
Definition control.h:235
char toast_message[10][300]
Definition control.h:236
gchar * shape_str
Definition control.h:221
gchar * current_shape_str
Definition control.h:222
int toast_busy
Definition control.h:238
pthread_t thread_res[DT_CTL_WORKER_RESERVED]
Definition control.h:260
dt_pthread_mutex_t log_mutex
Definition control.h:232
int32_t width
Definition control.h:207
pthread_cond_t cond
Definition control.h:249
guint log_message_timeout_id
Definition control.h:230
guint toast_message_timeout_id
Definition control.h:237
dt_pthread_mutex_t res_mutex
Definition control.h:257
dt_cursor_t current_shape
Definition control.h:220
int toast_pos
Definition control.h:235
pthread_t kick_on_workers_thread
Definition control.h:251
int32_t mouse_over_id
Definition control.h:211
dt_pthread_mutex_t queue_mutex
Definition control.h:248
struct dt_control_t::@7 cursor
dt_pthread_mutex_t toast_mutex
Definition control.h:239
int32_t keyboard_over_id
Definition control.h:212
char log_message[10][1000]
Definition control.h:229
double button_x
Definition control.h:209
dt_pthread_mutex_t cond_mutex
Definition control.h:248
dt_pthread_mutex_t global_mutex
Definition control.h:242
gboolean hide
Definition control.h:224
gboolean lock
Definition control.h:217
pthread_t gui_thread
Definition control.h:208
struct dt_control_t::@8 progress_system
struct dt_develop_t::@20 progress
int completed
Definition develop.h:479
struct dt_gui_gtk_t::@33 mouse
gboolean is_dragging
int32_t center_tooltip
gboolean is_painting
char module_name[64]
Definition view.h:155
void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
Definition view.c:525
void dt_view_manager_mouse_enter(dt_view_manager_t *vm)
Definition view.c:519
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
Definition view.c:633
int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
Definition view.c:574
void dt_view_manager_expose(dt_view_manager_t *vm, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition view.c:450
int dt_view_manager_key_pressed(dt_view_manager_t *vm, GdkEventKey *event)
Definition view.c:547
int dt_view_manager_button_pressed(dt_view_manager_t *vm, double x, double y, double pressure, int which, int type, uint32_t state)
Definition view.c:601
uint32_t dt_view_manager_current_flags(const dt_view_manager_t *vm)
Definition view.c:444
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:234
int dt_view_manager_switch_by_view(dt_view_manager_t *vm, const dt_view_t *nv)
Definition view.c:256
void dt_view_manager_mouse_leave(dt_view_manager_t *vm)
Definition view.c:497
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
@ VIEW_FLAGS_PAINTS_WHOLE_AREA
Definition view.h:90
void dt_widget_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
@ DT_GUI_COLOR_LOG_FG
@ DT_GUI_COLOR_LOG_BG
#define DT_PIXEL_APPLY_DPI(value)
void dt_hinter_set_message(dt_ui_t *ui, const char *message)
GtkWidget * dt_ui_center_base(dt_ui_t *ui)