1 /*
2 This file is part of darktable,
3 Copyright (C) 2015-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "common/collection.h"
20 #include "common/darktable.h"
21 #include "common/debug.h"
22 #include "common/history.h"
23 #include "common/metadata.h"
24 #include "common/mipmap_cache.h"
25 #include "common/selection.h"
26 #include "common/styles.h"
27 #include "control/conf.h"
28 #include "control/control.h"
29 #include "develop/develop.h"
30 #include "dtgtk/thumbnail.h"
31 #include "gui/accelerators.h"
32 #include "gui/gtk.h"
33 #include "gui/styles.h"
34 #include "libs/lib.h"
35
36 #define DUPLICATE_COMPARE_SIZE 40
37
38 DT_MODULE(1)
39
40 typedef struct dt_lib_duplicate_t
41 {
42 GtkWidget *duplicate_box;
43 int imgid;
44 gboolean busy;
45 int cur_final_width;
46 int cur_final_height;
47 int32_t preview_width;
48 int32_t preview_height;
49 gboolean allow_zoom;
50
51 cairo_surface_t *preview_surf;
52 float preview_zoom;
53 int preview_id;
54
55 GList *thumbs;
56 } dt_lib_duplicate_t;
57
name(dt_lib_module_t * self)58 const char *name(dt_lib_module_t *self)
59 {
60 return _("duplicate manager");
61 }
62
views(dt_lib_module_t * self)63 const char **views(dt_lib_module_t *self)
64 {
65 static const char *v[] = {"darkroom", NULL};
66 return v;
67 }
68
container(dt_lib_module_t * self)69 uint32_t container(dt_lib_module_t *self)
70 {
71 return DT_UI_CONTAINER_PANEL_LEFT_CENTER;
72 }
73
position()74 int position()
75 {
76 return 850;
77 }
78
79 static void _lib_duplicate_init_callback(gpointer instance, dt_lib_module_t *self);
80
_lib_duplicate_caption_out_callback(GtkWidget * widget,GdkEvent * event,dt_lib_module_t * self)81 static gboolean _lib_duplicate_caption_out_callback(GtkWidget *widget, GdkEvent *event, dt_lib_module_t *self)
82 {
83 const int imgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget),"imgid"));
84
85 // we write the content of the textbox to the caption field
86 dt_metadata_set(imgid, "Xmp.darktable.version_name", gtk_entry_get_text(GTK_ENTRY(widget)), FALSE);
87 dt_image_synch_xmp(imgid);
88
89 return FALSE;
90 }
91
_lib_duplicate_new_clicked_callback(GtkWidget * widget,GdkEventButton * event,dt_lib_module_t * self)92 static void _lib_duplicate_new_clicked_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
93 {
94 const int imgid = darktable.develop->image_storage.id;
95 const int newid = dt_image_duplicate(imgid);
96 if (newid <= 0) return;
97 dt_history_delete_on_image(newid);
98 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
99 dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_UNDEF, NULL);
100 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, newid);
101 }
_lib_duplicate_duplicate_clicked_callback(GtkWidget * widget,GdkEventButton * event,dt_lib_module_t * self)102 static void _lib_duplicate_duplicate_clicked_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
103 {
104 const int imgid = darktable.develop->image_storage.id;
105 const int newid = dt_image_duplicate(imgid);
106 if (newid <= 0) return;
107 dt_history_copy_and_paste_on_image(imgid, newid, FALSE, NULL, TRUE, TRUE);
108 dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_UNDEF, NULL);
109 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, newid);
110 }
111
_lib_duplicate_delete(GtkButton * button,dt_lib_module_t * self)112 static void _lib_duplicate_delete(GtkButton *button, dt_lib_module_t *self)
113 {
114 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
115 const int imgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "imgid"));
116
117 if(imgid == darktable.develop->image_storage.id)
118 {
119 // we find the duplicate image to show now
120 for(GList *l = d->thumbs; l; l = g_list_next(l))
121 {
122 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
123 if(thumb->imgid == imgid)
124 {
125 GList *l2 = g_list_next(l);
126 if(!l2) l2 = g_list_previous(l);
127 if(l2)
128 {
129 dt_thumbnail_t *th2 = (dt_thumbnail_t *)l2->data;
130 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, th2->imgid);
131 break;
132 }
133 }
134 }
135 }
136
137 // and we remove the image
138 dt_control_delete_image(imgid);
139 dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_UNDEF,
140 g_list_prepend(NULL, GINT_TO_POINTER(imgid)));
141 }
142
_lib_duplicate_thumb_press_callback(GtkWidget * widget,GdkEventButton * event,dt_lib_module_t * self)143 static void _lib_duplicate_thumb_press_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
144 {
145 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
146 dt_thumbnail_t *thumb = (dt_thumbnail_t *)g_object_get_data(G_OBJECT(widget), "thumb");
147 const int imgid = thumb->imgid;
148
149 if(event->button == 1)
150 {
151 if(event->type == GDK_BUTTON_PRESS)
152 {
153 dt_develop_t *dev = darktable.develop;
154 if(!dev) return;
155
156 dt_dev_invalidate(dev);
157 dt_control_queue_redraw_center();
158
159 dt_dev_invalidate(darktable.develop);
160
161 d->imgid = imgid;
162 int fw, fh;
163 fw = fh = 0;
164 dt_image_get_final_size(imgid, &fw, &fh);
165 if(d->cur_final_width <= 0)
166 dt_image_get_final_size(dev->image_storage.id, &d->cur_final_width, &d->cur_final_height);
167 d->allow_zoom
168 = (d->cur_final_width - fw < DUPLICATE_COMPARE_SIZE && d->cur_final_width - fw > -DUPLICATE_COMPARE_SIZE
169 && d->cur_final_height - fh < DUPLICATE_COMPARE_SIZE
170 && d->cur_final_height - fh > -DUPLICATE_COMPARE_SIZE);
171 dt_control_queue_redraw_center();
172 }
173 else if(event->type == GDK_2BUTTON_PRESS)
174 {
175 // let's switch to the new image
176 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, imgid);
177 }
178 }
179 }
180
_lib_duplicate_thumb_release_callback(GtkWidget * widget,GdkEventButton * event,dt_lib_module_t * self)181 static void _lib_duplicate_thumb_release_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
182 {
183 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
184
185 d->imgid = 0;
186 if(d->busy)
187 {
188 dt_control_log_busy_leave();
189 dt_control_toast_busy_leave();
190 }
191 d->busy = FALSE;
192 dt_control_queue_redraw_center();
193 }
194
view_leave(struct dt_lib_module_t * self,struct dt_view_t * old_view,struct dt_view_t * new_view)195 void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
196 {
197 // we leave the view. Let's destroy preview surf if any
198 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
199 if(d->preview_surf)
200 {
201 cairo_surface_destroy(d->preview_surf);
202 d->preview_surf = NULL;
203 }
204 }
gui_post_expose(dt_lib_module_t * self,cairo_t * cri,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)205 void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
206 {
207 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
208 if (d->imgid == 0) return;
209 dt_develop_t *dev = darktable.develop;
210 if(!dev->preview_pipe->backbuf || dev->preview_status != DT_DEV_PIXELPIPE_VALID) return;
211
212 // use the same resolution as main previem image to avoid blur
213 float img_wd, img_ht;
214 if(d->allow_zoom)
215 {
216 img_wd = dev->preview_pipe->backbuf_width;
217 img_ht = dev->preview_pipe->backbuf_height;
218 }
219 else
220 {
221 int w2, h2;
222 dt_image_get_final_size(d->imgid, &w2, &h2);
223 img_wd = w2;
224 img_ht = h2;
225 }
226
227 const int32_t tb = darktable.develop->border_size;
228
229 // we rescale the sizes to the screen size
230 if (img_ht * (width - 2 * tb) > img_wd * (height - 2 * tb))
231 {
232 img_wd = img_wd*(height - 2 * tb)/img_ht;
233 img_ht = (height - 2 * tb);
234 }
235 else
236 {
237 img_ht = img_ht*(width - 2 * tb)/img_wd;
238 img_wd = (width - 2 * tb);
239 }
240
241 // Get the resizing from borders - only to check validity of mipmap cache size
242 float zoom_ratio = 1.f;
243 if(dev->iso_12646.enabled)
244 {
245 if(img_wd - 2 * tb < img_ht - 2 * tb)
246 zoom_ratio = (img_ht - 2 * tb) / img_ht;
247 else
248 zoom_ratio = (img_wd - 2 * tb) / img_wd;
249 }
250
251 // if image have too different sizes, we show the full preview not zoomed
252 float nz = 1.0f;
253 if(d->allow_zoom)
254 {
255 const int closeup = dt_control_get_dev_closeup();
256 const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
257 const float min_scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_FIT, 1 << closeup, 0);
258 const float cur_scale = dt_dev_get_zoom_scale(dev, zoom, 1 << closeup, 0);
259 // if cur_scale is >=2.0f (200%) we disable preview as it can hit cairo size limits without warnings
260 if(cur_scale >= 2.0f)
261 {
262 /* xgettext:no-c-format */
263 dt_control_log(_("preview is only possible for zoom lower than 200%%..."));
264 return;
265 }
266 nz = cur_scale / min_scale;
267 }
268
269 // if not cached, load or reload a mipmap
270 dt_view_surface_value_t res = DT_VIEW_SURFACE_OK;
271 if(d->preview_id != d->imgid || d->preview_zoom != nz * zoom_ratio || !d->preview_surf
272 || d->preview_width != width || d->preview_height != height)
273 {
274 d->preview_width = width;
275 d->preview_height = height;
276
277 res = dt_view_image_get_surface(d->imgid, img_wd * nz, img_ht * nz, &d->preview_surf, TRUE);
278
279 if(res == DT_VIEW_SURFACE_OK)
280 {
281 d->preview_id = d->imgid;
282 d->preview_zoom = nz * zoom_ratio; // only to check validity of mipmap cache size
283 }
284 }
285
286 // if ready, we draw the surface
287 if(d->preview_surf)
288 {
289 cairo_save(cri);
290 // force middle grey in background
291 if(dev->iso_12646.enabled)
292 cairo_set_source_rgb(cri, 0.5, 0.5, 0.5);
293 else
294 dt_gui_gtk_set_source_rgb(cri, DT_GUI_COLOR_DARKROOM_BG);
295
296 // draw background
297 cairo_paint(cri);
298
299 // move coordinates according to margin
300 float wd, ht;
301 if(d->allow_zoom)
302 {
303 wd = dev->pipe->output_backbuf_width / darktable.gui->ppd;
304 ht = dev->pipe->output_backbuf_height / darktable.gui->ppd;
305 }
306 else
307 {
308 wd = img_wd / darktable.gui->ppd;
309 ht = img_ht / darktable.gui->ppd;
310 }
311 const float margin_left = ceilf(.5f * (width - wd));
312 const float margin_top = ceilf(.5f * (height - ht));
313 cairo_translate(cri, margin_left, margin_top);
314
315 if(dev->iso_12646.enabled)
316 {
317 // draw the white frame around picture
318 cairo_rectangle(cri, -tb / 3., -tb / 3., wd + 2. * tb / 3., ht + 2. * tb / 3.);
319 cairo_set_source_rgb(cri, 1., 1., 1.);
320 cairo_fill(cri);
321 }
322
323 // finally, draw the image
324 cairo_rectangle(cri, 0, 0, wd, ht);
325 cairo_clip_preserve(cri);
326
327 const float scaler = 1.0f / darktable.gui->ppd_thb;
328 cairo_scale(cri, scaler, scaler);
329
330
331 if(d->allow_zoom)
332 {
333 // compute the surface pixel shift to match reference image FIXME!
334 const float zoom_y = dt_control_get_dev_zoom_y();
335 const float zoom_x = dt_control_get_dev_zoom_x();
336 const float dx = -floorf(zoom_x * (img_wd)*nz + img_wd * nz / 2. - width / 2.) - margin_left;
337 const float dy = -floorf(zoom_y * (img_ht)*nz + img_ht * nz / 2. - height / 2.) - margin_top;
338 cairo_set_source_surface(cri, d->preview_surf, dx / scaler, dy / scaler);
339 }
340 else
341 cairo_set_source_surface(cri, d->preview_surf, 0, 0);
342
343 cairo_pattern_set_filter(cairo_get_source(cri), (darktable.gui->filter_image == CAIRO_FILTER_FAST)
344 ? CAIRO_FILTER_GOOD : darktable.gui->filter_image) ;
345 cairo_paint(cri);
346
347 cairo_restore(cri);
348 }
349
350 if(res != DT_VIEW_SURFACE_OK)
351 {
352 if(!d->busy)
353 {
354 dt_control_log_busy_enter();
355 dt_control_toast_busy_enter();
356 }
357 d->busy = TRUE;
358 }
359 else
360 {
361 if(d->busy)
362 {
363 dt_control_log_busy_leave();
364 dt_control_toast_busy_leave();
365 }
366 d->busy = FALSE;
367 }
368 }
369
_thumb_remove(gpointer user_data)370 static void _thumb_remove(gpointer user_data)
371 {
372 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
373 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(thumb->w_main)), thumb->w_main);
374 dt_thumbnail_destroy(thumb);
375 }
376
_lib_duplicate_init_callback(gpointer instance,dt_lib_module_t * self)377 static void _lib_duplicate_init_callback(gpointer instance, dt_lib_module_t *self)
378 {
379 //block signals to avoid concurrent calls
380 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_lib_duplicate_init_callback), self);
381
382 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
383
384 d->imgid = 0;
385 // we drop the preview if any
386 if(d->preview_surf)
387 {
388 cairo_surface_destroy(d->preview_surf);
389 d->preview_surf = NULL;
390 }
391 // we drop all the thumbs
392 g_list_free_full(d->thumbs, _thumb_remove);
393 d->thumbs = NULL;
394 // and the other widgets too
395 dt_gui_container_destroy_children(GTK_CONTAINER(d->duplicate_box));
396 // retrieve all the versions of the image
397 sqlite3_stmt *stmt;
398 dt_develop_t *dev = darktable.develop;
399
400 int count = 0;
401
402 // we get a summarize of all versions of the image
403 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
404 "SELECT i.version, i.id, m.value"
405 " FROM images AS i"
406 " LEFT JOIN meta_data AS m ON m.id = i.id AND m.key = ?3"
407 " WHERE film_id = ?1 AND filename = ?2"
408 " ORDER BY i.version",
409 -1, &stmt, NULL);
410 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, dev->image_storage.film_id);
411 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, dev->image_storage.filename, -1, SQLITE_TRANSIENT);
412 DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, DT_METADATA_XMP_VERSION_NAME);
413
414 GtkWidget *bt = NULL;
415
416 while(sqlite3_step(stmt) == SQLITE_ROW)
417 {
418 GtkWidget *hb = gtk_grid_new();
419 const int imgid = sqlite3_column_int(stmt, 1);
420
421 GtkStyleContext *context = gtk_widget_get_style_context(hb);
422 gtk_style_context_add_class(context, "dt_overlays_always");
423
424 dt_thumbnail_t *thumb = dt_thumbnail_new(100, 100, IMG_TO_FIT, imgid, -1, DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL,
425 DT_THUMBNAIL_CONTAINER_LIGHTTABLE, TRUE);
426 thumb->sel_mode = DT_THUMBNAIL_SEL_MODE_DISABLED;
427 thumb->disable_mouseover = TRUE;
428 thumb->disable_actions = TRUE;
429 dt_thumbnail_set_mouseover(thumb, imgid == dev->image_storage.id);
430
431 if (imgid != dev->image_storage.id)
432 {
433 g_signal_connect(G_OBJECT(thumb->w_main), "button-press-event",
434 G_CALLBACK(_lib_duplicate_thumb_press_callback), self);
435 g_signal_connect(G_OBJECT(thumb->w_main), "button-release-event",
436 G_CALLBACK(_lib_duplicate_thumb_release_callback), self);
437 }
438
439 gchar chl[256];
440 gchar *path = (gchar *)sqlite3_column_text(stmt, 2);
441 g_snprintf(chl, sizeof(chl), "%d", sqlite3_column_int(stmt, 0));
442
443 GtkWidget *tb = gtk_entry_new();
444 if(path) gtk_entry_set_text(GTK_ENTRY(tb), path);
445 gtk_entry_set_width_chars(GTK_ENTRY(tb), 0);
446 gtk_widget_set_hexpand(tb, TRUE);
447 g_object_set_data (G_OBJECT(tb), "imgid", GINT_TO_POINTER(imgid));
448 g_signal_connect(G_OBJECT(tb), "focus-out-event", G_CALLBACK(_lib_duplicate_caption_out_callback), self);
449 dt_gui_key_accel_block_on_focus_connect(GTK_WIDGET(tb));
450 GtkWidget *lb = gtk_label_new (g_strdup(chl));
451 gtk_widget_set_hexpand(lb, TRUE);
452 bt = dtgtk_button_new(dtgtk_cairo_paint_cancel, CPF_STYLE_FLAT, NULL);
453 // gtk_widget_set_halign(bt, GTK_ALIGN_END);
454 g_object_set_data(G_OBJECT(bt), "imgid", GINT_TO_POINTER(imgid));
455 g_signal_connect(G_OBJECT(bt), "clicked", G_CALLBACK(_lib_duplicate_delete), self);
456
457 gtk_grid_attach(GTK_GRID(hb), thumb->w_main, 0, 0, 1, 2);
458 gtk_grid_attach(GTK_GRID(hb), bt, 2, 0, 1, 1);
459 gtk_grid_attach(GTK_GRID(hb), lb, 1, 0, 1, 1);
460 gtk_grid_attach(GTK_GRID(hb), tb, 1, 1, 2, 1);
461
462 gtk_widget_show_all(hb);
463
464 gtk_box_pack_start(GTK_BOX(d->duplicate_box), hb, FALSE, FALSE, 0);
465 d->thumbs = g_list_append(d->thumbs, thumb);
466 count++;
467 }
468 sqlite3_finalize (stmt);
469
470 gtk_widget_show(d->duplicate_box);
471
472 // we have a single image, do not allow it to be removed so hide last bt
473 if(count==1)
474 {
475 gtk_widget_set_sensitive(bt, FALSE);
476 gtk_widget_set_visible(bt, FALSE);
477 }
478
479 // and reset the final size of the current image
480 if(dev->image_storage.id >= 0)
481 {
482 d->cur_final_width = 0;
483 d->cur_final_height = 0;
484 }
485
486 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_lib_duplicate_init_callback), self); //unblock signals
487 }
488
_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)489 static void _lib_duplicate_collection_changed(gpointer instance, dt_collection_change_t query_change,
490 dt_collection_properties_t changed_property, gpointer imgs, int next,
491 dt_lib_module_t *self)
492 {
493 _lib_duplicate_init_callback(instance, self);
494 }
495
_lib_duplicate_mipmap_updated_callback(gpointer instance,int imgid,dt_lib_module_t * self)496 static void _lib_duplicate_mipmap_updated_callback(gpointer instance, int imgid, dt_lib_module_t *self)
497 {
498 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
499 // we reset the final size of the current image
500 if(imgid > 0 && darktable.develop->image_storage.id == imgid)
501 {
502 d->cur_final_width = 0;
503 d->cur_final_height = 0;
504 }
505
506 gtk_widget_queue_draw(d->duplicate_box);
507 dt_control_queue_redraw_center();
508 }
_lib_duplicate_preview_updated_callback(gpointer instance,dt_lib_module_t * self)509 static void _lib_duplicate_preview_updated_callback(gpointer instance, dt_lib_module_t *self)
510 {
511 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;
512 // we reset the final size of the current image
513 if(darktable.develop->image_storage.id >= 0)
514 {
515 d->cur_final_width = 0;
516 d->cur_final_height = 0;
517 }
518
519 gtk_widget_queue_draw (d->duplicate_box);
520 dt_control_queue_redraw_center();
521 }
522
gui_init(dt_lib_module_t * self)523 void gui_init(dt_lib_module_t *self)
524 {
525 /* initialize ui widgets */
526 dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)g_malloc0(sizeof(dt_lib_duplicate_t));
527 self->data = (void *)d;
528
529 d->imgid = 0;
530 d->preview_surf = NULL;
531 d->preview_zoom = 1.0;
532 d->preview_width = 0;
533 d->preview_height = 0;
534
535 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
536 GtkStyleContext *context = gtk_widget_get_style_context(self->widget);
537 gtk_style_context_add_class(context, "duplicate-ui");
538 dt_gui_add_help_link(self->widget, dt_get_help_url(self->plugin_name));
539
540 d->duplicate_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
541
542 GtkWidget *hb = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
543 GtkWidget *bt = dt_ui_button_new(_("original"), _("create a 'virgin' duplicate of the image without any development"), NULL);
544 g_signal_connect(G_OBJECT(bt), "clicked", G_CALLBACK(_lib_duplicate_new_clicked_callback), self);
545 gtk_box_pack_end(GTK_BOX(hb), bt, TRUE, TRUE, 0);
546 bt = dt_ui_button_new(_("duplicate"), _("create a duplicate of the image with same history stack"), (char *)NULL);
547 g_signal_connect(G_OBJECT(bt), "clicked", G_CALLBACK(_lib_duplicate_duplicate_clicked_callback), self);
548 gtk_box_pack_end(GTK_BOX(hb), bt, TRUE, TRUE, 0);
549
550 /* add duplicate list and buttonbox to widget */
551 gtk_box_pack_start(GTK_BOX(self->widget),
552 dt_ui_scroll_wrap(d->duplicate_box, 1, "plugins/darkroom/duplicate/windowheight"), TRUE, TRUE, 0);
553 gtk_box_pack_start(GTK_BOX(self->widget), hb, TRUE, TRUE, 0);
554
555 gtk_widget_show_all(self->widget);
556
557 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_IMAGE_CHANGED, G_CALLBACK(_lib_duplicate_init_callback), self);
558 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_INITIALIZE, G_CALLBACK(_lib_duplicate_init_callback), self);
559 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_COLLECTION_CHANGED,
560 G_CALLBACK(_lib_duplicate_collection_changed), self);
561 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_MIPMAP_UPDATED, G_CALLBACK(_lib_duplicate_mipmap_updated_callback), (gpointer)self);
562 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED,
563 G_CALLBACK(_lib_duplicate_preview_updated_callback), self);
564 }
565
gui_cleanup(dt_lib_module_t * self)566 void gui_cleanup(dt_lib_module_t *self)
567 {
568 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_duplicate_init_callback), self);
569 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_duplicate_mipmap_updated_callback), self);
570 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_duplicate_preview_updated_callback), self);
571 g_free(self->data);
572 self->data = NULL;
573 }
574
575 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
576 // vim: shiftwidth=2 expandtab tabstop=2 cindent
577 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-space on;
578