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. The broadcast sends the idle workers and the kicker home now,
456 // so that what is still alive when dt_control_shutdown() comes is what is still working.
461 pthread_cond_broadcast(&darktable.control->cond);
463
464 if(gtk_main_level() > 0) gtk_main_quit();
465}
466
467// Set once at GUI startup, never cleared. NULL in every headless run.
469
474
476{
479 s->running = 0;
482 pthread_cond_broadcast(&s->cond);
483
484 // The joins below last as long as the jobs still running. Whoever owns a main loop spends
485 // that time in it: it can then say what is going on, and a job waiting for the GUI thread
486 // -- a synchronous signal, a dialog -- gets its answer instead of waiting for it forever.
488
489 /* then wait for kick_on_workers_thread */
490 pthread_join(s->kick_on_workers_thread, NULL);
491
492 int k;
493 for(k = 0; k < s->num_threads; k++)
494 // pthread_kill(s->thread[k], 9);
495 pthread_join(s->thread[k], NULL);
496 for(k = 0; k < DT_CTL_WORKER_RESERVED; k++)
497 // pthread_kill(s->thread_res[k], 9);
498 pthread_join(s->thread_res[k], NULL);
499
500 // Workers are joined: nothing else touches the queues now. Dispose every still-queued job here,
501 // while the plug-in .so files that own their callbacks (state_changed_cb, params_destroy) are
502 // still loaded. dt_control_cleanup() -> dt_control_jobs_cleanup() runs much later, after
503 // dt_lib_cleanup() has dlclose()'d those modules, so cancelling a leftover job there jumps into
504 // an unmapped callback and crashes (e.g. a pending textnotes-load job -> SIGSEGV).
506}
507
509{
510 // A vacuum used to be contemplated here. It belongs to whoever owns the connection, and
511 // src/database now does it -- VACUUM + ANALYZE on both schemas, on the maintenance policy
512 // the user configured. Nothing for the control layer to decide.
514 g_free(s->cursor.shape_str);
515 g_free(s->cursor.current_shape_str);
523}
524
525
526// ================================================================================
527// gui functions:
528// ================================================================================
529
530gboolean dt_control_configure(GtkWidget *da, GdkEventConfigure *event, gpointer user_data)
531{
532 // re-configure all components:
533 dt_view_manager_configure(darktable.view_manager, event->width, event->height);
534 return TRUE;
535}
536
537static GdkRGBA lookup_color(GtkStyleContext *context, const char *name)
538{
539 GdkRGBA color, fallback = {1.0, 0.0, 0.0, 1.0};
540 if(!gtk_style_context_lookup_color (context, name, &color))
541 color = fallback;
542 return color;
543}
544
545void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
546{
547 PangoRectangle ink;
548 PangoLayout *layout;
549 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
550 const float fontsize = DT_PIXEL_APPLY_DPI(14);
551 pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE);
552 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
553 layout = pango_cairo_create_layout(cr);
554 pango_layout_set_font_description(layout, desc);
555 // A COPY: a worker thread frees this string on the next dt_set_main_message(), which the
556 // pipeline issues once per module per frame while the darkroom renders.
557 char *const message = dt_get_main_message_copy();
558 pango_layout_set_text(layout, !IS_NULL_PTR(message) ? message : _("Working..."), -1);
559 dt_free(message);
560 pango_layout_get_pixel_extents(layout, &ink, NULL);
561 if(ink.width > width * 0.98)
562 {
563 pango_layout_set_text(layout, "...", -1);
564 pango_layout_get_pixel_extents(layout, &ink, NULL);
565 }
566 const float xc = width / 2.0, yc = height * 0.85 - DT_PIXEL_APPLY_DPI(30), wd = ink.width * .5f;
567 cairo_move_to(cr, xc - wd, yc + 1. / 3. * fontsize - fontsize);
568 pango_cairo_layout_path(cr, layout);
569 cairo_set_line_width(cr, 2.0);
571 cairo_stroke_preserve(cr);
573 cairo_fill(cr);
574 pango_font_description_free(desc);
575 g_object_unref(layout);
576}
577
578gboolean dt_control_button_down(int which)
579{
580 // The mask is device state, so any of our windows answers the same thing; the centre widget
581 // is the one every caller is dragging over.
583 GdkWindow *window = IS_NULL_PTR(center) ? NULL : gtk_widget_get_window(center);
584 GdkDisplay *display = IS_NULL_PTR(window) ? NULL : gdk_window_get_display(window);
585 GdkSeat *seat = IS_NULL_PTR(display) ? NULL : gdk_display_get_default_seat(display);
586 GdkDevice *pointer = IS_NULL_PTR(seat) ? NULL : gdk_seat_get_pointer(seat);
587 if(IS_NULL_PTR(window) || IS_NULL_PTR(pointer)) return FALSE;
588
589 GdkModifierType mask = 0;
590 gdk_window_get_device_position(window, pointer, NULL, NULL, &mask);
591
592 switch(which)
593 {
594 case 1: return (mask & GDK_BUTTON1_MASK) != 0;
595 case 2: return (mask & GDK_BUTTON2_MASK) != 0;
596 case 3: return (mask & GDK_BUTTON3_MASK) != 0;
597 default: return FALSE;
598 }
599}
600
601/* The centre used to be painted three times per frame: into a full-window ARGB surface
602 * allocated and freed on every expose, from there into a persistent pixmap, and from the pixmap
603 * into GTK's own double buffer -- with the pixmap's whole area filled first. Measured at a
604 * 2560x1440 window on a 2x screen (a 59 MB buffer): 29.5 ms to allocate and first-touch the
605 * throwaway surface, 5.9 ms per full fill, 5.8 ms per full blit, around 59 ms a frame before a
606 * single overlay was drawn, against the 16.7 ms a 60 fps frame allows. GTK's cr is already a
607 * double buffer, and it arrives clipped to the region that was invalidated, so painting into it
608 * directly costs one pass over that region and nothing over the rest of the window. */
609void dt_control_expose(cairo_t *cr, const int width, const int height)
610{
611 if(IS_NULL_PTR(cr) || width <= 0 || height <= 0) return;
612 int pointerx, pointery;
614 gdk_window_get_device_position(gtk_widget_get_window(widget),
615 gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget))),
616 &pointerx, &pointery, NULL);
617
620
621 GtkStyleContext *context = gtk_widget_get_style_context(widget);
622
623 // look up some colors once
624 GdkRGBA bg_color = lookup_color(context, "bg_color");
625
626 cairo_save(cr);
627 cairo_rectangle(cr, 0, 0, width, height);
628 cairo_clip(cr);
629 cairo_new_path(cr);
630 /* the background, only for a view that does not paint every pixel itself: the darkroom
631 * composes its own, and a fill under it was a full pass over the window for nothing */
633 {
634 gdk_cairo_set_source_rgba(cr, &bg_color);
635 cairo_paint(cr);
636 }
637 // draw view
638 dt_view_manager_expose(darktable.view_manager, cr, width, height, pointerx, pointery);
639 cairo_restore(cr);
640
641 // draw busy indicator
643
644 if(darktable.control->log_busy > 0)
645 {
647 // force set the cursor to arrow with busy indicator
649 }
650 else // Apply cursor change
652
654
655 // Draw progress bar (guard during early startup)
657 {
658 const float progress_h = DT_PIXEL_APPLY_DPI(5);
659 cairo_rectangle(cr, 0, height - progress_h,
661 progress_h);
662 cairo_set_source_rgba(cr, 0., 0., 0., 0.33);
663 cairo_fill(cr);
664 }
665}
666
671
676
677void dt_control_mouse_moved(double x, double y, double pressure, int which)
678{
680}
681
682void dt_control_key_pressed(GdkEventKey *event)
683{
685}
686
687void dt_control_button_released(double x, double y, int which, uint32_t state)
688{
689
691}
692
694{
697 gtk_widget_set_tooltip_text(widget, "");
698}
699
700static gboolean _dt_ctl_switch_mode_to(gpointer user_data)
701{
702 const char *mode = (const char*)user_data;
705 return FALSE;
706}
707
708static gboolean _dt_ctl_switch_mode_to_by_view(gpointer user_data)
709{
710 const dt_view_t *view = (const dt_view_t*)user_data;
713 return FALSE;
714}
715
716void dt_ctl_switch_mode_to(const char *mode)
717{
719 if(current_view && !strcmp(mode, current_view->module_name))
720 {
721 // if we are not in lighttable, we switch back to that view
722 if(strcmp(current_view->module_name, "lighttable")) dt_ctl_switch_mode_to("lighttable");
723 return;
724 }
725
726 g_main_context_invoke(NULL, _dt_ctl_switch_mode_to, (gpointer)mode);
727}
728
730{
732 g_main_context_invoke(NULL, _dt_ctl_switch_mode_to_by_view, (gpointer)view);
733}
734
735static gboolean _dt_ctl_open_image_in_darkroom(gpointer user_data)
736{
737 const int32_t imgid = GPOINTER_TO_INT(user_data);
739
740 // Round-trip through the lighttable, the same way the darkroom changes image from the
741 // filmstrip (_dev_change_image() in views/darkroom.c): everything is torn down and re-inited
742 // through the regular enter()/leave() path instead of a partial in-place reload. Skipped when
743 // the lighttable is already the current view -- there is no darkroom state to tear down then,
744 // and re-entering it would needlessly rebuild the grid and the whole panel layout.
746 if(IS_NULL_PTR(current_view) || g_strcmp0(current_view->module_name, "lighttable"))
748
749 // Publish the target AFTER leaving the darkroom, and from the GUI thread: the darkroom's
750 // leave() restores the selection to the image it was editing, and any pointer motion over the
751 // grid rewrites the mouse-over id. Both would silently overwrite a target published earlier
752 // from a worker thread, and the darkroom would re-open the previous image instead.
755
757 return FALSE;
758}
759
760void dt_ctl_open_image_in_darkroom(const int32_t imgid)
761{
762 g_main_context_invoke(NULL, _dt_ctl_open_image_in_darkroom, GINT_TO_POINTER(imgid));
763}
764
775
786
787void dt_control_button_pressed(double x, double y, double pressure, int which, int type, uint32_t state)
788{
791 // adding pressure to this data structure is not needed right now. should the need ever arise: here is the
792 // place to do it :)
793 //const float wd = darktable.control->width;
794 const float ht = darktable.control->height;
795
796 // ack log message:
798 const float /*xc = wd/4.0-20,*/ yc = ht * 0.85 + 10;
800 if(which == 1 /*&& x > xc - 10 && x < xc + 10*/ && y > yc - 10 && y < yc + 10)
801 {
803 {
804 g_source_remove(darktable.control->log_message_timeout_id);
806 }
809 return;
810 }
812
813 // ack toast message:
816 if(which == 1 /*&& x > xc - 10 && x < xc + 10*/ && y > yc - 10 && y < yc + 10)
817 {
819 {
822 }
825 return;
826 }
828
830}
831
832static gboolean _redraw_center(gpointer user_data)
833{
836 return FALSE; // don't call this again
837}
838
839void dt_control_log(const char *msg, ...)
840{
842 va_list ap;
843 va_start(ap, msg);
844 char *escaped_msg = g_markup_vprintf_escaped(msg, ap);
845 const int msglen = strlen(escaped_msg);
847 dt_free(escaped_msg);
848 va_end(ap);
850 g_source_remove(darktable.control->log_message_timeout_id);
853
855 = g_timeout_add(DT_CTL_LOG_TIMEOUT + 1000 * (msglen / 40),
858 // redraw center later in gui thread:
859 g_idle_add(_redraw_center, 0);
860}
861
862// Installed by dt_gui_gtk_init() before any worker runs, and never changed after: read without
863// a lock, like the other handlers the orchestrator installs.
865
870
871// Without a GUI, stderr: a dt_control_log() there goes to a log no view ever draws.
872static void _alert(const char *title, const char *message, const char *item, const int once)
873{
874 if(IS_NULL_PTR(message)) return;
876 _alert_handler(title, message, item, once);
877 else if(!IS_NULL_PTR(item))
878 fprintf(stderr, "%s: %s\n %s\n", title, message, item);
879 else
880 fprintf(stderr, "%s: %s\n", title, message);
881}
882
883void dt_control_alert(const char *title, const char *message, const char *item)
884{
885 _alert(title, message, item, FALSE);
886}
887
888void dt_control_alert_once(const char *title, const char *message, const char *item)
889{
890 _alert(title, message, item, TRUE);
891}
892
893static void _toast_log(const gboolean markup, const char *msg, va_list ap)
894{
896
897 // if we don't want markup, we escape <>&... so they are not interpreted later
898 if(markup)
900 else
901 {
902 char *escaped_msg = g_markup_vprintf_escaped(msg, ap);
904 dt_free(escaped_msg);
905 }
906
913 // redraw center later in gui thread:
914 g_idle_add(_redraw_center, 0);
915}
916
917void dt_toast_log(const char *msg, ...)
918{
919 va_list ap;
920 va_start(ap, msg);
921 _toast_log(FALSE, msg, ap);
922 va_end(ap);
923}
924
925void dt_toast_markup_log(const char *msg, ...)
926{
927 va_list ap;
928 va_start(ap, msg);
929 _toast_log(TRUE, msg, ap);
930 va_end(ap);
931}
932
940
948
956
964
969
974
979
984
989
994
995static int _widget_queue_draw(void *widget_data)
996{
997 dt_control_redraw_widget_t *const redraw_widget = (dt_control_redraw_widget_t *)widget_data;
998 GtkWidget *const widget = GTK_WIDGET(g_weak_ref_get(&redraw_widget->widget_ref));
999 if(!IS_NULL_PTR(widget))
1000 {
1001 gtk_widget_queue_draw(widget);
1002 g_object_unref(widget);
1003 }
1004
1005 return FALSE;
1006}
1007
1008static void _widget_queue_draw_cleanup(void *widget_data)
1009{
1010 dt_control_redraw_widget_t *const redraw_widget = (dt_control_redraw_widget_t *)widget_data;
1011 g_weak_ref_clear(&redraw_widget->widget_ref);
1012 dt_free(redraw_widget);
1013}
1014
1016{
1017 if(dt_control_running())
1018 {
1019 dt_control_redraw_widget_t *const redraw_widget = calloc(1, sizeof(dt_control_redraw_widget_t));
1020 if(IS_NULL_PTR(redraw_widget)) return;
1021
1022 g_weak_ref_init(&redraw_widget->widget_ref, G_OBJECT(widget));
1023 g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, _widget_queue_draw, redraw_widget, _widget_queue_draw_cleanup);
1024 }
1025}
1026
1027void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
1028{
1030}
1031
1039
1041{
1044 {
1046
1047 // If we reset mouse_over_id to -1, aka "none" signal,
1048 // reset also the keyboard_over_id, in a "loose focus" way,
1049 // to keep only the selection for common/act_on.h
1053 }
1054 else
1056}
1057
1065
1072
1073// clang-format off
1074// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1075// vim: shiftwidth=2 expandtab tabstop=2 cindent
1076// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1077// 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:716
static int _widget_queue_draw(void *widget_data)
Definition control.c:995
void dt_control_alert_once(const char *title, const char *message, const char *item)
dt_control_alert() for a failure that repeats as long as its cause lasts – memory running short,...
Definition control.c:888
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_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
static void _dt_ctl_switch_mode_prepare()
Definition control.c:693
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:1032
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:545
void dt_control_mouse_is_dragging(gboolean state)
Definition control.c:432
static dt_control_shutdown_wait_t _shutdown_wait
Definition control.c:468
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:917
void dt_ctl_switch_mode_to_by_view(const dt_view_t *view)
Definition control.c:729
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:1040
void dt_control_alert(const char *title, const char *message, const char *item)
Tell the user something that must not go unseen: what it says stays true after a toast would have fad...
Definition control.c:883
static gboolean _redraw_center(gpointer user_data)
Definition control.c:832
void dt_control_forbid_change_cursor()
Definition control.c:233
void dt_toast_markup_log(const char *msg,...)
Definition control.c:925
void dt_control_log(const char *msg,...)
Definition control.c:839
static GdkRGBA lookup_color(GtkStyleContext *context, const char *name)
Definition control.c:537
void dt_control_set_alert_handler(dt_control_alert_handler_t handler)
Install the alert handler; NULL restores the fallback to dt_control_log().
Definition control.c:866
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
static gboolean _dt_ctl_switch_mode_to_by_view(gpointer user_data)
Definition control.c:708
void dt_control_quit()
Definition control.c:452
void dt_control_mouse_leave()
Definition control.c:667
void dt_control_toast_busy_enter()
Definition control.c:941
void dt_control_shutdown(dt_control_t *s)
Definition control.c:475
void dt_control_log_busy_leave()
Definition control.c:949
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:530
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:508
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:687
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
static dt_control_alert_handler_t _alert_handler
Definition control.c:864
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:760
void dt_control_log_busy_enter()
Definition control.c:933
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:677
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:1015
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:1027
static void _alert(const char *title, const char *message, const char *item, const int once)
Definition control.c:872
int32_t dt_control_get_keyboard_over_id()
Definition control.c:1058
void dt_control_toast_redraw()
Request a redraw of the toast message label.
Definition control.c:985
void dt_control_mouse_enter()
Definition control.c:672
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:980
static gboolean _dt_ctl_toast_message_timeout_callback(gpointer data)
Definition control.c:776
void dt_control_navigation_redraw()
Request a redraw of the navigation module's widget.
Definition control.c:975
void dt_control_expose(cairo_t *cr, const int width, const int height)
Definition control.c:609
static void _widget_queue_draw_cleanup(void *widget_data)
Definition control.c:1008
void dt_control_toast_busy_leave()
Definition control.c:957
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:893
static gboolean _dt_ctl_open_image_in_darkroom(gpointer user_data)
Definition control.c:735
static gboolean _dt_ctl_log_message_timeout_callback(gpointer data)
Definition control.c:765
gboolean dt_control_button_down(int which)
Is mouse button which currently held down?
Definition control.c:578
static gboolean _dt_ctl_switch_mode_to(gpointer user_data)
Definition control.c:700
void dt_control_queue_redraw()
Request a redraw of the whole workspace.
Definition control.c:965
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:1066
#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
void(* dt_control_shutdown_wait_t)(void)
How dt_control_shutdown() spends the time the running jobs still need.
Definition control.h:320
char * dt_get_main_message_copy(void)
Definition darktable.c:586
#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:526
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
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:748
void dt_control_jobs_cleanup(dt_control_t *control)
Definition jobs.c:771
void dt_control_jobs_init(dt_control_t *control)
Definition jobs.c:715
#define DT_CTL_WORKER_RESERVED
Definition jobs.h:39
_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:2094
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:390
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ 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_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
Definition signal.h:63
@ 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_REDRAW_CENTER
This signal is raised when dt_control_queue_redraw_center() is called. no param, no returned value.
Definition signal.h:78
@ 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
@ DT_SIGNAL_CONTROL_NAVIGATION_REDRAW
This signal is raised when dt_control_navigation_redraw() is called. no param, no returned value.
Definition signal.h:282
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_control_alert_handler_t)(const char *title, const char *message, const char *item, int once)
What dt_control_alert() and dt_control_alert_once() hand their finished text to, with once non-zero f...
void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
Definition view.c:530
void dt_view_manager_mouse_enter(dt_view_manager_t *vm)
Definition view.c:524
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
Definition view.c:638
int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
Definition view.c:579
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:455
int dt_view_manager_key_pressed(dt_view_manager_t *vm, GdkEventKey *event)
Definition view.c:552
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:606
uint32_t dt_view_manager_current_flags(const dt_view_manager_t *vm)
Definition view.c:449
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:239
int dt_view_manager_switch_by_view(dt_view_manager_t *vm, const dt_view_t *nv)
Definition view.c:261
void dt_view_manager_mouse_leave(dt_view_manager_t *vm)
Definition view.c:502
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:144
@ 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)