Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
darkroom.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2014, 2018 johannes hanika.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010 Stuart Henderson.
6 Copyright (C) 2010-2018, 2020 Tobias Ellinghaus.
7 Copyright (C) 2011-2012 Antony Dovgal.
8 Copyright (C) 2011 Edouard Gomez.
9 Copyright (C) 2011, 2013-2015 Jérémy Rosen.
10 Copyright (C) 2011 Karl Mikaelsson.
11 Copyright (C) 2011 Omari Stephens.
12 Copyright (C) 2011 Robert Bieber.
13 Copyright (C) 2011 Steven Carter.
14 Copyright (C) 2012-2016, 2019-2022 Aldric Renaudin.
15 Copyright (C) 2012 Christian Tellefsen.
16 Copyright (C) 2012 Frédéric Grollier.
17 Copyright (C) 2012-2013 José Carlos García Sogo.
18 Copyright (C) 2012-2022 Pascal Obry.
19 Copyright (C) 2012 Richard Wonka.
20 Copyright (C) 2012-2014 Ulrich Pegelow.
21 Copyright (C) 2013 Dennis Gnad.
22 Copyright (C) 2013 Pascal de Bruijn.
23 Copyright (C) 2013-2017, 2020 Roman Lebedev.
24 Copyright (C) 2014, 2017, 2020-2021 Dan Torop.
25 Copyright (C) 2014, 2017, 2019 parafin.
26 Copyright (C) 2014 Pedro Côrte-Real.
27 Copyright (C) 2014 Stéphane Gimenez.
28 Copyright (C) 2015 Guillaume Subiron.
29 Copyright (C) 2016 Asma.
30 Copyright (C) 2016 itinerarium.
31 Copyright (C) 2017-2019 Edgardo Hoszowski.
32 Copyright (C) 2017 Matthieu Moy.
33 Copyright (C) 2017-2019 Peter Budai.
34 Copyright (C) 2017 Žilvinas Žaltiena.
35 Copyright (C) 2018 Hans Rosenfeld.
36 Copyright (C) 2018 rawfiner.
37 Copyright (C) 2018 Rikard Öxler.
38 Copyright (C) 2019 Alexander Blinne.
39 Copyright (C) 2019 Alexis Mousset.
40 Copyright (C) 2019-2020, 2022-2026 Aurélien PIERRE.
41 Copyright (C) 2019, 2021 Bill Ferguson.
42 Copyright (C) 2019-2022 Diederik Ter Rahe.
43 Copyright (C) 2019-2022 Hanno Schwalm.
44 Copyright (C) 2019-2020 Heiko Bauke.
45 Copyright (C) 2019 jakubfi.
46 Copyright (C) 2019, 2021 luzpaz.
47 Copyright (C) 2019-2020 Philippe Weyland.
48 Copyright (C) 2020-2022 Chris Elston.
49 Copyright (C) 2020 GrahamByrnes.
50 Copyright (C) 2020-2021 Hubert Kowalski.
51 Copyright (C) 2020-2021 Marco.
52 Copyright (C) 2020-2021 Mark-64.
53 Copyright (C) 2020 Miloš Komarčević.
54 Copyright (C) 2020 Reinout Nonhebel.
55 Copyright (C) 2020 U-DESKTOP-HQME86J\marco.
56 Copyright (C) 2021 darkelectron.
57 Copyright (C) 2021 lhietal.
58 Copyright (C) 2021-2022 Nicolas Auffray.
59 Copyright (C) 2021-2022 Ralf Brown.
60 Copyright (C) 2021-2022 Sakari Kapanen.
61 Copyright (C) 2021 Victor Forsiuk.
62 Copyright (C) 2022 Martin Bařinka.
63 Copyright (C) 2023 Alynx Zhou.
64 Copyright (C) 2023 Luca Zulberti.
65 Copyright (C) 2023 Maurizio Paglia.
66 Copyright (C) 2023 Ricky Moon.
67 Copyright (C) 2025-2026 Guillaume Stutin.
68
69 darktable is free software: you can redistribute it and/or modify
70 it under the terms of the GNU General Public License as published by
71 the Free Software Foundation, either version 3 of the License, or
72 (at your option) any later version.
73
74 darktable is distributed in the hope that it will be useful,
75 but WITHOUT ANY WARRANTY; without even the implied warranty of
76 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
77 GNU General Public License for more details.
78
79 You should have received a copy of the GNU General Public License
80 along with darktable. If not, see <http://www.gnu.org/licenses/>.
81*/
84#include "develop/imageop_gui.h"
85#include "widgets/bauhaus.h"
88#include <glib/gstdio.h>
89#include "common/paths.h"
90#include "common/collection.h"
91#include "widgets/gdkkeys.h"
92#include "common/hash.h"
93#include "common/iop-autoset.h"
95#include "caches/mipmap_cache.h"
97#include "common/selection.h"
98#include "common/undo.h"
99#include "common/conf.h"
100#include "control/input.h"
101#include "control/control.h"
102#include "control/jobs.h"
104#include "develop/dev_history.h"
105#include "develop/pixelpipe_hb.h"
106#include "develop/develop.h"
108#include "develop/imageop.h"
109#include "develop/supervisor.h"
110#include "develop/masks.h"
111#include "develop/masks_gui.h"
112#include "widgets/button.h"
113#include "gui/dtgtk/thumbtable.h"
114
116#include "gui/application.h"
117#include "develop/gui_throttle.h"
118// include-cleaner reports this unused while dt_iop_gui_blend_data_t is dereferenced
119// six times in _toggle_mask_visibility_callback() -- the cast from the void*
120// mod->blend_data seems to defeat its attribution. The definition is required.
121#include "develop/blend_gui.h" // NOLINT(misc-include-cleaner)
122#include "gui/guides.h"
123#include "libs/colorpicker.h"
124#include "libs/lib.h"
125#include "views/dev_backbuf.h"
126#include "views/dev_toolbox.h"
127#include "views/view.h"
128#include "views/view_api.h"
129#ifdef GDK_WINDOWING_QUARTZ
130#endif
131
132#include <gdk/gdkkeysyms.h>
133#include <glib.h>
134#include <math.h>
135#include <stdlib.h>
136#include <string.h>
137#include <unistd.h>
138#include <sys/types.h>
139#include <fcntl.h>
140#include "widgets/label.h"
141#include "widgets/widget_style.h"
142#include "gui/screen_metrics.h"
143#include "widgets/togglebutton.h"
144#include "control/signal.h"
145
146#ifndef G_SOURCE_FUNC // Defined for glib >= 2.58
147#define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void)) (f))
148#endif
149
150DT_MODULE(1)
151
152typedef struct coords_t
153{
154 double roi_coord[2]; // coordinates in ROI space
155 double roi_delta[2]; // delta in ROI space
156 double full_delta[2]; // delta in image space
157 double full[2]; // coordinates in image space
159
160#define DARKROOM_EDGE_PAN_INTERVAL_MS 64
161#define DARKROOM_EDGE_PAN_MARGIN_PX DT_PIXEL_APPLY_DPI(50)
162#define DARKROOM_EDGE_PAN_SPEED_PX_PER_S 360.0f
163
164/* signal handler for filmstrip image switching */
165static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data);
166static void _darkroom_image_loaded_callback(gpointer instance, guint request_id, guint result, gpointer user_data);
167
168static void _dev_change_image(dt_view_t *self, const int32_t imgid);
170
171static int _change_scaling(dt_develop_t *dev, const float point[2], const float new_scaling);
172static void _release_expose_source_caches(void);
173static void _darkroom_set_default_cursor(dt_view_t *self, double x, double y);
174
179
185static void _darkroom_autoset_popover_refresh(gpointer instance, gpointer user_data);
186static void _darkroom_autoset_button_set_running(const gboolean running);
187
188static void _darkroom_ioporder_quickbutton_clicked(GtkButton *button, gpointer user_data)
189{
190 dt_lib_module_t *module = dt_lib_get_module("ioporder");
191 if(module && module->show_popup)
192 module->show_popup(module);
193}
194
204static void _darkroom_autoset_button_set_running(const gboolean running)
205{
207 if(_darkroom_autoset_button_is_running == running) return;
208
210
211 gtk_widget_set_sensitive(_darkroom_autoset_button, !running);
212 gtk_widget_set_tooltip_text(_darkroom_autoset_button,
213 running ? _("Autoset is running on selected modules")
214 : _("Run autoset on selected modules\nRight click for options"));
215
216 if(running)
218 else
220}
221
222const char *name(const dt_view_t *self)
223{
224 return _("Darkroom");
225}
226
227
228void init(dt_view_t *self)
229{
230 self->data = malloc(sizeof(dt_develop_t));
231 dt_dev_init((dt_develop_t *)self->data, 1);
234}
235
236uint32_t view(const dt_view_t *self)
237{
238 return DT_VIEW_DARKROOM;
239}
240
241uint32_t flags()
242{
243 /* expose() composes background and image over the whole centre itself */
245}
246
247static void _reset_edge_pan()
248{
250 if(IS_NULL_PTR(gui)) return;
251 if(gui->pan_edge.timeout_source)
252 {
253 g_source_remove(gui->pan_edge.timeout_source);
254 }
255 memset(&gui->pan_edge, 0, sizeof(gui->pan_edge));
256}
257
259{
260 dt_develop_t *dev = (dt_develop_t *)self->data;
261
262 // Cancel any pending edge pan timeout and reset the state
264
270 {
272 {
274 "darkroom-cleanup-autoset-manager");
277 }
279 {
282 }
283 g_list_free(_autoset_manager->iop_to_set);
285 _autoset_manager = NULL;
286 }
290
291 // unref the grid lines popover if needed
292 if(dt_view_manager_get_global()->guides_popover) g_object_unref(dt_view_manager_get_global()->guides_popover);
295 dt_dev_cleanup(dev);
296 dt_free(dev);
297}
298
299static cairo_status_t _write_snapshot_data(void *closure, const unsigned char *data, unsigned int length)
300{
301 const int fd = GPOINTER_TO_INT(closure);
302 ssize_t res = write(fd, data, length);
303 if(res != length)
304 return CAIRO_STATUS_WRITE_ERROR;
305 return CAIRO_STATUS_SUCCESS;
306}
307
312
313static gboolean _darkroom_is_only_selected_sample(gboolean is_primary_sample, dt_colorpicker_sample_t *selected_sample, gboolean display_samples)
314{
315 return !is_primary_sample && selected_sample && !display_samples;
316}
317
319 float point[2])
320{
321 point[0] = sample->point[0];
322 point[1] = sample->point[1];
324}
325
327 float box[4])
328{
329 memcpy(box, sample->box, sizeof(float) * 4);
331}
332
345static void _darkroom_pickers_draw(dt_view_t *self, cairo_t *cri,
346 int32_t width, int32_t height, int32_t pozx, int32_t pozy,
347 GSList *samples, gboolean is_primary_sample)
348{
349 if(IS_NULL_PTR(samples)) return;
350
351 dt_develop_t *dev = (dt_develop_t *)self->data;
352
353 cairo_save(cri);
354 // The colorpicker samples bounding rectangle should only be displayed inside the visible image
355
356 const double wd = dt_dev_roi_request_preview_width(dev);
357 const double ht = dt_dev_roi_request_preview_height(dev);
358 const double scale = dt_dev_get_fit_scale(dev);
359 const double lw = 1.0 / scale;
360 const double dashes[1] = { lw * 4.0 };
361
362 dt_dev_rescale_roi(dev, cri, width, height);
363
364 // makes point sample crosshair gap look nicer
365 cairo_set_line_cap(cri, CAIRO_LINE_CAP_SQUARE);
366
368 const gboolean only_selected_sample
369 = _darkroom_is_only_selected_sample(is_primary_sample, selected_sample,
370 dt_dev_get_global()->color_picker.display_samples);
371
372 for( ; samples; samples = g_slist_next(samples))
373 {
374 dt_colorpicker_sample_t *sample = samples->data;
375 if(only_selected_sample && (sample != selected_sample))
376 continue;
377
378 // The picker is at the resolution of the preview pixelpipe. This
379 // is width/2 of a preview-pipe pixel in (scaled) user space
380 // coordinates. Use half pixel width so rounding to nearest device
381 // pixel doesn't make uneven centering.
382 double half_px = 0.5;
383 const double min_half_px_device = 4.0;
384 // FIXME: instead of going to all this effort to show how error-prone a preview pipe sample can be, just produce a better point sample
385 gboolean show_preview_pixel_scale = TRUE;
386
387 // overlays are aligned with pixels for a clean look
388 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
389 {
390 float image_box[4] = { 0.0f };
392 double x = image_box[0] * wd, y = image_box[1] * ht,
393 w = image_box[2] * wd, h = image_box[3] * ht;
394 cairo_user_to_device(cri, &x, &y);
395 cairo_user_to_device(cri, &w, &h);
396 x=round(x+0.5)-0.5;
397 y=round(y+0.5)-0.5;
398 w=round(w+0.5)-0.5;
399 h=round(h+0.5)-0.5;
400 cairo_device_to_user(cri, &x, &y);
401 cairo_device_to_user(cri, &w, &h);
402 cairo_rectangle(cri, x, y, w - x, h - y);
403 if(is_primary_sample)
404 {
405 // handles
406 const double hw = 5. / scale;
407 cairo_rectangle(cri, x - hw, y - hw, 2. * hw, 2. * hw);
408 cairo_rectangle(cri, x - hw, h - hw, 2. * hw, 2. * hw);
409 cairo_rectangle(cri, w - hw, y - hw, 2. * hw, 2. * hw);
410 cairo_rectangle(cri, w - hw, h - hw, 2. * hw, 2. * hw);
411 }
412 }
413 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
414 {
415 // FIXME: to be really accurate, the colorpicker should render precisely over the nearest pixelpipe pixel, but this gets particularly tricky to do with iop pickers with transformations after them in the pipeline
416 float image_point[2] = { 0.0f };
417 _darkroom_sample_raw_point_to_image_norm(sample, image_point);
418 double x = image_point[0] * wd;
419 double y = image_point[1] * ht;
420 cairo_user_to_device(cri, &x, &y);
421 x=round(x+0.5)-0.5;
422 y=round(y+0.5)-0.5;
423 // render picker center a reasonable size in device pixels
424 half_px = round(half_px * scale);
425 if(half_px < min_half_px_device)
426 {
427 half_px = min_half_px_device;
428 show_preview_pixel_scale = FALSE;
429 }
430 // crosshair radius
431 double crosshair = (is_primary_sample ? 4. : 5.) * half_px;
432 if(sample == selected_sample) crosshair *= 2;
433 cairo_device_to_user(cri, &x, &y);
434 cairo_device_to_user_distance(cri, &crosshair, &half_px);
435
436 // "handles"
437 if(is_primary_sample)
438 cairo_arc(cri, x, y, crosshair, 0., 2. * M_PI);
439 // crosshair
440 cairo_move_to(cri, x - crosshair, y);
441 cairo_line_to(cri, x + crosshair, y);
442 cairo_move_to(cri, x, y - crosshair);
443 cairo_line_to(cri, x, y + crosshair);
444 }
445
446 // default is to draw 1 (logical) pixel light lines with 1
447 // (logical) pixel dark outline for legibility
448 const double line_scale = (sample == selected_sample ? 2.0 : 1.0);
449 cairo_set_line_width(cri, lw * 3.0 * line_scale);
450 cairo_set_source_rgba(cri, 0.0, 0.0, 0.0, 0.4);
451 cairo_stroke_preserve(cri);
452
453 const gboolean draw_dashed = !is_primary_sample
454 && sample != selected_sample
455 && sample->size == DT_LIB_COLORPICKER_SIZE_BOX;
456 cairo_set_line_width(cri, lw * line_scale);
457 cairo_set_dash(cri, dashes, draw_dashed, 0.0);
458
459 cairo_set_source_rgba(cri, 1.0, 1.0, 1.0, 0.8);
460 cairo_stroke(cri);
461
462 // draw the actual color sampled
463 // FIXME: if an area sample is selected, when selected should fill it with colorpicker color?
464 // NOTE: The sample may be based on outdated data, but still
465 // display as it will update eventually. If we only drew on valid
466 // data, swatches on point live samples would flicker when the
467 // primary sample was drawn, and the primary sample swatch would
468 // flicker when an iop is adjusted.
470 {
471 float image_point[2] = { 0.0f };
472 _darkroom_sample_raw_point_to_image_norm(sample, image_point);
473 if(sample == selected_sample)
474 cairo_arc(cri, image_point[0] * wd, image_point[1] * ht, half_px * 2., 0., 2. * M_PI);
475 else if(show_preview_pixel_scale)
476 cairo_rectangle(cri, image_point[0] * wd - half_px, image_point[1] * ht - half_px, half_px * 2., half_px * 2.);
477 else
478 cairo_arc(cri, image_point[0] * wd, image_point[1] * ht, half_px, 0., 2. * M_PI);
479
480 set_color(cri, sample->swatch);
481 cairo_fill(cri);
482 }
483 }
484
485 cairo_restore(cri);
486}
487
488/* Debug-only darkroom expose mode:
489 * - no persistent surface locks,
490 * - no fallback cache,
491 * - no preview substitution,
492 * - only main backbuffer if currently available.
493 *
494 * Set to 1 for baseline debugging when troubleshooting display regressions. */
495#ifndef DARKROOM_EXPOSE_DUMB_DEBUG
496#define DARKROOM_EXPOSE_DUMB_DEBUG 0
497#endif
498
499#if DARKROOM_EXPOSE_DUMB_DEBUG
500static dt_dev_pixelpipe_cache_wait_t _darkroom_main_debug_wait = { 0 };
501
502static void _darkroom_debug_restart_cache_wait(gpointer user_data)
503{
504 dt_develop_t *dev = (dt_develop_t *)user_data;
505 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
507}
508
509static gboolean _render_main_direct_debug(cairo_t *cr, dt_develop_t *dev, const int width, const int height,
510 const int border, const dt_aligned_pixel_t bg_color)
511{
512 if(IS_NULL_PTR(cr) || IS_NULL_PTR(dev) || IS_NULL_PTR(dev->pipe)) return FALSE;
513
514 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
515 cairo_paint(cr);
516
517 if(!dt_dev_pixelpipe_is_backbufer_valid(dev->pipe, dev)) return FALSE;
518 // One read of the publication: the hash and the shape must come from the same frame (dt_backbuf_t).
519 const dt_backbuf_state_t published = dt_dev_backbuf_snapshot(&dev->pipe->backbuf);
520 const uint64_t hash = published.hash;
521 if(hash == (uint64_t)-1) return FALSE;
522
523 dt_pixel_cache_entry_t *entry = NULL;
524 void *data = NULL;
525 dt_dev_pixelpipe_cache_wait_set_owner(&_darkroom_main_debug_wait, "darkroom-debug-main", dev);
526 if(!dt_dev_pixelpipe_cache_peek_gui(dev->pipe, NULL, &data, &entry,
527 &_darkroom_main_debug_wait,
528 _darkroom_debug_restart_cache_wait, dev))
529 return FALSE;
530
532
533 const int bw = (int)published.width;
534 const int bh = (int)published.height;
535 if(bw <= 0 || bh <= 0)
536 {
538 return FALSE;
539 }
540
541 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, bw);
542 const size_t required_size = (size_t)stride * (size_t)bh;
543 const size_t entry_size = dt_pixel_cache_entry_get_size(entry);
544 if(entry_size < required_size || dt_pixel_cache_entry_get_data(entry) != data)
545 {
547 return FALSE;
548 }
549
550 cairo_surface_t *surface = cairo_image_surface_create_for_data((unsigned char *)data, CAIRO_FORMAT_RGB24,
551 bw, bh, stride);
552 if(IS_NULL_PTR(surface) || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS)
553 {
554 if(surface) cairo_surface_destroy(surface);
556 return FALSE;
557 }
558
559 float image_box[4] = { 0.0f };
561 const int wd = image_box[2];
562 const int ht = image_box[3];
563 cairo_translate(cr, image_box[0], image_box[1]);
564 if(dev->iso_12646.enabled) dt_dev_draw_iso12646_border(cr, wd, ht, border);
565 cairo_surface_set_device_scale(surface, dt_gui_get_global()->ppd, dt_gui_get_global()->ppd);
566 cairo_rectangle(cr, 0, 0, wd, ht);
567 cairo_set_source_surface(cr, surface, 0, 0);
568 cairo_fill(cr);
569
570 cairo_surface_destroy(surface);
572 return TRUE;
573}
574#endif
575
580static cairo_surface_t *_darkroom_preview_fallback_surface = NULL;
586
601
603{
608#if DARKROOM_EXPOSE_DUMB_DEBUG
609 dt_dev_pixelpipe_cache_wait_cleanup(&_darkroom_main_debug_wait, "darkroom-release-debug");
610#endif
612}
613
614static gboolean _build_preview_fallback_surface(dt_develop_t *dev, const int width, const int height, const int border,
615 const dt_aligned_pixel_t bg_color, const uint64_t zoom_hash)
616{
619 if(width <= 0 || height <= 0) return FALSE;
620
624 {
630 }
631
632 cairo_t *cr = cairo_create(_darkroom_preview_fallback_surface);
633
634 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
635 cairo_paint(cr);
636
637 const int wd = _darkroom_preview_locked.width;
638 const int ht = _darkroom_preview_locked.height;
639 const float ppd = dt_gui_get_global()->ppd;
640 const float preview_wd = wd / ppd;
641 const float preview_ht = ht / ppd;
642 const float preview_scale = dt_dev_viewport_scaling(dev);
643 float image_box[4] = { 0.0f };
645
646 if(dev->iso_12646.enabled)
647 {
648
649
650 if(image_box[2] > 0 && image_box[3] > 0)
651 {
652 cairo_save(cr);
653 cairo_translate(cr, image_box[0], image_box[1]);
654 dt_dev_draw_iso12646_border(cr, image_box[2], image_box[3], border);
655 cairo_restore(cr);
656 }
657 }
658
660 cairo_surface_set_device_scale(_darkroom_preview_locked.surface, ppd, ppd);
661
662 // The preview surface already embeds the fit-to-window scale. To emulate the
663 // main pipe while it catches up, we only apply the additional darkroom zoom
664 // and pan around the preview image center in GUI logical coordinates.
665 const float roi_width = fminf(width, preview_wd * preview_scale);
666 const float roi_height = fminf(height, preview_ht * preview_scale);
667 const float rec_x = fmaxf(border, (width - roi_width) * 0.5f);
668 const float rec_y = fmaxf(border, (height - roi_height) * 0.5f);
669 const float rec_w = fminf(width - 2 * border, roi_width);
670 const float rec_h = fminf(height - 2 * border, roi_height);
671 cairo_rectangle(cr, rec_x, rec_y, rec_w, rec_h);
672 cairo_clip(cr);
673
674 const float tx = 0.5f * width - dt_dev_viewport_center_x(dev) * preview_wd * preview_scale;
675 const float ty = 0.5f * height - dt_dev_viewport_center_y(dev) * preview_ht * preview_scale;
676 cairo_translate(cr, tx, ty);
677 cairo_scale(cr, preview_scale, preview_scale);
678 cairo_rectangle(cr, 0, 0, preview_wd, preview_ht);
679 cairo_set_source_surface(cr, _darkroom_preview_locked.surface, 0, 0);
680 /* A placeholder while the main pipe catches up, resampled by the zoom: nearest neighbour,
681 * on the pattern that actually scales. This filter used to be set on the context's default
682 * solid source before the surface replaced it, so the scaling ran with cairo's default GOOD
683 * filter -- measured at a 2560x1440 window on a 2x screen: 262 ms per frame zooming out,
684 * 120 ms zooming in, against 4 and 8 ms with the filter that was meant. */
685 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
686 cairo_fill(cr);
688 cairo_destroy(cr);
689
693 return TRUE;
694}
695
696static gboolean _render_preview_fallback_surface(cairo_t *cr)
697{
699 cairo_set_source_surface(cr, _darkroom_preview_fallback_surface, 0, 0);
700 cairo_paint(cr);
701 return TRUE;
702}
703
704
705static void _paint_all(cairo_t *cri, cairo_t *cr, cairo_surface_t *image_surface)
706{
707 cairo_destroy(cr);
708 if(IS_NULL_PTR(image_surface)) return;
709 cairo_set_source_surface(cri, image_surface, 0, 0);
710 cairo_paint(cri);
711}
712
714{
721 /* Main backbuf hash for which we already asked for a retry redraw after failing
722 * to lock/render it. Used to re-queue at most once per distinct main frame so a
723 * permanently-unlockable main backbuf (e.g. evicted under memory pressure) does
724 * not spin the center redraw at frame-clock rate while we show the preview
725 * fallback. Reset whenever a main frame is rendered successfully. */
727 /* What dev->image_surface currently holds: the source frame, the viewport, the colours and
728 * the frame size it was composed for. A frame with the same key is already on it, and an
729 * expose that only moved an overlay repaints from it without composing again -- composing
730 * was a full-window fill and a full-window blit on every frame, for an image that had not
731 * changed. */
734
735/* Everything the composed image depends on, and nothing else. */
736static uint64_t _darkroom_compose_key(const uint64_t source_hash, const uint64_t zoom_hash,
737 const dt_aligned_pixel_t bg_color, const int border, const gboolean iso,
738 const int width, const int height)
739{
740 uint64_t key = 5381;
741 key = dt_hash(key, (const char *)&source_hash, sizeof(source_hash));
742 key = dt_hash(key, (const char *)&zoom_hash, sizeof(zoom_hash));
743 key = dt_hash(key, (const char *)bg_color, 3 * sizeof(float));
744 key = dt_hash(key, (const char *)&border, sizeof(border));
745 key = dt_hash(key, (const char *)&iso, sizeof(iso));
746 key = dt_hash(key, (const char *)&width, sizeof(width));
747 key = dt_hash(key, (const char *)&height, sizeof(height));
748 return key;
749}
750
751static inline gboolean _darkroom_preview_fallback_valid(const dt_develop_t *dev, const int width,
752 const int height, const uint64_t zoom_hash)
753{
754 return dev
760}
761
777{
778 uint64_t hash = 5381;
779
780 // Three coherent reads, hashed as the three records they are. Hashing field by field through
781 // accessors would let a publication land between two of them and key the cached surface to a
782 // viewport that never existed -- which is the same tearing the objects were built to remove.
783 const dt_dev_viewport_state_t viewport = dt_dev_viewport_get(dev);
784 hash = dt_hash(hash, (const char *)&viewport, sizeof(viewport));
785
787 hash = dt_hash(hash, (const char *)&geometry, sizeof(geometry));
788
789 // The request's generation, not its bytes: the record carries four bytes of tail padding
790 // (its uint64_t forces 8-byte alignment) whose contents C does not define across a copy, so
791 // hashing it raw could key the cached surface differently for identical content. The
792 // generation exists precisely to answer "did this change", and advances only when it did.
793 // dt_dev_viewport_state_t and dt_dev_image_geometry_t above are padding-free, so their bytes
794 // are safe to hash directly -- checked, not assumed.
795 const dt_dev_roi_request_t request = dt_dev_roi_request_get(dev);
796 hash = dt_hash(hash, (const char *)&request.generation, sizeof(request.generation));
797 return hash;
798}
799
801 const uint64_t zoom_hash)
802{
803 return state
806 && state->main_zoom_hash == zoom_hash;
807}
808
810{
811 if(IS_NULL_PTR(state)) return;
812 state->image_surface_imgid = UNKNOWN_IMAGE;
813 state->image_surface_has_main = FALSE;
814 state->image_surface_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
815 state->main_zoom_hash = 0;
816 state->pending_main_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
817 state->composed_key = 0;
818}
819
820/* One expose's composition: the context of the image surface, the frame and the colours it
821 * is composed for, and the state that remembers what it holds. */
833
834static uint64_t _darkroom_compose_key_of(const _darkroom_compose_t *const c, const uint64_t source_hash)
835{
836 return _darkroom_compose_key(source_hash, c->zoom_hash, c->bg_color, c->border, c->dev->iso_12646.enabled,
837 c->width, c->height);
838}
839
840static gboolean _darkroom_composed_already(const _darkroom_compose_t *const c, const uint64_t key)
841{
842 return c->state->composed_key == key && c->state->image_surface_imgid == c->dev->image_storage.id;
843}
844
845/* Compose @p locked into the image surface unless the frame it would compose is already
846 * there. */
848{
849 if(IS_NULL_PTR(locked) || IS_NULL_PTR(locked->surface)) return FALSE;
850 const uint64_t key = _darkroom_compose_key_of(c, locked->hash);
851 if(_darkroom_composed_already(c, key)) return TRUE;
852 if(!dt_dev_render_locked_surface(c->cr, c->dev, locked, c->width, c->height, c->border, c->bg_color)) return FALSE;
853 c->state->composed_key = key;
854 return TRUE;
855}
856
857/* Copy the cached fallback into the image surface unless it is already the frame there. */
858static gboolean _darkroom_compose_fallback(const _darkroom_compose_t *const c)
859{
860 /* salted: a fallback is not the same frame as a main backbuf that happened to hash alike */
861 const uint64_t source = _darkroom_preview_fallback_backbuf_hash ^ 0x9e3779b97f4a7c15ULL;
862 const uint64_t key = _darkroom_compose_key_of(c, source);
863 if(_darkroom_composed_already(c, key)) return TRUE;
864 if(!_render_preview_fallback_surface(c->cr)) return FALSE;
865 c->state->composed_key = key;
866 return TRUE;
867}
868
869static void _darkroom_prepare_image_surface(dt_develop_t *dev, const int width, const int height,
871{
872 if(IS_NULL_PTR(dev) || !state) return;
873
874 if(state->image_surface_imgid != dev->image_storage.id
875 && state->image_surface_imgid != UNKNOWN_IMAGE)
876 {
879 }
880
881 if(state->image_surface_width == width
882 && state->image_surface_height == height
883 && !IS_NULL_PTR(dev->image_surface))
884 return;
885
886 state->image_surface_width = width;
887 state->image_surface_height = height;
888 if(dev->image_surface) cairo_surface_destroy(dev->image_surface);
889 dev->image_surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
890 state->image_surface_imgid = UNKNOWN_IMAGE;
891 state->image_surface_has_main = FALSE;
892 state->image_surface_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
893 state->composed_key = 0;
895}
896
898 dt_view_t *self,
899 cairo_t *cri,
900 int32_t width,
901 int32_t height,
902 int32_t pointerx,
903 int32_t pointery)
904{
905 dt_times_t start;
906 dt_get_times(&start);
907
908 cairo_save(cri);
909
910 dt_develop_t *dev = (dt_develop_t *)self->data;
911
912 const int32_t border = dt_dev_viewport_border_size(dev);
913
914#if DARKROOM_EXPOSE_DUMB_DEBUG
915 dt_aligned_pixel_t bg_color_dbg;
916 dt_dev_get_background_color(dev, bg_color_dbg);
917 _render_main_direct_debug(cri, dev, width, height, border, bg_color_dbg);
918 cairo_restore(cri);
919 return;
920#endif
921
922 static darkroom_expose_state_t expose_state = {
924 .image_surface_height = 0,
925 .image_surface_imgid = UNKNOWN_IMAGE,
926 .image_surface_has_main = FALSE,
927 .image_surface_hash = DT_PIXELPIPE_CACHE_HASH_INVALID,
928 .main_zoom_hash = 0,
929 .pending_main_hash = DT_PIXELPIPE_CACHE_HASH_INVALID,
930 .composed_key = 0,
931 };
932 const uint64_t zoom_hash = _darkroom_zoom_hash(dev);
933 const gboolean roi_changed = !_darkroom_locked_main_valid_for_zoom(&expose_state, zoom_hash);
934
935 /* The expose is timed as a whole and nothing inside it is, which is why a redraw that grows
936 * from 11 ms to 400 ms across a drawing session cannot be attributed: the mask overlay reports
937 * 1.5 ms of it and the rest is dark. Three stages, so the next log says which. */
938 dt_times_t stage = { 0 };
939 dt_get_times(&stage);
940
941 _darkroom_prepare_image_surface(dev, width, height, &expose_state);
942
943 if(dt_get_debug_flags() & DT_DEBUG_PERF) dt_show_times(&stage, "[darkroom] surface prepared");
944 dt_get_times(&stage);
945
946 cairo_t *cr = cairo_create(dev->image_surface);
947 const int full_width = dt_dev_roi_request_preview_width(dev);
948 const int full_height = dt_dev_roi_request_preview_height(dev);
949 const dt_backbuf_state_t preview_published = dt_dev_backbuf_snapshot(&dev->preview_pipe->backbuf);
950 const uint64_t main_backbuf_hash = dt_dev_backbuf_get_hash(&dev->pipe->backbuf);
951 const uint64_t preview_backbuf_hash = preview_published.hash;
952 const gboolean main_has_backbuf = main_backbuf_hash != DT_PIXELPIPE_CACHE_HASH_INVALID;
953 const gboolean preview_has_backbuf = preview_backbuf_hash != DT_PIXELPIPE_CACHE_HASH_INVALID;
954 // Compare main against the last main surface, not against the last source painted into
955 // image_surface. At fit the latter may be the preview pipe, whose different hash would make an
956 // old main backbuffer look new and briefly restore the previous zoom size.
957 const gboolean main_backbuf_is_newer
958 = main_has_backbuf && main_backbuf_hash != _darkroom_main_locked.hash;
959 const gboolean main_ready_for_current_view
960 = main_has_backbuf && (!roi_changed || main_backbuf_is_newer || !_darkroom_main_locked.surface);
961 const gboolean preview_matches_full_image
962 = preview_has_backbuf && dt_dev_pipelines_share_preview_output(dev)
963 && full_width > 0 && full_height > 0
964 && preview_published.width == (size_t)full_width
965 && preview_published.height == (size_t)full_height;
966 const gboolean full_image_backbuf_ready = main_ready_for_current_view || preview_matches_full_image;
967
968 dt_aligned_pixel_t bg_color = { 0.0f };
969 const char *draw_source = "background only";
970 uint64_t draw_hash = (uint64_t)-1;
971 gboolean drawn = FALSE;
972 gboolean drawn_from_main = FALSE;
973
974 dt_dev_get_background_color(dev, bg_color);
975 const _darkroom_compose_t compose = { .cr = cr, .dev = dev, .state = &expose_state, .width = width,
976 .height = height, .border = border, .zoom_hash = zoom_hash,
977 .bg_color = bg_color };
978
979 /* Selection policy, kept intentionally linear:
980 * 1. Prefer the main pipe whenever it already has the backbuf for the
981 * current darkroom view. We do not guess that from size. If ROI did not
982 * change, the existing main frame stays authoritative. If ROI changed, a
983 * new main backbuf hash means the updated main frame arrived.
984 * 2. If preview produced the exact same full-image buffer size first
985 * (zoom-to-fit / same ROI), treat it exactly like main.
986 * 3. Only when zoom/pan changed and main has not caught up yet, use a scaled
987 * preview fallback surface. This gives immediate ROI feedback without
988 * discarding the last valid main image for pure history changes.
989 * 4. If nothing newer is ready, keep showing the last valid composed frame to
990 * avoid flashing the background. */
991
992 /* Once a full-image backbuf exists again, the ROI-scaled preview fallback is
993 * obsolete. Keep main/preview selection simple and never let the fallback
994 * outlive a valid full-image source. */
995 if(full_image_backbuf_ready)
997
998 /* Rule 1: main wins whenever it is ready for the current view. During a
999 * zoom/pan/widget-size transition, the previous main hash stays visible until
1000 * a different main hash is published; only then does main override preview
1001 * fallback again. */
1002 if(main_ready_for_current_view)
1003 {
1007 {
1008 expose_state.main_zoom_hash = zoom_hash;
1009 expose_state.image_surface_imgid = dev->image_storage.id;
1010 expose_state.image_surface_has_main = TRUE;
1013 drawn = TRUE;
1014 drawn_from_main = TRUE;
1015 draw_source = "fresh main backbuf";
1016 draw_hash = _darkroom_main_locked.hash;
1017 }
1018 else if(main_backbuf_is_newer && expose_state.pending_main_hash != main_backbuf_hash)
1019 {
1020 /* The main backbuf hash advanced but its cacheline could not be locked this
1021 * frame. Ask for one retry, then remember the hash: if the same main frame
1022 * stays unlockable (e.g. evicted under memory pressure), we keep presenting
1023 * the preview fallback below instead of re-queueing a redraw every frame.
1024 * A genuinely new main frame (different hash) re-arms the retry, and the
1025 * pipeline itself re-queues a redraw when it publishes a fresh backbuf. */
1026 expose_state.pending_main_hash = main_backbuf_hash;
1028 }
1029 }
1030
1031 /* Rule 2: preview is directly equivalent to main only when both pipes target
1032 * the same ROI. An uncropped edit mode changes the module contract, not the
1033 * current zoom: once zoomed, preview must use the scaled fallback below. */
1034 if(!drawn && preview_matches_full_image)
1035 {
1037 "darkroom-preview", FALSE)
1040 {
1041 // This frame validates the composed image, not the locked main surface. Keep main_zoom_hash
1042 // unchanged so the old main backbuffer cannot be reused as if it had been rendered at fit.
1043 expose_state.image_surface_imgid = dev->image_storage.id;
1044 expose_state.image_surface_has_main = TRUE;
1046 drawn = TRUE;
1047 drawn_from_main = TRUE;
1048 draw_source = "fresh full-size preview backbuf";
1049 draw_hash = _darkroom_preview_locked.hash;
1050 }
1051 }
1052
1053 /* Rule 3: scaled preview fallback is only for ROI changes. If the user just
1054 * zoomed/panned and main has not recomputed yet, rebuild a ROI-scaled preview
1055 * placeholder so navigation remains responsive. */
1056 if(!drawn && roi_changed && !full_image_backbuf_ready && preview_has_backbuf)
1057 {
1059 "darkroom-preview", TRUE)
1060 && _build_preview_fallback_surface(dev, width, height, border, bg_color, zoom_hash)
1061 && _darkroom_compose_fallback(&compose))
1062 {
1063 expose_state.image_surface_imgid = dev->image_storage.id;
1064 expose_state.image_surface_has_main = FALSE;
1065 drawn = TRUE;
1066 drawn_from_main = FALSE;
1067 draw_source = "fresh preview fallback";
1069 }
1070 }
1071
1072 /* Reuse the cached ROI-scaled preview fallback if it is still geometrically
1073 * valid for this zoom/pan. This avoids rebuilding it every expose while main
1074 * is still catching up. */
1075 if(!drawn && roi_changed && !full_image_backbuf_ready
1076 && _darkroom_preview_fallback_valid(dev, width, height, zoom_hash)
1077 && _darkroom_compose_fallback(&compose))
1078 {
1079 expose_state.image_surface_imgid = dev->image_storage.id;
1080 expose_state.image_surface_has_main = FALSE;
1081 drawn = TRUE;
1082 drawn_from_main = FALSE;
1083 draw_source = "reused preview fallback";
1085 }
1086
1087 /* Rule 4: if no newer source is ready, keep the last valid main frame for
1088 * the same zoom/pan. History-only changes should not glitch to preview or
1089 * background while the new main backbuf is still being produced. */
1090 if(!drawn && _darkroom_locked_main_valid_for_zoom(&expose_state, zoom_hash)
1092 {
1093 expose_state.image_surface_imgid = dev->image_storage.id;
1094 expose_state.image_surface_has_main = TRUE;
1095 drawn = TRUE;
1096 drawn_from_main = TRUE;
1097 draw_source = "reused last main backbuf";
1098 draw_hash = _darkroom_main_locked.hash;
1099 }
1100
1101 if(drawn)
1102 {
1103 /* Persist the last composed frame so later exposes can reuse it if the
1104 * next requested source is temporarily unavailable. */
1105 expose_state.image_surface_has_main = drawn_from_main;
1106 expose_state.image_surface_hash = draw_hash;
1107 _paint_all(cri, cr, dev->image_surface);
1108 dt_print(DT_DEBUG_DEV, "[darkroom] expose drew %s (backbuf hash=%" PRIu64 ")\n",
1109 draw_source, draw_hash);
1110 cairo_restore(cri);
1111 }
1112 else if(dev->image_surface && expose_state.image_surface_imgid == dev->image_storage.id)
1113 {
1114 /* No fresh source this time: repaint the last composed image surface
1115 * rather than clearing to background and waiting for another expose. */
1116 draw_source = expose_state.image_surface_has_main ? "reused last main surface"
1117 : "reused last preview surface";
1118 draw_hash = expose_state.image_surface_hash;
1119 _paint_all(cri, cr, dev->image_surface);
1120 dt_print(DT_DEBUG_DEV, "[darkroom] expose drew %s (backbuf hash=%" PRIu64 ")\n",
1121 draw_source, draw_hash);
1122 cairo_restore(cri);
1123 }
1124 else
1125 {
1126 /* Cold-start / no valid frame cached anywhere yet. */
1127 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
1128 cairo_paint(cr);
1129 expose_state.composed_key = 0;
1130 _paint_all(cri, cr, dev->image_surface);
1131 dt_print(DT_DEBUG_DEV, "[darkroom] expose drew %s (backbuf hash=%" PRIu64 ")\n",
1132 draw_source, draw_hash);
1133 cairo_restore(cri);
1134 }
1135
1136 /* check if we should create a snapshot of view */
1138 {
1139 /* reset the request */
1141
1142 /* validation of snapshot filename */
1143 g_assert(!IS_NULL_PTR(dt_dev_get_global()->proxy.snapshot.filename));
1144
1145 /* Store current image surface to snapshot file.
1146 FIXME: add checks so that we don't make snapshots of preview pipe image surface.
1147 */
1148 const int fd = g_open(dt_dev_get_global()->proxy.snapshot.filename, O_CREAT | O_WRONLY | O_BINARY, 0600);
1149 cairo_surface_write_to_png_stream(dev->image_surface, _write_snapshot_data, GINT_TO_POINTER(fd));
1150 close(fd);
1151 }
1152
1153 // Displaying sample areas if enabled
1154 if(dt_dev_get_global()->color_picker.samples
1155 && (dt_dev_get_global()->color_picker.display_samples
1156 || (dt_dev_get_global()->color_picker.selected_sample &&
1157 dt_dev_get_global()->color_picker.selected_sample != dt_dev_get_global()->color_picker.primary_sample)))
1158 {
1159 _darkroom_pickers_draw(self, cri, width, height, pointerx, pointery, dt_dev_get_global()->color_picker.samples, FALSE);
1160 }
1161
1162 // draw guide lines if needed
1163 if(!dev->gui_module || !(dev->gui_module->flags() & IOP_FLAGS_GUIDES_SPECIAL_DRAW))
1164 {
1165 const float wd = dt_dev_roi_request_preview_width(dev);
1166 const float ht = dt_dev_roi_request_preview_height(dev);
1167 const float scaling = dt_dev_get_overlay_scale(dev);
1168
1169 cairo_save(cri);
1170 // don't draw guides on image margins
1171 dt_dev_clip_roi(dev, cri, width, height);
1172 // place origin at top-left corner of image
1173 dt_dev_rescale_roi(dev, cri, width, height);
1174
1175 // draw guides with backbuffer dimensions, positioning and scaling handled by transformations
1176 dt_guides_draw(cri, 0, 0, wd, ht, scaling);
1177 cairo_restore(cri);
1178 }
1179
1180 if(dt_get_debug_flags() & DT_DEBUG_PERF) dt_show_times(&stage, "[darkroom] image painted");
1181 dt_get_times(&stage);
1182
1183 const gboolean picker_active = dt_iop_color_picker_is_visible(dev);
1184
1185 // draw colorpicker for in focus module or execute module callback hook
1186 // FIXME: draw picker in gui_post_expose() hook in libs/colorpicker.c -- catch would be that live samples would appear over guides, softproof/gamut text overlay would be hidden by picker
1187 if(picker_active)
1188 {
1189 GSList samples = { .data = dt_dev_get_global()->color_picker.primary_sample, .next = NULL };
1190 _darkroom_pickers_draw(self, cri, width, height, pointerx, pointery, &samples, TRUE);
1191 }
1192 else
1193 {
1194 // display mask if we have a current module activated or if the shape manager window is open
1195 const gboolean display_masks = (dev->gui_module && dev->gui_module->enabled)
1197
1198 if(dt_get_debug_flags() & DT_DEBUG_PERF) dt_show_times(&stage, "[darkroom] overlay predicates");
1199
1200 if(dt_masks_get_visible_form(dev) && display_masks)
1201 dt_masks_events_post_expose(dev, dev->gui_module, cri, width, height, pointerx, pointery);
1202
1203 if(dt_get_debug_flags() & DT_DEBUG_PERF) dt_show_times(&stage, "[darkroom] overlays drawn");
1204 dt_get_times(&stage);
1205
1206 // module
1207 if(dev->gui_module && dev->gui_module->enabled && dev->gui_module->gui_post_expose)
1208 {
1209 dev->gui_module->gui_post_expose(dev->gui_module, cri, width, height, pointerx, pointery);
1210
1212 dt_show_times_f(&stage, "[darkroom]", "module %s gui_post_expose", dev->gui_module->op);
1213 }
1214 }
1215
1216 // indicate if we are in gamut check or softproof mode
1218
1219 dt_show_times_f(&start, "[darkroom]", "redraw");
1220}
1221
1222void reset(dt_view_t *self)
1223{
1225}
1226
1227static void _darkroom_log_image_load_error(const int ret)
1228{
1229 switch(ret)
1230 {
1232 dt_control_log(_("Could not load the image source data."));
1233 break;
1235 dt_control_log(_("Could not read image information from the database."));
1236 break;
1237 default:
1238 dt_control_log(_("We could not load the image."));
1239 break;
1240 }
1241}
1242
1243static void _darkroom_image_loaded_callback(gpointer instance, guint request_id, guint result, gpointer user_data)
1244{
1245 dt_view_t *self = (dt_view_t *)user_data;
1246 dt_develop_t *dev = (dt_develop_t *)self->data;
1247 if(request_id == 0) return;
1248 if(dt_view_manager_get_global()->current_view != self) return;
1249
1250
1251 if(result)
1252 {
1253 _darkroom_log_image_load_error((int)result);
1254 return;
1255 }
1256
1257 dt_dev_get_global()->proxy.wb_coeffs[0] = 0.f;
1258
1259 // synch gui and flag pipe as dirty
1260 // this is done here and not in dt_read_history, as it would else be triggered before module->gui_init.
1261 // locks history mutex internally
1264
1267
1271
1272 // Clean & Init the starting point of undo/redo
1276
1277 /* signal that darktable.develop is initialized and ready to be used */
1279
1281
1282 // clear selection, we don't want selections in darkroom
1284
1285 // change active image for global actions (menu)
1288
1290
1292
1294
1296}
1297
1299{
1300 uint32_t num_selected = dt_selection_get_length(dt_selection_get_global());
1301 int32_t imgid = dt_control_get_mouse_over_id();
1302 (void)self;
1303
1305
1306 if(imgid != UNKNOWN_IMAGE)
1307 {
1308 ; // Needed to open image from filmstrip
1309 }
1310 else if(num_selected > 1)
1311 {
1312 dt_control_log(_("The current selection contains more than one image, which is ambiguous.\n"
1313 "Select exactly one image to enter the darkroom."));
1314 return 1;
1315 }
1316 else if(num_selected == 0 && imgid == UNKNOWN_IMAGE)
1317 {
1318 dt_control_log(_("There is no image selected.\n"
1319 "Select exactly one image to enter the darkroom."));
1320 return 1;
1321 }
1322 else
1323 {
1324 // Needed to open image at startup
1326 }
1327
1329
1330 if(imgid < 0)
1331 {
1332 // fail :(
1333 dt_control_log(_("No image to open !"));
1334 return 1;
1335 }
1336
1338 return 0;
1339}
1340
1341static void _dev_change_image(dt_view_t *self, int32_t imgid)
1342{
1343 // Lazy trick to cleanup, reset, reinit, reload everything without
1344 // having to duplicate most of (but not all) the code in leave(),
1345 // try_enter() and enter() : simulate a roundtrip through lighttable.
1346 // This way, all images are loaded through the same path, handled at an higher level.
1347 // It's more robust, although slightly slower than re-initing only what is needed.
1351}
1352
1353static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
1354{
1355 if(imgid > UNKNOWN_IMAGE)
1356 {
1357 // switch images in darkroom mode:
1358 _dev_change_image(user_data, imgid);
1359 }
1360}
1361
1364static void _guides_popover_preshow(gpointer user_data)
1365{
1367}
1368
1369static void _autoset_popover_preshow(gpointer user_data)
1370{
1372}
1373
1374/* overlay color */
1375static void _guides_quickbutton_clicked(GtkWidget *widget, gpointer user_data)
1376{
1377 dt_guides_button_toggled(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
1379}
1380
1381static void _guides_view_changed(gpointer instance, dt_view_t *old_view, dt_view_t *new_view, dt_lib_module_t *self)
1382{
1384}
1385
1393/* display: darkroom-only extras appended to the popover dt_dev_toolbox_create() builds */
1394
1401static void display_mask_checker_1_callback(GtkColorButton *widget, gpointer user_data)
1402{
1403 dt_develop_t *d = (dt_develop_t *)user_data;
1404 GdkRGBA color;
1405 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &color);
1406 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker1/red", color.red);
1407 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker1/green", color.green);
1408 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker1/blue", color.blue);
1409 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1411}
1412
1413static void display_mask_checker_2_callback(GtkColorButton *widget, gpointer user_data)
1414{
1415 dt_develop_t *d = (dt_develop_t *)user_data;
1416 GdkRGBA color;
1417 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &color);
1418 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker2/red", color.red);
1419 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker2/green", color.green);
1420 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker2/blue", color.blue);
1421 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1423}
1424
1425static void display_mask_checker_size_callback(GtkWidget *slider, gpointer user_data)
1426{
1427 dt_develop_t *d = (dt_develop_t *)user_data;
1428 dt_conf_set_int("plugins/darkroom/colorbalancergb/checker/size", (int)dt_bauhaus_slider_get(slider));
1429 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1431}
1432
1433static void display_mask_black_and_white_callback(GtkToggleButton *toggle, gpointer user_data)
1434{
1435 dt_develop_t *d = (dt_develop_t *)user_data;
1436 dt_conf_set_bool("plugins/darkroom/colorbalancergb/mask_preview/greyscaled",
1437 gtk_toggle_button_get_active(toggle));
1438 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1440}
1441
1442static void _darkroom_change_rendering_size(GtkWidget *combobox, gpointer user_data)
1443{
1444 dt_develop_t *d = (dt_develop_t *)user_data;
1445 dt_conf_set_int("darkroom/render_size", dt_bauhaus_combobox_get(combobox));
1447}
1448
1449static void _darkroom_change_mask_rasterization(GtkWidget *combobox, gpointer user_data)
1450{
1451 dt_develop_t *d = (dt_develop_t *)user_data;
1452 dt_conf_set_string("plugins/darkroom/masks/rasterization",
1453 (dt_bauhaus_combobox_get(combobox) == 0) ? "auto" : "never");
1454 /* The step itself is recomputed by dt_dev_roi_request_publish(), on this thread. Only the
1455 * MAIN pipe is affected: the preview's step is always automatic, set from its own ROI, so
1456 * this preference cannot change it and resyncing it would be wasted work. */
1459}
1460
1463#if 0
1464
1465static void _overlay_cycle_callback(dt_action_t *action)
1466{
1467 const int currentval = dt_conf_get_int("darkroom/ui/overlay_color");
1468 const int nextval = (currentval + 1) % 6; // colors can go from 0 to 5
1469 dt_conf_set_int("darkroom/ui/overlay_color", nextval);
1472}
1473
1474static void _toggle_mask_visibility_callback(dt_action_t *action)
1475{
1476 if(dt_gui_widgets_suppressed()) return;
1477
1478 dt_develop_t *dev = dt_action_view(action)->data;
1479 dt_iop_module_t *mod = dev->gui_module;
1480
1481 //retouch and spot removal module use masks differently and have different buttons associated
1482 //keep the shortcuts independent
1483 if(mod && strcmp(mod->so->op, "spots") != 0 && strcmp(mod->so->op, "retouch") != 0)
1484 {
1485 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)mod->blend_data;
1486
1488
1490
1492 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
1493 {
1494 if(bd->masks_shown == DT_MASKS_EDIT_OFF)
1495 bd->masks_shown = DT_MASKS_EDIT_FULL;
1496 else
1497 bd->masks_shown = DT_MASKS_EDIT_OFF;
1498
1499 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), bd->masks_shown != DT_MASKS_EDIT_OFF);
1500 dt_masks_set_edit_mode(mod, bd->masks_shown);
1501
1502 // set all add shape buttons to inactive
1503 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
1504 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), FALSE);
1505 }
1506
1508 }
1509}
1510
1511
1512#endif
1513
1514gboolean _focus_main_image(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1515 GdkModifierType modifier, gpointer data)
1516{
1517 gtk_widget_grab_focus(dt_gui_center_widget());
1518 return TRUE;
1519}
1520
1521gboolean _switch_to_next_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1522 GdkModifierType modifier, gpointer data)
1523{
1524 dt_view_t *view = (dt_view_t *)data;
1525 dt_develop_t *dev = (dt_develop_t *)view->data;
1526 int32_t current_img = dev->image_storage.id;
1527 GList *current_collection = dt_collection_get_all(dt_collection_get_global(), -1);
1528 GList *current_item = g_list_find(current_collection, GINT_TO_POINTER(current_img));
1529
1530 if(current_item && current_item->next)
1531 {
1532 int32_t next_img = GPOINTER_TO_INT(current_item->next->data);
1533 g_list_free(current_collection);
1534 current_collection = NULL;
1535 _dev_change_image(data, next_img);
1536 }
1537 else
1538 {
1539 g_list_free(current_collection);
1540 current_collection = NULL;
1541 }
1542
1543 return TRUE;
1544}
1545
1546gboolean _switch_to_prev_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1547 GdkModifierType modifier, gpointer data)
1548{
1549 dt_view_t *view = (dt_view_t *)data;
1550 dt_develop_t *dev = (dt_develop_t *)view->data;
1551 int32_t current_img = dev->image_storage.id;
1552 GList *current_collection = dt_collection_get_all(dt_collection_get_global(), -1);
1553 GList *current_item = g_list_find(current_collection, GINT_TO_POINTER(current_img));
1554
1555 if(current_item && current_item->prev)
1556 {
1557 int32_t prev_img = GPOINTER_TO_INT(current_item->prev->data);
1558 g_list_free(current_collection);
1559 current_collection = NULL;
1560 _dev_change_image(data, prev_img);
1561 }
1562 else
1563 {
1564 g_list_free(current_collection);
1565 current_collection = NULL;
1566 }
1567
1568 return TRUE;
1569}
1570
1571static void _preview_pipe_finished(gpointer instance, gpointer user_data)
1572{
1573 // Get the mip size that is at most as big as our pipeline backbuf
1575 const gboolean autoset_running_before
1577 const int32_t imgid = dt_dev_get_global()->image_storage.id;
1578 const dt_backbuf_state_t published = dt_dev_backbuf_snapshot(&pipe->backbuf);
1579 dt_mipmap_size_t mip = dt_mipmap_cache_get_fitting_size(published.width, published.height, imgid);
1580
1581 // Check if the cache is ready for that mipmap size.
1583 dt_mipmap_cache_get(&tmp, imgid, mip, DT_MIPMAP_TESTLOCK, 'r');
1584 gboolean cache_ready = !IS_NULL_PTR(tmp.buf);
1586
1587 if(pipe->autoset)
1588 {
1591 }
1592
1593 const gboolean autoset_running_after
1595
1596 // While autoset iterates over modules, avoid spawning thumbnail refresh jobs on each preview completion.
1597 // We refresh once the autoset run is finished and the preview cache reached a stable state.
1598 if(cache_ready && !autoset_running_after)
1599 {
1600 const gboolean autoset_just_finished = autoset_running_before && !autoset_running_after;
1601 if(!autoset_running_before || autoset_just_finished)
1602 {
1603 dt_thumbtable_refresh_thumbnail(dt_gui_get_ui()->thumbtable_lighttable, imgid, TRUE);
1604 dt_thumbtable_refresh_thumbnail(dt_gui_get_ui()->thumbtable_filmstrip, imgid, TRUE);
1605 }
1606 }
1607}
1608
1609/*
1610static gboolean _darkroom_toolbox_button_activate_accel(GtkAccelGroup *accel_group, GObject *accelerable,
1611 guint keyval, GdkModifierType modifier,
1612 gpointer data)
1613{
1614 GtkWidget *button = GTK_WIDGET(data);
1615 if(IS_NULL_PTR(button) || !gtk_widget_is_visible(button) || !gtk_widget_is_sensitive(button)) return FALSE;
1616
1617 gtk_button_clicked(GTK_BUTTON(button));
1618 return TRUE;
1619}
1620*/
1621
1622static void _darkroom_autoset_quickbutton_clicked(GtkButton *button, gpointer user_data)
1623{
1626 fprintf(stdout, "lauching autoset\n");
1627}
1628
1629static gchar *_darkroom_autoset_label(const dt_iop_module_t *module)
1630{
1631 gchar *clean_name = dt_history_item_get_name(module);
1632 gchar *label = g_strdup_printf("%s (%i)", clean_name, module->multi_priority);
1633 dt_free(clean_name);
1634 return label;
1635}
1636
1637static void _darkroom_autoset_module_toggled(GtkToggleButton *toggle, gpointer user_data)
1638{
1639 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1640 dt_iop_autoset_module_set_enabled(module, gtk_toggle_button_get_active(toggle));
1641}
1642
1644{
1646
1647 GList *children = gtk_container_get_children(GTK_CONTAINER(_darkroom_autoset_list));
1648 for(GList *child = children; child; child = g_list_next(child))
1649 gtk_widget_destroy(GTK_WIDGET(child->data));
1650 g_list_free(children);
1651
1652 GtkWidget *title = gtk_label_new(_("Module instances to autoset"));
1653 gtk_box_pack_start(GTK_BOX(_darkroom_autoset_list), title, FALSE, FALSE, 0);
1654 dt_gui_add_class(title, "dt_section_label");
1655
1656 for(GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(modules))
1657 {
1658 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1659 if(IS_NULL_PTR(module->autoset)) continue;
1660
1661 gchar *label = _darkroom_autoset_label(module);
1662 GtkWidget *toggle = gtk_check_button_new_with_label(label);
1663 g_free(label);
1664
1665 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), dt_iop_autoset_module_is_enabled(module));
1666 gtk_box_pack_start(GTK_BOX(_darkroom_autoset_list), toggle, FALSE, FALSE, 0);
1667 g_signal_connect(G_OBJECT(toggle), "toggled", G_CALLBACK(_darkroom_autoset_module_toggled), module);
1668 }
1669
1670 gtk_widget_show_all(_darkroom_autoset_list);
1671}
1672
1673static void _darkroom_autoset_popover_refresh(gpointer instance, gpointer user_data)
1674{
1676}
1677
1679{
1680 dt_develop_t *dev = (dt_develop_t *)self->data;
1681
1682 // Register the GTK-driving handlers for history commits and undo restores (the inverted
1683 // dependency dev_history.c exposes). Done here, not in dt_init(): the call sites
1684 // NULL-check, so a GUI-less process runs with the slots empty instead of registering
1685 // callbacks that must never fire.
1687
1689 G_CALLBACK(_preview_pipe_finished), self);
1690
1691 dt_accels_new_darkroom_action(_switch_to_next_picture, self, NULL, N_("Darkroom/Actions"),
1692 N_("Switch to the next picture"), GDK_KEY_Right, GDK_MOD1_MASK, _("Triggers the action"));
1693 dt_accels_new_darkroom_action(_switch_to_prev_picture, self, NULL, N_("Darkroom/Actions"),
1694 N_("Switch to the previous picture"), GDK_KEY_Left, GDK_MOD1_MASK, _("Triggers the action"));
1695
1696 gchar *path = dt_accels_build_path(_("Darkroom/Actions"), _("Give focus to the main image"));
1698 path, dt_gui_center_widget(), GDK_KEY_Return, 0);
1699 dt_free(path);
1700
1701 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move up"));
1703 path, dt_gui_center_widget(), GDK_KEY_Up, 0);
1704 dt_free(path);
1705
1706 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move up (coarse step)"));
1708 path, dt_gui_center_widget(), GDK_KEY_Up, GDK_SHIFT_MASK);
1709 dt_free(path);
1710
1711 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move up (fine step)"));
1713 path, dt_gui_center_widget(), GDK_KEY_Up, DT_PRIMARY_MASK);
1714 dt_free(path);
1715
1716 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move down"));
1718 path, dt_gui_center_widget(), GDK_KEY_Down, 0);
1719 dt_free(path);
1720
1721 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move down (coarse step)"));
1723 path, dt_gui_center_widget(), GDK_KEY_Down, GDK_SHIFT_MASK);
1724 dt_free(path);
1725
1726 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move down (fine step)"));
1728 path, dt_gui_center_widget(), GDK_KEY_Down, DT_PRIMARY_MASK);
1729 dt_free(path);
1730
1731 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move left"));
1733 path, dt_gui_center_widget(), GDK_KEY_Left, 0);
1734 dt_free(path);
1735
1736 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move left (coarse step)"));
1738 path, dt_gui_center_widget(), GDK_KEY_Left, GDK_SHIFT_MASK);
1739 dt_free(path);
1740
1741 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move left (fine step)"));
1743 path, dt_gui_center_widget(), GDK_KEY_Left, DT_PRIMARY_MASK);
1744 dt_free(path);
1745
1746 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move right"));
1748 path, dt_gui_center_widget(), GDK_KEY_Right, 0);
1749 dt_free(path);
1750
1751 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move right (coarse step)"));
1753 path, dt_gui_center_widget(), GDK_KEY_Right, GDK_SHIFT_MASK);
1754 dt_free(path);
1755
1756 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move right (fine step)"));
1758 path, dt_gui_center_widget(), GDK_KEY_Right, DT_PRIMARY_MASK);
1759 dt_free(path);
1760
1761 path = dt_accels_build_path(_("Darkroom/Main image"), _("Zoom in"));
1763 path, dt_gui_center_widget(), GDK_KEY_plus, DT_PRIMARY_MASK);
1764 dt_free(path);
1765
1766 path = dt_accels_build_path(_("Darkroom/Main image"), _("Zoom out"));
1768 path, dt_gui_center_widget(), GDK_KEY_minus, DT_PRIMARY_MASK);
1769 dt_free(path);
1770 /*
1771 * Add view specific tool buttons
1772 */
1773
1774 static const dt_dev_toolbox_button_t darkroom_toolbox_buttons[]
1777 dt_dev_toolbox_create(dev, DT_VIEW_DARKROOM, darkroom_toolbox_buttons,
1778 G_N_ELEMENTS(darkroom_toolbox_buttons));
1779 dt_dev_toolbox_add_accels(dev, dt_gui_get_accels()->darkroom_accels, N_("Darkroom/Toolbox"),
1780 darkroom_toolbox_buttons, G_N_ELEMENTS(darkroom_toolbox_buttons));
1781
1782 /* display background options: dt_dev_toolbox_create() already built the
1783 * button and a popover with the generic controls (brightness, margins);
1784 * append darkroom-only extras (rendering size, mask preview) to that same
1785 * popover's content box. */
1786 {
1787 GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(dev->display.floating_window));
1788
1789 GtkWidget *rendering;
1791 N_("Rendering size"),
1792 _("Choose at what size the main preview is rendered.\n"
1793 "Full resolution renders the pipeline at the raw original resolution.\n"
1794 "It is pixel-perfect, especially regarding denoising and deblurring, but very slow.\n"
1795 "Scaled renders at screen resolution and is the best trade-off.\n"
1796 "Pixel-level accuracy is guaranteed only when zoomed-in at 100%."),
1797 dt_conf_get_int("darkroom/render_size"),
1799 N_("full resolution (slow)"),
1800 N_("scaled (default)")
1801 );
1802 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(rendering), TRUE, TRUE, 0);
1803
1804 gchar *rasterization = dt_conf_get_string("plugins/darkroom/masks/rasterization");
1805 const int rasterization_index = (!IS_NULL_PTR(rasterization) && !strcmp(rasterization, "never")) ? 1 : 0;
1806 g_free(rasterization);
1807
1808 GtkWidget *mask_rasterization;
1809 DT_BAUHAUS_COMBOBOX_NEW_FULL(dt_bauhaus_get_global(), mask_rasterization, NULL,
1810 N_("Fast drawn mask rasterization"),
1811 _("Choose how finely brush and polygon masks are drawn in the darkroom.\n"
1812 "Auto samples one point per displayed pixel: zoomed out costs less,\n"
1813 "and at 100% or closer masks are pixel-accurate.\n"
1814 "Never keeps pixel accuracy at every zoom level, at some cost when zoomed out.\n"
1815 "Exports, thumbnails and snapshots are always pixel-accurate either way."),
1816 rasterization_index,
1818 N_("auto (default)"),
1819 N_("never")
1820 );
1821 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(mask_rasterization), TRUE, TRUE, 0);
1822
1823 gtk_box_pack_start(GTK_BOX(vbox), dt_ui_section_label_new(_("Mask preview settings")), FALSE, FALSE, 0);
1824
1825 GtkWidget *checker_1_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1826 gtk_box_pack_start(GTK_BOX(checker_1_row), dt_ui_label_new(_("Checkerboard color 1")), TRUE, TRUE, 0);
1827 GtkWidget *checker_1 = gtk_color_button_new();
1828 GdkRGBA checker_color = {
1829 .red = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/red"),
1830 .green = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/green"),
1831 .blue = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/blue"),
1832 .alpha = 1.0
1833 };
1834 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(checker_1), &checker_color);
1835 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(checker_1), FALSE);
1836 gtk_color_button_set_title(GTK_COLOR_BUTTON(checker_1),
1837 _("Select color of the checkerboard from a swatch"));
1838 g_signal_connect(G_OBJECT(checker_1), "color-set", G_CALLBACK(display_mask_checker_1_callback), dev);
1839 gtk_box_pack_start(GTK_BOX(checker_1_row), checker_1, FALSE, FALSE, 0);
1840 gtk_box_pack_start(GTK_BOX(vbox), checker_1_row, FALSE, FALSE, 0);
1841
1842 GtkWidget *checker_2_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1843 gtk_box_pack_start(GTK_BOX(checker_2_row), dt_ui_label_new(_("Checkerboard color 2")), TRUE, TRUE, 0);
1844 GtkWidget *checker_2 = gtk_color_button_new();
1845 checker_color.red = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/red");
1846 checker_color.green = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/green");
1847 checker_color.blue = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/blue");
1848 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(checker_2), &checker_color);
1849 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(checker_2), FALSE);
1850 gtk_color_button_set_title(GTK_COLOR_BUTTON(checker_2),
1851 _("Select color of the checkerboard from a swatch"));
1852 g_signal_connect(G_OBJECT(checker_2), "color-set", G_CALLBACK(display_mask_checker_2_callback), dev);
1853 gtk_box_pack_start(GTK_BOX(checker_2_row), checker_2, FALSE, FALSE, 0);
1854 gtk_box_pack_start(GTK_BOX(vbox), checker_2_row, FALSE, FALSE, 0);
1855
1856 GtkWidget *checker_size
1858 dt_bauhaus_slider_set(checker_size,
1859 dt_conf_get_int("plugins/darkroom/colorbalancergb/checker/size"));
1860 dt_bauhaus_slider_set_format(checker_size, " px");
1861 dt_bauhaus_widget_set_label(checker_size, _("Checkerboard size"));
1862 g_signal_connect(G_OBJECT(checker_size), "value-changed",
1863 G_CALLBACK(display_mask_checker_size_callback), dev);
1864 gtk_box_pack_start(GTK_BOX(vbox), checker_size, TRUE, TRUE, 0);
1865
1866 GtkWidget *black_and_white = gtk_check_button_new_with_label(_("Show a greyscaled mask image"));
1867 gtk_toggle_button_set_active(
1868 GTK_TOGGLE_BUTTON(black_and_white),
1869 dt_conf_get_bool("plugins/darkroom/colorbalancergb/mask_preview/greyscaled"));
1870 g_signal_connect(G_OBJECT(black_and_white), "toggled",
1871 G_CALLBACK(display_mask_black_and_white_callback), dev);
1872 gtk_box_pack_start(GTK_BOX(vbox), black_and_white, FALSE, FALSE, 0);
1873
1874 gtk_widget_show_all(vbox);
1875 }
1876
1878 gtk_widget_set_tooltip_text(_darkroom_ioporder_button, _("show the pipeline node graph"));
1879 g_signal_connect(G_OBJECT(_darkroom_ioporder_button), "clicked",
1883 _darkroom_ioporder_button, N_("Darkroom/Toolbox"),
1884 N_("Show the pipeline node graph"), 0, 0,
1885 _("Triggers the action"));
1886
1887 /* create grid changer popup tool */
1888 {
1889 // the button
1892 gtk_widget_set_tooltip_text(vm->guides_toggle,
1893 _("toggle guide lines\nright click for guides options"));
1895 g_object_ref(vm->guides_popover);
1896 g_signal_connect(G_OBJECT(vm->guides_toggle), "clicked",
1897 G_CALLBACK(_guides_quickbutton_clicked), dev);
1900 g_object_set_data(G_OBJECT(vm->guides_toggle), DT_DEV_TOOLBOX_POPOVER_KEY,
1901 vm->guides_popover);
1905 vm->guides_toggle, vm->guides_toggle, N_("Darkroom/Toolbox"),
1906 N_("Toggle guide lines"), 0, 0,
1907 _("Triggers the action"));
1909 vm->guides_toggle, vm->guides_toggle, N_("Darkroom/Toolbox"),
1910 N_("Focus guide lines options"), 0, 0,
1911 _("Shows the options popover"));
1912 // we want to update button state each time the view change
1914 G_CALLBACK(_guides_view_changed), dev);
1915 }
1916
1917 // Auto-set feature
1918 {
1920
1922 gtk_widget_set_tooltip_text(_darkroom_autoset_button, _("Run autoset on selected modules\nRight click for options"));
1923 g_signal_connect(G_OBJECT(_darkroom_autoset_button), "clicked",
1924 G_CALLBACK(_darkroom_autoset_quickbutton_clicked), dev);
1925 /* Ensure autoset button is placed first in the toolbox. */
1926 g_object_set_data(G_OBJECT(_darkroom_autoset_button), "dt-toolbox-priority", GINT_TO_POINTER(1));
1928
1932 g_object_set_data(G_OBJECT(_darkroom_autoset_button), DT_DEV_TOOLBOX_POPOVER_KEY,
1934 /*
1935 dt_accels_new_darkroom_action(dt_dev_toolbox_activate_accel, _darkroom_autoset_button,
1936 _darkroom_autoset_button, N_("Darkroom/Toolbox"),
1937 N_("Show the pipeline node graph"), 0, 0,
1938 _("Triggers the action"));
1939 */
1940 _darkroom_autoset_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1941 gtk_container_add(GTK_CONTAINER(_darkroom_autoset_popover), _darkroom_autoset_list);
1944
1946 G_CALLBACK(_darkroom_autoset_popover_refresh), dev);
1948 G_CALLBACK(_darkroom_autoset_popover_refresh), dev);
1950 G_CALLBACK(_darkroom_autoset_popover_refresh), dev);
1951 }
1952
1955 dt_dev_viewport_set_border(dev, DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size")));
1956}
1957
1959{
1960 dt_accels_t *accels = dt_gui_get_accels();
1961 if(!dt_widget_scroll_focus() || accels->active_key.accel_key == 0) return FALSE;
1962
1963 // When declaring shortcuts, bauhaus widgets write their accel path into a private data field
1964 gchar *accel_path = g_object_get_data(G_OBJECT(dt_widget_scroll_focus()), "accel-path");
1965
1966 // Find if the registered accel keys matches currently pressed keys
1967 GtkAccelKey key = { 0 };
1968 return gtk_accel_map_lookup_entry(accel_path, &key)
1969 && key.accel_key == accels->active_key.accel_key
1970 && key.accel_mods == accels->active_key.accel_mods;
1971}
1972
1973
1974// If a bauhaus widget has the scroll focus from a keyboard shortcut,
1975// and the combination of keys attached to its accel path
1976// is still pressed, then we redirect any scroll event in the window to this widget.
1977// Warning: if mouse is over the central widget, central widget takes precedence over scrolling.
1978gboolean _scroll_on_focus(GdkEventScroll event, void *data)
1979{
1981 {
1982 // Pass-through the scrolling event to the scrolling handler of the widget
1983 gboolean ret;
1984 g_signal_emit_by_name(G_OBJECT(dt_widget_scroll_focus()), "scroll-event", &event, &ret);
1985 return ret;
1986 }
1987
1988 return FALSE;
1989}
1990
1991
1992void enter(dt_view_t *self)
1993{
1994 // Flush all background jobs (thumbnails generation) to spare resources for interactivity
1996
1997 dt_print(DT_DEBUG_CONTROL, "[run_job+] 11 %f in darkroom mode\n", dt_get_wtime());
1998 dt_develop_t *dev = (dt_develop_t *)self->data;
1999 dev->exit = 0;
2001
2002 // We need to init forms before we init module blending GUI
2003 dt_masks_gui_init(dev);
2004 dev->gui_module = NULL;
2005
2006 if(IS_NULL_PTR(dev->iop))
2007 dev->iop = dt_dev_load_modules(dev);
2008
2009 // Add IOP modules to the plugin list
2010 char option[1024];
2011 const char *active_plugin = dt_conf_get_string_const("plugins/darkroom/active");
2012 for(const GList *modules = g_list_first(dev->iop); modules; modules = g_list_next(modules))
2013 {
2014 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2015
2016 /* initialize gui if iop have one defined */
2017 if(!dt_iop_is_hidden(module))
2018 {
2019 dt_iop_gui_init(module);
2021
2022 if(module->multi_priority == 0)
2023 {
2024 snprintf(option, sizeof(option), "plugins/darkroom/%s/expanded", module->op);
2025 module->gui->expanded = dt_conf_get_bool(option);
2027
2028 if(active_plugin && !strcmp(module->op, active_plugin))
2030 }
2031 }
2032 }
2033
2034 // just make sure at this stage we have only history info into the undo, all automatic
2035 // tagging should be ignored.
2037
2039
2040 // Reset focus to center view
2042
2043 // Attach shortcuts to new widgets
2047
2048 // Attach bauhaus default signal callback to IOP
2050
2051 // This gets the first selected ID to scroll where relevant, so
2052 // runs it before clearing the selection
2053 dt_thumbtable_show(dt_gui_get_ui()->thumbtable_filmstrip);
2054 gtk_widget_show(dt_gui_center_widget());
2055 dt_thumbtable_update_parent(dt_gui_get_ui()->thumbtable_filmstrip);
2056
2057 /* connect signal for filmstrip image activate */
2060
2061 gtk_widget_grab_focus(dt_gui_center_widget()); // ensure the center view has focus for keybindings to work
2062
2063 const int32_t imgid = _darkroom_pending_imgid;
2067 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
2068 int ret = dt_dev_load_image(dt_dev_get_global(), imgid);
2069 _darkroom_image_loaded_callback(NULL, imgid, ret, self);
2070}
2071
2072void leave(dt_view_t *self)
2073{
2074 dt_develop_t *dev = (dt_develop_t *)self->data;
2077 "darkroom-leave-autoset");
2083 // Last moment at which committing is still safe -- everything below tears down
2084 // dev->pipe / dev->iop / dev->history. Run the pending edit rather than lose it.
2086
2088 if(dev->image_surface) cairo_surface_destroy(dev->image_surface);
2089 dev->image_surface = NULL;
2090
2091 // Send all pipeline shutdown signals first
2092 dev->exit = 1;
2095 dev->pipelines_started = FALSE;
2096
2097 /* dev->exit / the shutdown atomics above are only checked by the darkroom worker thread
2098 * (dt_dev_darkroom_pipeline(), running in the DT_CTL_WORKER_DARKROOM job) between loop
2099 * iterations and between servicing each pipe -- they do not preempt it mid-flight. Below,
2100 * this function tears down dev->pipe/preview_pipe nodes, dev->iop and dev->history straight
2101 * from the GUI thread. Proceeding before that worker thread has actually returned races it:
2102 * dt_dev_pixelpipe_set_input() and the history-hash resync in _resync_pipe_with_history() touch
2103 * pipe->nodes and pipe->dev->iop without holding busy_mutex, so the worker can still be
2104 * mid-access on structures we are about to free, corrupting unrelated heap state (observed as
2105 * a crash inside an IOP module's cleanup_pipe() far away from the actual race). Block here
2106 * until the worker has fully exited both pipes' service loop. */
2108 && dt_control_running())
2109 dt_iop_nap(10000); // 10 ms
2110
2111 dt_dev_pixelpipe_cache_wait_dump_pending("darkroom-leave-before-cleanup");
2112
2113 /* Stop module-owned background threads that may still be mutating the pipe, dev or history (e.g.
2114 * drawlayer's asynchronous paint/commit worker) BEFORE we tear down pipeline nodes and history
2115 * below. Otherwise an in-flight commit resync can run a pipeline synch against pieces this function
2116 * is about to free, faulting in whatever module commits next. */
2117 for(GList *m = dev->iop; m; m = g_list_next(m))
2118 {
2119 dt_iop_module_t *mod = (dt_iop_module_t *)m->data;
2120 if(mod && mod->quiesce) mod->quiesce(mod);
2121 }
2122
2124
2125 // While we wait for possible pipelines to finish,
2126 // do the GUI cleaning.
2127
2128 // store last active plugin:
2129 if(dt_dev_get_global()->gui_module)
2130 dt_conf_set_string("plugins/darkroom/active", dt_dev_get_global()->gui_module->op);
2131 else
2132 dt_conf_set_string("plugins/darkroom/active", "");
2133
2134 // Hide the popover floating windows
2135 gtk_widget_hide(dev->overexposed.floating_window);
2136 gtk_widget_hide(dev->rawoverexposed.floating_window);
2137 gtk_widget_hide(dev->profile.floating_window);
2138
2139 // Detach the default callback for bauhaus widgets
2141
2142 /* disconnect from filmstrip image activate */
2144 (gpointer)self);
2145
2147
2148 if(dt_dev_get_global()->color_picker.picker)
2149 dt_iop_color_picker_reset(dt_dev_get_global()->color_picker.picker->module, FALSE);
2150
2151 // Detach shortcuts
2153
2154 // Restore the previous selection
2157
2158 dt_thumbtable_hide(dt_gui_get_ui()->thumbtable_filmstrip);
2159 gtk_widget_hide(dt_gui_center_widget());
2160
2161 // Pipeline nodes reference modules from dev->iop
2162 // we need to destroy objects referencing modules
2163 // before destroying the actual modules being referenced.
2169
2176
2177 /* Device-side cache payloads are only an acceleration layer. Once darkroom
2178 * leaves, drop the cl_mem objects these pipes produced -- but only on the
2179 * device(s) they themselves last ran on, so we never touch cache entries
2180 * another, still-running pipe (e.g. a background thumbnail export) holds on
2181 * its own OpenCL device. */
2183 if(dev->preview_pipe->last_devid != dev->pipe->last_devid)
2185
2189
2190 // Not sure why using g_list_free_full() here shits the bed
2191 while(dev->iop)
2192 {
2193 dt_iop_module_t *module = (dt_iop_module_t *)(dev->iop->data);
2194 if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
2195 dt_iop_cleanup_module(module);
2196 dt_free(module);
2197 dev->iop = g_list_delete_link(dev->iop, dev->iop);
2198 }
2199 while(dev->alliop)
2200 {
2202 dt_free(dev->alliop->data);
2203 dev->alliop = g_list_delete_link(dev->alliop, dev->alliop);
2204 }
2205 dev->iop = dev->alliop = NULL;
2206
2207 // cleanup visible masks
2208 if(dev->form_gui)
2209 {
2210 dev->gui_module = NULL; // modules have already been g_free()
2212 }
2213
2214 // clear masks
2216
2217 // Fetch the new thumbnail if needed. Ensure it runs after we save history.
2218 dt_thumbtable_refresh_thumbnail(dt_gui_get_ui()->thumbtable_lighttable, dt_dev_get_global()->image_storage.id, TRUE);
2220
2221 // Release the cache entries for histogram buffers
2224
2227
2230
2231 /* GUI backbuffers were already released when each pipeline was quiesced above. Keep the view-side teardown
2232 * free of extra unref paths so pipeline ownership stays centralized. */
2233
2234
2235 dt_print(DT_DEBUG_CONTROL, "[run_job-] 11 %f in darkroom mode\n", dt_get_wtime());
2236}
2237
2238// Leave central view
2240{
2241 // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image.
2242 dt_develop_t *dev = (dt_develop_t *)self->data;
2246
2248
2249 if(gui->pan_edge.timeout_source
2252 {
2253 gui->pan_edge.velocity[0] = 0.0f;
2254 gui->pan_edge.velocity[1] = 0.0f;
2255 gui->pan_edge.last_time_us = 0;
2256 if(gui->pan_edge.timeout_source)
2257 {
2258 g_source_remove(gui->pan_edge.timeout_source);
2259 gui->pan_edge.timeout_source = 0;
2260 }
2261 gui->pan_edge.view = NULL;
2263 }
2264 else
2266
2267 // masks
2268 gboolean handled = FALSE;
2270 handled = TRUE;
2271 // module
2272 else if(dev->gui_module && dev->gui_module->mouse_leave
2273 && dev->gui_module->mouse_leave(dev->gui_module))
2274 handled = TRUE;
2275
2276 if(handled)
2278
2279 // reset any changes the selected plugin might have made.
2281 dt_control_change_cursor(GDK_LEFT_PTR);
2282}
2283
2284static gboolean _is_in_frame(const int width, const int height, const int x, const int y)
2285{
2286 return !((x < -DT_PIXEL_APPLY_DPI(2)) ||
2287 (x > (width + DT_PIXEL_APPLY_DPI(4))) ||
2288 (y < -DT_PIXEL_APPLY_DPI(2)) ||
2289 (y > (height + DT_PIXEL_APPLY_DPI(4))));
2290}
2291
2292/* This helper function tests for a position to be within the displayed area
2293 of an image. To avoid "border cases" we accept values to be slightly out of area too.
2294*/
2295static gboolean mouse_in_imagearea(dt_view_t *self, double x, double y)
2296{
2297 dt_develop_t *dev = (dt_develop_t *)self->data;
2298 float image_box[4] = { 0.0f };
2299 dt_dev_get_image_box_in_widget(dev, self->width, self->height, image_box);
2300
2301 return _is_in_frame(image_box[2], image_box[3], round(x - image_box[0]), round(y - image_box[1]));
2302}
2303
2304static gboolean mouse_in_actionarea(dt_view_t *self, double x, double y)
2305{
2306 return _is_in_frame(self->width, self->height, round(x), round(y));
2307}
2308
2309static void _darkroom_set_default_cursor(dt_view_t *self, double x, double y)
2310{
2311 if(mouse_in_imagearea(self, x, y))
2313 else if(mouse_in_actionarea(self, x, y))
2315 else
2317}
2318
2320{
2321 dt_develop_t *dev = (dt_develop_t *)self->data;
2323}
2324
2325// The module the pending mask-edit history commit must be addressed to, captured when the
2326// commit is queued (i.e. when the mask event actually happened). The commit itself fires after
2327// an adaptive throttle timeout, and dev->gui_module may have moved to another module meanwhile:
2328// addressing the commit to the *new* focus then records the mask edit on a module that owns no
2329// forms, whose history item hash does not change — the resync takes the cheap top-item path and
2330// the real owner's piece hash is never refreshed, so the pipe keeps serving the pre-edit
2331// cacheline (issue #1060 family: "the mask does nothing until you nudge a slider").
2333
2334/* Re-render the mask's owner from the LIVE geometry, without writing history.
2335 *
2336 * A drawn shape's geometry lives in dev->forms, not in any module's params, and the pipe reads
2337 * it fresh on every recompute (dt_dev_pixelpipe_process() re-snapshots pipe->forms). What the
2338 * pipe does NOT do on its own is notice that the geometry moved: a piece is re-keyed only when
2339 * its history item changes, so a drag used to be made visible by committing history on a
2340 * throttle, mid-drag -- one history rewrite, one DB write job and one full synch_top per
2341 * throttle tick, with the mask's owner re-hashed against the forms as a side effect.
2342 *
2343 * This is the drawlayer brush's route instead (see _publish_backend_progress() in
2344 * iop/drawlayer/worker.c): publish the owner's params through the transient channel and flag
2345 * the main pipe TOP_CHANGED. That routes the resync through _sync_focused_in_place(), which
2346 * re-commits the ONE focused piece against the live dev->forms -- dt_iop_compute_blendop_hash()
2347 * folds the group's geometry in -- so the piece re-keys from the moved shape and only it and
2348 * its downstream recompute. The params published are the module's own, unchanged: it is the
2349 * forms that moved, and the transient slot is only the ticket onto the focused-piece path.
2350 *
2351 * Flagged without the killswitch (dt_dev_pixelpipe_or_changed, not _change_pipe), as the
2352 * drawlayer heartbeat does: a fast drag must not abort every in-flight render or none would
2353 * ever finish. History is written once, when the interaction ends -- see the commit below,
2354 * which clears this slot so the pipe goes back to rendering the committed state. */
2356{
2357 dt_iop_module_t *module = dev->gui_module;
2358 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->params) || module->params_size <= 0) return;
2359 dt_dev_transient_params_set(module, module->params, (size_t)module->params_size, NULL, 0);
2361}
2362
2363static void _delayed_history_commit(gpointer data)
2364{
2365 dt_develop_t *dev = (dt_develop_t *)data;
2366
2367 /* Not while a shape is still being dragged. The throttle fires about once per pipe render,
2368 * which used to land a history commit -- and the resync it triggers -- in the middle of the
2369 * drag; the transient publish above is what feeds the pipe meanwhile, so the commit can wait
2370 * for the button to come up. Re-queueing re-arms the throttle; the pending module is left in
2371 * place so the eventual commit still lands on the owner of the interaction. */
2373 {
2375 return;
2376 }
2377
2378 dt_iop_module_t *module = _pending_mask_commit_module;
2380 // The captured module can be gone by now (image switch, instance removal): only trust it if
2381 // it is still a live member of dev->iop, else fall back to the current focus.
2382 if(IS_NULL_PTR(module) || IS_NULL_PTR(g_list_find(dev->iop, module)))
2383 module = dev->gui_module;
2384
2385 // The interaction is over: drop the transient ticket so the pipe renders the committed
2386 // history state again. Cleared BEFORE the commit, whose resync must come from history --
2387 // and cleared even when nothing changed, so a no-op drag does not leave the slot occupied.
2388 const gboolean was_transient = !IS_NULL_PTR(module) && dt_dev_transient_params_active(dev, module);
2389 if(was_transient) dt_dev_transient_params_clear(module);
2390
2391 // Figure out if an history item needs to be added
2392 // aka drawn masks have changed somehow. This is more expensive
2393 // but more reliable than handling individually all editing operations
2394 // in all callbacks in all possible mask types.
2395 // (dt_dev_masks_update_hash takes its own, correctly-scoped masks_mutex lock.)
2397
2398 if(dev->forms_changed)
2399 dt_dev_add_history_item(dev, module, FALSE, TRUE);
2400 else if(was_transient)
2401 // Nothing to commit, but the pipe was rendering from the transient slot: re-sync it from
2402 // history so it does not keep a render keyed to a slot that no longer exists.
2404}
2405
2406// Queue the throttled mask-edit history commit, capturing its target module NOW. Every mask
2407// event handler must use this instead of calling dt_gui_throttle_queue() directly, so the
2408// commit lands on the module that owned the mask interaction, not on whichever module holds
2409// the focus when the throttle fires.
2415
2425
2426static float _darkroom_edge_pan_velocity(const double position, const double size, const float margin)
2427{
2428 const double near_edge = position < margin ? margin - position
2429 : position > size - margin ? position - (size - margin)
2430 : 0.0;
2431 if(near_edge <= 0.0) return 0.0f;
2432
2433 const float edge_distance = CLAMPF(near_edge / margin, 0.0f, 1.0f);
2434 const float velocity = 0.10f + 0.90f * edge_distance * edge_distance;
2435 return position < margin ? -velocity : velocity;
2436}
2437
2439{
2441 if(IS_NULL_PTR(gui) || IS_NULL_PTR(dev)) return FALSE;
2442
2443 dt_masks_form_gui_t *form_gui = dev->form_gui;
2444 const gboolean mouse_action = gui->mouse.is_dragging && !gui->mouse.is_painting;
2445 const gboolean creating_shape_mode = !IS_NULL_PTR(form_gui) && form_gui->creation && !gui->mouse.is_painting;
2446
2447 return creating_shape_mode || mouse_action;
2448}
2449
2458 const double pointer_x,
2459 const double pointer_y,
2460 const int width,
2461 const int height,
2463{
2466 if(IS_NULL_PTR(gui) || IS_NULL_PTR(ctl) || IS_NULL_PTR(self)) return;
2467
2468 dt_develop_t *dev = (dt_develop_t *)self->data;
2469 if(IS_NULL_PTR(dev)) return;
2470
2471 // recheck the global eligibility conditions
2473
2474 if(!gui->pan_edge.enabled
2476 || dt_dev_viewport_scaling(dev) <= 1.0f)
2477 return;
2478
2479 float image_box[4] = { 0.0f };
2481 const double image_x = pointer_x - image_box[0];
2482 const double image_y = pointer_y - image_box[1];
2483
2484 edge->dev = dev;
2485 edge->drag = TRUE;
2486 edge->inside_image = mouse_in_imagearea(self, pointer_x, pointer_y);
2487 const gboolean inside_action_area = mouse_in_actionarea(self, pointer_x, pointer_y)
2488 && image_box[2] > 0.0f && image_box[3] > 0.0f;
2489 edge->margin = inside_action_area
2490 ? CLAMPF(MIN(image_box[2], image_box[3]) / 3, 1, DARKROOM_EDGE_PAN_MARGIN_PX)
2491 : 0.0f;
2492 edge->in_margin = inside_action_area
2493 && edge->margin > 0.0f
2494 && (image_x < edge->margin
2495 || image_x > (double)image_box[2] - edge->margin
2496 || image_y < edge->margin
2497 || image_y > (double)image_box[3] - edge->margin);
2498
2499 if(!edge->in_margin) return;
2500
2501 edge->velocity[0] = _darkroom_edge_pan_velocity(image_x, image_box[2], edge->margin);
2502 edge->velocity[1] = _darkroom_edge_pan_velocity(image_y, image_box[3], edge->margin);
2503}
2504
2513 const double pointer_x,
2514 const double pointer_y,
2515 const int width,
2516 const int height)
2517{
2520 darkroom_edge_pan_test_t edge = { 0 };
2521 _darkroom_edge_pan_update_state(self, pointer_x, pointer_y, width, height, &edge);
2522
2523 if(IS_NULL_PTR(gui))
2524 return FALSE;
2525
2526 // reset and exit conditions
2527 if(!gui->pan_edge.enabled || IS_NULL_PTR(self) || IS_NULL_PTR(ctl) || !edge.drag)
2528 {
2529 gui->pan_edge.timeout_source = 0;
2531 return FALSE;
2532 }
2533
2534 if(!edge.in_margin)
2535 {
2536 // Leaving the edge band stops automatic ROI motion and immediately hands
2537 // control back to the regular drag path.
2538 gui->pan_edge.timeout_source = 0;
2539 gui->pan_edge.view = self;
2540 gui->pan_edge.velocity[0] = 0.0f;
2541 gui->pan_edge.velocity[1] = 0.0f;
2542 gui->pan_edge.last_time_us = 0;
2544 ctl->button_x = pointer_x;
2545 ctl->button_y = pointer_y;
2546 return FALSE;
2547 }
2548
2549 if(gui->pan_edge.velocity[0] == 0.0f && gui->pan_edge.velocity[1] == 0.0f)
2550 {
2551 // A mouse event may have stopped edge-pan before this tick runs.
2552 // End the timeout without releasing the stored drag reference position.
2553 gui->pan_edge.timeout_source = 0;
2554 gui->pan_edge.last_time_us = 0;
2555 ctl->button_x = pointer_x;
2556 ctl->button_y = pointer_y;
2557 return FALSE;
2558 }
2559
2560 const gint64 now_us = g_get_monotonic_time();
2561 const float elapsed_s = CLAMPF((now_us - gui->pan_edge.last_time_us) / 1000000.0f, 0.001f, 0.100f);
2562 gui->pan_edge.last_time_us = now_us;
2563
2564 float delta[2] = { gui->pan_edge.velocity[0] * DARKROOM_EDGE_PAN_SPEED_PX_PER_S * elapsed_s,
2566 };
2567 dt_develop_t *dev = edge.dev;
2569
2570 float roi[2] = { dt_dev_viewport_center_x(dev) + delta[0] / (float)dt_dev_geometry_processed_width(dev),
2572 };
2573 dt_dev_check_zoom_pos_bounds(dev, &roi[0], &roi[1], NULL, NULL);
2574
2575 ctl->button_x = pointer_x;
2576 ctl->button_y = pointer_y;
2577
2578 if(dt_dev_viewport_center_x(dev) != roi[0] || dt_dev_viewport_center_y(dev) != roi[1])
2579 {
2580 dt_dev_viewport_set_center(dev, roi[0], roi[1]);
2581 // Updating ctl->button_x/y changes the cursor position, which is the same as a mouse move event.
2582 mouse_moved(self, pointer_x, pointer_y, 1.0, 0);
2583 //dt_control_queue_redraw_center();
2585 }
2586
2587 return TRUE;
2588}
2589
2596static gboolean _darkroom_edge_pan_tick(gpointer user_data)
2597{
2599 if(IS_NULL_PTR(gui))
2600 return FALSE;
2601
2602
2603 // Read the live pointer position instead of ctl->button_x/y: the latter only
2604 // stores the last mouse event, while the timeout must stop even if no event follows.
2605 dt_view_t *self = gui->pan_edge.view;
2606 GtkWidget *center = dt_gui_center_widget();
2607 GdkWindow *window = IS_NULL_PTR(center) ? NULL : gtk_widget_get_window(center);
2608 GdkDisplay *display = IS_NULL_PTR(window) ? NULL : gdk_window_get_display(window);
2609 GdkSeat *seat = IS_NULL_PTR(display) ? NULL : gdk_display_get_default_seat(display);
2610 GdkDevice *pointer = IS_NULL_PTR(seat) ? NULL : gdk_seat_get_pointer(seat);
2611 int pointer_x = 0;
2612 int pointer_y = 0;
2613 GtkAllocation allocation = { 0 };
2614
2615 if(IS_NULL_PTR(window) || IS_NULL_PTR(pointer))
2616 {
2617 gui->pan_edge.timeout_source = 0;
2619 return FALSE;
2620 }
2621
2622 gdk_window_get_device_position(window, pointer, &pointer_x, &pointer_y, NULL);
2623 gtk_widget_get_allocation(center, &allocation);
2624
2625 return _darkroom_edge_pan_apply(self, pointer_x, pointer_y, allocation.width, allocation.height);
2626}
2627
2628void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
2629{
2630 dt_develop_t *dev = (dt_develop_t *)self->data;
2633
2634 const gboolean picker_active = dt_iop_color_picker_is_visible(dev);
2635
2636 // change cursor appearance by default
2638 gboolean handled = FALSE;
2639 gboolean masks_handled = FALSE;
2640
2641 if(picker_active && dt_control_button_down(1))
2642 {
2643 // module requested a color box
2644 if(mouse_in_imagearea(self, x, y))
2645 {
2647 gboolean sample_changed = FALSE;
2648 float mouse_point[2] = { (float)x, (float)y };
2649 dt_dev_coordinates_widget_to_image_norm(dev, mouse_point, 1);
2650 const float delta[2] = {
2653 };
2654
2655 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2656 {
2657 float anchor[2] = { 0.0f };
2659 const float box[4] = {
2660 fmaxf(0.0, MIN(anchor[0], mouse_point[0]) - delta[0]),
2661 fmaxf(0.0, MIN(anchor[1], mouse_point[1]) - delta[1]),
2662 fminf(1.0, MAX(anchor[0], mouse_point[0]) + delta[0]),
2663 fminf(1.0, MAX(anchor[1], mouse_point[1]) + delta[1])
2664 };
2665 dt_boundingbox_t image_box = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
2667
2668 for(int k = 0; k < 4; k++)
2669 sample_changed |= (image_box[k] != box[k]);
2670
2671 if(sample_changed)
2673 }
2674 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2675 {
2676 float image_point[2] = { 0.0f };
2677 _darkroom_sample_raw_point_to_image_norm(sample, image_point);
2678 sample_changed = memcmp(image_point, mouse_point, sizeof(mouse_point)) != 0;
2679 if(sample_changed)
2681 }
2682 }
2683 handled = TRUE;
2684 }
2685 else if(picker_active)
2686 {
2687 // Keep module-specific hover overlays and live-edit cursors disabled while the picker owns the center view.
2688 handled = TRUE;
2689 }
2690
2691 // masks
2692 else if(dt_masks_get_visible_form(dev)
2693 && dt_masks_events_mouse_moved(dev, dev->gui_module, x, y, pressure, which))
2694 {
2695 masks_handled = TRUE;
2696 // There is no shape dragging in creation mode, so no need to commit history.
2697 if(!dev->form_gui->creation)
2698 {
2699 // A shape being dragged is re-rendered live from the transient slot on every motion;
2700 // hover motion changes no geometry and gets nothing. The history commit queued below
2701 // defers itself until the drag ends (see _delayed_history_commit).
2705 }
2706
2707 handled = TRUE;
2708 }
2709
2710 // module
2711 else if(dev->gui_module && dev->gui_module->mouse_moved
2712 &&dev->gui_module->mouse_moved(dev->gui_module, x, y, pressure, which))
2713 {
2714 handled = TRUE;
2715 }
2716
2717 darkroom_edge_pan_test_t edge = { 0 };
2718 _darkroom_edge_pan_update_state(self, x, y, self->width, self->height, &edge);
2719
2720 if(!edge.drag && (gui->pan_edge.timeout_source || gui->pan_edge.block_normal_pan))
2721 {
2723 }
2724
2725 if(edge.drag && !edge.in_margin && gui->pan_edge.timeout_source)
2726 {
2727 /* The drag has already activated edge-pan. Leaving the edge band must stop
2728 timeout-driven ROI motion immediately and restore normal pan right away. */
2729 gui->pan_edge.view = self;
2730 gui->pan_edge.velocity[0] = 0.0f;
2731 gui->pan_edge.velocity[1] = 0.0f;
2732 gui->pan_edge.last_time_us = 0;
2734 if(gui->pan_edge.timeout_source)
2735 {
2736 g_source_remove(gui->pan_edge.timeout_source);
2737 gui->pan_edge.timeout_source = 0;
2738 }
2739 ctl->button_x = x;
2740 ctl->button_y = y;
2741 }
2742
2743 // While a left-button drag is in the edge band, the timeout owns ROI motion.
2744 // The current mouse position only updates the velocity that each tick applies.
2745 if(edge.in_margin)
2746 {
2747 gui->pan_edge.view = self;
2749 gui->pan_edge.velocity[0] = edge.velocity[0];
2750 gui->pan_edge.velocity[1] = edge.velocity[1];
2751 if(!gui->pan_edge.timeout_source)
2752 {
2753 gui->pan_edge.last_time_us = g_get_monotonic_time();
2756 }
2757 }
2758
2760
2762 {
2763 float delta[2] = { x - ctl->button_x, y - ctl->button_y };
2765
2766 float roi[2] = { dt_dev_viewport_center_x(dev) - (delta[0] / dt_dev_geometry_processed_width(dev)),
2768 dt_dev_check_zoom_pos_bounds(dev, &roi[0], &roi[1], NULL, NULL);
2769
2770 dt_dev_viewport_set_center(dev, roi[0], roi[1]);
2771 ctl->button_x = x;
2772 ctl->button_y = y;
2773
2775 return;
2776 }
2777
2778 if(handled)
2779 {
2780 /* a motion the masks handled changed the overlay and nothing else: repaint what it touched,
2781 * not the image under the whole window */
2782 if(masks_handled)
2784 else
2786 return;
2787 }
2788
2789 // Edge-pan owns ROI motion only while the timeout is actively driving updates.
2791 {
2792 ctl->button_x = x;
2793 ctl->button_y = y;
2794 return;
2795 }
2796
2797 // panning with left mouse button.
2798 // _darkroom_center_pan_drag is what makes this OUR drag: dt_control_button_down() reports the
2799 // device's state, so without it a button pressed elsewhere and still held on entry would pan
2800 // the image from a stale ctl->button_x/y anchor. The sibling path at :2563 always had this
2801 // guard; here it was implicit, back when button_down was set only by our own press handler.
2803 {
2804 float delta[2] = { x - ctl->button_x, y - ctl->button_y };
2806
2807 // new roi position in full image scale
2808 float roi[2] = { dt_dev_viewport_center_x(dev) - (delta[0] / dt_dev_geometry_processed_width(dev)),
2810 dt_dev_check_zoom_pos_bounds(dev, &roi[0], &roi[1], NULL, NULL);
2811
2812 dt_dev_viewport_set_center(dev, roi[0], roi[1]);
2813
2814 // update clicked position
2815 ctl->button_x = x;
2816 ctl->button_y = y;
2817
2819 }
2820}
2821
2822
2823int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
2824{
2825 dt_develop_t *dev = (dt_develop_t *)self->data;
2826
2827 dt_print(DT_DEBUG_INPUT, "[darkroom] button released which: %d state: %d x: %.2f y: %.2f\n",
2828 which, state, x, y);
2829
2830 if(which == 1)
2831 {
2835
2837 }
2838
2839 if(dt_iop_color_picker_is_visible(dev) && which == 1)
2840 {
2841 // only sample box picker at end, for speed
2843 dt_control_queue_cursor(GDK_LEFT_PTR);
2844
2846 return 1;
2847 }
2848 // masks
2850 && dt_masks_events_button_released(dev, dev->gui_module, x, y, which, state))
2851 {
2852 // Change on mask parameters and image output.
2854 dt_dev_pixelpipe_update_history_preview(dev); // Needed if mask selection changed
2856 return 1;
2857 }
2858
2859 // module
2860 if(dev->gui_module && dev->gui_module->enabled && dev->gui_module->button_released
2861 && dev->gui_module->button_released(dev->gui_module, x, y, which, state))
2862 {
2863 // Click in modules should handle history changes internally.
2864 return 1;
2865 }
2866
2867 return 0;
2868}
2869
2870
2871int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
2872{
2874 dt_develop_t *dev = (dt_develop_t *)self->data;
2875
2876 dt_print(DT_DEBUG_INPUT, "[darkroom] button pressed which: %d type: %d x: %.2f y: %.2f pressure: %f\n",
2877 which, type, x, y, pressure);
2878
2879 // Grab focus on any click so we can interact from keyboard
2880 gtk_widget_grab_focus(dt_gui_center_widget());
2881 if(which == 1)
2882 {
2886
2887 }
2888
2890 {
2891 float point[2] = { (float)x, (float)y };
2893
2894 const float zoom_scale = dt_dev_get_fit_scale(dev);
2895 float handle[2] = { 6.0f, 6.0f };
2897 handle[0] /= dt_dev_geometry_processed_width(dev);
2898 handle[1] /= dt_dev_geometry_processed_height(dev);
2899
2900 if(which == 1)
2901 {
2902 if(mouse_in_imagearea(self, x, y))
2903 {
2904 // The default box will be a square with 1% of the image width
2905 const float delta_x = 0.01f;
2906 const float delta_y = delta_x * (float)dt_dev_geometry_processed_width(dev) / (float)dt_dev_geometry_processed_height(dev);
2907
2908 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2909 {
2910 /* Box drags need one anchor corner stored in sample->point so mouse motion can stretch
2911 the rectangle against that fixed corner. Keep that anchor local to the darkroom drag
2912 state, but let the picker API own the actual sampling geometry and update signaling. */
2913 float raw_point[2] = { point[0], point[1] };
2915 memcpy(sample->point, raw_point, sizeof(raw_point));
2916
2917 dt_boundingbox_t image_box = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
2919
2920 // this is slightly more than as drawn, to give room for slop
2921 gboolean on_corner_prev_box = TRUE;
2922 float opposite[2] = { 0.0f };
2923
2924 if(fabsf(point[0] - image_box[0]) <= handle[0])
2925 opposite[0] = image_box[2];
2926 else if(fabsf(point[0] - image_box[2]) <= handle[0])
2927 opposite[0] = image_box[0];
2928 else
2929 on_corner_prev_box = FALSE;
2930
2931 if(fabsf(point[1] - image_box[1]) <= handle[1])
2932 opposite[1] = image_box[3];
2933 else if(fabsf(point[1] - image_box[3]) <= handle[1])
2934 opposite[1] = image_box[1];
2935 else
2936 on_corner_prev_box = FALSE;
2937
2938 if(on_corner_prev_box)
2939 {
2940 float raw_opposite[2] = { opposite[0], opposite[1] };
2941 dt_dev_coordinates_image_norm_to_raw_norm(dev, raw_opposite, 1);
2942 memcpy(sample->point, raw_opposite, sizeof(raw_opposite));
2943 }
2944 else
2945 {
2946 const dt_boundingbox_t box = {
2947 fmaxf(0.0, point[0] - delta_x),
2948 fmaxf(0.0, point[1] - delta_y),
2949 fminf(1.0, point[0] + delta_x),
2950 fminf(1.0, point[1] + delta_y)
2951 };
2953 }
2954 dt_control_queue_cursor(GDK_FLEUR);
2955 }
2956 else
2957 {
2958 /* Point pickers must not pre-write sample->point before going through the picker API,
2959 otherwise the setter sees no geometry change and skips the resample request. */
2961 }
2962 }
2964 return 1;
2965 }
2966
2967 if(which == 3)
2968 {
2969 // apply a live sample's area to the active picker?
2970 // FIXME: this is a naive implementation, nicer would be to cycle through overlapping samples then reset
2972 if(dt_dev_get_global()->color_picker.display_samples && mouse_in_imagearea(self, x, y))
2973 for(GSList *samples = dt_dev_get_global()->color_picker.samples; samples; samples = g_slist_next(samples))
2974 {
2975 dt_colorpicker_sample_t *live_sample = samples->data;
2976 if(live_sample->size == DT_LIB_COLORPICKER_SIZE_BOX && picker->kind != DT_COLOR_PICKER_POINT)
2977 {
2978 dt_boundingbox_t live_box = { live_sample->box[0], live_sample->box[1], live_sample->box[2], live_sample->box[3] };
2980 if(point[0] < live_box[0] || point[0] > live_box[2]
2981 || point[1] < live_box[1] || point[1] > live_box[3])
2982 continue;
2984 }
2985 else if(live_sample->size == DT_LIB_COLORPICKER_SIZE_POINT && picker->kind != DT_COLOR_PICKER_AREA)
2986 {
2987 // magic values derived from _darkroom_pickers_draw
2988 float slop[2] = {
2989 MAX(26.0f, roundf(3.0f * zoom_scale)),
2990 MAX(26.0f, roundf(3.0f * zoom_scale))
2991 };
2993 slop[0] /= dt_dev_geometry_processed_width(dev);
2994 slop[1] /= dt_dev_geometry_processed_height(dev);
2995 float live_point[2] = { live_sample->point[0], live_sample->point[1] };
2997 if(fabsf(point[0] - live_point[0]) > slop[0]
2998 || fabsf(point[1] - live_point[1]) > slop[1])
2999 continue;
3001 }
3002 else
3003 continue;
3004 return 1;
3005 }
3006
3007 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
3008 {
3009 // default is hardcoded this way
3010 // FIXME: color_pixer_proxy should have an dt_iop_color_picker_clear_area() function for this
3011 dt_boundingbox_t reset = { 0.01f, 0.01f, 0.99f, 0.99f };
3013 }
3014 return 1;
3015 }
3016 }
3017
3018 // masks
3020 && dt_masks_events_button_pressed(dev, dev->gui_module, x, y, pressure, which, type, state))
3021 {
3022 if(!dt_dev_get_global()->form_gui->creation)
3024 return 1;
3025 }
3026 // module
3027 if(dev->gui_module && dev->gui_module->enabled && dev->gui_module->button_pressed
3028 && dev->gui_module->button_pressed(dev->gui_module, x, y, pressure, which, type, state))
3029 {
3030 return 1;
3031 }
3032
3033 if(which == 1 && dt_dev_viewport_scaling(dev) > 1.0f && mouse_in_imagearea(self, x, y))
3035
3036 if(which == 2)
3037 {
3038 // Incremental zoom-in on middle button click, from fit to 800%
3039 // by power of 2 increments (100%, 200%, 400%, 800%).
3040 float new_scale = 1.f;
3042 new_scale = 1.f; // zoom to fit
3044 new_scale = 1.f / dt_dev_roi_request_natural_scale(dev); // 100 %
3045 else
3047
3048 const float point[2] = { x, y };
3049 return _change_scaling(dev, point, new_scale);
3050 }
3051
3052 return 0;
3053}
3054
3055static int _change_scaling(dt_develop_t *dev, const float point[2], const float new_scaling)
3056{
3057 const float old_scaling = dt_dev_viewport_scaling(dev);
3058
3059 // Round scaling to 1.0 (fit) if close enough
3060 const float epsilon = fabsf(old_scaling - new_scaling);
3061 if(fabsf(new_scaling - 1.0f) < epsilon)
3062 dt_dev_viewport_set_scaling(dev, 1.0f);
3063 else
3064 dt_dev_viewport_set_scaling(dev, new_scaling);
3065
3067 {
3068 // Calculate zoom position offset to keep mouse position fixed during zoom
3069 float center[2] = { 0.0f };
3070 float mouse_offset[2] = { point[0], point[1] };
3071 dt_dev_get_widget_center(dev, center);
3072 mouse_offset[0] -= center[0];
3073 mouse_offset[1] -= center[1];
3074
3075
3076 // Keep the image point under the mouse fixed in widget coordinates while
3077 // the pipeline zoom stays DPI-invariant.
3078 const float old_zoom = dt_dev_get_widget_zoom_scale(dev, old_scaling);
3079 const float new_zoom = dt_dev_get_widget_zoom_scale(dev, dt_dev_viewport_scaling(dev));
3080 if(old_zoom <= 1e-6f || new_zoom <= 1e-6f)
3081 {
3082 dt_dev_viewport_set_scaling(dev, old_scaling);
3083 return 0;
3084 }
3085
3086 // Adjust the center to compensate for the scale change
3087 int proc_w = 0.f, proc_h = 0.f;
3088 dt_dev_get_processed_size(dev, &proc_w, &proc_h);
3089 const dt_dev_viewport_state_t anchored = dt_dev_viewport_get(dev);
3091 anchored.center_x + mouse_offset[0] * (1.f / old_zoom - 1.f / new_zoom) / proc_w,
3092 anchored.center_y + mouse_offset[1] * (1.f / old_zoom - 1.f / new_zoom) / proc_h);
3093
3096 return 1;
3097 }
3098 else
3099 {
3100 // Invalid zoom level, keep previous value
3101 dt_dev_viewport_set_scaling(dev, old_scaling);
3102 return 0;
3103 }
3104}
3105
3106static gboolean _center_view_free_zoom(dt_view_t *self, double x, double y, int up, int state, int flow)
3107{
3109
3110 // Commit the new scaling
3111 const float step = 1.02f;
3112 const float new_scaling = dt_dev_viewport_scaling(dev) * powf(step, (float)-flow);
3113 const float point[2] = { x, y };
3114 return _change_scaling(dev, point, new_scaling);
3115}
3116
3117
3118int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
3119{
3121
3122 dt_develop_t *dev = (dt_develop_t *)self->data;
3123
3124 dt_print(DT_DEBUG_INPUT, "[darkroom] scrolled: up: %i x: %.2f y: %.2f state: %i flow: %i\n",
3125 up, x, y, state, delta_y);
3126
3127 // masks
3129 && dt_masks_events_mouse_scrolled(dev, dev->gui_module, x, y, up, state, delta_y))
3130 {
3132 if(!dev->form_gui->creation)
3133 {
3134 // Every scroll step changes the shape (size, feather or opacity) and is rendered live
3135 // from the transient slot at once; a burst of steps then lands ONE history commit when
3136 // the throttle fires after the last one, which also clears the slot. Scrolling has no
3137 // "release" the way a drag does, so the throttle is what marks the end of the burst.
3140 }
3141
3142 return TRUE;
3143 }
3144
3145 // module -- skip while the color picker overlay is active, consistent with button_pressed/
3146 // button_released/mouse_moved above, which all give the picker priority over the module.
3147 // Unlike those three, a module's own scrolled() has no reliable way to know the picker is
3148 // showing unless it checks dt_iop_color_picker_is_visible() itself, and not every module does.
3150 && dev->gui_module && dev->gui_module->enabled && dev->gui_module->scrolled && dev->gui_module->scrolled(dev->gui_module, x, y, up, state))
3151 {
3152 // Scroll in modules should handle history changes internally.
3153 return TRUE;
3154 }
3155
3156 // free zoom
3157 return _center_view_free_zoom(self, x, y, up, state, delta_y);
3158}
3159
3166
3167
3168int key_pressed(dt_view_t *self, GdkEventKey *event)
3169{
3170 if(!gtk_window_is_active(GTK_WINDOW(dt_gui_get_ui()->main_window))) return FALSE;
3171
3172 dt_develop_t *dev = (dt_develop_t *)self->data;
3173
3175 {
3177 return 1;
3178 }
3179
3180 // module
3181 else if(!IS_NULL_PTR(dev->gui_module)
3182 && !IS_NULL_PTR(dev->gui_module->key_pressed)
3183 && dev->gui_module->key_pressed(dev->gui_module, event))
3184 {
3186 return 1;
3187 }
3188
3189 const gboolean shift = dt_modifier_is(event->state, GDK_SHIFT_MASK);
3190 const gboolean ctrl = dt_modifier_is(event->state, DT_PRIMARY_MASK);
3191 const gboolean ctrl_any = dt_modifiers_include(event->state, DT_PRIMARY_MASK);
3192 guint key = dt_keys_mainpad_alternatives(event->keyval);
3193
3194 if(ctrl_any)
3195 {
3196 const float zoom_step = 1.1f;
3197 float center[2] = { 0.0f };
3198 dt_dev_get_widget_center(dev, center);
3199
3200 switch(key)
3201 {
3202 case GDK_KEY_plus:
3203 return _change_scaling(dev, center, dt_dev_viewport_scaling(dev) * zoom_step);
3204 case GDK_KEY_minus:
3205 return _change_scaling(dev, center, dt_dev_viewport_scaling(dev) / zoom_step);
3206 }
3207 }
3208
3209 float multiplier = (shift) ? 4.f :
3210 (ctrl) ? 0.5f :
3211 1.f;
3212
3213 float delta[2] = { 10.f * multiplier, 10.f * multiplier };
3215
3216 switch(key)
3217 {
3218 case GDK_KEY_Up:
3219 {
3222 - delta[1] / (float)dt_dev_geometry_processed_height(dev));
3223 _key_scroll(dev);
3224 return 1;
3225 }
3226 case GDK_KEY_Down:
3227 {
3230 + delta[1] / (float)dt_dev_geometry_processed_height(dev));
3231 _key_scroll(dev);
3232 return 1;
3233 }
3234 case GDK_KEY_Left:
3235 {
3237 - delta[0] / (float)dt_dev_geometry_processed_width(dev),
3239 _key_scroll(dev);
3240 return 1;
3241 }
3242 case GDK_KEY_Right:
3243 {
3245 + delta[0] / (float)dt_dev_geometry_processed_width(dev),
3247 _key_scroll(dev);
3248 return 1;
3249 }
3250 case GDK_KEY_Escape:
3251 {
3252 dt_ctl_switch_mode_to("lighttable");
3253 return TRUE;
3254 }
3255 }
3256
3257 return 0;
3258}
3259
3260void configure(dt_view_t *self, int wd, int ht)
3261{
3262 dt_develop_t *dev = (dt_develop_t *)self->data;
3263
3264 // Configure event is called when initing the view AND upon window resizes events (through Gtk widget/window resize commands).
3265 // At init time, final window size may not be correct just yet.
3266 // It will be when we call dt_ui_restore_panels(), which will resize stuff properly,
3267 // but that will be only when entering the current view.
3268 // Until we run dt_dev_configure(), main preview pipe gets output size -1×-1 px
3269 // which aborts the pipe recompute early. As soon as we init
3270 // sizes with something "valid" with regard to the pipe, pipeline runs.
3271 // Problem is it will not be valid with regard to the window size and the output will be thrown out
3272 // until we get the final size.
3273 // TD;DR: until we get the final window size, which happens
3274 // only when entering the view, don't configure the main preview pipeline, which will disable useless recomputes.
3276 {
3277 // Reference dimensions before ISO 12646 mode
3280 }
3281}
3282
3283// clang-format off
3284// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3285// vim: shiftwidth=2 expandtab tabstop=2 cindent
3286// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3287// clang-format on
void dt_accels_connect_accels(dt_accels_t *accels)
Actually enable accelerators after having loaded user config.
void dt_accels_connect_active_group(dt_accels_t *accels, const gchar *group)
Connect the contextual active accels group to the window. Views can declare their own set of contextu...
void dt_accels_disconnect_active_group(dt_accels_t *accels)
Disconnect the contextual active accels group from the window.
void dt_accels_new_virtual_shortcut(dt_accels_t *accels, GtkAccelGroup *accel_group, const gchar *accel_path, GtkWidget *widget, guint key_val, GdkModifierType accel_mods)
Add a new virtual shortcut. Virtual shortcuts are immutable, read-only and don't trigger any action....
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
void dt_accels_attach_scroll_handler(dt_accels_t *accels, gboolean(*callback)(GdkEventScroll event, void *data), void *data)
Attach a new global scroll event callback. So far this is used in darkroom to redirect scroll events ...
void dt_accels_detach_scroll_handler(dt_accels_t *accels)
Handle default and user-set shortcuts (accelerators)
#define dt_accels_new_darkroom_action(a, b, w, c, d, e, f, g)
#define DT_PRIMARY_MASK
struct dt_accels_t * dt_gui_get_accels(void)
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
void dt_gui_refocus_center()
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
int dt_atomic_get_int(dt_atomic_int *var)
int dt_atomic_add_int(dt_atomic_int *var, int incr)
int position()
#define m
Definition basecurve.c:283
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1632
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
#define DT_BAUHAUS_COMBOBOX_NEW_FULL(bauhaus, widget, action, label, tip, pos, callback, data,...)
Definition bauhaus.h:379
The blending panel's GUI state and API, cut out of develop/blend.h.
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:135
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const float scaling
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
GList * dt_collection_get_all(const dt_collection_t *collection, int limit)
Definition collection.c:442
void dt_iop_color_picker_init(void)
void dt_iop_color_picker_cleanup(void)
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
@ DT_COLOR_PICKER_AREA
@ DT_COLOR_PICKER_POINT
@ DT_LIB_COLORPICKER_SIZE_POINT
Definition colorpicker.h:38
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:39
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float delta
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
void dt_image_check_camera_missing_sample(const struct dt_image_t *img)
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:701
int32_t dt_control_get_mouse_over_id()
Definition control.c:986
void dt_control_mouse_is_dragging(gboolean state)
Definition control.c:432
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:994
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_log_busy_leave()
Definition control.c:903
void dt_control_mouse_is_painting(gboolean state)
Definition control.c:437
void dt_control_commit_cursor()
Definition control.c:343
int dt_control_running()
Definition control.c:442
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
gboolean dt_control_button_down(int which)
Is mouse button which currently held down?
Definition control.c:563
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:1020
#define dt_control_change_cursor(cursor)
Definition control.h:121
#define dt_control_queue_cursor(cursor)
Definition control.h:140
#define dt_control_set_cursor_visible(visible)
Definition control.h:153
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
static void _publish_mask_edit_transient(dt_develop_t *dev)
Definition darkroom.c:2355
void init(dt_view_t *self)
Definition darkroom.c:228
static dt_darkroom_layout_t _lib_darkroom_get_layout(dt_view_t *self)
Definition darkroom.c:308
static uint64_t _darkroom_compose_key(const uint64_t source_hash, const uint64_t zoom_hash, const dt_aligned_pixel_t bg_color, const int border, const gboolean iso, const int width, const int height)
Definition darkroom.c:736
static uint64_t _darkroom_compose_key_of(const _darkroom_compose_t *const c, const uint64_t source_hash)
Definition darkroom.c:834
static gboolean _darkroom_autoset_button_is_running
Definition darkroom.c:184
static gboolean mouse_in_actionarea(dt_view_t *self, double x, double y)
Definition darkroom.c:2304
static gboolean _darkroom_edge_pan_tick(gpointer user_data)
Move the darkroom ROI while a drag stays in the center-widget edge band.
Definition darkroom.c:2596
static void _darkroom_ioporder_quickbutton_clicked(GtkButton *button, gpointer user_data)
Definition darkroom.c:188
uint32_t flags()
Definition darkroom.c:241
void leave(dt_view_t *self)
Definition darkroom.c:2072
static void _darkroom_sample_raw_point_to_image_norm(const dt_colorpicker_sample_t *const sample, float point[2])
Definition darkroom.c:318
static uint64_t _darkroom_preview_fallback_backbuf_hash
Definition darkroom.c:583
static void _darkroom_image_loaded_callback(gpointer instance, guint request_id, guint result, gpointer user_data)
Definition darkroom.c:1243
#define DARKROOM_EDGE_PAN_SPEED_PX_PER_S
Definition darkroom.c:162
static int _darkroom_preview_fallback_height
Definition darkroom.c:585
static dt_dev_pixelpipe_cache_wait_t _darkroom_preview_wait
Definition darkroom.c:579
static gboolean _is_scroll_captured_by_widget()
Definition darkroom.c:1958
int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition darkroom.c:2871
static void _release_expose_source_caches(void)
Definition darkroom.c:602
static void _darkroom_change_mask_rasterization(GtkWidget *combobox, gpointer user_data)
Definition darkroom.c:1449
static void _autoset_popover_preshow(gpointer user_data)
Definition darkroom.c:1369
static void _guides_quickbutton_clicked(GtkWidget *widget, gpointer user_data)
Definition darkroom.c:1375
gboolean _switch_to_prev_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
Definition darkroom.c:1546
void cleanup(dt_view_t *self)
Definition darkroom.c:258
static dt_iop_module_t * _darkroom_pending_focus_module
Definition darkroom.c:176
#define DARKROOM_EDGE_PAN_INTERVAL_MS
Definition darkroom.c:160
static cairo_status_t _write_snapshot_data(void *closure, const unsigned char *data, unsigned int length)
Definition darkroom.c:299
static void display_mask_checker_1_callback(GtkColorButton *widget, gpointer user_data)
Persist the global mask-preview appearance and resynchronize every node using it.
Definition darkroom.c:1401
static gboolean _darkroom_is_only_selected_sample(gboolean is_primary_sample, dt_colorpicker_sample_t *selected_sample, gboolean display_samples)
Definition darkroom.c:313
static void display_mask_black_and_white_callback(GtkToggleButton *toggle, gpointer user_data)
Definition darkroom.c:1433
static gboolean mouse_in_imagearea(dt_view_t *self, double x, double y)
Definition darkroom.c:2295
static gboolean _is_in_frame(const int width, const int height, const int x, const int y)
Definition darkroom.c:2284
static gboolean _darkroom_compose_fallback(const _darkroom_compose_t *const c)
Definition darkroom.c:858
static int _darkroom_preview_fallback_width
Definition darkroom.c:584
void reset(dt_view_t *self)
Definition darkroom.c:1222
static gboolean _darkroom_locked_main_valid_for_zoom(const darkroom_expose_state_t *state, const uint64_t zoom_hash)
Definition darkroom.c:800
static void display_mask_checker_2_callback(GtkColorButton *widget, gpointer user_data)
Definition darkroom.c:1413
static dt_dev_locked_surface_t _darkroom_preview_locked
Definition darkroom.c:577
static gboolean _render_preview_fallback_surface(cairo_t *cr)
Definition darkroom.c:696
static dt_iop_module_t * _pending_mask_commit_module
Definition darkroom.c:2332
#define DARKROOM_EDGE_PAN_MARGIN_PX
Definition darkroom.c:161
static void _dev_change_image(dt_view_t *self, const int32_t imgid)
Definition darkroom.c:1341
static GtkWidget * _darkroom_ioporder_button
Definition darkroom.c:177
static void _darkroom_set_default_cursor(dt_view_t *self, double x, double y)
Definition darkroom.c:2309
static gboolean _darkroom_edge_pan_apply(dt_view_t *self, const double pointer_x, const double pointer_y, const int width, const int height)
Apply one edge-pan step when the current drag is still eligible.
Definition darkroom.c:2512
static void _preview_pipe_finished(gpointer instance, gpointer user_data)
Definition darkroom.c:1571
static void _darkroom_edge_pan_update_state(dt_view_t *self, const double pointer_x, const double pointer_y, const int width, const int height, darkroom_edge_pan_test_t *edge)
Test every condition that allows edge-pan for the current pointer.
Definition darkroom.c:2457
int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
Definition darkroom.c:3118
static gboolean _build_preview_fallback_surface(dt_develop_t *dev, const int width, const int height, const int border, const dt_aligned_pixel_t bg_color, const uint64_t zoom_hash)
Definition darkroom.c:614
static dt_dev_locked_surface_t _darkroom_main_locked
Definition darkroom.c:576
static void _delayed_history_commit(gpointer data)
Definition darkroom.c:2363
static gboolean _darkroom_compose_locked(const _darkroom_compose_t *const c, dt_dev_locked_surface_t *locked)
Definition darkroom.c:847
static void _guides_popover_preshow(gpointer user_data)
Definition darkroom.c:1364
int key_pressed(dt_view_t *self, GdkEventKey *event)
Definition darkroom.c:3168
static GtkWidget * _darkroom_autoset_popover
Definition darkroom.c:182
static uint64_t _darkroom_preview_fallback_zoom_hash
Definition darkroom.c:582
static void _key_scroll(dt_develop_t *dev)
Definition darkroom.c:3160
static gboolean _darkroom_composed_already(const _darkroom_compose_t *const c, const uint64_t key)
Definition darkroom.c:840
static void _release_preview_fallback_surface(void)
Definition darkroom.c:587
gboolean _focus_main_image(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
Definition darkroom.c:1514
static gboolean _center_view_free_zoom(dt_view_t *self, double x, double y, int up, int state, int flow)
Definition darkroom.c:3106
void mouse_leave(dt_view_t *self)
Definition darkroom.c:2239
static void _darkroom_reset_expose_state(darkroom_expose_state_t *state)
Definition darkroom.c:809
void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
Definition darkroom.c:2628
void configure(dt_view_t *self, int wd, int ht)
Definition darkroom.c:3260
static void _darkroom_log_image_load_error(const int ret)
Definition darkroom.c:1227
int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
Definition darkroom.c:2823
static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
Definition darkroom.c:1353
static dt_autoset_manager_t * _autoset_manager
Definition darkroom.c:180
static void _reset_edge_pan()
Definition darkroom.c:247
static void _guides_view_changed(gpointer instance, dt_view_t *old_view, dt_view_t *new_view, dt_lib_module_t *self)
Definition darkroom.c:1381
void enter(dt_view_t *self)
Definition darkroom.c:1992
static void _darkroom_change_rendering_size(GtkWidget *combobox, gpointer user_data)
Definition darkroom.c:1442
static cairo_surface_t * _darkroom_preview_fallback_surface
Definition darkroom.c:580
static void display_mask_checker_size_callback(GtkWidget *slider, gpointer user_data)
Definition darkroom.c:1425
static float _darkroom_edge_pan_velocity(const double position, const double size, const float margin)
Definition darkroom.c:2426
static GtkWidget * _darkroom_autoset_list
Definition darkroom.c:183
static void _darkroom_autoset_popover_rebuild(dt_develop_t *dev)
Definition darkroom.c:1643
static void _darkroom_autoset_quickbutton_clicked(GtkButton *button, gpointer user_data)
Definition darkroom.c:1622
static void _queue_delayed_history_commit(dt_develop_t *dev)
Definition darkroom.c:2410
static dt_dev_pixelpipe_cache_wait_t _darkroom_main_wait
Definition darkroom.c:578
void expose(dt_view_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition darkroom.c:897
static void _darkroom_sample_raw_box_to_image_norm(const dt_colorpicker_sample_t *const sample, float box[4])
Definition darkroom.c:326
static int _change_scaling(dt_develop_t *dev, const float point[2], const float new_scaling)
Definition darkroom.c:3055
gboolean _switch_to_next_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
Definition darkroom.c:1521
static gchar * _darkroom_autoset_label(const dt_iop_module_t *module)
Definition darkroom.c:1629
static int32_t _darkroom_preview_fallback_imgid
Definition darkroom.c:581
static gboolean _darkroom_edge_pan_enable_check(dt_develop_t *dev)
Definition darkroom.c:2438
static void _paint_all(cairo_t *cri, cairo_t *cr, cairo_surface_t *image_surface)
Definition darkroom.c:705
static void _darkroom_autoset_popover_refresh(gpointer instance, gpointer user_data)
Definition darkroom.c:1673
void gui_init(dt_view_t *self)
Definition darkroom.c:1678
static void _darkroom_autoset_button_set_running(const gboolean running)
Reflect autoset processing state on the darkroom quick button.
Definition darkroom.c:204
static GtkWidget * _darkroom_autoset_button
Definition darkroom.c:181
gboolean _scroll_on_focus(GdkEventScroll event, void *data)
Definition darkroom.c:1978
int try_enter(dt_view_t *self)
Definition darkroom.c:1298
void mouse_enter(dt_view_t *self)
Definition darkroom.c:2319
static int32_t _darkroom_pending_imgid
Definition darkroom.c:175
static void _darkroom_autoset_module_toggled(GtkToggleButton *toggle, gpointer user_data)
Definition darkroom.c:1637
static gboolean _darkroom_center_pan_drag
Definition darkroom.c:178
static void _darkroom_prepare_image_surface(dt_develop_t *dev, const int width, const int height, darkroom_expose_state_t *state)
Definition darkroom.c:869
static void _darkroom_pickers_draw(dt_view_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pozx, int32_t pozy, GSList *samples, gboolean is_primary_sample)
Draw colorpicker samples overlays in darkroom view.
Definition darkroom.c:345
static uint64_t _darkroom_zoom_hash(const dt_develop_t *dev)
The frame-validity key for the locked main surface.
Definition darkroom.c:776
static gboolean _darkroom_preview_fallback_valid(const dt_develop_t *dev, const int width, const int height, const uint64_t zoom_hash)
Definition darkroom.c:751
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:621
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
void dt_dev_set_global(struct dt_develop_t *dev)
Definition darktable.c:543
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
void dt_dev_draw_iso12646_border(cairo_t *cr, double width, double height, int border)
Definition dev_backbuf.c:56
gboolean dt_dev_lock_pipe_surface(dt_develop_t *dev, dt_dev_pixelpipe_t *pipe, dt_dev_locked_surface_t *locked, dt_dev_pixelpipe_cache_wait_t *wait, const char *wait_owner_tag, gboolean keep_previous_on_fail)
gboolean dt_dev_render_locked_surface(cairo_t *cr, const dt_develop_t *dev, dt_dev_locked_surface_t *locked, const int width, const int height, const int border, const dt_aligned_pixel_t bg_color)
void dt_dev_get_background_color(const dt_develop_t *dev, dt_aligned_pixel_t bg_color)
Definition dev_backbuf.c:47
void dt_dev_release_locked_surface(dt_dev_locked_surface_t *locked)
Definition dev_backbuf.c:99
void dt_dev_draw_profile_mode_label(cairo_t *cri, int height)
Definition dev_backbuf.c:69
int32_t dt_dev_geometry_processed_height(const dt_develop_t *dev)
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
int32_t dt_dev_geometry_processed_width(const dt_develop_t *dev)
void dt_dev_transient_params_clear(dt_iop_module_t *module)
Drop the transient slot if it belongs to module.
void dt_dev_transient_params_set(dt_iop_module_t *module, const void *params, const size_t params_size, const void *blend_params, const size_t blend_size)
Out-of-history transient param channel.
void dt_dev_history_free_history(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Free the whole history list attached to dev->history.
void dt_dev_pop_history_items(dt_develop_t *dev)
Thread-safe wrapper around dt_dev_pop_history_items_ext(), then update GUI.
gboolean dt_dev_transient_params_active(dt_develop_t *dev, const dt_iop_module_t *module)
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_dev_history_drop_pending_commits(struct dt_develop_t *dev)
Discard every history commit still queued for dev, without running it.
void dt_dev_history_gui_update(struct dt_develop_t *dev)
Apply history-loaded params to module GUIs.
void dt_dev_history_flush_pending_commits(struct dt_develop_t *dev)
Run every history commit still queued for dev, now.
void dt_dev_history_gui_init(void)
Install the history engine's GUI handlers. Called once from dt_init() beside the other handler instal...
The GTK half of the history engine, following the blend.c/blend_gui.c pattern.
void dt_dev_pixelpipe_cache_wait_cleanup(dt_dev_pixelpipe_cache_wait_t *wait, const char *reason)
Cancel one pending GUI cache wait request and clear its runtime state.
gboolean dt_dev_pixelpipe_is_backbufer_valid(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_cache_wait_dump_pending(const char *reason)
Dump pending GUI cache wait requests for lifecycle debugging.
void dt_dev_pixelpipe_change_zoom_main(dt_develop_t *dev)
void dt_dev_pixelpipe_cache_wait_set_owner(dt_dev_pixelpipe_cache_wait_t *wait, const char *owner_tag, gpointer owner_object)
Attach debug ownership metadata to one cache wait request.
gboolean dt_dev_pixelpipe_cache_peek_gui(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, void **data, dt_pixel_cache_entry_t **cache_entry, dt_dev_pixelpipe_cache_wait_t *wait, dt_dev_pixelpipe_cache_ready_callback_t restart, gpointer restart_data)
#define dt_dev_pixelpipe_rebuild_all(dev)
#define dt_dev_pixelpipe_resync_history_main(dev)
#define dt_dev_pixelpipe_update_history_main(dev)
#define dt_dev_pixelpipe_update_history_preview(dev)
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
float dt_dev_roi_request_natural_scale(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
uint64_t dt_dev_roi_request_publish(dt_develop_t *dev)
Recompute the derived members from the viewport and the geometry record, and publish if anything chan...
dt_dev_roi_request_t dt_dev_roi_request_get(const dt_develop_t *dev)
gboolean dt_dev_toolbox_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
void dt_dev_toolbox_connect_popover(GtkWidget *button, GtkWidget *popover)
gboolean dt_dev_toolbox_activate_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
void dt_dev_toolbox_popover_set_preshow(GtkWidget *popover, void(*preshow)(gpointer user_data), gpointer user_data)
Definition dev_toolbox.c:67
void dt_dev_toolbox_create(dt_develop_t *dev, dt_view_type_flags_t views, const dt_dev_toolbox_button_t *buttons, size_t n_buttons)
void dt_dev_toolbox_apply_iso_12646_size(dt_develop_t *dev)
Definition dev_toolbox.c:39
void dt_dev_toolbox_add_accels(dt_develop_t *dev, GtkAccelGroup *accel_group, const char *category, const dt_dev_toolbox_button_t *buttons, size_t n_buttons)
dt_dev_toolbox_button_t
Definition dev_toolbox.h:28
@ DT_DEV_TOOLBOX_DISPLAY
Definition dev_toolbox.h:34
@ DT_DEV_TOOLBOX_GAMUT
Definition dev_toolbox.h:33
@ DT_DEV_TOOLBOX_RAWOVEREXPOSED
Definition dev_toolbox.h:31
@ DT_DEV_TOOLBOX_OVEREXPOSED
Definition dev_toolbox.h:30
@ DT_DEV_TOOLBOX_SOFTPROOF
Definition dev_toolbox.h:32
@ DT_DEV_TOOLBOX_ISO_12646
Definition dev_toolbox.h:29
#define DT_DEV_TOOLBOX_POPOVER_KEY
gboolean dt_dev_viewport_set_border(dt_develop_t *dev, const int32_t border_size)
gboolean dt_dev_viewport_set_center(dt_develop_t *dev, const float center_x, const float center_y)
gboolean dt_dev_viewport_set_widget_size(dt_develop_t *dev, const int32_t widget_width, const int32_t widget_height)
float dt_dev_viewport_center_y(const dt_develop_t *dev)
gboolean dt_dev_viewport_set_scaling(dt_develop_t *dev, const float scaling)
float dt_dev_viewport_center_x(const dt_develop_t *dev)
int32_t dt_dev_viewport_border_size(const dt_develop_t *dev)
float dt_dev_viewport_scaling(const dt_develop_t *dev)
dt_dev_viewport_state_t dt_dev_viewport_get(const dt_develop_t *dev)
void dt_dev_get_processed_size(const dt_develop_t *dev, int *procw, int *proch)
Definition develop.c:1109
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:353
void dt_dev_get_image_box_in_widget(const dt_develop_t *dev, const int32_t width, const int32_t height, float *box)
Get the displayed image rectangle in darkroom widget coordinates.
Definition develop.c:1977
void dt_dev_get_widget_center(const dt_develop_t *dev, float *point)
Get the center of the darkroom widget in logical coordinates.
Definition develop.c:1970
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:237
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:1000
gboolean dt_dev_masks_manager_is_visible(dt_develop_t *dev)
Definition develop.c:1334
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:143
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1854
gboolean dt_dev_clamp_viewport_center(dt_develop_t *dev)
Clamp the viewport centre against the box currently visible at this zoom.
Definition develop.c:1065
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1278
void dt_dev_check_zoom_pos_bounds(dt_develop_t *dev, float *dev_x, float *dev_y, float *box_w, float *box_h)
Ensure that the current ROI position is within allowed bounds .
Definition develop.c:1074
gboolean dt_dev_check_zoom_scale_bounds(dt_develop_t *dev)
Ensure that the current zoom level is within allowed bounds (for scrolling).
Definition develop.c:2091
void dt_dev_reset_roi(dt_develop_t *dev)
Definition develop.c:1998
GList * dt_dev_load_modules(dt_develop_t *dev)
Definition develop.c:98
void dt_dev_set_backbuf(dt_backbuf_t *backbuf, const int width, const int height, const size_t bpp, const int64_t hash, const int64_t history_hash)
Definition develop.c:2134
float dt_dev_get_widget_zoom_scale(const dt_develop_t *dev, const float scaling)
Convert a darkroom scaling factor to GUI logical zoom.
Definition develop.c:1964
void dt_dev_coordinates_image_norm_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1271
gboolean dt_dev_pipelines_share_preview_output(dt_develop_t *dev)
Tell whether the darkroom main and preview pipes currently target the same GUI output.
Definition develop.c:504
void dt_dev_masks_update_hash(dt_develop_t *dev)
Definition develop.c:1921
gboolean dt_dev_clip_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Clip the view to the ROI. WARNING: this must be done before any translation.
Definition develop.c:2028
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1865
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:2071
void dt_dev_coordinates_widget_delta_to_image_delta(dt_develop_t *dev, float *points, size_t num_points)
Convert a widget-space distance to processed-image pixels.
Definition develop.c:1127
float dt_dev_get_fit_scale(dt_develop_t *dev)
Get the scale factor that maps preview-buffer pixels to GUI coordinates.
Definition develop.c:1953
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
void dt_dev_start_all_pipelines(dt_develop_t *dev)
Definition develop.c:908
@ DT_DEV_IMAGE_STORAGE_DB_NOT_READ
Definition develop.h:516
@ DT_DEV_IMAGE_STORAGE_MIPMAP_NOT_FOUND
Definition develop.h:515
GtkWidget * geometry
its size, under the preview
GtkWidget * window
GtkWidget * view
the tree view
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
const int res
Definition dtpthread.h:351
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_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
GdkRGBA color[]
Definition geotagging.c:541
#define DT_GUI_MODULE(x)
void dt_gui_throttle_cancel(gpointer source)
void dt_gui_throttle_queue(gpointer source, dt_gui_throttle_callback_t callback, gpointer user_data)
void dt_guides_draw(cairo_t *cr, const float left, const float top, const float width, const float height, const float zoom_scale)
Definition guides.c:800
void dt_guides_update_popover_values()
Definition guides.c:864
void dt_guides_set_overlay_colors()
Definition guides.c:680
GtkWidget * dt_guides_popover(dt_view_t *self, GtkWidget *button)
Definition guides.c:718
void dt_guides_button_toggled(gboolean active)
Definition guides.c:793
void dt_guides_update_button_state()
Definition guides.c:782
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
#define UNKNOWN_IMAGE
Definition image.h:78
float dt_boundingbox_t[4]
Definition image.h:83
void dt_iop_cleanup_module(dt_iop_module_t *module)
Definition imageop.c:1113
void dt_iop_nap(int32_t usec)
Definition imageop.c:1653
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:736
void dt_iop_gui_cleanup_module(dt_iop_module_t *module)
void dt_iop_gui_set_expander(dt_iop_module_t *module)
void dt_iop_gui_init(dt_iop_module_t *module)
void dt_iop_gui_update_expanded(dt_iop_module_t *module)
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_GUIDES_SPECIAL_DRAW
Definition imageop.h:197
void dt_bauhaus_value_changed_default_callback(GtkWidget *widget)
Which mouse buttons are held, asked of the toolkit rather than remembered.
int dt_iop_autoset_advance(struct dt_develop_t *dev, dt_autoset_manager_t *manager)
gboolean dt_iop_autoset_module_is_enabled(const dt_iop_module_t *module)
Definition iop-autoset.c:89
void dt_iop_autoset_build_list(struct dt_develop_t *dev, dt_autoset_manager_t *manager)
void dt_iop_autoset_module_set_enabled(const dt_iop_module_t *module, const gboolean enabled)
Definition iop-autoset.c:99
void dt_control_flush_jobs_queue(dt_control_t *control, dt_job_queue_t queue_id)
Definition jobs.c:386
@ DT_JOB_QUEUE_SYSTEM_FG
Definition jobs.h:55
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1229
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1251
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_INPUT
Definition logging.h:64
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_CONTROL
Definition logging.h:52
@ DT_DEBUG_DEV
Definition logging.h:53
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
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
void dt_masks_events_post_expose(dt_develop_t *dev, struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition masks_gui.c:4017
int dt_masks_events_button_released(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, int button, uint32_t state)
Definition masks_gui.c:2906
int dt_masks_events_button_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int button, int event_type, uint32_t state)
Definition masks_gui.c:2961
int dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int which)
Definition masks_gui.c:2824
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
int dt_masks_events_mouse_leave(struct dt_iop_module_t *module)
Definition masks_gui.c:2648
gboolean dt_masks_gui_is_dragging(const dt_masks_form_gui_t *gui)
Definition masks_gui.c:1475
int dt_masks_events_mouse_enter(struct dt_iop_module_t *module)
Definition masks_gui.c:2653
void dt_masks_gui_cleanup(dt_develop_t *dev)
Definition masks_gui.c:1436
int dt_masks_events_key_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, GdkEventKey *event)
Definition masks_gui.c:3040
void dt_masks_overlay_queue_redraw(GtkWidget *widget)
Definition masks_gui.c:4550
int dt_masks_events_mouse_scrolled(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, int scroll_up, uint32_t key_state, int scrolling_delta)
Definition masks_gui.c:3218
void dt_masks_gui_init(dt_develop_t *dev)
Definition masks_gui.c:1420
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
void dt_masks_release_all_forms(dt_develop_t *dev)
@ DT_MASKS_EDIT_OFF
@ DT_MASKS_EDIT_FULL
@ DT_MASKS_GROUP
Definition masks_types.h:66
#define DEVELOP_MASKS_NB_SHAPES
Definition masks_types.h:58
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
dt_mipmap_size_t dt_mipmap_cache_get_fitting_size(const int32_t width, const int32_t height, const uint32_t imgid)
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
@ DT_MIPMAP_TESTLOCK
dt_mipmap_size_t
#define dt_mipmap_cache_get(B, C, D, E, F)
#define dt_mipmap_cache_release(B)
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
#define O_BINARY
Definition paths.h:24
const char * name
Definition pdf.h:90
void * dt_pixel_cache_entry_get_data(dt_pixel_cache_entry_t *entry)
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
void dt_dev_pixelpipe_cache_flush_clmem_for_pipe(const int devid)
Like dt_dev_pixelpipe_cache_flush_clmem(), for callers that do not hold the device lock (dt_opencl_re...
size_t dt_pixel_cache_entry_get_size(dt_pixel_cache_entry_t *entry)
Peek the size (in bytes) reserved for the host buffer of a cache entry.
void dt_dev_pixelpipe_cache_unref_hash(const uint64_t hash)
Find the entry matching hash, and decrease its ref_count if found.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
void dt_dev_pixelpipe_cleanup_nodes(dt_dev_pixelpipe_t *pipe)
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
@ DT_DEV_PIPE_TOP_CHANGED
static dt_backbuf_state_t dt_dev_backbuf_snapshot(const dt_backbuf_t *backbuf)
Read the whole record as one publication.
static void dt_dev_backbuf_set_hash(dt_backbuf_t *backbuf, const uint64_t hash)
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
#define lw
Definition retouch.c:1078
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
int32_t dt_selection_get_first_id(struct dt_selection_t *selection)
Definition selection.c:103
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:193
void dt_selection_clear(dt_selection_t *selection)
Definition selection.c:271
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:297
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#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_DEVELOP_INITIALIZE
This signal is raised when darktable.develop is initialized.
Definition signal.h:172
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:98
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:207
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:224
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:177
@ DT_SIGNAL_DEVELOP_MODULE_REMOVE
This signal is raised when a module is removed from the history stack 1 module no returned value.
Definition signal.h:218
@ DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED
This signal is raised by viewmanager when a view has changed. 1 : dt_view_t * the old view 2 : dt_vie...
Definition signal.h:84
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
const dt_develop_t * dev
Definition darkroom.c:825
darkroom_expose_state_t * state
Definition darkroom.c:826
const float * bg_color
Definition darkroom.c:831
uint64_t image_surface_hash
Definition darkroom.c:719
gboolean image_surface_has_main
Definition darkroom.c:718
GtkAccelKey active_key
gboolean progress_cursor_active
Definition iop-autoset.h:27
One coherent publication, by value.
void(* default_value_changed_callback)(GtkWidget *widget)
Definition bauhaus.h:288
dt_lib_colorpicker_size_t size
Definition colorpicker.h:64
dt_boundingbox_t box
Definition colorpicker.h:63
double button_y
Definition control.h:209
double button_x
Definition control.h:209
Objective facts about the image a dev is working on.
struct dt_pixel_cache_entry_t * entry
Definition dev_backbuf.h:45
cairo_surface_t * surface
Definition dev_backbuf.h:46
dt_pthread_mutex_t busy_mutex
dt_backbuf_t backbuf
dt_atomic_int shutdown
dt_atomic_int running
The coherent set of numbers a darkroom pipe plans its ROI from.
What the darkroom view asks the pipeline to show: the window onto the image.
int32_t gui_attached
Definition develop.h:167
cairo_surface_t * image_surface
Definition develop.h:485
dt_image_t image_storage
Definition develop.h:225
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:374
dt_backbuf_t display_histogram
Definition develop.h:320
GList * iop
Definition develop.h:269
gboolean request
Definition develop.h:402
dt_backbuf_t output_histogram
Definition develop.h:319
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
struct dt_develop_t::@17 display
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
GSList * samples
Definition develop.h:375
gboolean forms_changed
Definition develop.h:309
GtkWidget * floating_window
Definition develop.h:435
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
struct dt_develop_t::@15 overexposed
gboolean pipelines_started
Definition develop.h:328
struct dt_develop_t::@14::@21 snapshot
GList * alliop
Definition develop.h:271
struct dt_develop_t::@19 profile
dt_aligned_pixel_t wb_coeffs
Definition develop.h:428
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:366
dt_backbuf_t raw_histogram
Definition develop.h:318
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
struct dt_colorpicker_sample_t * selected_sample
Definition develop.h:376
gboolean enabled
Definition develop.h:370
struct dt_develop_t::@18 iso_12646
const gchar * filename
Definition develop.h:403
struct dt_develop_t::@14 proxy
struct dt_develop_t::@16 rawoverexposed
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
gboolean display_samples
Definition develop.h:377
struct dt_gui_gtk_t::@33 mouse
gboolean block_normal_pan
gboolean is_dragging
gboolean is_painting
gint64 last_time_us
gboolean enabled
guint timeout_source
struct dt_gui_gtk_t::@32 pan_edge
float velocity[2]
struct dt_view_t * view
int32_t id
Definition image.h:401
dt_iop_module_t *dt_iop_color_picker_kind_t kind
dt_dev_operation_t op
Definition imageop.h:235
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
gboolean enabled
Definition imageop.h:313
dt_iop_module_so_t * so
Definition imageop.h:376
dt_dev_operation_t op
Definition imageop.h:259
dt_masks_type_t type
Definition masks.h:254
GList * points
Definition masks.h:253
One consumer's outstanding request, owned by that consumer, not by the queue.
GtkWidget * guides_popover
Definition view.h:213
dt_darkroom_layout_t(* get_layout)(struct dt_view_t *view)
Definition view.h:238
void(* set_default_cursor)(struct dt_view_t *view, double x, double y)
Definition view.h:239
struct dt_view_t * view
Definition view.h:237
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@63 darkroom
GtkWidget * guides_toggle
Definition view.h:213
uint32_t height
Definition view.h:161
GModule *void * data
Definition view.h:159
uint32_t width
Definition view.h:161
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
int dt_thumbtable_scroll_to_selection(dt_thumbtable_t *table)
Scroll to show selected content.
Definition thumbtable.c:457
void dt_thumbtable_update_parent(dt_thumbtable_t *table)
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
#define dt_thumbtable_refresh_thumbnail(table, imgid, reinit)
Definition thumbtable.h:298
static void dt_thumbtable_show(dt_thumbtable_t *table)
Show the thumbnail table widget.
Definition thumbtable.h:395
static void dt_thumbtable_hide(dt_thumbtable_t *table)
Hide the thumbnail table widget.
Definition thumbtable.h:410
void dt_show_times(const dt_times_t *start, const char *prefix)
Definition darktable.c:2138
static void dt_get_times(dt_times_t *t)
Definition times.h:50
static double dt_get_wtime(void)
Definition times.h:43
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...) __attribute__((format(printf
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
void dt_undo_clear(dt_undo_t *self, uint32_t filter)
Definition undo.c:367
@ DT_UNDO_TAGS
Definition undo.h:47
@ DT_UNDO_DEVELOP
Definition undo.h:54
dt_undo_t * dt_undo_get_global(void)
Definition darktable.c:611
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
Definition view.c:1329
void dt_view_active_images_add(int32_t imgid, gboolean raise)
Definition view.c:1276
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1266
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:234
void dt_view_image_info_update(int32_t imgid)
Definition view.c:1473
int32_t dt_view_active_images_get_first()
Definition view.c:1307
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
@ DT_VIEW_STUDIO_CAPTURE
Definition view.h:78
@ DT_VIEW_DARKROOM
Definition view.h:79
@ VIEW_FLAGS_PAINTS_WHOLE_AREA
Definition view.h:90
dt_darkroom_layout_t
Definition view.h:94
@ DT_DARKROOM_LAYOUT_EDITING
Definition view.h:96
GtkWidget * dt_widget_scroll_focus(void)
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
static gboolean dt_modifiers_include(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_grid(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_flowchart(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Paint the flowchart icon using normalized 0..1 coordinates.
void dtgtk_cairo_paint_wand(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)