Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
snapshots.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Alexandre Prokoudine.
4 Copyright (C) 2011 Henrik Andersson.
5 Copyright (C) 2011, 2014, 2016 johannes hanika.
6 Copyright (C) 2011-2012, 2014-2017 Jérémy Rosen.
7 Copyright (C) 2012 Richard Wonka.
8 Copyright (C) 2012-2018 Tobias Ellinghaus.
9 Copyright (C) 2013-2014, 2016 Roman Lebedev.
10 Copyright (C) 2013 Simon Spannagel.
11 Copyright (C) 2013 Wolfgang Goetz.
12 Copyright (C) 2015 parafin.
13 Copyright (C) 2018 Maurizio Paglia.
14 Copyright (C) 2019, 2023, 2025-2026 Aurélien PIERRE.
15 Copyright (C) 2019 jakubfi.
16 Copyright (C) 2019 luzpaz.
17 Copyright (C) 2020, 2022 Chris Elston.
18 Copyright (C) 2020, 2022 Diederik Ter Rahe.
19 Copyright (C) 2020 Heiko Bauke.
20 Copyright (C) 2020-2021 Pascal Obry.
21 Copyright (C) 2021 Bill Ferguson.
22 Copyright (C) 2021 Philippe Weyland.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2023 Alynx Zhou.
25 Copyright (C) 2026 Guillaume STUTIN.
26
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41
42#include "widgets/bauhaus.h"
43#include "common/logging.h"
44#include "system/macros.h"
46#include "develop/iop_order.h"
47#include "control/control.h"
48#include "develop/develop.h"
49#include "develop/dev_history.h"
51#include "widgets/paint.h"
52
54#include "widgets/draw.h"
55#include "libs/lib.h"
56#include "libs/lib_api.h"
57#include "views/view.h"
58
59#include <math.h>
60#include "gui/window_manager.h"
61#include "widgets/scroll_wrap.h"
62
63DT_MODULE(1)
64
65#define DT_LIB_SNAPSHOTS_COUNT 4
66#define SNAP_LOG(...) dt_print(DT_DEBUG_DEV, __VA_ARGS__)
67#define HANDLE_SIZE DT_PIXEL_APPLY_DPI_DPP(36)
68
69/* a snapshot */
70typedef struct dt_lib_snapshot_t
71{
72 GtkWidget *row; // container for button + delete_button; shown/hidden as a unit
75 dt_dev_snapshot_t snap; // ROI-scoped render, recomputed on pan/zoom, see develop/dev_snapshot.h
76 int32_t imgid;
77 int32_t history_end;
79
80
81typedef struct dt_lib_snapshots_t
82{
84
85 uint32_t selected;
86
87 /* current active snapshots */
88 uint32_t num_snapshots;
89
90 /* size of snapshots */
91 uint32_t size;
92
93 /* snapshots */
95
96
97 /* change snapshot overlay controls */
100 gboolean on_going;
102 gboolean hover_line; // cursor is within DT_GUI_MOUSE_EFFECT_RADIUS of the split line itself
103
106
107/* callback for take snapshot */
108static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data);
109static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data);
110static void _lib_snapshots_delete_button_clicked_callback(GtkWidget *widget, gpointer user_data);
111
112// Reset the value fields to "empty" without releasing the snapshot engine or touching GTK
113// widgets. Used when a snapshot's engine is being handed off to another slot (compacting the
114// list after a delete) rather than dropped -- the caller is responsible for the engine.
115// dt_dev_snapshot_t is just a refcounted-engine pointer (develop/dev_snapshot.h), so the move
116// itself already happened via the plain struct assignment at the call site; this only needs to
117// forget this now-empty slot's copy of that pointer, never release it.
119{
120 snap->snap.engine = NULL;
121 snap->imgid = UNKNOWN_IMAGE;
122 snap->history_end = 0;
123}
124
126{
127 if(IS_NULL_PTR(snap)) return;
129 snap->imgid = UNKNOWN_IMAGE;
130 snap->history_end = 0;
131}
132
133// Freeze the current darkroom develop state and render it at `source`'s current viewport (ROI),
134// recomputed as pan/zoom change afterward -- see develop/dev_snapshot.h. The frozen context and
135// its pipe are kept alive for the snapshot's whole lifetime, released by _lib_snapshot_clear_state().
136// Returns 0 on success, 1 on failure.
138{
139 if(IS_NULL_PTR(snapshot) || IS_NULL_PTR(source))
140 {
141 SNAP_LOG("[snapshots] capture failed: invalid inputs snapshot=%p source=%p\n", (void *)snapshot,
142 (void *)source);
143 return 1;
144 }
145 if(source->image_storage.id <= 0)
146 {
147 SNAP_LOG("[snapshots] capture failed: invalid source imgid=%d\n", source->image_storage.id);
148 return 1;
149 }
150
152
153 // dt_dev_snapshot_capture() takes ownership of these two lists -- duplicate the live,
154 // possibly-uncommitted history/iop_order under the lock here, since it must not read
155 // source->history_mutex-guarded state itself (it also serves duplicate.c, which captures a
156 // *different*, not-currently-open image and has no such live source to lock).
157 GList *history_copy = NULL;
158 GList *iop_order_copy = NULL;
159 int32_t history_end = 0;
160
162 history_copy = dt_history_duplicate(source->history);
163 iop_order_copy = dt_ioppr_iop_order_copy_deep(source->iop_order_list);
164 history_end = dt_dev_get_history_end_ext(source);
166
167 snapshot->imgid = source->image_storage.id;
168 snapshot->history_end = history_end;
169
171 const gboolean ok = dt_dev_snapshot_capture(&snapshot->snap, source, source->image_storage.id,
172 history_copy, iop_order_copy, history_end);
174
175 SNAP_LOG("[snapshots] capture: imgid=%d history_end=%d image=%s\n",
176 snapshot->imgid, snapshot->history_end, ok ? "ok" : "FAILED");
177
178 return ok ? 0 : 1;
179}
180
181const char *name(struct dt_lib_module_t *self)
182{
183 return _("Snapshots");
184}
185
186const char **views(dt_lib_module_t *self)
187{
188 static const char *v[] = {"darkroom", NULL};
189 return v;
190}
191
193{
195}
196
198{
199 return 800;
200}
201
202// draw snapshot sign
203static void _draw_sym(cairo_t *cr, float x, float y, gboolean vertical, gboolean inverted)
204{
205 const double inv = inverted ? -0.1 : 1.0;
206
207 PangoRectangle ink;
208 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
209 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
210 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(12) * PANGO_SCALE);
211 PangoLayout *layout = pango_cairo_create_layout(cr);
212 pango_layout_set_font_description(layout, desc);
213 pango_layout_set_text(layout, C_("snapshot sign", "S"), -1);
214 pango_layout_get_pixel_extents(layout, &ink, NULL);
215
216 if(vertical)
217 cairo_move_to(cr, x - (inv * ink.width * 1.2f), y - (ink.height / 2.0f) - DT_PIXEL_APPLY_DPI(3));
218 else
219 cairo_move_to(cr, x - (ink.width / 2.0), y + (-inv * (ink.height * 1.2f) - DT_PIXEL_APPLY_DPI(2)));
220
222 pango_cairo_show_layout(cr, layout);
223 pango_font_description_free(desc);
224 g_object_unref(layout);
225}
226
227/* expose snapshot over center viewport */
228void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx,
229 int32_t pointery)
230{
232 if(IS_NULL_PTR(d)) return;
234
235 if(d->selected >= 1 && d->selected <= d->size)
236 {
237 dt_lib_snapshot_t *snap = d->snapshot + (d->selected - 1);
239 {
240 float image_box[4] = { 0.0f };
242 if(image_box[2] <= 0.0f || image_box[3] <= 0.0f) return;
243 d->vp_x = image_box[0];
244 d->vp_y = image_box[1];
245 d->vp_width = image_box[2];
246 d->vp_height = image_box[3];
247
248 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
249 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
250
251 /* set x,y,w,h of surface depending on split align and invert */
252 double x = d->vp_x;
253 double y = d->vp_y;
254 double w = d->vp_width;
255 double h = d->vp_height;
256 if(d->vertical)
257 {
258 x = d->inverted ? d->vp_x + d->vp_width * split_x : d->vp_x;
259 w = d->inverted ? d->vp_width * (1.0 - split_x) : d->vp_width * split_x;
260 }
261 else
262 {
263 y = d->inverted ? d->vp_y + d->vp_height * split_y : d->vp_y;
264 h = d->inverted ? d->vp_height * (1.0 - split_y) : d->vp_height * split_y;
265 }
266
267 const double size = DT_PIXEL_APPLY_DPI(d->inverted ? -15 : 15);
268
269 dt_dev_snapshot_draw(&snap->snap, cri, dev, width, height, x, y, w, h);
270
271 // draw the split line using the selected overlay color
273 cairo_set_line_width(cri, 1.);
274
275 if(d->vertical)
276 {
277 const double lx = d->vp_x + d->vp_width * split_x;
278 const double center = d->vp_y + 0.5 * d->vp_height;
279
280 cairo_move_to(cri, lx, d->vp_y);
281 cairo_line_to(cri, lx, d->vp_y + d->vp_height);
282 cairo_stroke(cri);
283
284 cairo_move_to(cri, lx, center - size);
285 cairo_line_to(cri, lx - (size * 1.2), center);
286 cairo_line_to(cri, lx, center + size);
287 cairo_close_path(cri);
288 cairo_fill(cri);
289 _draw_sym(cri, lx, center, TRUE, d->inverted);
290 }
291 else
292 {
293 const double ly = d->vp_y + d->vp_height * split_y;
294 const double center = d->vp_x + 0.5 * d->vp_width;
295
296 cairo_move_to(cri, d->vp_x, ly);
297 cairo_line_to(cri, d->vp_x + d->vp_width, ly);
298 cairo_stroke(cri);
299
300 cairo_move_to(cri, center - size, ly);
301 cairo_line_to(cri, center, ly - (size * 1.2));
302 cairo_line_to(cri, center + size, ly);
303 cairo_close_path(cri);
304 cairo_fill(cri);
305 _draw_sym(cri, center, ly, FALSE, d->inverted);
306 }
307
308 /* if mouse over control, draw center rotate handle (hidden while dragging) */
309 if(!d->dragging)
310 {
311 const double half_handle_size = HANDLE_SIZE * 0.5;
312 const gint rx = (d->vertical ? d->vp_x + d->vp_width * split_x : d->vp_x + d->vp_width * 0.5)
313 - half_handle_size;
314 const gint ry = (d->vertical ? d->vp_y + d->vp_height * 0.5 : d->vp_y + d->vp_height * split_y)
315 - half_handle_size;
316
317 dt_draw_set_color_overlay(cri, TRUE, d->hover_rotation ? 1.0 : 0.3);
318 cairo_set_line_width(cri, 0.5);
319 dtgtk_cairo_paint_refresh(cri, rx, ry, HANDLE_SIZE, HANDLE_SIZE, 0, NULL);
320 }
321
322 d->on_going = FALSE;
323
324 if(d->hover_rotation) dt_control_queue_cursor_by_name("exchange");
325 else if(d->dragging) dt_control_queue_cursor_by_name("grabbing");
326 else if(d->hover_line) dt_control_queue_cursor_by_name(d->vertical ? "col-resize" : "row-resize");
327 else
328 {
332 else
334 }
335 }
336 }
337}
338
339int button_released(struct dt_lib_module_t *self, double x, double y, int which, uint32_t state)
340{
342 const gboolean visible_picker = dt_iop_color_picker_is_visible(dt_dev_get_global());
343
344 if(!visible_picker && d->selected > 0 && which == 1)
345 {
346 if(d->dragging)
347 {
348 d->dragging = FALSE;
349 d->hover_rotation = FALSE;
350 }
351 // Refresh mouse_moved event
352 return mouse_moved(self, x, y, 0.0, which);
353 }
354 return 0;
355}
356
358
359int button_pressed(struct dt_lib_module_t *self, double x, double y, double pressure, int which, int type,
360 uint32_t state)
361{
362 // only react to left click
363 if(which != 1) return 0;
364
366
367 const gboolean visible_picker = dt_iop_color_picker_is_visible(dt_dev_get_global());
368
369 if(!visible_picker && d->selected > 0)
370 {
371 if(d->on_going) return 1;
372 if(d->vp_width <= 0.0 || d->vp_height <= 0.0) return 0;
373 if(x < d->vp_x || x > d->vp_x + d->vp_width || y < d->vp_y || y > d->vp_y + d->vp_height) return 0;
374
375 const double xp = CLAMP((x - d->vp_x) / d->vp_width, 0.0, 1.0);
376 const double yp = CLAMP((y - d->vp_y) / d->vp_height, 0.0, 1.0);
377
378 if(d->hover_rotation)
379 {
380 /* let's rotate */
382
383 d->vertical = !d->vertical;
384 if(_lib_snapshot_rotation_cnt % 2) d->inverted = !d->inverted;
385
386 d->vp_xpointer = xp;
387 d->vp_ypointer = yp;
388 d->vp_xrotate = xp;
389 d->vp_yrotate = yp;
390 d->on_going = TRUE;
392 }
393 /* do the dragging !? -- only grab the split line if the click lands within the mouse
394 action radius of its current position, same hit-test radius (DT_GUI_MOUSE_EFFECT_RADIUS)
395 used to grab a mask node, rather than anywhere in the image */
396 else
397 {
398 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
399 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
400 const double lx = d->vp_x + d->vp_width * split_x;
401 const double ly = d->vp_y + d->vp_height * split_y;
402 const double dist_to_line = d->vertical ? fabs(x - lx) : fabs(y - ly);
403 if(dist_to_line > DT_GUI_MOUSE_EFFECT_RADIUS) return 0;
404
405 d->dragging = TRUE;
406 d->vp_ypointer = yp;
407 d->vp_xpointer = xp;
408 d->vp_xrotate = 0.0;
409 d->vp_yrotate = 0.0;
411 }
412 return 1;
413 }
414 return 0;
415}
416
417int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
418{
420
421 const gboolean visible_picker = dt_iop_color_picker_is_visible(dt_dev_get_global());
422
423 if(!visible_picker && d->selected > 0)
424 {
425 if(d->vp_width <= 0.0 || d->vp_height <= 0.0) return 0;
426 const double xp = CLAMP((x - d->vp_x) / d->vp_width, 0.0, 1.0);
427 const double yp = CLAMP((y - d->vp_y) / d->vp_height, 0.0, 1.0);
428
429 if(d->dragging)
430 {
431 /* update pointer pos */
432 d->vp_xpointer = xp;
433 d->vp_ypointer = yp;
435 return 1;
436 }
437
438 const gboolean was_hovering = d->hover_rotation || d->hover_line;
439 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
440 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
441 const double handle_mouse = (DT_GUI_MOUSE_EFFECT_RADIUS + HANDLE_SIZE) * 0.5;
442 const double rxc = d->vertical ? d->vp_x + d->vp_width * split_x : d->vp_x + d->vp_width * 0.5;
443 const double ryc = d->vertical ? d->vp_y + d->vp_height * 0.5 : d->vp_y + d->vp_height * split_y;
444 const double dx = x - rxc;
445 const double dy = y - ryc;
446 d->hover_rotation = (dx * dx + dy * dy) < (handle_mouse * handle_mouse);
447
448 const double lx = d->vp_x + d->vp_width * split_x;
449 const double ly = d->vp_y + d->vp_height * split_y;
450 const double dist_to_line = d->vertical ? fabs(x - lx) : fabs(y - ly);
451 d->hover_line = !d->hover_rotation && dist_to_line <= DT_GUI_MOUSE_EFFECT_RADIUS;
452
453 if(!d->hover_rotation && !d->hover_line)
454 {
455 if(was_hovering) dt_control_queue_redraw_center(); // clear the stale handle/line highlight
456 return 0;
457 }
458
460 return 1;
461 }
462
463 return 0;
464}
465
467{
469 d->num_snapshots = 0;
470 d->selected = 0;
471 d->hover_rotation = FALSE;
472 d->hover_line = FALSE;
473
474 for(uint32_t k = 0; k < d->size; k++)
475 {
476 _lib_snapshot_clear_state(d->snapshot + k);
477 gtk_widget_hide(d->snapshot[k].row);
478 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), FALSE);
479 }
480
482}
483
485{
486 /* initialize ui widgets */
488 self->data = (void *)d;
489
490 /* initialize snapshot storages */
491 d->size = 4;
492 d->snapshot = (dt_lib_snapshot_t *)g_malloc0_n(d->size, sizeof(dt_lib_snapshot_t));
493 d->vp_x = 0.0;
494 d->vp_y = 0.0;
495 d->vp_width = 1.0;
496 d->vp_height = 1.0;
497 d->vp_xpointer = 0.5;
498 d->vp_ypointer = 0.5;
499 d->vp_xrotate = 0.0;
500 d->vp_yrotate = 0.0;
501 d->vertical = TRUE;
502 d->on_going = FALSE;
503 d->hover_rotation = FALSE;
504 d->hover_line = FALSE;
505 /* initialize ui containers */
506 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
507 d->snapshots_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
508
509 /* create take snapshot button */
510 d->take_button = dt_action_button_new(self, N_("take snapshot"), _lib_snapshots_add_button_clicked_callback, self,
511 _("take snapshot to compare with another image "
512 "or the same image at another stage of development"), 0, 0);
513
514 for(int k = 0; k < d->size; k++)
515 {
516 d->snapshot[k].button = gtk_toggle_button_new_with_label("");
517 GtkWidget *label = gtk_bin_get_child(GTK_BIN(d->snapshot[k].button));
518 gtk_widget_set_halign(label, GTK_ALIGN_START);
519 gtk_label_set_xalign(GTK_LABEL(label), 0);
520 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
521 gtk_widget_set_hexpand(d->snapshot[k].button, TRUE);
522
523 g_signal_connect(G_OBJECT(d->snapshot[k].button), "clicked",
524 G_CALLBACK(_lib_snapshots_toggled_callback), self);
525
526 g_object_set_data(G_OBJECT(d->snapshot[k].button), "snapshot", GINT_TO_POINTER(k + 1));
527
528 // Same trash icon as the shapes list under "drawn mask" in develop/blend_gui.c
529 // (group_delete_col): themed "user-trash-symbolic", not a dtgtk cairo glyph.
530 d->snapshot[k].delete_button = gtk_button_new();
531 gtk_button_set_relief(GTK_BUTTON(d->snapshot[k].delete_button), GTK_RELIEF_NONE);
532 gtk_button_set_image(GTK_BUTTON(d->snapshot[k].delete_button),
533 gtk_image_new_from_icon_name("user-trash-symbolic", GTK_ICON_SIZE_MENU));
534 gtk_widget_set_tooltip_text(d->snapshot[k].delete_button, _("remove this snapshot"));
535 g_object_set_data(G_OBJECT(d->snapshot[k].delete_button), "snapshot", GINT_TO_POINTER(k + 1));
536 g_signal_connect(G_OBJECT(d->snapshot[k].delete_button), "clicked",
538
539 d->snapshot[k].row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
540 gtk_box_pack_start(GTK_BOX(d->snapshot[k].row), d->snapshot[k].button, TRUE, TRUE, 0);
541 gtk_box_pack_start(GTK_BOX(d->snapshot[k].row), d->snapshot[k].delete_button, FALSE, FALSE, 0);
542
543 gtk_box_pack_start(GTK_BOX(d->snapshots_box), d->snapshot[k].row, FALSE, FALSE, 0);
544 gtk_widget_set_no_show_all(d->snapshot[k].row, TRUE);
545 }
546
547 /* add snapshot box and take snapshot button to widget ui*/
548 gtk_box_pack_start(GTK_BOX(self->widget),
549 dt_ui_scroll_wrap(d->snapshots_box, 1, "plugins/darkroom/snapshots/windowheight",
551 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->take_button), TRUE, TRUE, 0);
552}
553
555{
556 if(IS_NULL_PTR(self->data)) return;
558
559 for(uint32_t k = 0; k < d->size; k++) _lib_snapshot_clear_state(d->snapshot + k);
560 dt_free(d->snapshot);
561
562 dt_free(self->data);
563}
564
565static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data)
566{
567 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
568 if(IS_NULL_PTR(self->data)) return;
570 if(IS_NULL_PTR(d)) return;
571 if(d->size <= 0) return;
572
573 // Capture into a scratch slot first, off to the side of the visible array -- only once it has
574 // actually succeeded do we touch the slot rotation and GTK labels below. This guarantees a
575 // failed capture (e.g. out of memory) leaves the panel in exactly the state it was in before
576 // this click, instead of a rotated slot 0 stuck showing a label for a snapshot that was never
577 // created.
578 dt_lib_snapshot_t scratch = { 0 };
580 {
582 return;
583 }
584
585 /* backup last snapshot slot */
586 dt_lib_snapshot_t last = d->snapshot[d->size - 1];
587
588 /* rotate slots down to make room for new one on top */
589 for(int k = d->size - 1; k > 0; k--)
590 {
591 GtkWidget *r = d->snapshot[k].row;
592 GtkWidget *b = d->snapshot[k].button;
593 GtkWidget *db = d->snapshot[k].delete_button;
594 d->snapshot[k] = d->snapshot[k - 1];
595 d->snapshot[k].row = r;
596 d->snapshot[k].button = b;
597 d->snapshot[k].delete_button = db;
598 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k].button))),
599 gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k - 1].button)))));
600 }
601
602 /* update top slot with the already-captured scratch snapshot */
603 GtkWidget *r = d->snapshot[0].row;
604 GtkWidget *b = d->snapshot[0].button;
605 GtkWidget *db = d->snapshot[0].delete_button;
606 d->snapshot[0] = last;
607 d->snapshot[0].row = r;
608 d->snapshot[0].button = b;
609 d->snapshot[0].delete_button = db;
610 // `last` may itself have held a valid engine (all `d->size` slots were already full) --
611 // release it before overwriting with the scratch capture, or it would leak (nothing else
612 // references it: `last` was never shown/toggled, so no other slot's `.snap` points to it).
613 dt_dev_snapshot_clear(&d->snapshot[0].snap);
614 d->snapshot[0].snap = scratch.snap;
615 d->snapshot[0].imgid = scratch.imgid;
616 d->snapshot[0].history_end = scratch.history_end;
617
618 char label[64];
619 const gchar *name = _("original");
620 gchar *dynamic_name = NULL;
622 {
623 dt_dev_history_item_t *history_item = g_list_nth_data(dt_dev_get_global()->history,
625 if(!IS_NULL_PTR(history_item) && !IS_NULL_PTR(history_item->module))
626 {
627 dynamic_name = dt_history_item_get_name(history_item->module);
628 if(!IS_NULL_PTR(dynamic_name)) name = dynamic_name;
629 }
630 else
631 name = _("unknown");
632 }
633 g_snprintf(label, sizeof(label), "%s (%d)", name, dt_dev_get_history_end_ext(dt_dev_get_global()));
634 if(!IS_NULL_PTR(dynamic_name)) dt_free(dynamic_name);
635 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[0].button))), label);
636
637 /* update slots used */
638 if(d->num_snapshots != d->size) d->num_snapshots++;
639
640 /* show active snapshot slots. row has no-show-all set (so ambient show_all() calls on
641 * an ancestor leave inactive slots hidden), which means show_all() on row itself is
642 * *also* a no-op -- it must be shown explicitly, and so must its children since they
643 * were never individually shown either. */
644 for(uint32_t k = 0; k < d->num_snapshots; k++)
645 {
646 gtk_widget_show(d->snapshot[k].row);
647 gtk_widget_show(d->snapshot[k].button);
648 gtk_widget_show(d->snapshot[k].delete_button);
649 }
650}
651
652static void _lib_snapshots_delete_button_clicked_callback(GtkWidget *widget, gpointer user_data)
653{
654 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
655 if(IS_NULL_PTR(self->data)) return;
657 if(IS_NULL_PTR(d)) return;
658
659 const uint32_t which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "snapshot"));
660 if(which < 1 || which > d->num_snapshots) return;
661 const uint32_t p = which - 1;
662
663 if(d->selected == which) d->selected = 0;
664 else if(d->selected > which) d->selected--;
665
666 // Free the deleted snapshot's surfaces now, before its slot gets overwritten by the shift
667 // below -- otherwise they would be silently leaked (overwritten with no destroy call).
668 _lib_snapshot_clear_state(d->snapshot + p);
669
670 /* shift every older slot up by one to fill the gap, keeping row/button/delete_button
671 * pinned to their screen position (same pattern as the "take snapshot" rotation). Each
672 * `d->snapshot[k] = d->snapshot[k + 1]` *moves* the slot's engine pointer, so nothing here
673 * may release it: the slot being overwritten already handed its own reference to k-1 in the
674 * previous iteration (or, for k == p, it was just cleared above). */
675 for(uint32_t k = p; k < d->num_snapshots - 1; k++)
676 {
677 GtkWidget *r = d->snapshot[k].row;
678 GtkWidget *b = d->snapshot[k].button;
679 GtkWidget *db = d->snapshot[k].delete_button;
680 d->snapshot[k] = d->snapshot[k + 1];
681 d->snapshot[k].row = r;
682 d->snapshot[k].button = b;
683 d->snapshot[k].delete_button = db;
684 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k].button))),
685 gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k + 1].button)))));
686 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), (k + 1) == d->selected);
687 }
688
689 // The last active slot's surfaces (if any) were just relocated into the slot above it by
690 // the loop's last iteration, so only reset its fields here -- destroying them would be a
691 // double-free of surfaces now owned by that other slot.
692 const uint32_t last = d->num_snapshots - 1;
693 _lib_snapshot_reset_fields(d->snapshot + last);
694 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[last].button), FALSE);
695 gtk_widget_hide(d->snapshot[last].row);
696 d->num_snapshots--;
697
699}
700
701static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data)
702{
703 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
705 /* get current snapshot index */
706 int which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "snapshot"));
707
708 /* check if snapshot is activated */
709 if(gtk_toggle_button_get_active(widget))
710 {
711 /* lets deactivate all togglebuttons except for self */
712 for(uint32_t k = 0; k < d->size; k++)
713 if(GTK_WIDGET(widget) != d->snapshot[k].button)
714 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), FALSE);
715
716 const dt_lib_snapshot_t *s = d->snapshot + (which - 1);
717 d->selected = dt_dev_snapshot_is_valid(&s->snap) ? which : 0;
718 }
719 else if(d->selected == (uint32_t)which)
720 {
721 d->selected = 0;
722 }
723
724 /* redraw center view */
726}
727
728// clang-format off
729// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
730// vim: shiftwidth=2 expandtab tabstop=2 cindent
731// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
732// clang-format on
static double * inv
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
static const float x
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_change_cursor_by_name_and_flush(const char *curs_str)
Apply a named cursor immediatelly and flush display updates for immediate feedback.
Definition control.c:335
void dt_control_commit_cursor()
Definition control.c:343
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
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
GList * dt_history_duplicate(GList *hist)
Deep-copy a history list.
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1899
void dt_dev_snapshot_draw(dt_dev_snapshot_t *snap, cairo_t *cri, struct dt_develop_t *dev, int32_t width, int32_t height, double clip_x, double clip_y, double clip_w, double clip_h)
void dt_dev_snapshot_clear(dt_dev_snapshot_t *snap)
gboolean dt_dev_snapshot_is_valid(const dt_dev_snapshot_t *snap)
gboolean dt_dev_snapshot_capture(dt_dev_snapshot_t *snap, dt_develop_t *dev, int32_t imgid, GList *history_override, GList *iop_order_override, int32_t history_end_override)
void dt_dev_get_image_box_in_widget(const dt_develop_t *dev, const int32_t width, const int32_t height, float *box)
Get the displayed image rectangle in darkroom widget coordinates.
Definition develop.c:1977
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
GtkWidget * view
the tree view
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:147
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
#define UNKNOWN_IMAGE
Definition image.h:78
GList * dt_ioppr_iop_order_copy_deep(GList *iop_order_list)
Deep-copy an order list.
Definition iop_order.c:1970
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1336
_lib_location_type_t type
Definition location.c:1
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
const char * name
Definition pdf.h:90
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:42
static int _lib_snapshot_rotation_cnt
Definition snapshots.c:357
void gui_reset(dt_lib_module_t *self)
Definition snapshots.c:466
#define HANDLE_SIZE
Definition snapshots.c:67
static void _draw_sym(cairo_t *cr, float x, float y, gboolean vertical, gboolean inverted)
Definition snapshots.c:203
static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data)
Definition snapshots.c:565
static int _lib_snapshot_capture_state(dt_lib_snapshot_t *snapshot, dt_develop_t *source)
Definition snapshots.c:137
static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data)
Definition snapshots.c:701
static void _lib_snapshot_reset_fields(dt_lib_snapshot_t *snap)
Definition snapshots.c:118
static void _lib_snapshots_delete_button_clicked_callback(GtkWidget *widget, gpointer user_data)
Definition snapshots.c:652
void gui_cleanup(dt_lib_module_t *self)
Definition snapshots.c:554
static void _lib_snapshot_clear_state(dt_lib_snapshot_t *snap)
Definition snapshots.c:125
int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
Definition snapshots.c:417
uint32_t container(dt_lib_module_t *self)
Definition snapshots.c:192
int button_pressed(struct dt_lib_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition snapshots.c:359
void gui_init(dt_lib_module_t *self)
Definition snapshots.c:484
int position()
Definition snapshots.c:197
const char ** views(dt_lib_module_t *self)
Definition snapshots.c:186
void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition snapshots.c:228
int button_released(struct dt_lib_module_t *self, double x, double y, int which, uint32_t state)
Definition snapshots.c:339
#define SNAP_LOG(...)
Definition snapshots.c:66
const float uint32_t state[4]
const float r
struct dt_dev_snapshot_engine_t * engine
GList * iop_order_list
Definition develop.h:275
dt_image_t image_storage
Definition develop.h:225
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
GList * history
Definition develop.h:259
int32_t id
Definition image.h:401
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
dt_dev_snapshot_t snap
Definition snapshots.c:75
GtkWidget * delete_button
Definition snapshots.c:74
GtkWidget * button
Definition snapshots.c:73
int32_t history_end
Definition snapshots.c:77
GtkWidget * row
Definition snapshots.c:72
uint32_t num_snapshots
Definition snapshots.c:88
gboolean hover_line
Definition snapshots.c:102
gboolean inverted
Definition snapshots.c:98
GtkWidget * take_button
Definition snapshots.c:104
uint32_t selected
Definition snapshots.c:85
gboolean hover_rotation
Definition snapshots.c:101
dt_lib_snapshot_t * snapshot
Definition snapshots.c:94
gboolean vertical
Definition snapshots.c:98
GtkWidget * snapshots_box
Definition snapshots.c:83
gboolean dragging
Definition snapshots.c:98
void(* set_default_cursor)(struct dt_view_t *view, double x, double y)
Definition view.h:239
struct dt_view_t * view
Definition view.h:237
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@63 darkroom
#define DT_GUI_MOUSE_EFFECT_RADIUS
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_refresh(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER