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_alert(_("Image could not be opened"), _("Could not load the image source data."), NULL);
1233 break;
1235 dt_control_alert(_("Image could not be opened"),
1236 _("Could not read image information from the database."), NULL);
1237 break;
1238 default:
1239 dt_control_alert(_("Image could not be opened"), _("We could not load the image."), NULL);
1240 break;
1241 }
1242}
1243
1244static void _darkroom_image_loaded_callback(gpointer instance, guint request_id, guint result, gpointer user_data)
1245{
1246 dt_view_t *self = (dt_view_t *)user_data;
1247 dt_develop_t *dev = (dt_develop_t *)self->data;
1248 if(request_id == 0) return;
1249 if(dt_view_manager_get_global()->current_view != self) return;
1250
1251
1252 if(result)
1253 {
1254 _darkroom_log_image_load_error((int)result);
1255 return;
1256 }
1257
1258 dt_dev_get_global()->proxy.wb_coeffs[0] = 0.f;
1259
1260 // synch gui and flag pipe as dirty
1261 // this is done here and not in dt_read_history, as it would else be triggered before module->gui_init.
1262 // locks history mutex internally
1265
1268
1272
1273 // Clean & Init the starting point of undo/redo
1277
1278 /* signal that darktable.develop is initialized and ready to be used */
1280
1282
1283 // clear selection, we don't want selections in darkroom
1285
1286 // change active image for global actions (menu)
1289
1291
1293
1295
1297}
1298
1300{
1301 uint32_t num_selected = dt_selection_get_length(dt_selection_get_global());
1302 int32_t imgid = dt_control_get_mouse_over_id();
1303 (void)self;
1304
1306
1307 if(imgid != UNKNOWN_IMAGE)
1308 {
1309 ; // Needed to open image from filmstrip
1310 }
1311 else if(num_selected > 1)
1312 {
1313 dt_control_log(_("The current selection contains more than one image, which is ambiguous.\n"
1314 "Select exactly one image to enter the darkroom."));
1315 return 1;
1316 }
1317 else if(num_selected == 0 && imgid == UNKNOWN_IMAGE)
1318 {
1319 dt_control_log(_("There is no image selected.\n"
1320 "Select exactly one image to enter the darkroom."));
1321 return 1;
1322 }
1323 else
1324 {
1325 // Needed to open image at startup
1327 }
1328
1330
1331 if(imgid < 0)
1332 {
1333 // fail :(
1334 dt_control_log(_("No image to open !"));
1335 return 1;
1336 }
1337
1339 return 0;
1340}
1341
1342static void _dev_change_image(dt_view_t *self, int32_t imgid)
1343{
1344 // Lazy trick to cleanup, reset, reinit, reload everything without
1345 // having to duplicate most of (but not all) the code in leave(),
1346 // try_enter() and enter() : simulate a roundtrip through lighttable.
1347 // This way, all images are loaded through the same path, handled at an higher level.
1348 // It's more robust, although slightly slower than re-initing only what is needed.
1352}
1353
1354static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
1355{
1356 if(imgid > UNKNOWN_IMAGE)
1357 {
1358 // switch images in darkroom mode:
1359 _dev_change_image(user_data, imgid);
1360 }
1361}
1362
1365static void _guides_popover_preshow(gpointer user_data)
1366{
1368}
1369
1370static void _autoset_popover_preshow(gpointer user_data)
1371{
1373}
1374
1375/* overlay color */
1376static void _guides_quickbutton_clicked(GtkWidget *widget, gpointer user_data)
1377{
1378 dt_guides_button_toggled(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
1380}
1381
1382static void _guides_view_changed(gpointer instance, dt_view_t *old_view, dt_view_t *new_view, dt_lib_module_t *self)
1383{
1385}
1386
1394/* display: darkroom-only extras appended to the popover dt_dev_toolbox_create() builds */
1395
1402static void display_mask_checker_1_callback(GtkColorButton *widget, gpointer user_data)
1403{
1404 dt_develop_t *d = (dt_develop_t *)user_data;
1405 GdkRGBA color;
1406 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &color);
1407 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker1/red", color.red);
1408 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker1/green", color.green);
1409 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker1/blue", color.blue);
1410 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1412}
1413
1414static void display_mask_checker_2_callback(GtkColorButton *widget, gpointer user_data)
1415{
1416 dt_develop_t *d = (dt_develop_t *)user_data;
1417 GdkRGBA color;
1418 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &color);
1419 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker2/red", color.red);
1420 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker2/green", color.green);
1421 dt_conf_set_float("plugins/darkroom/colorbalancergb/checker2/blue", color.blue);
1422 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1424}
1425
1426static void display_mask_checker_size_callback(GtkWidget *slider, gpointer user_data)
1427{
1428 dt_develop_t *d = (dt_develop_t *)user_data;
1429 dt_conf_set_int("plugins/darkroom/colorbalancergb/checker/size", (int)dt_bauhaus_slider_get(slider));
1430 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1432}
1433
1434static void display_mask_black_and_white_callback(GtkToggleButton *toggle, gpointer user_data)
1435{
1436 dt_develop_t *d = (dt_develop_t *)user_data;
1437 dt_conf_set_bool("plugins/darkroom/colorbalancergb/mask_preview/greyscaled",
1438 gtk_toggle_button_get_active(toggle));
1439 dt_atomic_add_int(&d->mask_preview_settings_revision, 1);
1441}
1442
1443static void _darkroom_change_rendering_size(GtkWidget *combobox, gpointer user_data)
1444{
1445 dt_develop_t *d = (dt_develop_t *)user_data;
1446 dt_conf_set_int("darkroom/render_size", dt_bauhaus_combobox_get(combobox));
1448}
1449
1450static void _darkroom_change_mask_rasterization(GtkWidget *combobox, gpointer user_data)
1451{
1452 dt_develop_t *d = (dt_develop_t *)user_data;
1453 dt_conf_set_string("plugins/darkroom/masks/rasterization",
1454 (dt_bauhaus_combobox_get(combobox) == 0) ? "auto" : "never");
1455 /* The step itself is recomputed by dt_dev_roi_request_publish(), on this thread. Only the
1456 * MAIN pipe is affected: the preview's step is always automatic, set from its own ROI, so
1457 * this preference cannot change it and resyncing it would be wasted work. */
1460}
1461
1464#if 0
1465
1466static void _overlay_cycle_callback(dt_action_t *action)
1467{
1468 const int currentval = dt_conf_get_int("darkroom/ui/overlay_color");
1469 const int nextval = (currentval + 1) % 6; // colors can go from 0 to 5
1470 dt_conf_set_int("darkroom/ui/overlay_color", nextval);
1473}
1474
1475static void _toggle_mask_visibility_callback(dt_action_t *action)
1476{
1477 if(dt_gui_widgets_suppressed()) return;
1478
1479 dt_develop_t *dev = dt_action_view(action)->data;
1480 dt_iop_module_t *mod = dev->gui_module;
1481
1482 //retouch and spot removal module use masks differently and have different buttons associated
1483 //keep the shortcuts independent
1484 if(mod && strcmp(mod->so->op, "spots") != 0 && strcmp(mod->so->op, "retouch") != 0)
1485 {
1486 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)mod->blend_data;
1487
1489
1491
1493 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
1494 {
1495 if(bd->masks_shown == DT_MASKS_EDIT_OFF)
1496 bd->masks_shown = DT_MASKS_EDIT_FULL;
1497 else
1498 bd->masks_shown = DT_MASKS_EDIT_OFF;
1499
1500 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), bd->masks_shown != DT_MASKS_EDIT_OFF);
1501 dt_masks_set_edit_mode(mod, bd->masks_shown);
1502
1503 // set all add shape buttons to inactive
1504 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
1505 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), FALSE);
1506 }
1507
1509 }
1510}
1511
1512
1513#endif
1514
1515gboolean _focus_main_image(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1516 GdkModifierType modifier, gpointer data)
1517{
1518 gtk_widget_grab_focus(dt_gui_center_widget());
1519 return TRUE;
1520}
1521
1522gboolean _switch_to_next_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1523 GdkModifierType modifier, gpointer data)
1524{
1525 dt_view_t *view = (dt_view_t *)data;
1526 dt_develop_t *dev = (dt_develop_t *)view->data;
1527 int32_t current_img = dev->image_storage.id;
1528 GList *current_collection = dt_collection_get_all(dt_collection_get_global(), -1);
1529 GList *current_item = g_list_find(current_collection, GINT_TO_POINTER(current_img));
1530
1531 if(current_item && current_item->next)
1532 {
1533 int32_t next_img = GPOINTER_TO_INT(current_item->next->data);
1534 g_list_free(current_collection);
1535 current_collection = NULL;
1536 _dev_change_image(data, next_img);
1537 }
1538 else
1539 {
1540 g_list_free(current_collection);
1541 current_collection = NULL;
1542 }
1543
1544 return TRUE;
1545}
1546
1547gboolean _switch_to_prev_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1548 GdkModifierType modifier, gpointer data)
1549{
1550 dt_view_t *view = (dt_view_t *)data;
1551 dt_develop_t *dev = (dt_develop_t *)view->data;
1552 int32_t current_img = dev->image_storage.id;
1553 GList *current_collection = dt_collection_get_all(dt_collection_get_global(), -1);
1554 GList *current_item = g_list_find(current_collection, GINT_TO_POINTER(current_img));
1555
1556 if(current_item && current_item->prev)
1557 {
1558 int32_t prev_img = GPOINTER_TO_INT(current_item->prev->data);
1559 g_list_free(current_collection);
1560 current_collection = NULL;
1561 _dev_change_image(data, prev_img);
1562 }
1563 else
1564 {
1565 g_list_free(current_collection);
1566 current_collection = NULL;
1567 }
1568
1569 return TRUE;
1570}
1571
1572static gboolean _zoom_to_fit(GtkAccelGroup *accel_group __attribute__((unused)),
1573 GObject *accelerable __attribute__((unused)),
1574 guint keyval __attribute__((unused)),
1575 GdkModifierType modifier __attribute__((unused)), gpointer data)
1576{
1577 dt_develop_t *dev = (dt_develop_t *)data;
1578 float center[2] = { 0.0f };
1579 dt_dev_get_widget_center(dev, center);
1580 _change_scaling(dev, center, 1.f);
1581 return TRUE;
1582}
1583
1584static gboolean _zoom_to_100(GtkAccelGroup *accel_group __attribute__((unused)),
1585 GObject *accelerable __attribute__((unused)),
1586 guint keyval __attribute__((unused)),
1587 GdkModifierType modifier __attribute__((unused)), gpointer data)
1588{
1589 dt_develop_t *dev = (dt_develop_t *)data;
1590
1591 // The viewport scaling is relative to the fit-to-screen scale, so 100% is its inverse. That
1592 // scale is the -1 sentinel until an image has been fitted into the view.
1593 const float natural_scale = dt_dev_roi_request_natural_scale(dev);
1594 if(natural_scale <= 0.f) return TRUE;
1595
1596 float center[2] = { 0.0f };
1597 dt_dev_get_widget_center(dev, center);
1598 _change_scaling(dev, center, 1.f / natural_scale);
1599 return TRUE;
1600}
1601
1620static void _preferences_changed(gpointer instance, gpointer user_data)
1621{
1622 dt_develop_t *dev = (dt_develop_t *)user_data;
1623 if(IS_NULL_PTR(dev)) return;
1625}
1626
1627static void _preview_pipe_finished(gpointer instance, gpointer user_data)
1628{
1629 // Get the mip size that is at most as big as our pipeline backbuf
1631 const gboolean autoset_running_before
1633 const int32_t imgid = dt_dev_get_global()->image_storage.id;
1635 dt_mipmap_size_t mip = dt_mipmap_cache_get_fitting_size(published.width, published.height, imgid);
1636
1637 // Check if the cache is ready for that mipmap size.
1639 dt_mipmap_cache_get(&tmp, imgid, mip, DT_MIPMAP_TESTLOCK, 'r');
1640 gboolean cache_ready = !IS_NULL_PTR(tmp.buf);
1642
1643 if(pipe->autoset)
1644 {
1647 }
1648
1649 const gboolean autoset_running_after
1651
1652 // While autoset iterates over modules, avoid spawning thumbnail refresh jobs on each preview completion.
1653 // We refresh once the autoset run is finished and the preview cache reached a stable state.
1654 if(cache_ready && !autoset_running_after)
1655 {
1656 const gboolean autoset_just_finished = autoset_running_before && !autoset_running_after;
1657 if(!autoset_running_before || autoset_just_finished)
1658 {
1659 dt_thumbtable_refresh_thumbnail(dt_gui_get_ui()->thumbtable_lighttable, imgid, TRUE);
1660 dt_thumbtable_refresh_thumbnail(dt_gui_get_ui()->thumbtable_filmstrip, imgid, TRUE);
1661 }
1662 }
1663}
1664
1665/*
1666static gboolean _darkroom_toolbox_button_activate_accel(GtkAccelGroup *accel_group, GObject *accelerable,
1667 guint keyval, GdkModifierType modifier,
1668 gpointer data)
1669{
1670 GtkWidget *button = GTK_WIDGET(data);
1671 if(IS_NULL_PTR(button) || !gtk_widget_is_visible(button) || !gtk_widget_is_sensitive(button)) return FALSE;
1672
1673 gtk_button_clicked(GTK_BUTTON(button));
1674 return TRUE;
1675}
1676*/
1677
1678static void _darkroom_autoset_quickbutton_clicked(GtkButton *button, gpointer user_data)
1679{
1682 fprintf(stdout, "lauching autoset\n");
1683}
1684
1685static gchar *_darkroom_autoset_label(const dt_iop_module_t *module)
1686{
1687 gchar *clean_name = dt_history_item_get_name(module);
1688 gchar *label = g_strdup_printf("%s (%i)", clean_name, module->multi_priority);
1689 dt_free(clean_name);
1690 return label;
1691}
1692
1693static void _darkroom_autoset_module_toggled(GtkToggleButton *toggle, gpointer user_data)
1694{
1695 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1696 dt_iop_autoset_module_set_enabled(module, gtk_toggle_button_get_active(toggle));
1697}
1698
1700{
1702
1703 GList *children = gtk_container_get_children(GTK_CONTAINER(_darkroom_autoset_list));
1704 for(GList *child = children; child; child = g_list_next(child))
1705 gtk_widget_destroy(GTK_WIDGET(child->data));
1706 g_list_free(children);
1707
1708 GtkWidget *title = gtk_label_new(_("Module instances to autoset"));
1709 gtk_box_pack_start(GTK_BOX(_darkroom_autoset_list), title, FALSE, FALSE, 0);
1710 dt_gui_add_class(title, "dt_section_label");
1711
1712 for(GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(modules))
1713 {
1714 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1715 if(IS_NULL_PTR(module->autoset)) continue;
1716
1717 gchar *label = _darkroom_autoset_label(module);
1718 GtkWidget *toggle = gtk_check_button_new_with_label(label);
1719 g_free(label);
1720
1721 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), dt_iop_autoset_module_is_enabled(module));
1722 gtk_box_pack_start(GTK_BOX(_darkroom_autoset_list), toggle, FALSE, FALSE, 0);
1723 g_signal_connect(G_OBJECT(toggle), "toggled", G_CALLBACK(_darkroom_autoset_module_toggled), module);
1724 }
1725
1726 gtk_widget_show_all(_darkroom_autoset_list);
1727}
1728
1729static void _darkroom_autoset_popover_refresh(gpointer instance, gpointer user_data)
1730{
1732}
1733
1735{
1736 dt_develop_t *dev = (dt_develop_t *)self->data;
1737
1738 // Register the GTK-driving handlers for history commits and undo restores (the inverted
1739 // dependency dev_history.c exposes). Done here, not in dt_init(): the call sites
1740 // NULL-check, so a GUI-less process runs with the slots empty instead of registering
1741 // callbacks that must never fire.
1743
1745 G_CALLBACK(_preview_pipe_finished), self);
1746
1747 // Both resync entry points early-out on !dev->gui_attached, so this is inert outside darkroom.
1749 G_CALLBACK(_preferences_changed), dev);
1750
1751 dt_accels_new_darkroom_action(_switch_to_next_picture, self, NULL, N_("Darkroom/Actions"),
1752 N_("Switch to the next picture"), GDK_KEY_Right, GDK_MOD1_MASK, _("Triggers the action"));
1753 dt_accels_new_darkroom_action(_switch_to_prev_picture, self, NULL, N_("Darkroom/Actions"),
1754 N_("Switch to the previous picture"), GDK_KEY_Left, GDK_MOD1_MASK, _("Triggers the action"));
1755
1756 gchar *path = dt_accels_build_path(_("Darkroom/Actions"), _("Give focus to the main image"));
1758 path, dt_gui_center_widget(), GDK_KEY_Return, 0);
1759 dt_free(path);
1760
1761 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move up"));
1763 path, dt_gui_center_widget(), GDK_KEY_Up, 0);
1764 dt_free(path);
1765
1766 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move up (coarse step)"));
1768 path, dt_gui_center_widget(), GDK_KEY_Up, GDK_SHIFT_MASK);
1769 dt_free(path);
1770
1771 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move up (fine step)"));
1773 path, dt_gui_center_widget(), GDK_KEY_Up, DT_PRIMARY_MASK);
1774 dt_free(path);
1775
1776 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move down"));
1778 path, dt_gui_center_widget(), GDK_KEY_Down, 0);
1779 dt_free(path);
1780
1781 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move down (coarse step)"));
1783 path, dt_gui_center_widget(), GDK_KEY_Down, GDK_SHIFT_MASK);
1784 dt_free(path);
1785
1786 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move down (fine step)"));
1788 path, dt_gui_center_widget(), GDK_KEY_Down, DT_PRIMARY_MASK);
1789 dt_free(path);
1790
1791 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move left"));
1793 path, dt_gui_center_widget(), GDK_KEY_Left, 0);
1794 dt_free(path);
1795
1796 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move left (coarse step)"));
1798 path, dt_gui_center_widget(), GDK_KEY_Left, GDK_SHIFT_MASK);
1799 dt_free(path);
1800
1801 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move left (fine step)"));
1803 path, dt_gui_center_widget(), GDK_KEY_Left, DT_PRIMARY_MASK);
1804 dt_free(path);
1805
1806 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move right"));
1808 path, dt_gui_center_widget(), GDK_KEY_Right, 0);
1809 dt_free(path);
1810
1811 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move right (coarse step)"));
1813 path, dt_gui_center_widget(), GDK_KEY_Right, GDK_SHIFT_MASK);
1814 dt_free(path);
1815
1816 path = dt_accels_build_path(_("Darkroom/Main image"), _("Move right (fine step)"));
1818 path, dt_gui_center_widget(), GDK_KEY_Right, DT_PRIMARY_MASK);
1819 dt_free(path);
1820
1821 path = dt_accels_build_path(_("Darkroom/Main image"), _("Zoom in"));
1823 path, dt_gui_center_widget(), GDK_KEY_plus, DT_PRIMARY_MASK);
1824 dt_free(path);
1825
1826 path = dt_accels_build_path(_("Darkroom/Main image"), _("Zoom out"));
1828 path, dt_gui_center_widget(), GDK_KEY_minus, DT_PRIMARY_MASK);
1829 dt_free(path);
1830
1831 dt_accels_new_darkroom_action(_zoom_to_fit, dev, NULL, _("Darkroom/Main image"), _("Zoom to fit"),
1832 GDK_KEY_0, DT_PRIMARY_MASK,
1833 _("Zooms the main image to fit the window"));
1834 dt_accels_new_darkroom_action(_zoom_to_100, dev, NULL, _("Darkroom/Main image"), _("Zoom to 100%"),
1835 GDK_KEY_1, DT_PRIMARY_MASK,
1836 _("Zooms the main image to 100%"));
1837 /*
1838 * Add view specific tool buttons
1839 */
1840
1841 static const dt_dev_toolbox_button_t darkroom_toolbox_buttons[]
1844 dt_dev_toolbox_create(dev, DT_VIEW_DARKROOM, darkroom_toolbox_buttons,
1845 G_N_ELEMENTS(darkroom_toolbox_buttons));
1846 dt_dev_toolbox_add_accels(dev, dt_gui_get_accels()->darkroom_accels, N_("Darkroom/Toolbox"),
1847 darkroom_toolbox_buttons, G_N_ELEMENTS(darkroom_toolbox_buttons));
1848
1849 /* display background options: dt_dev_toolbox_create() already built the
1850 * button and a popover with the generic controls (brightness, margins);
1851 * append darkroom-only extras (rendering size, mask preview) to that same
1852 * popover's content box. */
1853 {
1854 GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(dev->display.floating_window));
1855
1856 GtkWidget *rendering;
1858 N_("Rendering size"),
1859 _("Choose at what size the main preview is rendered.\n"
1860 "Full resolution renders the pipeline at the raw original resolution.\n"
1861 "It is pixel-perfect, especially regarding denoising and deblurring, but very slow.\n"
1862 "Scaled renders at screen resolution and is the best trade-off.\n"
1863 "Pixel-level accuracy is guaranteed only when zoomed-in at 100%."),
1864 dt_conf_get_int("darkroom/render_size"),
1866 N_("full resolution (slow)"),
1867 N_("scaled (default)")
1868 );
1869 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(rendering), TRUE, TRUE, 0);
1870
1871 gchar *rasterization = dt_conf_get_string("plugins/darkroom/masks/rasterization");
1872 const int rasterization_index = (!IS_NULL_PTR(rasterization) && !strcmp(rasterization, "never")) ? 1 : 0;
1873 g_free(rasterization);
1874
1875 GtkWidget *mask_rasterization;
1876 DT_BAUHAUS_COMBOBOX_NEW_FULL(dt_bauhaus_get_global(), mask_rasterization, NULL,
1877 N_("Fast drawn mask rasterization"),
1878 _("Choose how finely brush and polygon masks are drawn in the darkroom.\n"
1879 "Auto samples one point per displayed pixel: zoomed out costs less,\n"
1880 "and at 100% or closer masks are pixel-accurate.\n"
1881 "Never keeps pixel accuracy at every zoom level, at some cost when zoomed out.\n"
1882 "Exports, thumbnails and snapshots are always pixel-accurate either way."),
1883 rasterization_index,
1885 N_("auto (default)"),
1886 N_("never")
1887 );
1888 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(mask_rasterization), TRUE, TRUE, 0);
1889
1890 gtk_box_pack_start(GTK_BOX(vbox), dt_ui_section_label_new(_("Mask preview settings")), FALSE, FALSE, 0);
1891
1892 GtkWidget *checker_1_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1893 gtk_box_pack_start(GTK_BOX(checker_1_row), dt_ui_label_new(_("Checkerboard color 1")), TRUE, TRUE, 0);
1894 GtkWidget *checker_1 = gtk_color_button_new();
1895 GdkRGBA checker_color = {
1896 .red = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/red"),
1897 .green = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/green"),
1898 .blue = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/blue"),
1899 .alpha = 1.0
1900 };
1901 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(checker_1), &checker_color);
1902 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(checker_1), FALSE);
1903 gtk_color_button_set_title(GTK_COLOR_BUTTON(checker_1),
1904 _("Select color of the checkerboard from a swatch"));
1905 g_signal_connect(G_OBJECT(checker_1), "color-set", G_CALLBACK(display_mask_checker_1_callback), dev);
1906 gtk_box_pack_start(GTK_BOX(checker_1_row), checker_1, FALSE, FALSE, 0);
1907 gtk_box_pack_start(GTK_BOX(vbox), checker_1_row, FALSE, FALSE, 0);
1908
1909 GtkWidget *checker_2_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1910 gtk_box_pack_start(GTK_BOX(checker_2_row), dt_ui_label_new(_("Checkerboard color 2")), TRUE, TRUE, 0);
1911 GtkWidget *checker_2 = gtk_color_button_new();
1912 checker_color.red = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/red");
1913 checker_color.green = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/green");
1914 checker_color.blue = dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/blue");
1915 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(checker_2), &checker_color);
1916 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(checker_2), FALSE);
1917 gtk_color_button_set_title(GTK_COLOR_BUTTON(checker_2),
1918 _("Select color of the checkerboard from a swatch"));
1919 g_signal_connect(G_OBJECT(checker_2), "color-set", G_CALLBACK(display_mask_checker_2_callback), dev);
1920 gtk_box_pack_start(GTK_BOX(checker_2_row), checker_2, FALSE, FALSE, 0);
1921 gtk_box_pack_start(GTK_BOX(vbox), checker_2_row, FALSE, FALSE, 0);
1922
1923 GtkWidget *checker_size
1925 dt_bauhaus_slider_set(checker_size,
1926 dt_conf_get_int("plugins/darkroom/colorbalancergb/checker/size"));
1927 dt_bauhaus_slider_set_format(checker_size, " px");
1928 dt_bauhaus_widget_set_label(checker_size, _("Checkerboard size"));
1929 g_signal_connect(G_OBJECT(checker_size), "value-changed",
1930 G_CALLBACK(display_mask_checker_size_callback), dev);
1931 gtk_box_pack_start(GTK_BOX(vbox), checker_size, TRUE, TRUE, 0);
1932
1933 GtkWidget *black_and_white = gtk_check_button_new_with_label(_("Show a greyscaled mask image"));
1934 gtk_toggle_button_set_active(
1935 GTK_TOGGLE_BUTTON(black_and_white),
1936 dt_conf_get_bool("plugins/darkroom/colorbalancergb/mask_preview/greyscaled"));
1937 g_signal_connect(G_OBJECT(black_and_white), "toggled",
1938 G_CALLBACK(display_mask_black_and_white_callback), dev);
1939 gtk_box_pack_start(GTK_BOX(vbox), black_and_white, FALSE, FALSE, 0);
1940
1941 gtk_widget_show_all(vbox);
1942 }
1943
1945 gtk_widget_set_tooltip_text(_darkroom_ioporder_button, _("show the pipeline node graph"));
1946 g_signal_connect(G_OBJECT(_darkroom_ioporder_button), "clicked",
1950 _darkroom_ioporder_button, N_("Darkroom/Toolbox"),
1951 N_("Show the pipeline node graph"), 0, 0,
1952 _("Triggers the action"));
1953
1954 /* create grid changer popup tool */
1955 {
1956 // the button
1959 gtk_widget_set_tooltip_text(vm->guides_toggle,
1960 _("toggle guide lines\nright click for guides options"));
1962 g_object_ref(vm->guides_popover);
1963 g_signal_connect(G_OBJECT(vm->guides_toggle), "clicked",
1964 G_CALLBACK(_guides_quickbutton_clicked), dev);
1967 g_object_set_data(G_OBJECT(vm->guides_toggle), DT_DEV_TOOLBOX_POPOVER_KEY,
1968 vm->guides_popover);
1972 vm->guides_toggle, vm->guides_toggle, N_("Darkroom/Toolbox"),
1973 N_("Toggle guide lines"), 0, 0,
1974 _("Triggers the action"));
1976 vm->guides_toggle, vm->guides_toggle, N_("Darkroom/Toolbox"),
1977 N_("Focus guide lines options"), 0, 0,
1978 _("Shows the options popover"));
1979 // we want to update button state each time the view change
1981 G_CALLBACK(_guides_view_changed), dev);
1982 }
1983
1984 // Auto-set feature
1985 {
1987
1989 gtk_widget_set_tooltip_text(_darkroom_autoset_button, _("Run autoset on selected modules\nRight click for options"));
1990 g_signal_connect(G_OBJECT(_darkroom_autoset_button), "clicked",
1991 G_CALLBACK(_darkroom_autoset_quickbutton_clicked), dev);
1992 /* Ensure autoset button is placed first in the toolbox. */
1993 g_object_set_data(G_OBJECT(_darkroom_autoset_button), "dt-toolbox-priority", GINT_TO_POINTER(1));
1995
1999 g_object_set_data(G_OBJECT(_darkroom_autoset_button), DT_DEV_TOOLBOX_POPOVER_KEY,
2001 /*
2002 dt_accels_new_darkroom_action(dt_dev_toolbox_activate_accel, _darkroom_autoset_button,
2003 _darkroom_autoset_button, N_("Darkroom/Toolbox"),
2004 N_("Show the pipeline node graph"), 0, 0,
2005 _("Triggers the action"));
2006 */
2007 _darkroom_autoset_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2008 gtk_container_add(GTK_CONTAINER(_darkroom_autoset_popover), _darkroom_autoset_list);
2011
2013 G_CALLBACK(_darkroom_autoset_popover_refresh), dev);
2015 G_CALLBACK(_darkroom_autoset_popover_refresh), dev);
2017 G_CALLBACK(_darkroom_autoset_popover_refresh), dev);
2018 }
2019
2022 dt_dev_viewport_set_border(dev, DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size")));
2023}
2024
2026{
2027 dt_accels_t *accels = dt_gui_get_accels();
2028 if(!dt_widget_scroll_focus() || accels->active_key.accel_key == 0) return FALSE;
2029
2030 // When declaring shortcuts, bauhaus widgets write their accel path into a private data field
2031 gchar *accel_path = g_object_get_data(G_OBJECT(dt_widget_scroll_focus()), "accel-path");
2032
2033 // Find if the registered accel keys matches currently pressed keys
2034 GtkAccelKey key = { 0 };
2035 return gtk_accel_map_lookup_entry(accel_path, &key)
2036 && key.accel_key == accels->active_key.accel_key
2037 && key.accel_mods == accels->active_key.accel_mods;
2038}
2039
2040
2041// If a bauhaus widget has the scroll focus from a keyboard shortcut,
2042// and the combination of keys attached to its accel path
2043// is still pressed, then we redirect any scroll event in the window to this widget.
2044// Warning: if mouse is over the central widget, central widget takes precedence over scrolling.
2045gboolean _scroll_on_focus(GdkEventScroll event, void *data)
2046{
2048 {
2049 // Pass-through the scrolling event to the scrolling handler of the widget
2050 gboolean ret;
2051 g_signal_emit_by_name(G_OBJECT(dt_widget_scroll_focus()), "scroll-event", &event, &ret);
2052 return ret;
2053 }
2054
2055 return FALSE;
2056}
2057
2058
2059void enter(dt_view_t *self)
2060{
2061 // Flush all background jobs (thumbnails generation) to spare resources for interactivity
2063
2064 dt_print(DT_DEBUG_CONTROL, "[run_job+] 11 %f in darkroom mode\n", dt_get_wtime());
2065 dt_develop_t *dev = (dt_develop_t *)self->data;
2066 dev->exit = 0;
2068
2069 // We need to init forms before we init module blending GUI
2070 dt_masks_gui_init(dev);
2071 dev->gui_module = NULL;
2072
2073 if(IS_NULL_PTR(dev->iop))
2074 dev->iop = dt_dev_load_modules(dev);
2075
2076 // Add IOP modules to the plugin list
2077 char option[1024];
2078 const char *active_plugin = dt_conf_get_string_const("plugins/darkroom/active");
2079 for(const GList *modules = g_list_first(dev->iop); modules; modules = g_list_next(modules))
2080 {
2081 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2082
2083 /* initialize gui if iop have one defined */
2084 if(!dt_iop_is_hidden(module))
2085 {
2086 dt_iop_gui_init(module);
2088
2089 if(module->multi_priority == 0)
2090 {
2091 snprintf(option, sizeof(option), "plugins/darkroom/%s/expanded", module->op);
2092 module->gui->expanded = dt_conf_get_bool(option);
2094
2095 if(active_plugin && !strcmp(module->op, active_plugin))
2097 }
2098 }
2099 }
2100
2101 // just make sure at this stage we have only history info into the undo, all automatic
2102 // tagging should be ignored.
2104
2106
2107 // Reset focus to center view
2109
2110 // Attach shortcuts to new widgets
2114
2115 // Attach bauhaus default signal callback to IOP
2117
2118 // This gets the first selected ID to scroll where relevant, so
2119 // runs it before clearing the selection
2120 dt_thumbtable_show(dt_gui_get_ui()->thumbtable_filmstrip);
2121 gtk_widget_show(dt_gui_center_widget());
2122 dt_thumbtable_update_parent(dt_gui_get_ui()->thumbtable_filmstrip);
2123
2124 /* connect signal for filmstrip image activate */
2127
2128 gtk_widget_grab_focus(dt_gui_center_widget()); // ensure the center view has focus for keybindings to work
2129
2130 const int32_t imgid = _darkroom_pending_imgid;
2134 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
2135 int ret = dt_dev_load_image(dt_dev_get_global(), imgid);
2136 _darkroom_image_loaded_callback(NULL, imgid, ret, self);
2137}
2138
2139void leave(dt_view_t *self)
2140{
2141 dt_develop_t *dev = (dt_develop_t *)self->data;
2144 "darkroom-leave-autoset");
2150 // Last moment at which committing is still safe -- everything below tears down
2151 // dev->pipe / dev->iop / dev->history. Run the pending edit rather than lose it.
2153
2155 if(dev->image_surface) cairo_surface_destroy(dev->image_surface);
2156 dev->image_surface = NULL;
2157
2158 // Send all pipeline shutdown signals first
2159 dev->exit = 1;
2162 dev->pipelines_started = FALSE;
2163
2164 /* dev->exit / the shutdown atomics above are only checked by the darkroom worker thread
2165 * (dt_dev_darkroom_pipeline(), running in the DT_CTL_WORKER_DARKROOM job) between loop
2166 * iterations and between servicing each pipe -- they do not preempt it mid-flight. Below,
2167 * this function tears down dev->pipe/preview_pipe nodes, dev->iop and dev->history straight
2168 * from the GUI thread. Proceeding before that worker thread has actually returned races it:
2169 * dt_dev_pixelpipe_set_input() and the history-hash resync in _resync_pipe_with_history() touch
2170 * pipe->nodes and pipe->dev->iop without holding busy_mutex, so the worker can still be
2171 * mid-access on structures we are about to free, corrupting unrelated heap state (observed as
2172 * a crash inside an IOP module's cleanup_pipe() far away from the actual race). Block here
2173 * until the worker has fully exited both pipes' service loop. */
2175 && dt_control_running())
2176 dt_iop_nap(10000); // 10 ms
2177
2178 dt_dev_pixelpipe_cache_wait_dump_pending("darkroom-leave-before-cleanup");
2179
2180 /* Stop module-owned background threads that may still be mutating the pipe, dev or history (e.g.
2181 * drawlayer's asynchronous paint/commit worker) BEFORE we tear down pipeline nodes and history
2182 * below. Otherwise an in-flight commit resync can run a pipeline synch against pieces this function
2183 * is about to free, faulting in whatever module commits next. */
2184 for(GList *m = dev->iop; m; m = g_list_next(m))
2185 {
2186 dt_iop_module_t *mod = (dt_iop_module_t *)m->data;
2187 if(mod && mod->quiesce) mod->quiesce(mod);
2188 }
2189
2191
2192 // While we wait for possible pipelines to finish,
2193 // do the GUI cleaning.
2194
2195 // store last active plugin:
2196 if(dt_dev_get_global()->gui_module)
2197 dt_conf_set_string("plugins/darkroom/active", dt_dev_get_global()->gui_module->op);
2198 else
2199 dt_conf_set_string("plugins/darkroom/active", "");
2200
2201 // Hide the popover floating windows
2202 gtk_widget_hide(dev->overexposed.floating_window);
2203 gtk_widget_hide(dev->rawoverexposed.floating_window);
2204 gtk_widget_hide(dev->profile.floating_window);
2205
2206 // Detach the default callback for bauhaus widgets
2208
2209 /* disconnect from filmstrip image activate */
2211 (gpointer)self);
2212
2214
2215 if(dt_dev_get_global()->color_picker.picker)
2216 dt_iop_color_picker_reset(dt_dev_get_global()->color_picker.picker->module, FALSE);
2217
2218 // Detach shortcuts
2220
2221 // Restore the previous selection
2224
2225 dt_thumbtable_hide(dt_gui_get_ui()->thumbtable_filmstrip);
2226 gtk_widget_hide(dt_gui_center_widget());
2227
2228 // Pipeline nodes reference modules from dev->iop
2229 // we need to destroy objects referencing modules
2230 // before destroying the actual modules being referenced.
2236
2243
2244 /* Device-side cache payloads are only an acceleration layer. Once darkroom
2245 * leaves, drop the cl_mem objects these pipes produced -- but only on the
2246 * device(s) they themselves last ran on, so we never touch cache entries
2247 * another, still-running pipe (e.g. a background thumbnail export) holds on
2248 * its own OpenCL device. */
2250 if(dev->preview_pipe->last_devid != dev->pipe->last_devid)
2252
2256
2257 // Not sure why using g_list_free_full() here shits the bed
2258 while(dev->iop)
2259 {
2260 dt_iop_module_t *module = (dt_iop_module_t *)(dev->iop->data);
2261 if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
2262 dt_iop_cleanup_module(module);
2263 dt_free(module);
2264 dev->iop = g_list_delete_link(dev->iop, dev->iop);
2265 }
2266 while(dev->alliop)
2267 {
2269 dt_free(dev->alliop->data);
2270 dev->alliop = g_list_delete_link(dev->alliop, dev->alliop);
2271 }
2272 dev->iop = dev->alliop = NULL;
2273
2274 // cleanup visible masks
2275 if(dev->form_gui)
2276 {
2277 dev->gui_module = NULL; // modules have already been g_free()
2279 }
2280
2281 // clear masks
2283
2284 // Fetch the new thumbnail if needed. Ensure it runs after we save history.
2285 dt_thumbtable_refresh_thumbnail(dt_gui_get_ui()->thumbtable_lighttable, dt_dev_get_global()->image_storage.id, TRUE);
2287
2288 // Release the cache entries for histogram buffers
2291
2294
2297
2298 /* GUI backbuffers were already released when each pipeline was quiesced above. Keep the view-side teardown
2299 * free of extra unref paths so pipeline ownership stays centralized. */
2300
2301
2302 dt_print(DT_DEBUG_CONTROL, "[run_job-] 11 %f in darkroom mode\n", dt_get_wtime());
2303}
2304
2305// Leave central view
2307{
2308 // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image.
2309 dt_develop_t *dev = (dt_develop_t *)self->data;
2313
2315
2316 if(gui->pan_edge.timeout_source
2319 {
2320 gui->pan_edge.velocity[0] = 0.0f;
2321 gui->pan_edge.velocity[1] = 0.0f;
2322 gui->pan_edge.last_time_us = 0;
2323 if(gui->pan_edge.timeout_source)
2324 {
2325 g_source_remove(gui->pan_edge.timeout_source);
2326 gui->pan_edge.timeout_source = 0;
2327 }
2328 gui->pan_edge.view = NULL;
2330 }
2331 else
2333
2334 // masks
2335 gboolean handled = FALSE;
2337 handled = TRUE;
2338 // module
2339 else if(dev->gui_module && dev->gui_module->mouse_leave
2340 && dev->gui_module->mouse_leave(dev->gui_module))
2341 handled = TRUE;
2342
2343 if(handled)
2345
2346 // reset any changes the selected plugin might have made.
2348 dt_control_change_cursor(GDK_LEFT_PTR);
2349}
2350
2351static gboolean _is_in_frame(const int width, const int height, const int x, const int y)
2352{
2353 return !((x < -DT_PIXEL_APPLY_DPI(2)) ||
2354 (x > (width + DT_PIXEL_APPLY_DPI(4))) ||
2355 (y < -DT_PIXEL_APPLY_DPI(2)) ||
2356 (y > (height + DT_PIXEL_APPLY_DPI(4))));
2357}
2358
2359/* This helper function tests for a position to be within the displayed area
2360 of an image. To avoid "border cases" we accept values to be slightly out of area too.
2361*/
2362static gboolean mouse_in_imagearea(dt_view_t *self, double x, double y)
2363{
2364 dt_develop_t *dev = (dt_develop_t *)self->data;
2365 float image_box[4] = { 0.0f };
2366 dt_dev_get_image_box_in_widget(dev, self->width, self->height, image_box);
2367
2368 return _is_in_frame(image_box[2], image_box[3], round(x - image_box[0]), round(y - image_box[1]));
2369}
2370
2371static gboolean mouse_in_actionarea(dt_view_t *self, double x, double y)
2372{
2373 return _is_in_frame(self->width, self->height, round(x), round(y));
2374}
2375
2376static void _darkroom_set_default_cursor(dt_view_t *self, double x, double y)
2377{
2378 if(mouse_in_imagearea(self, x, y))
2380 else if(mouse_in_actionarea(self, x, y))
2382 else
2384}
2385
2387{
2388 dt_develop_t *dev = (dt_develop_t *)self->data;
2390}
2391
2392// The module the pending mask-edit history commit must be addressed to, captured when the
2393// commit is queued (i.e. when the mask event actually happened). The commit itself fires after
2394// an adaptive throttle timeout, and dev->gui_module may have moved to another module meanwhile:
2395// addressing the commit to the *new* focus then records the mask edit on a module that owns no
2396// forms, whose history item hash does not change — the resync takes the cheap top-item path and
2397// the real owner's piece hash is never refreshed, so the pipe keeps serving the pre-edit
2398// cacheline (issue #1060 family: "the mask does nothing until you nudge a slider").
2400
2401/* Re-render the mask's owner from the LIVE geometry, without writing history.
2402 *
2403 * A drawn shape's geometry lives in dev->forms, not in any module's params, and the pipe reads
2404 * it fresh on every recompute (dt_dev_pixelpipe_process() re-snapshots pipe->forms). What the
2405 * pipe does NOT do on its own is notice that the geometry moved: a piece is re-keyed only when
2406 * its history item changes, so a drag used to be made visible by committing history on a
2407 * throttle, mid-drag -- one history rewrite, one DB write job and one full synch_top per
2408 * throttle tick, with the mask's owner re-hashed against the forms as a side effect.
2409 *
2410 * This is the drawlayer brush's route instead (see _publish_backend_progress() in
2411 * iop/drawlayer/worker.c): publish the owner's params through the transient channel and flag
2412 * the main pipe TOP_CHANGED. That routes the resync through _sync_focused_in_place(), which
2413 * re-commits the ONE focused piece against the live dev->forms -- dt_iop_compute_blendop_hash()
2414 * folds the group's geometry in -- so the piece re-keys from the moved shape and only it and
2415 * its downstream recompute. The params published are the module's own, unchanged: it is the
2416 * forms that moved, and the transient slot is only the ticket onto the focused-piece path.
2417 *
2418 * Flagged without the killswitch (dt_dev_pixelpipe_or_changed, not _change_pipe), as the
2419 * drawlayer heartbeat does: a fast drag must not abort every in-flight render or none would
2420 * ever finish. History is written once, when the interaction ends -- see the commit below,
2421 * which clears this slot so the pipe goes back to rendering the committed state. */
2423{
2424 dt_iop_module_t *module = dev->gui_module;
2425 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->params) || module->params_size <= 0) return;
2426 dt_dev_transient_params_set(module, module->params, (size_t)module->params_size, NULL, 0);
2428}
2429
2430static void _delayed_history_commit(gpointer data)
2431{
2432 dt_develop_t *dev = (dt_develop_t *)data;
2433
2434 /* Not while a shape is still being dragged. The throttle fires about once per pipe render,
2435 * which used to land a history commit -- and the resync it triggers -- in the middle of the
2436 * drag; the transient publish above is what feeds the pipe meanwhile, so the commit can wait
2437 * for the button to come up. Re-queueing re-arms the throttle; the pending module is left in
2438 * place so the eventual commit still lands on the owner of the interaction. */
2440 {
2442 return;
2443 }
2444
2445 dt_iop_module_t *module = _pending_mask_commit_module;
2447 // The captured module can be gone by now (image switch, instance removal): only trust it if
2448 // it is still a live member of dev->iop, else fall back to the current focus.
2449 if(IS_NULL_PTR(module) || IS_NULL_PTR(g_list_find(dev->iop, module)))
2450 module = dev->gui_module;
2451
2452 // The interaction is over: drop the transient ticket so the pipe renders the committed
2453 // history state again. Cleared BEFORE the commit, whose resync must come from history --
2454 // and cleared even when nothing changed, so a no-op drag does not leave the slot occupied.
2455 const gboolean was_transient = !IS_NULL_PTR(module) && dt_dev_transient_params_active(dev, module);
2456 if(was_transient) dt_dev_transient_params_clear(module);
2457
2458 // Figure out if an history item needs to be added
2459 // aka drawn masks have changed somehow. This is more expensive
2460 // but more reliable than handling individually all editing operations
2461 // in all callbacks in all possible mask types.
2462 // (dt_dev_masks_update_hash takes its own, correctly-scoped masks_mutex lock.)
2464
2465 if(dev->forms_changed)
2466 dt_dev_add_history_item(dev, module, FALSE, TRUE);
2467 else if(was_transient)
2468 // Nothing to commit, but the pipe was rendering from the transient slot: re-sync it from
2469 // history so it does not keep a render keyed to a slot that no longer exists.
2471}
2472
2473// Queue the throttled mask-edit history commit, capturing its target module NOW. Every mask
2474// event handler must use this instead of calling dt_gui_throttle_queue() directly, so the
2475// commit lands on the module that owned the mask interaction, not on whichever module holds
2476// the focus when the throttle fires.
2482
2492
2493static float _darkroom_edge_pan_velocity(const double position, const double size, const float margin)
2494{
2495 const double near_edge = position < margin ? margin - position
2496 : position > size - margin ? position - (size - margin)
2497 : 0.0;
2498 if(near_edge <= 0.0) return 0.0f;
2499
2500 const float edge_distance = CLAMPF(near_edge / margin, 0.0f, 1.0f);
2501 const float velocity = 0.10f + 0.90f * edge_distance * edge_distance;
2502 return position < margin ? -velocity : velocity;
2503}
2504
2506{
2508 if(IS_NULL_PTR(gui) || IS_NULL_PTR(dev)) return FALSE;
2509
2510 dt_masks_form_gui_t *form_gui = dev->form_gui;
2511 const gboolean mouse_action = gui->mouse.is_dragging && !gui->mouse.is_painting;
2512 const gboolean creating_shape_mode = !IS_NULL_PTR(form_gui) && form_gui->creation && !gui->mouse.is_painting;
2513
2514 return creating_shape_mode || mouse_action;
2515}
2516
2525 const double pointer_x,
2526 const double pointer_y,
2527 const int width,
2528 const int height,
2530{
2533 if(IS_NULL_PTR(gui) || IS_NULL_PTR(ctl) || IS_NULL_PTR(self)) return;
2534
2535 dt_develop_t *dev = (dt_develop_t *)self->data;
2536 if(IS_NULL_PTR(dev)) return;
2537
2538 // recheck the global eligibility conditions
2540
2541 if(!gui->pan_edge.enabled
2543 || dt_dev_viewport_scaling(dev) <= 1.0f)
2544 return;
2545
2546 float image_box[4] = { 0.0f };
2548 const double image_x = pointer_x - image_box[0];
2549 const double image_y = pointer_y - image_box[1];
2550
2551 edge->dev = dev;
2552 edge->drag = TRUE;
2553 edge->inside_image = mouse_in_imagearea(self, pointer_x, pointer_y);
2554 const gboolean inside_action_area = mouse_in_actionarea(self, pointer_x, pointer_y)
2555 && image_box[2] > 0.0f && image_box[3] > 0.0f;
2556 edge->margin = inside_action_area
2557 ? CLAMPF(MIN(image_box[2], image_box[3]) / 3, 1, DARKROOM_EDGE_PAN_MARGIN_PX)
2558 : 0.0f;
2559 edge->in_margin = inside_action_area
2560 && edge->margin > 0.0f
2561 && (image_x < edge->margin
2562 || image_x > (double)image_box[2] - edge->margin
2563 || image_y < edge->margin
2564 || image_y > (double)image_box[3] - edge->margin);
2565
2566 if(!edge->in_margin) return;
2567
2568 edge->velocity[0] = _darkroom_edge_pan_velocity(image_x, image_box[2], edge->margin);
2569 edge->velocity[1] = _darkroom_edge_pan_velocity(image_y, image_box[3], edge->margin);
2570}
2571
2580 const double pointer_x,
2581 const double pointer_y,
2582 const int width,
2583 const int height)
2584{
2587 darkroom_edge_pan_test_t edge = { 0 };
2588 _darkroom_edge_pan_update_state(self, pointer_x, pointer_y, width, height, &edge);
2589
2590 if(IS_NULL_PTR(gui))
2591 return FALSE;
2592
2593 // reset and exit conditions
2594 if(!gui->pan_edge.enabled || IS_NULL_PTR(self) || IS_NULL_PTR(ctl) || !edge.drag)
2595 {
2596 gui->pan_edge.timeout_source = 0;
2598 return FALSE;
2599 }
2600
2601 if(!edge.in_margin)
2602 {
2603 // Leaving the edge band stops automatic ROI motion and immediately hands
2604 // control back to the regular drag path.
2605 gui->pan_edge.timeout_source = 0;
2606 gui->pan_edge.view = self;
2607 gui->pan_edge.velocity[0] = 0.0f;
2608 gui->pan_edge.velocity[1] = 0.0f;
2609 gui->pan_edge.last_time_us = 0;
2611 ctl->button_x = pointer_x;
2612 ctl->button_y = pointer_y;
2613 return FALSE;
2614 }
2615
2616 if(gui->pan_edge.velocity[0] == 0.0f && gui->pan_edge.velocity[1] == 0.0f)
2617 {
2618 // A mouse event may have stopped edge-pan before this tick runs.
2619 // End the timeout without releasing the stored drag reference position.
2620 gui->pan_edge.timeout_source = 0;
2621 gui->pan_edge.last_time_us = 0;
2622 ctl->button_x = pointer_x;
2623 ctl->button_y = pointer_y;
2624 return FALSE;
2625 }
2626
2627 const gint64 now_us = g_get_monotonic_time();
2628 const float elapsed_s = CLAMPF((now_us - gui->pan_edge.last_time_us) / 1000000.0f, 0.001f, 0.100f);
2629 gui->pan_edge.last_time_us = now_us;
2630
2631 float delta[2] = { gui->pan_edge.velocity[0] * DARKROOM_EDGE_PAN_SPEED_PX_PER_S * elapsed_s,
2633 };
2634 dt_develop_t *dev = edge.dev;
2636
2637 float roi[2] = { dt_dev_viewport_center_x(dev) + delta[0] / (float)dt_dev_geometry_processed_width(dev),
2639 };
2640 dt_dev_check_zoom_pos_bounds(dev, &roi[0], &roi[1], NULL, NULL);
2641
2642 ctl->button_x = pointer_x;
2643 ctl->button_y = pointer_y;
2644
2645 if(dt_dev_viewport_center_x(dev) != roi[0] || dt_dev_viewport_center_y(dev) != roi[1])
2646 {
2647 dt_dev_viewport_set_center(dev, roi[0], roi[1]);
2648 // Updating ctl->button_x/y changes the cursor position, which is the same as a mouse move event.
2649 mouse_moved(self, pointer_x, pointer_y, 1.0, 0);
2650 //dt_control_queue_redraw_center();
2652 }
2653
2654 return TRUE;
2655}
2656
2663static gboolean _darkroom_edge_pan_tick(gpointer user_data)
2664{
2666 if(IS_NULL_PTR(gui))
2667 return FALSE;
2668
2669
2670 // Read the live pointer position instead of ctl->button_x/y: the latter only
2671 // stores the last mouse event, while the timeout must stop even if no event follows.
2672 dt_view_t *self = gui->pan_edge.view;
2673 GtkWidget *center = dt_gui_center_widget();
2674 GdkWindow *window = IS_NULL_PTR(center) ? NULL : gtk_widget_get_window(center);
2675 GdkDisplay *display = IS_NULL_PTR(window) ? NULL : gdk_window_get_display(window);
2676 GdkSeat *seat = IS_NULL_PTR(display) ? NULL : gdk_display_get_default_seat(display);
2677 GdkDevice *pointer = IS_NULL_PTR(seat) ? NULL : gdk_seat_get_pointer(seat);
2678 int pointer_x = 0;
2679 int pointer_y = 0;
2680 GtkAllocation allocation = { 0 };
2681
2682 if(IS_NULL_PTR(window) || IS_NULL_PTR(pointer))
2683 {
2684 gui->pan_edge.timeout_source = 0;
2686 return FALSE;
2687 }
2688
2689 gdk_window_get_device_position(window, pointer, &pointer_x, &pointer_y, NULL);
2690 gtk_widget_get_allocation(center, &allocation);
2691
2692 return _darkroom_edge_pan_apply(self, pointer_x, pointer_y, allocation.width, allocation.height);
2693}
2694
2695void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
2696{
2697 dt_develop_t *dev = (dt_develop_t *)self->data;
2700
2701 const gboolean picker_active = dt_iop_color_picker_is_visible(dev);
2702
2703 // change cursor appearance by default
2705 gboolean handled = FALSE;
2706 gboolean masks_handled = FALSE;
2707
2708 if(picker_active && dt_control_button_down(1))
2709 {
2710 // module requested a color box
2711 if(mouse_in_imagearea(self, x, y))
2712 {
2714 gboolean sample_changed = FALSE;
2715 float mouse_point[2] = { (float)x, (float)y };
2716 dt_dev_coordinates_widget_to_image_norm(dev, mouse_point, 1);
2717 const float delta[2] = {
2720 };
2721
2722 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2723 {
2724 float anchor[2] = { 0.0f };
2726 const float box[4] = {
2727 fmaxf(0.0, MIN(anchor[0], mouse_point[0]) - delta[0]),
2728 fmaxf(0.0, MIN(anchor[1], mouse_point[1]) - delta[1]),
2729 fminf(1.0, MAX(anchor[0], mouse_point[0]) + delta[0]),
2730 fminf(1.0, MAX(anchor[1], mouse_point[1]) + delta[1])
2731 };
2732 dt_boundingbox_t image_box = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
2734
2735 for(int k = 0; k < 4; k++)
2736 sample_changed |= (image_box[k] != box[k]);
2737
2738 if(sample_changed)
2740 }
2741 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2742 {
2743 float image_point[2] = { 0.0f };
2744 _darkroom_sample_raw_point_to_image_norm(sample, image_point);
2745 sample_changed = memcmp(image_point, mouse_point, sizeof(mouse_point)) != 0;
2746 if(sample_changed)
2748 }
2749 }
2750 handled = TRUE;
2751 }
2752 else if(picker_active)
2753 {
2754 // Keep module-specific hover overlays and live-edit cursors disabled while the picker owns the center view.
2755 handled = TRUE;
2756 }
2757
2758 // masks
2759 else if(dt_masks_get_visible_form(dev)
2760 && dt_masks_events_mouse_moved(dev, dev->gui_module, x, y, pressure, which))
2761 {
2762 masks_handled = TRUE;
2763 // There is no shape dragging in creation mode, so no need to commit history.
2764 if(!dev->form_gui->creation)
2765 {
2766 // A shape being dragged is re-rendered live from the transient slot on every motion;
2767 // hover motion changes no geometry and gets nothing. The history commit queued below
2768 // defers itself until the drag ends (see _delayed_history_commit).
2772 }
2773
2774 handled = TRUE;
2775 }
2776
2777 // module
2778 else if(dev->gui_module && dev->gui_module->mouse_moved
2779 &&dev->gui_module->mouse_moved(dev->gui_module, x, y, pressure, which))
2780 {
2781 handled = TRUE;
2782 }
2783
2784 darkroom_edge_pan_test_t edge = { 0 };
2785 _darkroom_edge_pan_update_state(self, x, y, self->width, self->height, &edge);
2786
2787 if(!edge.drag && (gui->pan_edge.timeout_source || gui->pan_edge.block_normal_pan))
2788 {
2790 }
2791
2792 if(edge.drag && !edge.in_margin && gui->pan_edge.timeout_source)
2793 {
2794 /* The drag has already activated edge-pan. Leaving the edge band must stop
2795 timeout-driven ROI motion immediately and restore normal pan right away. */
2796 gui->pan_edge.view = self;
2797 gui->pan_edge.velocity[0] = 0.0f;
2798 gui->pan_edge.velocity[1] = 0.0f;
2799 gui->pan_edge.last_time_us = 0;
2801 if(gui->pan_edge.timeout_source)
2802 {
2803 g_source_remove(gui->pan_edge.timeout_source);
2804 gui->pan_edge.timeout_source = 0;
2805 }
2806 ctl->button_x = x;
2807 ctl->button_y = y;
2808 }
2809
2810 // While a left-button drag is in the edge band, the timeout owns ROI motion.
2811 // The current mouse position only updates the velocity that each tick applies.
2812 if(edge.in_margin)
2813 {
2814 gui->pan_edge.view = self;
2816 gui->pan_edge.velocity[0] = edge.velocity[0];
2817 gui->pan_edge.velocity[1] = edge.velocity[1];
2818 if(!gui->pan_edge.timeout_source)
2819 {
2820 gui->pan_edge.last_time_us = g_get_monotonic_time();
2823 }
2824 }
2825
2827
2829 {
2830 float delta[2] = { x - ctl->button_x, y - ctl->button_y };
2832
2833 float roi[2] = { dt_dev_viewport_center_x(dev) - (delta[0] / dt_dev_geometry_processed_width(dev)),
2835 dt_dev_check_zoom_pos_bounds(dev, &roi[0], &roi[1], NULL, NULL);
2836
2837 dt_dev_viewport_set_center(dev, roi[0], roi[1]);
2838 ctl->button_x = x;
2839 ctl->button_y = y;
2840
2842 return;
2843 }
2844
2845 if(handled)
2846 {
2847 /* a motion the masks handled changed the overlay and nothing else: repaint what it touched,
2848 * not the image under the whole window */
2849 if(masks_handled)
2851 else
2853 return;
2854 }
2855
2856 // Edge-pan owns ROI motion only while the timeout is actively driving updates.
2858 {
2859 ctl->button_x = x;
2860 ctl->button_y = y;
2861 return;
2862 }
2863
2864 // panning with left mouse button.
2865 // _darkroom_center_pan_drag is what makes this OUR drag: dt_control_button_down() reports the
2866 // device's state, so without it a button pressed elsewhere and still held on entry would pan
2867 // the image from a stale ctl->button_x/y anchor. The sibling path at :2563 always had this
2868 // guard; here it was implicit, back when button_down was set only by our own press handler.
2870 {
2871 float delta[2] = { x - ctl->button_x, y - ctl->button_y };
2873
2874 // new roi position in full image scale
2875 float roi[2] = { dt_dev_viewport_center_x(dev) - (delta[0] / dt_dev_geometry_processed_width(dev)),
2877 dt_dev_check_zoom_pos_bounds(dev, &roi[0], &roi[1], NULL, NULL);
2878
2879 dt_dev_viewport_set_center(dev, roi[0], roi[1]);
2880
2881 // update clicked position
2882 ctl->button_x = x;
2883 ctl->button_y = y;
2884
2886 }
2887}
2888
2889
2890int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
2891{
2892 dt_develop_t *dev = (dt_develop_t *)self->data;
2893
2894 dt_print(DT_DEBUG_INPUT, "[darkroom] button released which: %d state: %d x: %.2f y: %.2f\n",
2895 which, state, x, y);
2896
2897 if(which == 1)
2898 {
2902
2904 }
2905
2906 if(dt_iop_color_picker_is_visible(dev) && which == 1)
2907 {
2908 // only sample box picker at end, for speed
2910 dt_control_queue_cursor(GDK_LEFT_PTR);
2911
2913 return 1;
2914 }
2915 // masks
2917 && dt_masks_events_button_released(dev, dev->gui_module, x, y, which, state))
2918 {
2919 // Change on mask parameters and image output.
2921 dt_dev_pixelpipe_update_history_preview(dev); // Needed if mask selection changed
2923 return 1;
2924 }
2925
2926 // module
2927 if(dev->gui_module && dev->gui_module->enabled && dev->gui_module->button_released
2928 && dev->gui_module->button_released(dev->gui_module, x, y, which, state))
2929 {
2930 // Click in modules should handle history changes internally.
2931 return 1;
2932 }
2933
2934 return 0;
2935}
2936
2937
2938int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
2939{
2941 dt_develop_t *dev = (dt_develop_t *)self->data;
2942
2943 dt_print(DT_DEBUG_INPUT, "[darkroom] button pressed which: %d type: %d x: %.2f y: %.2f pressure: %f\n",
2944 which, type, x, y, pressure);
2945
2946 // Grab focus on any click so we can interact from keyboard
2947 gtk_widget_grab_focus(dt_gui_center_widget());
2948 if(which == 1)
2949 {
2953
2954 }
2955
2957 {
2958 float point[2] = { (float)x, (float)y };
2960
2961 const float zoom_scale = dt_dev_get_fit_scale(dev);
2962 float handle[2] = { 6.0f, 6.0f };
2964 handle[0] /= dt_dev_geometry_processed_width(dev);
2965 handle[1] /= dt_dev_geometry_processed_height(dev);
2966
2967 if(which == 1)
2968 {
2969 if(mouse_in_imagearea(self, x, y))
2970 {
2971 // The default box will be a square with 1% of the image width
2972 const float delta_x = 0.01f;
2973 const float delta_y = delta_x * (float)dt_dev_geometry_processed_width(dev) / (float)dt_dev_geometry_processed_height(dev);
2974
2975 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2976 {
2977 /* Box drags need one anchor corner stored in sample->point so mouse motion can stretch
2978 the rectangle against that fixed corner. Keep that anchor local to the darkroom drag
2979 state, but let the picker API own the actual sampling geometry and update signaling. */
2980 float raw_point[2] = { point[0], point[1] };
2982 memcpy(sample->point, raw_point, sizeof(raw_point));
2983
2984 dt_boundingbox_t image_box = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
2986
2987 // this is slightly more than as drawn, to give room for slop
2988 gboolean on_corner_prev_box = TRUE;
2989 float opposite[2] = { 0.0f };
2990
2991 if(fabsf(point[0] - image_box[0]) <= handle[0])
2992 opposite[0] = image_box[2];
2993 else if(fabsf(point[0] - image_box[2]) <= handle[0])
2994 opposite[0] = image_box[0];
2995 else
2996 on_corner_prev_box = FALSE;
2997
2998 if(fabsf(point[1] - image_box[1]) <= handle[1])
2999 opposite[1] = image_box[3];
3000 else if(fabsf(point[1] - image_box[3]) <= handle[1])
3001 opposite[1] = image_box[1];
3002 else
3003 on_corner_prev_box = FALSE;
3004
3005 if(on_corner_prev_box)
3006 {
3007 float raw_opposite[2] = { opposite[0], opposite[1] };
3008 dt_dev_coordinates_image_norm_to_raw_norm(dev, raw_opposite, 1);
3009 memcpy(sample->point, raw_opposite, sizeof(raw_opposite));
3010 }
3011 else
3012 {
3013 const dt_boundingbox_t box = {
3014 fmaxf(0.0, point[0] - delta_x),
3015 fmaxf(0.0, point[1] - delta_y),
3016 fminf(1.0, point[0] + delta_x),
3017 fminf(1.0, point[1] + delta_y)
3018 };
3020 }
3021 dt_control_queue_cursor(GDK_FLEUR);
3022 }
3023 else
3024 {
3025 /* Point pickers must not pre-write sample->point before going through the picker API,
3026 otherwise the setter sees no geometry change and skips the resample request. */
3028 }
3029 }
3031 return 1;
3032 }
3033
3034 if(which == 3)
3035 {
3036 // apply a live sample's area to the active picker?
3037 // FIXME: this is a naive implementation, nicer would be to cycle through overlapping samples then reset
3039 if(dt_dev_get_global()->color_picker.display_samples && mouse_in_imagearea(self, x, y))
3040 for(GSList *samples = dt_dev_get_global()->color_picker.samples; samples; samples = g_slist_next(samples))
3041 {
3042 dt_colorpicker_sample_t *live_sample = samples->data;
3043 if(live_sample->size == DT_LIB_COLORPICKER_SIZE_BOX && picker->kind != DT_COLOR_PICKER_POINT)
3044 {
3045 dt_boundingbox_t live_box = { live_sample->box[0], live_sample->box[1], live_sample->box[2], live_sample->box[3] };
3047 if(point[0] < live_box[0] || point[0] > live_box[2]
3048 || point[1] < live_box[1] || point[1] > live_box[3])
3049 continue;
3051 }
3052 else if(live_sample->size == DT_LIB_COLORPICKER_SIZE_POINT && picker->kind != DT_COLOR_PICKER_AREA)
3053 {
3054 // magic values derived from _darkroom_pickers_draw
3055 float slop[2] = {
3056 MAX(26.0f, roundf(3.0f * zoom_scale)),
3057 MAX(26.0f, roundf(3.0f * zoom_scale))
3058 };
3060 slop[0] /= dt_dev_geometry_processed_width(dev);
3061 slop[1] /= dt_dev_geometry_processed_height(dev);
3062 float live_point[2] = { live_sample->point[0], live_sample->point[1] };
3064 if(fabsf(point[0] - live_point[0]) > slop[0]
3065 || fabsf(point[1] - live_point[1]) > slop[1])
3066 continue;
3068 }
3069 else
3070 continue;
3071 return 1;
3072 }
3073
3074 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
3075 {
3076 // default is hardcoded this way
3077 // FIXME: color_pixer_proxy should have an dt_iop_color_picker_clear_area() function for this
3078 dt_boundingbox_t reset = { 0.01f, 0.01f, 0.99f, 0.99f };
3080 }
3081 return 1;
3082 }
3083 }
3084
3085 // masks
3087 && dt_masks_events_button_pressed(dev, dev->gui_module, x, y, pressure, which, type, state))
3088 {
3089 if(!dt_dev_get_global()->form_gui->creation)
3091 return 1;
3092 }
3093 // module
3094 if(dev->gui_module && dev->gui_module->enabled && dev->gui_module->button_pressed
3095 && dev->gui_module->button_pressed(dev->gui_module, x, y, pressure, which, type, state))
3096 {
3097 return 1;
3098 }
3099
3100 if(which == 1 && dt_dev_viewport_scaling(dev) > 1.0f && mouse_in_imagearea(self, x, y))
3102
3103 if(which == 2)
3104 {
3105 // Incremental zoom-in on middle button click, from fit to 800%
3106 // by power of 2 increments (100%, 200%, 400%, 800%).
3107 float new_scale = 1.f;
3109 new_scale = 1.f; // zoom to fit
3111 new_scale = 1.f / dt_dev_roi_request_natural_scale(dev); // 100 %
3112 else
3114
3115 const float point[2] = { x, y };
3116 return _change_scaling(dev, point, new_scale);
3117 }
3118
3119 return 0;
3120}
3121
3122/* A zoom step that lands closer to fit (1.0) than its own size lands on fit, so stepping through
3123 * fit stops there. Only steps snap: a jump to an absolute level is usually farther from where it
3124 * starts than from fit, and would land on fit instead -- 100% from 200%, or from below fit. */
3125static float _snap_step_to_fit(const float old_scaling, const float new_scaling)
3126{
3127 return (fabsf(new_scaling - 1.0f) < fabsf(old_scaling - new_scaling)) ? 1.0f : new_scaling;
3128}
3129
3130static int _change_scaling(dt_develop_t *dev, const float point[2], const float new_scaling)
3131{
3132 const float old_scaling = dt_dev_viewport_scaling(dev);
3133 dt_dev_viewport_set_scaling(dev, new_scaling);
3134
3136 {
3137 // Calculate zoom position offset to keep mouse position fixed during zoom
3138 float center[2] = { 0.0f };
3139 float mouse_offset[2] = { point[0], point[1] };
3140 dt_dev_get_widget_center(dev, center);
3141 mouse_offset[0] -= center[0];
3142 mouse_offset[1] -= center[1];
3143
3144
3145 // Keep the image point under the mouse fixed in widget coordinates while
3146 // the pipeline zoom stays DPI-invariant.
3147 const float old_zoom = dt_dev_get_widget_zoom_scale(dev, old_scaling);
3148 const float new_zoom = dt_dev_get_widget_zoom_scale(dev, dt_dev_viewport_scaling(dev));
3149 if(old_zoom <= 1e-6f || new_zoom <= 1e-6f)
3150 {
3151 dt_dev_viewport_set_scaling(dev, old_scaling);
3152 return 0;
3153 }
3154
3155 // Adjust the center to compensate for the scale change
3156 int proc_w = 0.f, proc_h = 0.f;
3157 dt_dev_get_processed_size(dev, &proc_w, &proc_h);
3158 const dt_dev_viewport_state_t anchored = dt_dev_viewport_get(dev);
3160 anchored.center_x + mouse_offset[0] * (1.f / old_zoom - 1.f / new_zoom) / proc_w,
3161 anchored.center_y + mouse_offset[1] * (1.f / old_zoom - 1.f / new_zoom) / proc_h);
3162
3165 return 1;
3166 }
3167 else
3168 {
3169 // Invalid zoom level, keep previous value
3170 dt_dev_viewport_set_scaling(dev, old_scaling);
3171 return 0;
3172 }
3173}
3174
3175static gboolean _center_view_free_zoom(dt_view_t *self, double x, double y, int up, int state, int flow)
3176{
3178
3179 // Commit the new scaling
3180 const float step = 1.02f;
3181 const float old_scaling = dt_dev_viewport_scaling(dev);
3182 const float new_scaling = _snap_step_to_fit(old_scaling, old_scaling * powf(step, (float)-flow));
3183 const float point[2] = { x, y };
3184 return _change_scaling(dev, point, new_scaling);
3185}
3186
3187
3188int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
3189{
3191
3192 dt_develop_t *dev = (dt_develop_t *)self->data;
3193
3194 dt_print(DT_DEBUG_INPUT, "[darkroom] scrolled: up: %i x: %.2f y: %.2f state: %i flow: %i\n",
3195 up, x, y, state, delta_y);
3196
3197 // masks
3199 && dt_masks_events_mouse_scrolled(dev, dev->gui_module, x, y, up, state, delta_y))
3200 {
3202 if(!dev->form_gui->creation)
3203 {
3204 // Every scroll step changes the shape (size, feather or opacity) and is rendered live
3205 // from the transient slot at once; a burst of steps then lands ONE history commit when
3206 // the throttle fires after the last one, which also clears the slot. Scrolling has no
3207 // "release" the way a drag does, so the throttle is what marks the end of the burst.
3210 }
3211
3212 return TRUE;
3213 }
3214
3215 // module -- skip while the color picker overlay is active, consistent with button_pressed/
3216 // button_released/mouse_moved above, which all give the picker priority over the module.
3217 // Unlike those three, a module's own scrolled() has no reliable way to know the picker is
3218 // showing unless it checks dt_iop_color_picker_is_visible() itself, and not every module does.
3220 && dev->gui_module && dev->gui_module->enabled && dev->gui_module->scrolled && dev->gui_module->scrolled(dev->gui_module, x, y, up, state))
3221 {
3222 // Scroll in modules should handle history changes internally.
3223 return TRUE;
3224 }
3225
3226 // free zoom
3227 return _center_view_free_zoom(self, x, y, up, state, delta_y);
3228}
3229
3236
3237
3238int key_pressed(dt_view_t *self, GdkEventKey *event)
3239{
3240 if(!gtk_window_is_active(GTK_WINDOW(dt_gui_get_ui()->main_window))) return FALSE;
3241
3242 dt_develop_t *dev = (dt_develop_t *)self->data;
3243
3245 {
3247 return 1;
3248 }
3249
3250 // module
3251 else if(!IS_NULL_PTR(dev->gui_module)
3252 && !IS_NULL_PTR(dev->gui_module->key_pressed)
3253 && dev->gui_module->key_pressed(dev->gui_module, event))
3254 {
3256 return 1;
3257 }
3258
3259 const gboolean shift = dt_modifier_is(event->state, GDK_SHIFT_MASK);
3260 const gboolean ctrl = dt_modifier_is(event->state, DT_PRIMARY_MASK);
3261 const gboolean ctrl_any = dt_modifiers_include(event->state, DT_PRIMARY_MASK);
3262 guint key = dt_keys_mainpad_alternatives(event->keyval);
3263
3264 if(ctrl_any)
3265 {
3266 const float zoom_step = 1.1f;
3267 const float old_scaling = dt_dev_viewport_scaling(dev);
3268 float center[2] = { 0.0f };
3269 dt_dev_get_widget_center(dev, center);
3270
3271 switch(key)
3272 {
3273 case GDK_KEY_plus:
3274 return _change_scaling(dev, center, _snap_step_to_fit(old_scaling, old_scaling * zoom_step));
3275 case GDK_KEY_minus:
3276 return _change_scaling(dev, center, _snap_step_to_fit(old_scaling, old_scaling / zoom_step));
3277 }
3278 }
3279
3280 float multiplier = (shift) ? 4.f :
3281 (ctrl) ? 0.5f :
3282 1.f;
3283
3284 float delta[2] = { 10.f * multiplier, 10.f * multiplier };
3286
3287 switch(key)
3288 {
3289 case GDK_KEY_Up:
3290 {
3293 - delta[1] / (float)dt_dev_geometry_processed_height(dev));
3294 _key_scroll(dev);
3295 return 1;
3296 }
3297 case GDK_KEY_Down:
3298 {
3301 + delta[1] / (float)dt_dev_geometry_processed_height(dev));
3302 _key_scroll(dev);
3303 return 1;
3304 }
3305 case GDK_KEY_Left:
3306 {
3308 - delta[0] / (float)dt_dev_geometry_processed_width(dev),
3310 _key_scroll(dev);
3311 return 1;
3312 }
3313 case GDK_KEY_Right:
3314 {
3316 + delta[0] / (float)dt_dev_geometry_processed_width(dev),
3318 _key_scroll(dev);
3319 return 1;
3320 }
3321 case GDK_KEY_Escape:
3322 {
3323 dt_ctl_switch_mode_to("lighttable");
3324 return TRUE;
3325 }
3326 }
3327
3328 return 0;
3329}
3330
3331void configure(dt_view_t *self, int wd, int ht)
3332{
3333 dt_develop_t *dev = (dt_develop_t *)self->data;
3334
3335 // Configure event is called when initing the view AND upon window resizes events (through Gtk widget/window resize commands).
3336 // At init time, final window size may not be correct just yet.
3337 // It will be when we call dt_ui_restore_panels(), which will resize stuff properly,
3338 // but that will be only when entering the current view.
3339 // Until we run dt_dev_configure(), main preview pipe gets output size -1×-1 px
3340 // which aborts the pipe recompute early. As soon as we init
3341 // sizes with something "valid" with regard to the pipe, pipeline runs.
3342 // Problem is it will not be valid with regard to the window size and the output will be thrown out
3343 // until we get the final size.
3344 // TD;DR: until we get the final window size, which happens
3345 // only when entering the view, don't configure the main preview pipeline, which will disable useless recomputes.
3347 {
3348 // Reference dimensions before ISO 12646 mode
3351 }
3352}
3353
3354// clang-format off
3355// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3356// vim: shiftwidth=2 expandtab tabstop=2 cindent
3357// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3358// 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)
Build the accel path of feature under scope.
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:3283
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2139
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
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:1635
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
#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:716
int32_t dt_control_get_mouse_over_id()
Definition control.c:1032
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:1040
void dt_control_alert(const char *title, const char *message, const char *item)
Tell the user something that must not go unseen: what it says stays true after a toast would have fad...
Definition control.c:883
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
void dt_control_log_busy_leave()
Definition control.c:949
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:578
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:1066
#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:654
static void _publish_mask_edit_transient(dt_develop_t *dev)
Definition darkroom.c:2422
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:2371
static void _preferences_changed(gpointer instance, gpointer user_data)
Re-settle every node against the preferences that were just changed.
Definition darkroom.c:1620
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:2663
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:2139
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:1244
#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:2025
int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition darkroom.c:2938
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:1450
static void _autoset_popover_preshow(gpointer user_data)
Definition darkroom.c:1370
static void _guides_quickbutton_clicked(GtkWidget *widget, gpointer user_data)
Definition darkroom.c:1376
gboolean _switch_to_prev_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
Definition darkroom.c:1547
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:1402
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:1434
static gboolean mouse_in_imagearea(dt_view_t *self, double x, double y)
Definition darkroom.c:2362
static gboolean _is_in_frame(const int width, const int height, const int x, const int y)
Definition darkroom.c:2351
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:1414
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:2399
#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:1342
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:2376
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:2579
static void _preview_pipe_finished(gpointer instance, gpointer user_data)
Definition darkroom.c:1627
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:2524
int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
Definition darkroom.c:3188
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:2430
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:1365
int key_pressed(dt_view_t *self, GdkEventKey *event)
Definition darkroom.c:3238
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:3230
static gboolean _zoom_to_fit(GtkAccelGroup *accel_group __attribute__((unused)), GObject *accelerable __attribute__((unused)), guint keyval __attribute__((unused)), GdkModifierType modifier __attribute__((unused)), gpointer data)
Definition darkroom.c:1572
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:1515
static gboolean _zoom_to_100(GtkAccelGroup *accel_group __attribute__((unused)), GObject *accelerable __attribute__((unused)), guint keyval __attribute__((unused)), GdkModifierType modifier __attribute__((unused)), gpointer data)
Definition darkroom.c:1584
static gboolean _center_view_free_zoom(dt_view_t *self, double x, double y, int up, int state, int flow)
Definition darkroom.c:3175
void mouse_leave(dt_view_t *self)
Definition darkroom.c:2306
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:2695
void configure(dt_view_t *self, int wd, int ht)
Definition darkroom.c:3331
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:2890
static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
Definition darkroom.c:1354
static float _snap_step_to_fit(const float old_scaling, const float new_scaling)
Definition darkroom.c:3125
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:1382
void enter(dt_view_t *self)
Definition darkroom.c:2059
static void _darkroom_change_rendering_size(GtkWidget *combobox, gpointer user_data)
Definition darkroom.c:1443
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:1426
static float _darkroom_edge_pan_velocity(const double position, const double size, const float margin)
Definition darkroom.c:2493
static GtkWidget * _darkroom_autoset_list
Definition darkroom.c:183
static void _darkroom_autoset_popover_rebuild(dt_develop_t *dev)
Definition darkroom.c:1699
static void _darkroom_autoset_quickbutton_clicked(GtkButton *button, gpointer user_data)
Definition darkroom.c:1678
static void _queue_delayed_history_commit(dt_develop_t *dev)
Definition darkroom.c:2477
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:3130
gboolean _switch_to_next_picture(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
Definition darkroom.c:1522
static gchar * _darkroom_autoset_label(const dt_iop_module_t *module)
Definition darkroom.c:1685
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:2505
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:1729
void gui_init(dt_view_t *self)
Definition darkroom.c:1734
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:2045
int try_enter(dt_view_t *self)
Definition darkroom.c:1299
void mouse_enter(dt_view_t *self)
Definition darkroom.c:2386
static int32_t _darkroom_pending_imgid
Definition darkroom.c:175
static void _darkroom_autoset_module_toggled(GtkToggleButton *toggle, gpointer user_data)
Definition darkroom.c:1693
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:531
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:624
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:602
void dt_dev_set_global(struct dt_develop_t *dev)
Definition darktable.c:546
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
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)
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_resync_history_all(dev)
#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:1176
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:356
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:2044
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:2037
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:240
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:2026
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:1054
gboolean dt_dev_masks_manager_is_visible(dt_develop_t *dev)
Definition develop.c:1401
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:146
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1921
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:1132
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1345
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:1141
void dt_dev_backbuf_release_keepalive(dt_backbuf_t *backbuf)
Release this backbuffer's keepalive reference, if it holds one. Safe to call twice.
Definition develop.c:2217
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:2158
void dt_dev_reset_roi(dt_develop_t *dev)
Definition develop.c:2065
GList * dt_dev_load_modules(dt_develop_t *dev)
Definition develop.c:101
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:2226
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:2031
void dt_dev_coordinates_image_norm_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1338
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:507
void dt_dev_masks_update_hash(dt_develop_t *dev)
Definition develop.c:1988
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:2095
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1932
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:2138
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:1194
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:2020
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:1211
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1712
void dt_dev_start_all_pipelines(dt_develop_t *dev)
Definition develop.c:950
@ 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:1669
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:398
@ DT_JOB_QUEUE_SYSTEM_FG
Definition jobs.h:56
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
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
hash
Definition layers.h:90
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1240
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1262
_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:2094
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:925
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)
Peek the host pixels a cache entry holds for its current hash, without allocating.
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(const dt_pixel_cache_entry_t *entry)
#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:1121
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:411
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:390
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_DEVELOP_INITIALIZE
This signal is raised when darktable.develop is initialized.
Definition signal.h:173
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:99
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:208
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:225
@ 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:178
@ DT_SIGNAL_PREFERENCES_CHANGE
This signal is raised after preferences have been changed no parameters no return.
Definition signal.h:277
@ 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:219
@ 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:85
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
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:410
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.
struct dt_view_manager_t::@61 proxy
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
GtkWidget * guides_toggle
Definition view.h:213
struct dt_view_manager_t::@61::@64 darkroom
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:458
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:301
static void dt_thumbtable_show(dt_thumbtable_t *table)
Show the thumbnail table widget.
Definition thumbtable.h:398
static void dt_thumbtable_hide(dt_thumbtable_t *table)
Hide the thumbnail table widget.
Definition thumbtable.h:413
void dt_show_times(const dt_times_t *start, const char *prefix)
Definition darktable.c:2147
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:614
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
Definition view.c:1400
void dt_view_active_images_add(int32_t imgid, gboolean raise)
Definition view.c:1347
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1337
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:239
void dt_view_image_info_update(int32_t imgid)
Definition view.c:1544
int32_t dt_view_active_images_get_first()
Definition view.c:1378
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:144
@ 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)