Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
duplicate.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2015-2016, 2019-2022 Aldric Renaudin.
4 Copyright (C) 2018-2019 Matthieu Moy.
5 Copyright (C) 2018-2021 Pascal Obry.
6 Copyright (C) 2018 rawfiner.
7 Copyright (C) 2019-2020, 2022-2023, 2025-2026 Aurélien PIERRE.
8 Copyright (C) 2019 Edgardo Hoszowski.
9 Copyright (C) 2019-2020 Philippe Weyland.
10 Copyright (C) 2020 Chris Elston.
11 Copyright (C) 2020-2022 Diederik Ter Rahe.
12 Copyright (C) 2020 Hanno Schwalm.
13 Copyright (C) 2020 Hubert Kowalski.
14 Copyright (C) 2020 Marco.
15 Copyright (C) 2020 Mark-64.
16 Copyright (C) 2020, 2022 Nicolas Auffray.
17 Copyright (C) 2021 Fabio Heer.
18 Copyright (C) 2021 Ralf Brown.
19 Copyright (C) 2022 Martin Bařinka.
20 Copyright (C) 2023 Alynx Zhou.
21
22 darktable is free software: you can redistribute it and/or modify
23 it under the terms of the GNU General Public License as published by
24 the Free Software Foundation, either version 3 of the License, or
25 (at your option) any later version.
26
27 darktable is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with darktable. If not, see <http://www.gnu.org/licenses/>.
34*/
35
36#include "common/collection.h"
37#include "common/darktable.h"
38#include "common/debug.h"
39#include "common/history.h"
40#include "common/metadata.h"
41#include "common/mipmap_cache.h"
42#include "common/selection.h"
43#include "common/styles.h"
44#include "control/conf.h"
45#include "control/control.h"
46#include "develop/develop.h"
47#include "dtgtk/thumbnail.h"
48
49#include "gui/gtk.h"
50#include "gui/styles.h"
51#include "libs/lib.h"
52
53#define DUPLICATE_COMPARE_SIZE 40
54
55DT_MODULE(1)
56
57static sqlite3_stmt *_duplicate_versions_stmt = NULL;
58
76
77const char *name(struct dt_lib_module_t *self)
78{
79 return _("Duplicates");
80}
81
82const char **views(dt_lib_module_t *self)
83{
84 static const char *v[] = {"darkroom", NULL};
85 return v;
86}
87
92
94{
95 return 850;
96}
97
98static void _lib_duplicate_init_callback(gpointer instance, dt_lib_module_t *self);
99
100static gboolean _lib_duplicate_caption_out_callback(GtkWidget *widget, GdkEvent *event, dt_lib_module_t *self)
101{
102 const int32_t imgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget),"imgid"));
103
104 // we write the content of the textbox to the caption field
105 dt_metadata_set(imgid, "Xmp.darktable.version_name", gtk_entry_get_text(GTK_ENTRY(widget)), FALSE);
106 dt_control_save_xmp(imgid);
107
108 return FALSE;
109}
110
111static void _lib_duplicate_delete(GtkButton *button, dt_lib_module_t *self)
112{
114 const int32_t imgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "imgid"));
115
116 if(imgid == darktable.develop->image_storage.id)
117 {
118 // we find the duplicate image to show now
119 for(GList *l = d->thumbs; l; l = g_list_next(l))
120 {
121 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
122 if(thumb->info.id == imgid)
123 {
124 GList *l2 = g_list_next(l);
125 if(IS_NULL_PTR(l2)) l2 = g_list_previous(l);
126 if(l2)
127 {
128 dt_thumbnail_t *th2 = (dt_thumbnail_t *)l2->data;
130 th2->info.id);
131 break;
132 }
133 }
134 }
135 }
136
137 // and we remove the image
140 g_list_prepend(NULL, GINT_TO_POINTER(imgid)));
141}
142
143static gboolean _lib_duplicate_thumb_press_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
144{
145 if(event->button == 1)
146 {
147 if(event->type == GDK_BUTTON_PRESS)
148 {
150 if(IS_NULL_PTR(dev)) return FALSE;
151 return TRUE;
152 }
153 }
154 return FALSE;
155}
156
157static gboolean _lib_duplicate_thumb_release_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
158{
160
161 d->imgid = UNKNOWN_IMAGE;
162 if(d->busy)
163 {
166 }
167 d->busy = FALSE;
169
170 return FALSE;
171}
172
173void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
174{
175 // we leave the view. Let's destroy preview surf if any
177 if(d->preview_surf)
178 {
179 cairo_surface_destroy(d->preview_surf);
180 d->preview_surf = NULL;
181 }
182}
183
184static void _thumb_remove(gpointer user_data)
185{
186 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
187 if(IS_NULL_PTR(thumb)) return;
188
189 GtkWidget *parent = NULL;
190 if(!IS_NULL_PTR(thumb->w_main)) parent = gtk_widget_get_parent(thumb->w_main);
191 if(!IS_NULL_PTR(parent) && GTK_IS_CONTAINER(parent) && !IS_NULL_PTR(thumb->w_main))
192 gtk_container_remove(GTK_CONTAINER(parent), thumb->w_main);
193
195}
196
197static void _lib_duplicate_init_callback(gpointer instance, dt_lib_module_t *self)
198{
199 //block signals to avoid concurrent calls
201
203
204 d->imgid = UNKNOWN_IMAGE;
205 // we drop the preview if any
206 if(d->preview_surf)
207 {
208 cairo_surface_destroy(d->preview_surf);
209 d->preview_surf = NULL;
210 }
211 // we drop all the thumbs
212 g_list_free_full(d->thumbs, _thumb_remove);
213 d->thumbs = NULL;
214 // and the other widgets too
215 dt_gui_container_destroy_children(GTK_CONTAINER(d->duplicate_box));
216 // retrieve all the versions of the image
217 sqlite3_stmt *stmt;
219
220 int count = 0;
221
222 // we get a summarize of all versions of the image
223 // clang-format off
225 {
227 "SELECT i.version, i.id, m.value"
228 " FROM images AS i"
229 " LEFT JOIN meta_data AS m ON m.id = i.id AND m.key = ?3"
230 " WHERE film_id = ?1 AND filename = ?2"
231 " ORDER BY i.version",
232 -1, &_duplicate_versions_stmt, NULL);
233 }
235 sqlite3_reset(stmt);
236 sqlite3_clear_bindings(stmt);
237 // clang-format on
239 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, dev->image_storage.filename, -1, SQLITE_TRANSIENT);
241
242 GtkWidget *bt = NULL;
243
244 while(sqlite3_step(stmt) == SQLITE_ROW)
245 {
246 GtkWidget *hb = gtk_grid_new();
247 const int32_t imgid = sqlite3_column_int(stmt, 1);
248 dt_image_t info = { 0 };
249 info.id = imgid;
251
252 thumb->disable_mouseover = TRUE;
253 thumb->disable_actions = TRUE;
255 dt_thumbnail_set_mouseover(thumb, imgid == dev->image_storage.id);
256 dt_thumbnail_update_selection(thumb, imgid == dev->image_storage.id);
257 gtk_widget_queue_draw(thumb->widget);
258
259 if(imgid != dev->image_storage.id)
260 {
261 g_signal_connect(G_OBJECT(thumb->widget), "button-press-event",
262 G_CALLBACK(_lib_duplicate_thumb_press_callback), self);
263 g_signal_connect(G_OBJECT(thumb->widget), "button-release-event",
264 G_CALLBACK(_lib_duplicate_thumb_release_callback), self);
265 }
266
267 gchar chl[256];
268 gchar *path = (gchar *)sqlite3_column_text(stmt, 2);
269 g_snprintf(chl, sizeof(chl), "%d", sqlite3_column_int(stmt, 0));
270
271 GtkWidget *tb = gtk_entry_new();
273 if(path) gtk_entry_set_text(GTK_ENTRY(tb), path);
274 gtk_entry_set_width_chars(GTK_ENTRY(tb), 0);
275 gtk_widget_set_hexpand(tb, TRUE);
276 g_object_set_data (G_OBJECT(tb), "imgid", GINT_TO_POINTER(imgid));
277 gtk_widget_add_events(tb, GDK_FOCUS_CHANGE_MASK);
278 g_signal_connect(G_OBJECT(tb), "focus-out-event", G_CALLBACK(_lib_duplicate_caption_out_callback), self);
279 GtkWidget *lb = gtk_label_new(chl);
280 gtk_widget_set_hexpand(lb, TRUE);
282 // gtk_widget_set_halign(bt, GTK_ALIGN_END);
283 g_object_set_data(G_OBJECT(bt), "imgid", GINT_TO_POINTER(imgid));
284 g_signal_connect(G_OBJECT(bt), "clicked", G_CALLBACK(_lib_duplicate_delete), self);
285
286 gtk_grid_attach(GTK_GRID(hb), thumb->widget, 0, 0, 1, 2);
287 gtk_grid_attach(GTK_GRID(hb), bt, 2, 0, 1, 1);
288 gtk_grid_attach(GTK_GRID(hb), lb, 1, 0, 1, 1);
289 gtk_grid_attach(GTK_GRID(hb), tb, 1, 1, 2, 1);
290
291 // Can't use gtk_widget_show_all here or the buttons of the thumbnail will show too
292 gtk_widget_show(thumb->widget);
293 gtk_widget_show(hb);
294 gtk_widget_show(lb);
295 gtk_widget_show(tb);
296 gtk_box_pack_start(GTK_BOX(d->duplicate_box), hb, FALSE, FALSE, 0);
297 d->thumbs = g_list_append(d->thumbs, thumb);
298 count++;
299 }
300 sqlite3_reset(stmt);
301
302 gtk_widget_show(d->duplicate_box);
303
304 // we have a single image, do not allow it to be removed so hide last bt
305 if(count==1)
306 {
307 gtk_widget_set_sensitive(bt, FALSE);
308 gtk_widget_set_visible(bt, FALSE);
309 }
310
311 // and reset the final size of the current image
312 if(dev->image_storage.id >= 0)
313 {
314 d->cur_final_width = 0;
315 d->cur_final_height = 0;
316 }
317
319}
320
321static void _lib_duplicate_collection_changed(gpointer instance, dt_collection_change_t query_change,
322 dt_collection_properties_t changed_property, gpointer imgs, int next,
323 dt_lib_module_t *self)
324{
325 _lib_duplicate_init_callback(instance, self);
326}
327
328static void _lib_duplicate_preview_updated_callback(gpointer instance, dt_lib_module_t *self)
329{
331 // we reset the final size of the current image
333 {
334 d->cur_final_width = 0;
335 d->cur_final_height = 0;
336 }
337
338 gtk_widget_queue_draw (d->duplicate_box);
340}
341
342
344{
345 /* initialize ui widgets */
347 self->data = (void *)d;
348
349 d->imgid = UNKNOWN_IMAGE;
350 d->preview_surf = NULL;
351 d->preview_zoom = 1.0;
352 d->preview_width = 0;
353 d->preview_height = 0;
354
355 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
356 dt_gui_add_class(self->widget, "dt_duplicate_ui");
357
358 d->duplicate_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
359
360 GtkWidget *hb = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
361
362 /* add duplicate list and buttonbox to widget */
363 gtk_box_pack_start(GTK_BOX(self->widget),
364 dt_ui_scroll_wrap(d->duplicate_box, 1, "plugins/darkroom/duplicate/windowheight",
366 gtk_box_pack_start(GTK_BOX(self->widget), hb, TRUE, TRUE, 0);
367
368 gtk_widget_show_all(self->widget);
369
373 G_CALLBACK(_lib_duplicate_collection_changed), self);
376}
377
379{
380 if(IS_NULL_PTR(self->data)) return;
382
386
387 if(!IS_NULL_PTR(d))
388 {
389 if(d->preview_surf)
390 {
391 cairo_surface_destroy(d->preview_surf);
392 d->preview_surf = NULL;
393 }
394
395 g_list_free_full(d->thumbs, _thumb_remove);
396 d->thumbs = NULL;
397 dt_gui_container_destroy_children(GTK_CONTAINER(d->duplicate_box));
398 }
399
401 {
402 sqlite3_finalize(_duplicate_versions_stmt);
404 }
405
406 dt_free(self->data);
407}
408
409// clang-format off
410// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
411// vim: shiftwidth=2 expandtab tabstop=2 cindent
412// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
413// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:134
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
dt_collection_properties_t
Definition collection.h:107
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:142
dt_collection_change_t
Definition collection.h:147
@ DT_COLLECTION_CHANGE_RELOAD
Definition collection.h:151
void dt_metadata_set(const int32_t imgid, const char *key, const char *value, const gboolean undo_on)
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:861
void dt_control_log_busy_leave()
Definition control.c:840
void dt_control_toast_busy_leave()
Definition control.c:848
void dt_control_save_xmp(const int32_t imgid)
void dt_control_delete_image(int32_t imgid)
darktable_t darktable
Definition darktable.c:181
#define UNKNOWN_IMAGE
Definition darktable.h:182
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
#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:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_TEXT(a, b, c, d, e)
Definition debug.h:118
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
void dtgtk_cairo_paint_remove(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
static sqlite3_stmt * _duplicate_versions_stmt
Definition duplicate.c:57
static void _lib_duplicate_preview_updated_callback(gpointer instance, dt_lib_module_t *self)
Definition duplicate.c:328
static void _lib_duplicate_collection_changed(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, int next, dt_lib_module_t *self)
Definition duplicate.c:321
static void _lib_duplicate_init_callback(gpointer instance, dt_lib_module_t *self)
Definition duplicate.c:197
void gui_cleanup(dt_lib_module_t *self)
Definition duplicate.c:378
static void _lib_duplicate_delete(GtkButton *button, dt_lib_module_t *self)
Definition duplicate.c:111
static void _thumb_remove(gpointer user_data)
Definition duplicate.c:184
uint32_t container(dt_lib_module_t *self)
Definition duplicate.c:88
static gboolean _lib_duplicate_thumb_release_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
Definition duplicate.c:157
void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
Definition duplicate.c:173
void gui_init(dt_lib_module_t *self)
Definition duplicate.c:343
int position()
Definition duplicate.c:93
const char ** views(dt_lib_module_t *self)
Definition duplicate.c:82
static gboolean _lib_duplicate_thumb_press_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
Definition duplicate.c:143
static gboolean _lib_duplicate_caption_out_callback(GtkWidget *widget, GdkEvent *event, dt_lib_module_t *self)
Definition duplicate.c:100
void dt_gui_container_destroy_children(GtkContainer *container)
Definition gtk.c:2919
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:2713
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3225
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
const float l2
const float v
@ DT_METADATA_XMP_VERSION_NAME
Definition metadata.h:50
void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:481
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:476
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_DEVELOP_INITIALIZE
This signal is raised when darktable.develop is initialized.
Definition signal.h:169
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:95
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:221
@ 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:174
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:122
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_collection_t * collection
Definition darktable.h:781
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_develop_t * develop
Definition darktable.h:770
dt_image_t image_storage
Definition develop.h:259
int32_t film_id
Definition image.h:319
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
int32_t id
Definition image.h:319
cairo_surface_t * preview_surf
Definition duplicate.c:70
int32_t preview_width
Definition duplicate.c:66
GtkWidget * duplicate_box
Definition duplicate.c:61
int32_t preview_height
Definition duplicate.c:67
gboolean allow_zoom
Definition duplicate.c:68
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
gboolean disable_actions
Definition thumbnail.h:104
dt_image_t info
Definition thumbnail.h:76
GtkWidget * w_main
Definition thumbnail.h:80
GtkWidget * widget
Definition thumbnail.h:79
gboolean disable_mouseover
Definition thumbnail.h:103
dt_thumbnail_t * dt_thumbnail_new(int rowid, dt_thumbnail_overlay_t over, dt_thumbtable_t *table, dt_image_t *info)
Definition thumbnail.c:1338
void dt_thumbnail_set_mouseover(dt_thumbnail_t *thumb, gboolean over)
Definition thumbnail.c:1611
int dt_thumbnail_destroy(dt_thumbnail_t *thumb)
Definition thumbnail.c:1366
void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
Definition thumbnail.c:870
void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1547
@ DT_THUMBNAIL_OVERLAYS_NONE
Definition thumbnail.h:59
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER