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
26 darktable is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 darktable is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with darktable. If not, see <http://www.gnu.org/licenses/>.
38*/
39
40#include "common/darktable.h"
41#include "bauhaus/bauhaus.h"
42#include "common/debug.h"
44#include "common/history.h"
45#include "common/iop_order.h"
46#include "control/conf.h"
47#include "control/control.h"
48#include "develop/develop.h"
49#include "develop/dev_history.h"
51#include "dtgtk/paint.h"
52
54#include "gui/gtk.h"
55#include "gui/draw.h"
56#include "libs/lib.h"
57#include "libs/lib_api.h"
58
59DT_MODULE(1)
60
61#define DT_LIB_SNAPSHOTS_COUNT 4
62#define SNAP_LOG(...) dt_print(DT_DEBUG_DEV, __VA_ARGS__)
63#define HANDLE_SIZE DT_PIXEL_APPLY_DPI_DPP(36)
64
65/* a snapshot */
66typedef struct dt_lib_snapshot_t
67{
68 GtkWidget *row; // container for button + delete_button; shown/hidden as a unit
71 dt_dev_snapshot_t snap; // rendered still + pan/zoom-synced crop cache, see develop/dev_snapshot.h
72 int32_t imgid;
73 int32_t history_end;
75
76
77typedef struct dt_lib_snapshots_t
78{
80
81 uint32_t selected;
82
83 /* current active snapshots */
84 uint32_t num_snapshots;
85
86 /* size of snapshots */
87 uint32_t size;
88
89 /* snapshots */
91
92
93 /* change snapshot overlay controls */
96 gboolean on_going;
98
101
102/* callback for take snapshot */
103static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data);
104static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data);
105static void _lib_snapshots_delete_button_clicked_callback(GtkWidget *widget, gpointer user_data);
106
107// Reset the value fields to "empty" without destroying any cairo surface or touching GTK
108// widgets. Used when a snapshot's surfaces are being handed off to another slot (compacting
109// the list after a delete) rather than dropped -- the caller is responsible for the surfaces.
111{
112 snap->snap.image = NULL;
113 snap->snap.display_image = NULL;
114 snap->snap.display_scale = 0.0f;
115 snap->snap.crop_x = snap->snap.crop_y = snap->snap.crop_w = snap->snap.crop_h = 0;
116 snap->snap.sample_scale = 1.0f;
117 snap->imgid = UNKNOWN_IMAGE;
118 snap->history_end = 0;
119}
120
122{
123 if(IS_NULL_PTR(snap)) return;
125 snap->imgid = UNKNOWN_IMAGE;
126 snap->history_end = 0;
127}
128
129// Freeze the current darkroom develop state and render a full-resolution snapshot image.
130// The frozen context is used only during capture and freed immediately after.
131// Returns 0 on success, 1 on failure.
133{
134 if(IS_NULL_PTR(snapshot) || IS_NULL_PTR(source))
135 {
136 SNAP_LOG("[snapshots] capture failed: invalid inputs snapshot=%p source=%p\n", (void *)snapshot,
137 (void *)source);
138 return 1;
139 }
140 if(source->image_storage.id <= 0)
141 {
142 SNAP_LOG("[snapshots] capture failed: invalid source imgid=%d\n", source->image_storage.id);
143 return 1;
144 }
145
147
148 // dt_dev_snapshot_capture() takes ownership of these two lists -- duplicate the live,
149 // possibly-uncommitted history/iop_order under the lock here, since it must not read
150 // source->history_mutex-guarded state itself (it also serves duplicate.c, which captures a
151 // *different*, not-currently-open image and has no such live source to lock).
152 GList *history_copy = NULL;
153 GList *iop_order_copy = NULL;
154 int32_t history_end = 0;
155
157 history_copy = dt_history_duplicate(source->history);
158 iop_order_copy = dt_ioppr_iop_order_copy_deep(source->iop_order_list);
159 history_end = dt_dev_get_history_end_ext(source);
161
162 snapshot->imgid = source->image_storage.id;
163 snapshot->history_end = history_end;
164
166 const gboolean ok = dt_dev_snapshot_capture(&snapshot->snap, source->image_storage.id, 1.0f,
167 history_copy, iop_order_copy, history_end);
169
170 SNAP_LOG("[snapshots] capture: imgid=%d history_end=%d image=%s\n",
171 snapshot->imgid, snapshot->history_end, ok ? "ok" : "FAILED");
172
173 return ok ? 0 : 1;
174}
175
176const char *name(struct dt_lib_module_t *self)
177{
178 return _("Snapshots");
179}
180
181const char **views(dt_lib_module_t *self)
182{
183 static const char *v[] = {"darkroom", NULL};
184 return v;
185}
186
188{
190}
191
193{
194 return 800;
195}
196
197// draw snapshot sign
198static void _draw_sym(cairo_t *cr, float x, float y, gboolean vertical, gboolean inverted)
199{
200 const double inv = inverted ? -0.1 : 1.0;
201
202 PangoRectangle ink;
203 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
204 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
205 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(12) * PANGO_SCALE);
206 PangoLayout *layout = pango_cairo_create_layout(cr);
207 pango_layout_set_font_description(layout, desc);
208 pango_layout_set_text(layout, C_("snapshot sign", "S"), -1);
209 pango_layout_get_pixel_extents(layout, &ink, NULL);
210
211 if(vertical)
212 cairo_move_to(cr, x - (inv * ink.width * 1.2f), y - (ink.height / 2.0f) - DT_PIXEL_APPLY_DPI(3));
213 else
214 cairo_move_to(cr, x - (ink.width / 2.0), y + (-inv * (ink.height * 1.2f) - DT_PIXEL_APPLY_DPI(2)));
215
217 pango_cairo_show_layout(cr, layout);
218 pango_font_description_free(desc);
219 g_object_unref(layout);
220}
221
222/* expose snapshot over center viewport */
223void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx,
224 int32_t pointery)
225{
227 if(IS_NULL_PTR(d)) return;
229
230 if(d->selected >= 1 && d->selected <= d->size)
231 {
232 dt_lib_snapshot_t *snap = d->snapshot + (d->selected - 1);
233 if(!IS_NULL_PTR(snap->snap.image))
234 {
235 float image_box[4] = { 0.0f };
237 if(image_box[2] <= 0.0f || image_box[3] <= 0.0f) return;
238 d->vp_x = image_box[0];
239 d->vp_y = image_box[1];
240 d->vp_width = image_box[2];
241 d->vp_height = image_box[3];
242
243 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
244 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
245
246 /* set x,y,w,h of surface depending on split align and invert */
247 double x = d->vp_x;
248 double y = d->vp_y;
249 double w = d->vp_width;
250 double h = d->vp_height;
251 if(d->vertical)
252 {
253 x = d->inverted ? d->vp_x + d->vp_width * split_x : d->vp_x;
254 w = d->inverted ? d->vp_width * (1.0 - split_x) : d->vp_width * split_x;
255 }
256 else
257 {
258 y = d->inverted ? d->vp_y + d->vp_height * split_y : d->vp_y;
259 h = d->inverted ? d->vp_height * (1.0 - split_y) : d->vp_height * split_y;
260 }
261
262 const double size = DT_PIXEL_APPLY_DPI(d->inverted ? -15 : 15);
263
264 dt_dev_snapshot_draw(&snap->snap, cri, dev, width, height, x, y, w, h);
265
266 // draw the split line using the selected overlay color
268 cairo_set_line_width(cri, 1.);
269
270 if(d->vertical)
271 {
272 const double lx = d->vp_x + d->vp_width * split_x;
273 const double center = d->vp_y + 0.5 * d->vp_height;
274
275 cairo_move_to(cri, lx, d->vp_y);
276 cairo_line_to(cri, lx, d->vp_y + d->vp_height);
277 cairo_stroke(cri);
278
279 if(!d->dragging)
280 {
281 cairo_move_to(cri, lx, center - size);
282 cairo_line_to(cri, lx - (size * 1.2), center);
283 cairo_line_to(cri, lx, center + size);
284 cairo_close_path(cri);
285 cairo_fill(cri);
286 _draw_sym(cri, lx, center, TRUE, d->inverted);
287 }
288 }
289 else
290 {
291 const double ly = d->vp_y + d->vp_height * split_y;
292 const double center = d->vp_x + 0.5 * d->vp_width;
293
294 cairo_move_to(cri, d->vp_x, ly);
295 cairo_line_to(cri, d->vp_x + d->vp_width, ly);
296 cairo_stroke(cri);
297
298 if(!d->dragging)
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
309 /* if mouse over control, draw center rotate handle (hidden while dragging) */
310 if(!d->dragging)
311 {
312 const double half_handle_size = HANDLE_SIZE * 0.5;
313 const gint rx = (d->vertical ? d->vp_x + d->vp_width * split_x : d->vp_x + d->vp_width * 0.5)
314 - half_handle_size;
315 const gint ry = (d->vertical ? d->vp_y + d->vp_height * 0.5 : d->vp_y + d->vp_height * split_y)
316 - half_handle_size;
317
318 dt_draw_set_color_overlay(cri, TRUE, d->hover_rotation ? 1.0 : 0.3);
319 cairo_set_line_width(cri, 0.5);
320 dtgtk_cairo_paint_refresh(cri, rx, ry, HANDLE_SIZE, HANDLE_SIZE, 0, NULL);
321 }
322
323 d->on_going = FALSE;
324
325 if(d->hover_rotation) dt_control_queue_cursor_by_name("exchange");
326 else if(d->dragging) dt_control_queue_cursor_by_name("grabbing");
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(darktable.develop);
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(darktable.develop);
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 !? */
394 else
395 {
396 d->dragging = TRUE;
397 d->vp_ypointer = yp;
398 d->vp_xpointer = xp;
399 d->vp_xrotate = 0.0;
400 d->vp_yrotate = 0.0;
402 }
403 return 1;
404 }
405 return 0;
406}
407
408int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
409{
411
412 const gboolean visible_picker = dt_iop_color_picker_is_visible(darktable.develop);
413
414 if(!visible_picker && d->selected > 0)
415 {
416 if(d->vp_width <= 0.0 || d->vp_height <= 0.0) return 0;
417 const double xp = CLAMP((x - d->vp_x) / d->vp_width, 0.0, 1.0);
418 const double yp = CLAMP((y - d->vp_y) / d->vp_height, 0.0, 1.0);
419 d->hover_rotation = FALSE;
420
421 if(!d->dragging)
422 {
423 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
424 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
425 const double handle_mouse = (DT_GUI_MOUSE_EFFECT_RADIUS + HANDLE_SIZE) * 0.5;
426 const double rxc = d->vertical ? d->vp_x + d->vp_width * split_x : d->vp_x + d->vp_width * 0.5;
427 const double ryc = d->vertical ? d->vp_y + d->vp_height * 0.5 : d->vp_y + d->vp_height * split_y;
428 const double dx = x - rxc;
429 const double dy = y - ryc;
430 d->hover_rotation = (dx * dx + dy * dy) < (handle_mouse * handle_mouse);
431 }
432 else
433 {
434 /* update pointer pos */
435 d->vp_xpointer = xp;
436 d->vp_ypointer = yp;
437 }
439 return 1;
440 }
441
442 return 0;
443}
444
446{
448 d->num_snapshots = 0;
449 d->selected = 0;
450 d->hover_rotation = FALSE;
451
452 for(uint32_t k = 0; k < d->size; k++)
453 {
454 _lib_snapshot_clear_state(d->snapshot + k);
455 gtk_widget_hide(d->snapshot[k].row);
456 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), FALSE);
457 }
458
460}
461
463{
464 /* initialize ui widgets */
466 self->data = (void *)d;
467
468 /* initialize snapshot storages */
469 d->size = 4;
470 d->snapshot = (dt_lib_snapshot_t *)g_malloc0_n(d->size, sizeof(dt_lib_snapshot_t));
471 d->vp_x = 0.0;
472 d->vp_y = 0.0;
473 d->vp_width = 1.0;
474 d->vp_height = 1.0;
475 d->vp_xpointer = 0.5;
476 d->vp_ypointer = 0.5;
477 d->vp_xrotate = 0.0;
478 d->vp_yrotate = 0.0;
479 d->vertical = TRUE;
480 d->on_going = FALSE;
481 d->hover_rotation = FALSE;
482 /* initialize ui containers */
483 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
484 d->snapshots_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
485
486 /* create take snapshot button */
487 d->take_button = dt_action_button_new(self, N_("take snapshot"), _lib_snapshots_add_button_clicked_callback, self,
488 _("take snapshot to compare with another image "
489 "or the same image at another stage of development"), 0, 0);
490
491 for(int k = 0; k < d->size; k++)
492 {
493 d->snapshot[k].button = gtk_toggle_button_new_with_label("");
494 GtkWidget *label = gtk_bin_get_child(GTK_BIN(d->snapshot[k].button));
495 gtk_widget_set_halign(label, GTK_ALIGN_START);
496 gtk_label_set_xalign(GTK_LABEL(label), 0);
497 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
498 gtk_widget_set_hexpand(d->snapshot[k].button, TRUE);
499
500 g_signal_connect(G_OBJECT(d->snapshot[k].button), "clicked",
501 G_CALLBACK(_lib_snapshots_toggled_callback), self);
502
503 g_object_set_data(G_OBJECT(d->snapshot[k].button), "snapshot", GINT_TO_POINTER(k + 1));
504
505 // Same trash icon as the shapes list under "drawn mask" in develop/blend_gui.c
506 // (group_delete_col): themed "user-trash-symbolic", not a dtgtk cairo glyph.
507 d->snapshot[k].delete_button = gtk_button_new();
508 gtk_button_set_relief(GTK_BUTTON(d->snapshot[k].delete_button), GTK_RELIEF_NONE);
509 gtk_button_set_image(GTK_BUTTON(d->snapshot[k].delete_button),
510 gtk_image_new_from_icon_name("user-trash-symbolic", GTK_ICON_SIZE_MENU));
511 gtk_widget_set_tooltip_text(d->snapshot[k].delete_button, _("remove this snapshot"));
512 g_object_set_data(G_OBJECT(d->snapshot[k].delete_button), "snapshot", GINT_TO_POINTER(k + 1));
513 g_signal_connect(G_OBJECT(d->snapshot[k].delete_button), "clicked",
515
516 d->snapshot[k].row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
517 gtk_box_pack_start(GTK_BOX(d->snapshot[k].row), d->snapshot[k].button, TRUE, TRUE, 0);
518 gtk_box_pack_start(GTK_BOX(d->snapshot[k].row), d->snapshot[k].delete_button, FALSE, FALSE, 0);
519
520 gtk_box_pack_start(GTK_BOX(d->snapshots_box), d->snapshot[k].row, FALSE, FALSE, 0);
521 gtk_widget_set_no_show_all(d->snapshot[k].row, TRUE);
522 }
523
524 /* add snapshot box and take snapshot button to widget ui*/
525 gtk_box_pack_start(GTK_BOX(self->widget),
526 dt_ui_scroll_wrap(d->snapshots_box, 1, "plugins/darkroom/snapshots/windowheight",
528 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->take_button), TRUE, TRUE, 0);
529}
530
532{
533 if(IS_NULL_PTR(self->data)) return;
535
536 for(uint32_t k = 0; k < d->size; k++) _lib_snapshot_clear_state(d->snapshot + k);
537 dt_free(d->snapshot);
538
539 dt_free(self->data);
540}
541
542static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data)
543{
544 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
545 if(IS_NULL_PTR(self->data)) return;
547 if(IS_NULL_PTR(d)) return;
548
549 /* backup last snapshot slot */
550 if(d->size <= 0) return;
551 dt_lib_snapshot_t last = d->snapshot[d->size - 1];
552
553 /* rotate slots down to make room for new one on top */
554 for(int k = d->size - 1; k > 0; k--)
555 {
556 GtkWidget *r = d->snapshot[k].row;
557 GtkWidget *b = d->snapshot[k].button;
558 GtkWidget *db = d->snapshot[k].delete_button;
559 d->snapshot[k] = d->snapshot[k - 1];
560 d->snapshot[k].row = r;
561 d->snapshot[k].button = b;
562 d->snapshot[k].delete_button = db;
563 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k].button))),
564 gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k - 1].button)))));
565 }
566
567 /* update top slot with new snapshot */
568 char label[64];
569 GtkWidget *r = d->snapshot[0].row;
570 GtkWidget *b = d->snapshot[0].button;
571 GtkWidget *db = d->snapshot[0].delete_button;
572 d->snapshot[0] = last;
573 d->snapshot[0].row = r;
574 d->snapshot[0].button = b;
575 d->snapshot[0].delete_button = db;
576 const gchar *name = _("original");
577 gchar *dynamic_name = NULL;
579 {
580 dt_dev_history_item_t *history_item = g_list_nth_data(darktable.develop->history,
582 if(!IS_NULL_PTR(history_item) && !IS_NULL_PTR(history_item->module))
583 {
584 dynamic_name = dt_history_item_get_name(history_item->module);
585 if(!IS_NULL_PTR(dynamic_name)) name = dynamic_name;
586 }
587 else
588 name = _("unknown");
589 }
590 g_snprintf(label, sizeof(label), "%s (%d)", name, dt_dev_get_history_end_ext(darktable.develop));
591 if(!IS_NULL_PTR(dynamic_name)) dt_free(dynamic_name);
592 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[0].button))), label);
593
594 dt_lib_snapshot_t *s = d->snapshot + 0;
596 {
598 return;
599 }
600
601 /* update slots used */
602 if(d->num_snapshots != d->size) d->num_snapshots++;
603
604 /* show active snapshot slots. row has no-show-all set (so ambient show_all() calls on
605 * an ancestor leave inactive slots hidden), which means show_all() on row itself is
606 * *also* a no-op -- it must be shown explicitly, and so must its children since they
607 * were never individually shown either. */
608 for(uint32_t k = 0; k < d->num_snapshots; k++)
609 {
610 gtk_widget_show(d->snapshot[k].row);
611 gtk_widget_show(d->snapshot[k].button);
612 gtk_widget_show(d->snapshot[k].delete_button);
613 }
614}
615
616static void _lib_snapshots_delete_button_clicked_callback(GtkWidget *widget, gpointer user_data)
617{
618 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
619 if(IS_NULL_PTR(self->data)) return;
621 if(IS_NULL_PTR(d)) return;
622
623 const uint32_t which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "snapshot"));
624 if(which < 1 || which > d->num_snapshots) return;
625 const uint32_t p = which - 1;
626
627 if(d->selected == which) d->selected = 0;
628 else if(d->selected > which) d->selected--;
629
630 // Free the deleted snapshot's surfaces now, before its slot gets overwritten by the shift
631 // below -- otherwise they would be silently leaked (overwritten with no destroy call).
632 _lib_snapshot_clear_state(d->snapshot + p);
633
634 /* shift every older slot up by one to fill the gap, keeping row/button/delete_button
635 * pinned to their screen position (same pattern as the "take snapshot" rotation). Each
636 * `d->snapshot[k] = d->snapshot[k + 1]` *moves* ownership of the cairo surfaces (struct
637 * assignment, no refcounting) rather than copying it, so nothing here may destroy them:
638 * the slot being overwritten already handed its own surfaces to k-1 in the previous
639 * iteration (or, for k == p, they were just freed above). */
640 for(uint32_t k = p; k < d->num_snapshots - 1; k++)
641 {
642 GtkWidget *r = d->snapshot[k].row;
643 GtkWidget *b = d->snapshot[k].button;
644 GtkWidget *db = d->snapshot[k].delete_button;
645 d->snapshot[k] = d->snapshot[k + 1];
646 d->snapshot[k].row = r;
647 d->snapshot[k].button = b;
648 d->snapshot[k].delete_button = db;
649 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k].button))),
650 gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k + 1].button)))));
651 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), (k + 1) == d->selected);
652 }
653
654 // The last active slot's surfaces (if any) were just relocated into the slot above it by
655 // the loop's last iteration, so only reset its fields here -- destroying them would be a
656 // double-free of surfaces now owned by that other slot.
657 const uint32_t last = d->num_snapshots - 1;
658 _lib_snapshot_reset_fields(d->snapshot + last);
659 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[last].button), FALSE);
660 gtk_widget_hide(d->snapshot[last].row);
661 d->num_snapshots--;
662
664}
665
666static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data)
667{
668 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
670 /* get current snapshot index */
671 int which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "snapshot"));
672
673 /* check if snapshot is activated */
674 if(gtk_toggle_button_get_active(widget))
675 {
676 /* lets deactivate all togglebuttons except for self */
677 for(uint32_t k = 0; k < d->size; k++)
678 if(GTK_WIDGET(widget) != d->snapshot[k].button)
679 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), FALSE);
680
681 const dt_lib_snapshot_t *s = d->snapshot + (which - 1);
682 d->selected = (!IS_NULL_PTR(s->snap.image)) ? which : 0;
683 }
684 else if(d->selected == (uint32_t)which)
685 {
686 d->selected = 0;
687 }
688
689 /* redraw center view */
691}
692
693// clang-format off
694// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
695// vim: shiftwidth=2 expandtab tabstop=2 cindent
696// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
697// clang-format on
static double * inv
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
int type
char * name
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
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:326
void dt_control_commit_cursor()
Definition control.c:334
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:398
uint32_t view(const dt_view_t *self)
Definition darkroom.c:228
darktable_t darktable
Definition darktable.c:183
#define UNKNOWN_IMAGE
Definition darktable.h:194
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:478
#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 darktable.h:293
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1723
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_capture(dt_dev_snapshot_t *snap, int32_t imgid, float scale, 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:1801
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1557
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:106
void dtgtk_cairo_paint_refresh(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:502
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2945
#define DT_GUI_MOUSE_EFFECT_RADIUS
Definition gtk.h:70
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:266
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
GList * dt_history_duplicate(GList *hist)
Deep-copy a history list.
GList * dt_ioppr_iop_order_copy_deep(GList *iop_order_list)
Deep-copy an order list.
Definition iop_order.c:2002
static const float x
const float v
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:1563
float *const restrict const size_t k
size_t size
Definition mipmap_cache.c:3
static int _lib_snapshot_rotation_cnt
Definition snapshots.c:357
void gui_reset(dt_lib_module_t *self)
Definition snapshots.c:445
#define HANDLE_SIZE
Definition snapshots.c:63
static void _draw_sym(cairo_t *cr, float x, float y, gboolean vertical, gboolean inverted)
Definition snapshots.c:198
static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data)
Definition snapshots.c:542
static int _lib_snapshot_capture_state(dt_lib_snapshot_t *snapshot, dt_develop_t *source)
Definition snapshots.c:132
static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data)
Definition snapshots.c:666
static void _lib_snapshot_reset_fields(dt_lib_snapshot_t *snap)
Definition snapshots.c:110
static void _lib_snapshots_delete_button_clicked_callback(GtkWidget *widget, gpointer user_data)
Definition snapshots.c:616
void gui_cleanup(dt_lib_module_t *self)
Definition snapshots.c:531
static void _lib_snapshot_clear_state(dt_lib_snapshot_t *snap)
Definition snapshots.c:121
int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
Definition snapshots.c:408
uint32_t container(dt_lib_module_t *self)
Definition snapshots.c:187
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:462
int position()
Definition snapshots.c:192
const char ** views(dt_lib_module_t *self)
Definition snapshots.c:181
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:223
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:62
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
struct dt_develop_t * develop
Definition darktable.h:798
struct dt_view_manager_t * view_manager
Definition darktable.h:800
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
cairo_surface_t * image
cairo_surface_t * display_image
GList * iop_order_list
Definition develop.h:291
dt_image_t image_storage
Definition develop.h:259
dt_pthread_rwlock_t history_mutex
Definition develop.h:263
GList * history
Definition develop.h:275
int32_t id
Definition image.h:319
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
dt_dev_snapshot_t snap
Definition snapshots.c:71
GtkWidget * delete_button
Definition snapshots.c:70
GtkWidget * button
Definition snapshots.c:69
int32_t history_end
Definition snapshots.c:73
GtkWidget * row
Definition snapshots.c:68
uint32_t num_snapshots
Definition snapshots.c:84
gboolean inverted
Definition snapshots.c:94
GtkWidget * take_button
Definition snapshots.c:99
uint32_t selected
Definition snapshots.c:81
gboolean hover_rotation
Definition snapshots.c:97
gboolean on_going
Definition snapshots.c:96
dt_lib_snapshot_t * snapshot
Definition snapshots.c:90
gboolean vertical
Definition snapshots.c:94
GtkWidget * snapshots_box
Definition snapshots.c:79
gboolean dragging
Definition snapshots.c:94
struct dt_view_manager_t::@69 proxy
void(* set_default_cursor)(struct dt_view_t *view, double x, double y)
Definition view.h:247
struct dt_view_t * view
Definition view.h:245
struct dt_view_manager_t::@69::@72 darkroom
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER