1 /*
2 This file is part of darktable,
3 Copyright (C) 2011-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/darktable.h"
20 #include "common/debug.h"
21 #include "common/styles.h"
22 #include "common/undo.h"
23 #include "control/conf.h"
24 #include "control/control.h"
25 #include "develop/develop.h"
26 #include "develop/masks.h"
27 #include "gui/accelerators.h"
28 #include "gui/gtk.h"
29 #include "gui/styles.h"
30 #include "libs/lib.h"
31 #include "libs/lib_api.h"
32 #include "common/history.h"
33 #include <complex.h>
34
35 #ifdef GDK_WINDOWING_QUARTZ
36 #include "osx/osx.h"
37 #endif
38
39 DT_MODULE(1)
40
41
42 typedef struct dt_undo_history_t
43 {
44 GList *before_snapshot, *after_snapshot;
45 int before_end, after_end;
46 GList *before_iop_order_list, *after_iop_order_list;
47 dt_masks_edit_mode_t mask_edit_mode;
48 dt_dev_pixelpipe_display_mask_t request_mask_display;
49 } dt_undo_history_t;
50
51 typedef struct dt_lib_history_t
52 {
53 /* vbox with managed history items */
54 GtkWidget *history_box;
55 GtkWidget *create_button;
56 GtkWidget *compress_button;
57 gboolean record_undo;
58 int record_history_level; // set to +1 in signal DT_SIGNAL_DEVELOP_HISTORY_WILL_CHANGE
59 // and back to -1 in DT_SIGNAL_DEVELOP_HISTORY_CHANGE. We want
60 // to avoid multiple will-change before a change cb.
61 // previous_* below store values sent by signal DT_SIGNAL_DEVELOP_HISTORY_WILL_CHANGE
62 GList *previous_snapshot;
63 int previous_history_end;
64 GList *previous_iop_order_list;
65 } dt_lib_history_t;
66
67 /* 3 widgets in each history line */
68 #define HIST_WIDGET_NUMBER 0
69 #define HIST_WIDGET_MODULE 1
70 #define HIST_WIDGET_STATUS 2
71
72 /* compress history stack */
73 static gboolean _lib_compress_stack_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
74 GdkModifierType modifier, gpointer data);
75 static gboolean _lib_truncate_stack_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
76 GdkModifierType modifier, gpointer data);
77 static void _lib_history_compress_clicked_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data);
78 static void _lib_history_button_clicked_callback(GtkWidget *widget, gpointer user_data);
79 static void _lib_history_create_style_button_clicked_callback(GtkWidget *widget, gpointer user_data);
80 /* signal callback for history change */
81 static void _lib_history_will_change_callback(gpointer instance, GList *history, int history_end,
82 GList *iop_order_list, gpointer user_data);
83 static void _lib_history_change_callback(gpointer instance, gpointer user_data);
84 static void _lib_history_module_remove_callback(gpointer instance, dt_iop_module_t *module, gpointer user_data);
85
name(dt_lib_module_t * self)86 const char *name(dt_lib_module_t *self)
87 {
88 return _("history");
89 }
90
views(dt_lib_module_t * self)91 const char **views(dt_lib_module_t *self)
92 {
93 static const char *v[] = {"darkroom", NULL};
94 return v;
95 }
96
container(dt_lib_module_t * self)97 uint32_t container(dt_lib_module_t *self)
98 {
99 return DT_UI_CONTAINER_PANEL_LEFT_CENTER;
100 }
101
position()102 int position()
103 {
104 return 900;
105 }
106
init_key_accels(dt_lib_module_t * self)107 void init_key_accels(dt_lib_module_t *self)
108 {
109 dt_accel_register_lib(self, NC_("accel", "create style from history"), 0, 0);
110 // dt_accel_register_lib(self, NC_("accel", "apply style from popup menu"), 0, 0);
111 dt_accel_register_lib(self, NC_("accel", "compress history stack"), 0, 0);
112 dt_accel_register_lib(self, NC_("accel", "truncate history stack"), 0, 0);
113 }
114
connect_key_accels(dt_lib_module_t * self)115 void connect_key_accels(dt_lib_module_t *self)
116 {
117 dt_lib_history_t *d = (dt_lib_history_t *)self->data;
118
119 dt_accel_connect_button_lib(self, "create style from history", d->create_button);
120 // dt_accel_connect_button_lib(self, "apply style from popup menu", d->apply_button);
121 GClosure *closure;
122 closure = g_cclosure_new(G_CALLBACK(_lib_compress_stack_accel), (gpointer)self, NULL);
123 dt_accel_connect_lib(self, "compress history stack", closure);
124
125 closure = g_cclosure_new(G_CALLBACK(_lib_truncate_stack_accel), (gpointer)self, NULL);
126 dt_accel_connect_lib(self, "truncate history stack", closure);
127 }
128
gui_init(dt_lib_module_t * self)129 void gui_init(dt_lib_module_t *self)
130 {
131 /* initialize ui widgets */
132 dt_lib_history_t *d = (dt_lib_history_t *)g_malloc0(sizeof(dt_lib_history_t));
133 self->data = (void *)d;
134
135 d->record_undo = TRUE;
136 d->record_history_level = 0;
137 d->previous_snapshot = NULL;
138 d->previous_history_end = 0;
139 d->previous_iop_order_list = NULL;
140
141 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
142 dt_gui_add_help_link(self->widget, dt_get_help_url(self->plugin_name));
143 gtk_widget_set_name(self->widget, "history-ui");
144
145 d->history_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
146
147 GtkWidget *hhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
148
149 d->compress_button = dt_ui_button_new(_("compress history stack"),
150 _("create a minimal history stack which produces the same image\n"
151 "ctrl-click to truncate history to the selected item"), NULL);
152 g_signal_connect(G_OBJECT(d->compress_button), "button-press-event", G_CALLBACK(_lib_history_compress_clicked_callback), self);
153
154 /* add toolbar button for creating style */
155 d->create_button = dtgtk_button_new(dtgtk_cairo_paint_styles, CPF_NONE, NULL);
156 g_signal_connect(G_OBJECT(d->create_button), "clicked",
157 G_CALLBACK(_lib_history_create_style_button_clicked_callback), NULL);
158 gtk_widget_set_name(d->create_button, "non-flat");
159 gtk_widget_set_tooltip_text(d->create_button, _("create a style from the current history stack"));
160
161 /* add buttons to buttonbox */
162 gtk_box_pack_start(GTK_BOX(hhbox), d->compress_button, TRUE, TRUE, 0);
163 gtk_box_pack_start(GTK_BOX(hhbox), d->create_button, FALSE, FALSE, 0);
164
165 /* add history list and buttonbox to widget */
166 gtk_box_pack_start(GTK_BOX(self->widget),
167 dt_ui_scroll_wrap(d->history_box, 1, "plugins/darkroom/history/windowheight"), FALSE, FALSE, 0);
168 gtk_box_pack_start(GTK_BOX(self->widget), hhbox, FALSE, FALSE, 0);
169
170 gtk_widget_show_all(self->widget);
171
172 /* connect to history change signal for updating the history view */
173 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_HISTORY_WILL_CHANGE,
174 G_CALLBACK(_lib_history_will_change_callback), self);
175 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_HISTORY_CHANGE,
176 G_CALLBACK(_lib_history_change_callback), self);
177 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_MODULE_REMOVE,
178 G_CALLBACK(_lib_history_module_remove_callback), self);
179 }
180
gui_cleanup(dt_lib_module_t * self)181 void gui_cleanup(dt_lib_module_t *self)
182 {
183 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_history_change_callback), self);
184 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_history_module_remove_callback), self);
185 g_free(self->data);
186 self->data = NULL;
187 }
188
_lib_history_create_button(dt_lib_module_t * self,int num,const char * label,gboolean enabled,gboolean default_enabled,gboolean always_on,gboolean selected,gboolean deprecated)189 static GtkWidget *_lib_history_create_button(dt_lib_module_t *self, int num, const char *label,
190 gboolean enabled, gboolean default_enabled, gboolean always_on, gboolean selected, gboolean deprecated)
191 {
192 /* create label */
193 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
194 gchar numlab[10];
195
196 g_snprintf(numlab, sizeof(numlab), "%2d", num + 1);
197 GtkWidget *numwidget = gtk_label_new(numlab);
198 gtk_widget_set_name(numwidget, "history-number");
199
200 GtkWidget *onoff = NULL;
201
202 /* create toggle button */
203 GtkWidget *widget = gtk_toggle_button_new_with_label(label);
204 GtkWidget *lab = gtk_bin_get_child(GTK_BIN(widget));
205 gtk_widget_set_halign(lab, GTK_ALIGN_START);
206 gtk_label_set_xalign(GTK_LABEL(lab), 0);
207 gtk_label_set_ellipsize(GTK_LABEL(lab), PANGO_ELLIPSIZE_END);
208 if(always_on)
209 {
210 onoff = dtgtk_button_new(dtgtk_cairo_paint_switch_on, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
211 gtk_widget_set_name(onoff, "history-switch-always-enabled");
212 gtk_widget_set_name(widget, "history-button-always-enabled");
213 dtgtk_button_set_active(DTGTK_BUTTON(onoff), TRUE);
214 gtk_widget_set_tooltip_text(onoff, _("always-on module"));
215 }
216 else if(default_enabled)
217 {
218 onoff = dtgtk_button_new(dtgtk_cairo_paint_switch, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
219 gtk_widget_set_name(onoff, "history-switch-default-enabled");
220 gtk_widget_set_name(widget, "history-button-default-enabled");
221 dtgtk_button_set_active(DTGTK_BUTTON(onoff), enabled);
222 gtk_widget_set_tooltip_text(onoff, _("default enabled module"));
223 }
224 else
225 {
226 if(deprecated)
227 {
228 onoff = dtgtk_button_new(dtgtk_cairo_paint_switch_deprecated, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
229 gtk_widget_set_name(onoff, "history-switch-deprecated");
230 gtk_widget_set_tooltip_text(onoff, _("deprecated module"));
231 }
232 else
233 {
234 onoff = dtgtk_button_new(dtgtk_cairo_paint_switch, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
235 gtk_widget_set_name(onoff, enabled ? "history-switch-enabled" : "history-switch");
236 }
237 gtk_widget_set_name(widget, enabled ? "history-button-enabled" : "history-button");
238 dtgtk_button_set_active(DTGTK_BUTTON(onoff), enabled);
239 }
240
241 gtk_widget_set_sensitive (onoff, FALSE);
242
243 g_object_set_data(G_OBJECT(widget), "history_number", GINT_TO_POINTER(num + 1));
244 g_object_set_data(G_OBJECT(widget), "label", (gpointer)label);
245 if(selected) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
246
247 /* set callback when clicked */
248 g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(_lib_history_button_clicked_callback), self);
249
250 /* associate the history number */
251 g_object_set_data(G_OBJECT(widget), "history-number", GINT_TO_POINTER(num + 1));
252
253 gtk_box_pack_start(GTK_BOX(hbox), numwidget, FALSE, FALSE, 0);
254 gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
255 gtk_box_pack_end(GTK_BOX(hbox), onoff, FALSE, FALSE, 0);
256
257 return hbox;
258 }
259
_reset_module_instance(GList * hist,dt_iop_module_t * module,int multi_priority)260 static void _reset_module_instance(GList *hist, dt_iop_module_t *module, int multi_priority)
261 {
262 for(; hist; hist = g_list_next(hist))
263 {
264 dt_dev_history_item_t *hit = (dt_dev_history_item_t *)hist->data;
265
266 if(!hit->module && strcmp(hit->op_name, module->op) == 0 && hit->multi_priority == multi_priority)
267 {
268 hit->module = module;
269 }
270 }
271 }
272
273 struct _cb_data
274 {
275 dt_iop_module_t *module;
276 int multi_priority;
277 };
278
_undo_items_cb(gpointer user_data,dt_undo_type_t type,dt_undo_data_t data)279 static void _undo_items_cb(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data)
280 {
281 struct _cb_data *udata = (struct _cb_data *)user_data;
282 dt_undo_history_t *hdata = (dt_undo_history_t *)data;
283 _reset_module_instance(hdata->after_snapshot, udata->module, udata->multi_priority);
284 }
285
_history_invalidate_cb(gpointer user_data,dt_undo_type_t type,dt_undo_data_t item)286 static void _history_invalidate_cb(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item)
287 {
288 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
289 dt_undo_history_t *hist = (dt_undo_history_t *)item;
290 dt_dev_invalidate_history_module(hist->after_snapshot, module);
291 }
292
_add_module_expander(GList * iop_list,dt_iop_module_t * module)293 static void _add_module_expander(GList *iop_list, dt_iop_module_t *module)
294 {
295 // dt_dev_reload_history_items won't do this for base instances
296 // and it will call gui_init() for the rest
297 // so we do it here
298 if(!dt_iop_is_hidden(module) && !module->expander)
299 {
300 /* add module to right panel */
301 dt_iop_gui_set_expander(module);
302 dt_iop_gui_set_expanded(module, TRUE, FALSE);
303 dt_iop_gui_update_blending(module);
304 }
305 }
306
307 // return the 1st history entry that matches module
_search_history_by_module(GList * history_list,dt_iop_module_t * module)308 static dt_dev_history_item_t *_search_history_by_module(GList *history_list, dt_iop_module_t *module)
309 {
310 dt_dev_history_item_t *hist_ret = NULL;
311
312 for(GList *history = history_list; history; history = g_list_next(history))
313 {
314 dt_dev_history_item_t *hist_item = (dt_dev_history_item_t *)history->data;
315
316 if(hist_item->module == module)
317 {
318 hist_ret = hist_item;
319 break;
320 }
321 }
322 return hist_ret;
323 }
324
_check_deleted_instances(dt_develop_t * dev,GList ** _iop_list,GList * history_list)325 static int _check_deleted_instances(dt_develop_t *dev, GList **_iop_list, GList *history_list)
326 {
327 GList *iop_list = *_iop_list;
328 int deleted_module_found = 0;
329
330 // we will check on dev->iop if there's a module that is not in history
331 GList *modules = iop_list;
332 while(modules)
333 {
334 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
335
336 int delete_module = 0;
337
338 // base modules are a special case
339 // most base modules won't be in history and must not be deleted
340 // but the user may have deleted a base instance of a multi-instance module
341 // and then undo and redo, so we will end up with two entries in dev->iop
342 // with multi_priority == 0, this can't happen and the extra one must be deleted
343 // dev->iop is sorted by (priority, multi_priority DESC), so if the next one is
344 // a base instance too, one must be deleted
345 if(mod->multi_priority == 0)
346 {
347 GList *modules_next = g_list_next(modules);
348 if(modules_next)
349 {
350 dt_iop_module_t *mod_next = (dt_iop_module_t *)modules_next->data;
351 if(strcmp(mod_next->op, mod->op) == 0 && mod_next->multi_priority == 0)
352 {
353 // is the same one, check which one must be deleted
354 const int mod_in_history = (_search_history_by_module(history_list, mod) != NULL);
355 const int mod_next_in_history = (_search_history_by_module(history_list, mod_next) != NULL);
356
357 // current is in history and next is not, delete next
358 if(mod_in_history && !mod_next_in_history)
359 {
360 mod = mod_next;
361 modules = modules_next;
362 delete_module = 1;
363 }
364 // current is not in history and next is, delete current
365 else if(!mod_in_history && mod_next_in_history)
366 {
367 delete_module = 1;
368 }
369 else
370 {
371 if(mod_in_history && mod_next_in_history)
372 fprintf(
373 stderr,
374 "[_check_deleted_instances] found duplicate module %s %s (%i) and %s %s (%i) both in history\n",
375 mod->op, mod->multi_name, mod->multi_priority, mod_next->op, mod_next->multi_name,
376 mod_next->multi_priority);
377 else
378 fprintf(
379 stderr,
380 "[_check_deleted_instances] found duplicate module %s %s (%i) and %s %s (%i) none in history\n",
381 mod->op, mod->multi_name, mod->multi_priority, mod_next->op, mod_next->multi_name,
382 mod_next->multi_priority);
383 }
384 }
385 }
386 }
387 // this is a regular multi-instance and must be in history
388 else
389 {
390 delete_module = (_search_history_by_module(history_list, mod) == NULL);
391 }
392
393 // if module is not in history we delete it
394 if(delete_module)
395 {
396 deleted_module_found = 1;
397
398 if(darktable.develop->gui_module == mod) dt_iop_request_focus(NULL);
399
400 ++darktable.gui->reset;
401
402 // we remove the plugin effectively
403 if(!dt_iop_is_hidden(mod))
404 {
405 // we just hide the module to avoid lots of gtk critical warnings
406 gtk_widget_hide(mod->expander);
407
408 // this is copied from dt_iop_gui_delete_callback(), not sure why the above sentence...
409 gtk_widget_destroy(mod->widget);
410 dt_iop_gui_cleanup_module(mod);
411 }
412
413 iop_list = g_list_remove_link(iop_list, modules);
414
415 // remove it from all snapshots
416 dt_undo_iterate_internal(darktable.undo, DT_UNDO_HISTORY, mod, &_history_invalidate_cb);
417
418 // we cleanup the module
419 dt_accel_cleanup_closures_iop(mod);
420
421 // don't delete the module, a pipe may still need it
422 dev->alliop = g_list_append(dev->alliop, mod);
423
424 --darktable.gui->reset;
425
426 // and reset the list
427 modules = iop_list;
428 continue;
429 }
430
431 modules = g_list_next(modules);
432 }
433 if(deleted_module_found) iop_list = g_list_sort(iop_list, dt_sort_iop_by_order);
434
435 *_iop_list = iop_list;
436
437 return deleted_module_found;
438 }
439
_reorder_gui_module_list(dt_develop_t * dev)440 static void _reorder_gui_module_list(dt_develop_t *dev)
441 {
442 int pos_module = 0;
443 for(const GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(modules))
444 {
445 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
446
447 GtkWidget *expander = module->expander;
448 if(expander)
449 {
450 gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER), expander,
451 pos_module++);
452 }
453 }
454 }
455
_rebuild_multi_priority(GList * history_list)456 static int _rebuild_multi_priority(GList *history_list)
457 {
458 int changed = 0;
459 for(const GList *history = history_list; history; history = g_list_next(history))
460 {
461 dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)history->data;
462
463 // if multi_priority is different in history and dev->iop
464 // we keep the history version
465 if(hitem->module && hitem->module->multi_priority != hitem->multi_priority)
466 {
467 dt_iop_update_multi_priority(hitem->module, hitem->multi_priority);
468 changed = 1;
469 }
470 }
471 return changed;
472 }
473
_create_deleted_modules(GList ** _iop_list,GList * history_list)474 static int _create_deleted_modules(GList **_iop_list, GList *history_list)
475 {
476 GList *iop_list = *_iop_list;
477 int changed = 0;
478 gboolean done = FALSE;
479
480 GList *l = history_list;
481 while(l)
482 {
483 GList *next = g_list_next(l);
484 dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)l->data;
485
486 // this fixes the duplicate module when undo: hitem->multi_priority = 0;
487 if(hitem->module == NULL)
488 {
489 changed = 1;
490
491 const dt_iop_module_t *base_module = dt_iop_get_module_from_list(iop_list, hitem->op_name);
492 if(base_module == NULL)
493 {
494 fprintf(stderr, "[_create_deleted_modules] can't find base module for %s\n", hitem->op_name);
495 return changed;
496 }
497
498 // from there we create a new module for this base instance. The goal is to do a very minimal setup of the
499 // new module to be able to write the history items. From there we reload the whole history back and this
500 // will recreate the proper module instances.
501 dt_iop_module_t *module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
502 if(dt_iop_load_module(module, base_module->so, base_module->dev))
503 {
504 return changed;
505 }
506 module->instance = base_module->instance;
507
508 if(!dt_iop_is_hidden(module))
509 {
510 ++darktable.gui->reset;
511 module->gui_init(module);
512 --darktable.gui->reset;
513 }
514
515 // adjust the multi_name of the new module
516 g_strlcpy(module->multi_name, hitem->multi_name, sizeof(module->multi_name));
517 dt_iop_update_multi_priority(module, hitem->multi_priority);
518 module->iop_order = hitem->iop_order;
519
520 // we insert this module into dev->iop
521 iop_list = g_list_insert_sorted(iop_list, module, dt_sort_iop_by_order);
522
523 // add the expander, dt_dev_reload_history_items() don't work well without one
524 _add_module_expander(iop_list, module);
525
526 // if not already done, set the module to all others same instance
527 if(!done)
528 {
529 _reset_module_instance(history_list, module, hitem->multi_priority);
530
531 // and do that also in the undo/redo lists
532 struct _cb_data udata = { module, hitem->multi_priority };
533 dt_undo_iterate_internal(darktable.undo, DT_UNDO_HISTORY, &udata, &_undo_items_cb);
534 done = TRUE;
535 }
536
537 hitem->module = module;
538 }
539 l = next;
540 }
541
542 *_iop_list = iop_list;
543
544 return changed;
545 }
546
_pop_undo(gpointer user_data,dt_undo_type_t type,dt_undo_data_t data,dt_undo_action_t action,GList ** imgs)547 static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
548 {
549 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
550
551 if(type == DT_UNDO_HISTORY)
552 {
553 dt_lib_history_t *d = (dt_lib_history_t *)self->data;
554 dt_undo_history_t *hist = (dt_undo_history_t *)data;
555 dt_develop_t *dev = darktable.develop;
556
557 // we will work on a copy of history and modules
558 // when we're done we'll replace dev->history and dev->iop
559 GList *history_temp = NULL;
560 int hist_end = 0;
561
562 if(action == DT_ACTION_UNDO)
563 {
564 history_temp = dt_history_duplicate(hist->before_snapshot);
565 hist_end = hist->before_end;
566 dev->iop_order_list = dt_ioppr_iop_order_copy_deep(hist->before_iop_order_list);
567 }
568 else
569 {
570 history_temp = dt_history_duplicate(hist->after_snapshot);
571 hist_end = hist->after_end;
572 dev->iop_order_list = dt_ioppr_iop_order_copy_deep(hist->after_iop_order_list);
573 }
574
575 GList *iop_temp = g_list_copy(dev->iop);
576
577 // topology has changed?
578 int pipe_remove = 0;
579
580 // we have to check if multi_priority has changed since history was saved
581 // we will adjust it here
582 if(_rebuild_multi_priority(history_temp))
583 {
584 pipe_remove = 1;
585 iop_temp = g_list_sort(iop_temp, dt_sort_iop_by_order);
586 }
587
588 // check if this undo a delete module and re-create it
589 if(_create_deleted_modules(&iop_temp, history_temp))
590 {
591 pipe_remove = 1;
592 }
593
594 // check if this is a redo of a delete module or an undo of an add module
595 if(_check_deleted_instances(dev, &iop_temp, history_temp))
596 {
597 pipe_remove = 1;
598 }
599
600 // disable recording undo as the _lib_history_change_callback will be triggered by the calls below
601 d->record_undo = FALSE;
602
603 dt_pthread_mutex_lock(&dev->history_mutex);
604
605 // set history and modules to dev
606 GList *history_temp2 = dev->history;
607 dev->history = history_temp;
608 dev->history_end = hist_end;
609 g_list_free_full(history_temp2, dt_dev_free_history_item);
610 GList *iop_temp2 = dev->iop;
611 dev->iop = iop_temp;
612 g_list_free(iop_temp2);
613
614 // topology has changed
615 if(pipe_remove)
616 {
617 // we refresh the pipe
618 dev->pipe->changed |= DT_DEV_PIPE_REMOVE;
619 dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
620 dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
621 dev->pipe->cache_obsolete = 1;
622 dev->preview_pipe->cache_obsolete = 1;
623 dev->preview2_pipe->cache_obsolete = 1;
624
625 // invalidate buffers and force redraw of darkroom
626 dt_dev_invalidate_all(dev);
627 }
628
629 dt_pthread_mutex_unlock(&dev->history_mutex);
630
631 // if dev->iop has changed reflect that on module list
632 if(pipe_remove) _reorder_gui_module_list(dev);
633
634 // write new history and reload
635 dt_dev_write_history(dev);
636 dt_dev_reload_history_items(dev);
637
638 dt_ioppr_resync_modules_order(dev);
639
640 dt_dev_modulegroups_set(darktable.develop, dt_dev_modulegroups_get(darktable.develop));
641
642 if(dev->gui_module)
643 {
644 dt_masks_set_edit_mode(dev->gui_module, hist->mask_edit_mode);
645 darktable.develop->gui_module->request_mask_display = hist->request_mask_display;
646 dt_iop_gui_update_blendif(darktable.develop->gui_module);
647 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)(dev->gui_module->blend_data);
648 if(bd)
649 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->showmask),
650 hist->request_mask_display == DT_DEV_PIXELPIPE_DISPLAY_MASK);
651 }
652 }
653 }
654
_history_undo_data_free(gpointer data)655 static void _history_undo_data_free(gpointer data)
656 {
657 dt_undo_history_t *hist = (dt_undo_history_t *)data;
658 g_list_free_full(hist->before_snapshot, dt_dev_free_history_item);
659 g_list_free_full(hist->after_snapshot, dt_dev_free_history_item);
660 g_list_free_full(hist->before_iop_order_list, free);
661 g_list_free_full(hist->after_iop_order_list, free);
662 free(data);
663 }
664
_lib_history_module_remove_callback(gpointer instance,dt_iop_module_t * module,gpointer user_data)665 static void _lib_history_module_remove_callback(gpointer instance, dt_iop_module_t *module, gpointer user_data)
666 {
667 dt_undo_iterate(darktable.undo, DT_UNDO_HISTORY, module, &_history_invalidate_cb);
668 }
669
_lib_history_will_change_callback(gpointer instance,GList * history,int history_end,GList * iop_order_list,gpointer user_data)670 static void _lib_history_will_change_callback(gpointer instance, GList *history, int history_end, GList *iop_order_list,
671 gpointer user_data)
672 {
673 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
674 dt_lib_history_t *lib = (dt_lib_history_t *)self->data;
675
676 if(lib->record_undo && (lib->record_history_level == 0))
677 {
678 // history is about to change, here we want to record a snapshot of the history for the undo
679 // record previous history
680 g_list_free_full(lib->previous_snapshot, free);
681 g_list_free_full(lib->previous_iop_order_list, free);
682 lib->previous_snapshot = history;
683 lib->previous_history_end = history_end;
684 lib->previous_iop_order_list = iop_order_list;
685 }
686
687 lib->record_history_level += 1;
688 }
689
_lib_history_change_text(dt_introspection_field_t * field,const char * d,dt_iop_params_t * params,dt_iop_params_t * oldpar)690 static gchar *_lib_history_change_text(dt_introspection_field_t *field, const char *d, dt_iop_params_t *params, dt_iop_params_t *oldpar)
691 {
692 dt_iop_params_t *p = params + field->header.offset;
693 dt_iop_params_t *o = oldpar + field->header.offset;
694
695 switch(field->header.type)
696 {
697 case DT_INTROSPECTION_TYPE_STRUCT:
698 case DT_INTROSPECTION_TYPE_UNION:
699 {
700 gchar **change_parts = g_malloc0_n(field->Struct.entries + 1, sizeof(char*));
701 int num_parts = 0;
702
703 for(int i = 0; i < field->Struct.entries; i++)
704 {
705 dt_introspection_field_t *entry = field->Struct.fields[i];
706
707 gchar *description = _(*entry->header.description ?
708 entry->header.description :
709 entry->header.field_name);
710
711 if(d) description = g_strdup_printf("%s.%s", d, description);
712
713 if((change_parts[num_parts] = _lib_history_change_text(entry, description, params, oldpar)))
714 num_parts++;
715
716 if(d) g_free(description);
717 }
718
719 gchar *struct_text = num_parts ? g_strjoinv("\n", change_parts) : NULL;
720 g_strfreev(change_parts);
721
722 return struct_text;
723 }
724 break;
725 case DT_INTROSPECTION_TYPE_ARRAY:
726 if(field->Array.type == DT_INTROSPECTION_TYPE_CHAR)
727 {
728 if(strncmp((char*)o, (char*)p, field->Array.count))
729 return g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", d, (char*)o, (char*)p);
730 }
731 else
732 {
733 const int max_elements = 4;
734 gchar **change_parts = g_malloc0_n(max_elements + 1, sizeof(char*));
735 int num_parts = 0;
736
737 for(int i = 0, item_offset = 0; i < field->Array.count; i++, item_offset += field->Array.field->header.size)
738 {
739 char *description = g_strdup_printf("%s[%d]", d, i);
740 char *element_text = _lib_history_change_text(field->Array.field, description, params + item_offset, oldpar + item_offset);
741 g_free(description);
742
743 if(element_text && ++num_parts <= max_elements)
744 change_parts[num_parts - 1] = element_text;
745 else
746 g_free(element_text);
747 }
748
749 gchar *array_text = NULL;
750 if(num_parts > max_elements)
751 array_text = g_strdup_printf("%s\t%d changes", d, num_parts);
752 else if(num_parts > 0)
753 array_text = g_strjoinv("\n", change_parts);
754
755 g_strfreev(change_parts);
756
757 return array_text;
758 }
759 break;
760 case DT_INTROSPECTION_TYPE_FLOAT:
761 if(*(float*)o != *(float*)p && (isfinite(*(float*)o) || isfinite(*(float*)p)))
762 return g_strdup_printf("%s\t%.4f\t\u2192\t%.4f", d, *(float*)o, *(float*)p);
763 break;
764 case DT_INTROSPECTION_TYPE_INT:
765 if(*(int*)o != *(int*)p)
766 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(int*)o, *(int*)p);
767 break;
768 case DT_INTROSPECTION_TYPE_UINT:
769 if(*(unsigned int*)o != *(unsigned int*)p)
770 return g_strdup_printf("%s\t%u\t\u2192\t%u", d, *(unsigned int*)o, *(unsigned int*)p);
771 break;
772 case DT_INTROSPECTION_TYPE_USHORT:
773 if(*(unsigned short int*)o != *(unsigned short int*)p)
774 return g_strdup_printf("%s\t%hu\t\u2192\t%hu", d, *(unsigned short int*)o, *(unsigned short int*)p);
775 break;
776 case DT_INTROSPECTION_TYPE_INT8:
777 if(*(uint8_t*)o != *(uint8_t*)p)
778 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(uint8_t*)o, *(uint8_t*)p);
779 break;
780 case DT_INTROSPECTION_TYPE_CHAR:
781 if(*(char*)o != *(char*)p)
782 return g_strdup_printf("%s\t'%c'\t\u2192\t'%c'", d, *(char *)o, *(char *)p);
783 break;
784 case DT_INTROSPECTION_TYPE_FLOATCOMPLEX:
785 if(*(float complex*)o != *(float complex*)p)
786 return g_strdup_printf("%s\t%.4f + %.4fi\t\u2192\t%.4f + %.4fi", d,
787 creal(*(float complex*)o), cimag(*(float complex*)o),
788 creal(*(float complex*)p), cimag(*(float complex*)p));
789 break;
790 case DT_INTROSPECTION_TYPE_ENUM:
791 if(*(int*)o != *(int*)p)
792 {
793 const char *old_str = N_("unknown"), *new_str = N_("unknown");
794 for(dt_introspection_type_enum_tuple_t *i = field->Enum.values; i && i->name; i++)
795 {
796 if(i->value == *(int*)o)
797 {
798 old_str = i->description;
799 if(!*old_str) old_str = i->name;
800 }
801 if(i->value == *(int*)p)
802 {
803 new_str = i->description;
804 if(!*new_str) new_str = i->name;
805 }
806 }
807
808 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
809 }
810 break;
811 case DT_INTROSPECTION_TYPE_BOOL:
812 if(*(gboolean*)o != *(gboolean*)p)
813 {
814 char *old_str = *(gboolean*)o ? "on" : "off";
815 char *new_str = *(gboolean*)p ? "on" : "off";
816 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
817 }
818 break;
819 case DT_INTROSPECTION_TYPE_OPAQUE:
820 {
821 // TODO: special case float2
822 }
823 break;
824 default:
825 fprintf(stderr, "unsupported introspection type \"%s\" encountered in _lib_history_change_text (field %s)\n", field->header.type_name, field->header.field_name);
826 break;
827 }
828
829 return NULL;
830 }
831
_changes_tooltip_callback(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,const dt_dev_history_item_t * hitem)832 static gboolean _changes_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
833 GtkTooltip *tooltip, const dt_dev_history_item_t *hitem)
834 {
835 dt_iop_params_t *old_params = hitem->module->default_params;
836 dt_develop_blend_params_t *old_blend = hitem->module->default_blendop_params;
837
838 for(const GList *find_old = darktable.develop->history;
839 find_old && find_old->data != hitem;
840 find_old = g_list_next(find_old))
841 {
842 const dt_dev_history_item_t *hiprev = (dt_dev_history_item_t *)(find_old->data);
843
844 if(hiprev->module == hitem->module)
845 {
846 old_params = hiprev->params;
847 old_blend = hiprev->blend_params;
848 }
849 }
850
851 gchar **change_parts = g_malloc0_n(sizeof(dt_develop_blend_params_t) / (sizeof(float)) + 10, sizeof(char*));
852
853 if(hitem->module->have_introspection)
854 change_parts[0] = _lib_history_change_text(hitem->module->get_introspection()->field, NULL,
855 hitem->params, old_params);
856 int num_parts = change_parts[0] ? 1 : 0;
857
858 if(hitem->module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
859 {
860 #define add_blend_history_change(field, format, label) \
861 if((hitem->blend_params->field) != (old_blend->field)) \
862 { \
863 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
864 change_parts[num_parts++] = g_strdup_printf(full_format, label, \
865 (old_blend->field), (hitem->blend_params->field)); \
866 g_free(full_format); \
867 }
868
869 #define add_blend_history_change_enum(field, label, list) \
870 if((hitem->blend_params->field) != (old_blend->field)) \
871 { \
872 const char *old_str = NULL, *new_str = NULL; \
873 for(const dt_develop_name_value_t *i = list; *i->name; i++) \
874 { \
875 if(i->value == (old_blend->field)) old_str = i->name; \
876 if(i->value == (hitem->blend_params->field)) new_str = i->name; \
877 } \
878 \
879 change_parts[num_parts++] = (!old_str || !new_str) \
880 ? g_strdup_printf("%s\t%d\t\u2192\t%d", label, \
881 old_blend->field, hitem->blend_params->field)\
882 : g_strdup_printf("%s\t%s\t\u2192\t%s", label, \
883 _(g_dpgettext2(NULL, "blendmode", old_str)), \
884 _(g_dpgettext2(NULL, "blendmode", new_str)));\
885 }
886
887 add_blend_history_change_enum(blend_cst, _("colorspace"), dt_develop_blend_colorspace_names);
888 add_blend_history_change_enum(mask_mode, _("mask mode"), dt_develop_mask_mode_names);
889 add_blend_history_change_enum(blend_mode & DEVELOP_BLEND_MODE_MASK, _("blend mode"), dt_develop_blend_mode_names);
890 add_blend_history_change_enum(blend_mode & DEVELOP_BLEND_REVERSE, _("blend operation"), dt_develop_blend_mode_flag_names);
891 add_blend_history_change(blend_parameter, _("%.2f EV"), _("blend fulcrum"));
892 add_blend_history_change(opacity, "%.4f", _("mask opacity"));
893 add_blend_history_change_enum(mask_combine & (DEVELOP_COMBINE_INV | DEVELOP_COMBINE_INCL), _("combine masks"), dt_develop_combine_masks_names);
894 add_blend_history_change(feathering_radius, "%.4f", _("feathering radius"));
895 add_blend_history_change_enum(feathering_guide, _("feathering guide"), dt_develop_feathering_guide_names);
896 add_blend_history_change(blur_radius, "%.4f", _("mask blur"));
897 add_blend_history_change(contrast, "%.4f", _("mask contrast"));
898 add_blend_history_change(brightness, "%.4f", _("brightness"));
899 add_blend_history_change(raster_mask_instance, "%d", _("raster mask instance"));
900 add_blend_history_change(raster_mask_id, "%d", _("raster mask id"));
901 add_blend_history_change_enum(raster_mask_invert, _("invert mask"), dt_develop_invert_mask_names);
902
903 add_blend_history_change(mask_combine & DEVELOP_COMBINE_MASKS_POS ? '-' : '+', "%c", _("drawn mask polarity"));
904
905 if(hitem->blend_params->mask_id != old_blend->mask_id)
906 change_parts[num_parts++] = old_blend->mask_id == 0
907 ? g_strdup_printf(_("a drawn mask was added"))
908 : hitem->blend_params->mask_id == 0
909 ? g_strdup_printf(_("the drawn mask was removed"))
910 : g_strdup_printf(_("the drawn mask was changed"));
911
912 dt_iop_gui_blend_data_t *bd = hitem->module->blend_data;
913
914 for(int in_out = 1; in_out >= 0; in_out--)
915 {
916 gboolean first = TRUE;
917
918 for(const dt_iop_gui_blendif_channel_t *b = bd ? bd->channel : NULL;
919 b && b->label != NULL;
920 b++)
921 {
922 const dt_develop_blendif_channels_t ch = b->param_channels[in_out];
923
924 const int oactive = old_blend->blendif & (1 << ch);
925 const int nactive = hitem->blend_params->blendif & (1 << ch);
926
927 const int opolarity = old_blend->blendif & (1 << (ch + 16));
928 const int npolarity = hitem->blend_params->blendif & (1 << (ch + 16));
929
930 float *of = &old_blend->blendif_parameters[4 * ch];
931 float *nf = &hitem->blend_params->blendif_parameters[4 * ch];
932
933 const float oboost = exp2f(old_blend->blendif_boost_factors[ch]);
934 const float nboost = exp2f(hitem->blend_params->blendif_boost_factors[ch]);
935
936 if((oactive || nactive) && (memcmp(of, nf, sizeof(float) * 4) || opolarity != npolarity))
937 {
938 if(first)
939 {
940 change_parts[num_parts++] = g_strdup(in_out ? _("parametric output mask:") : _("parametric input mask:"));
941 first = FALSE;
942 }
943 char s[4][2][25];
944 for(int k = 0; k < 4; k++)
945 {
946 b->scale_print(of[k], oboost, s[k][0], sizeof(s[k][0]));
947 b->scale_print(nf[k], nboost, s[k][1], sizeof(s[k][1]));
948 }
949
950 char *opol = !oactive ? "" : (opolarity ? "(-)" : "(+)");
951 char *npol = !nactive ? "" : (npolarity ? "(-)" : "(+)");
952
953 change_parts[num_parts++] = g_strdup_printf("%s\t%s| %s- %s| %s%s\t\u2192\t%s| %s- %s| %s%s", _(b->name),
954 s[0][0], s[1][0], s[2][0], s[3][0], opol,
955 s[0][1], s[1][1], s[2][1], s[3][1], npol);
956 }
957 }
958 }
959 }
960
961 gchar *tooltip_text = g_strjoinv("\n", change_parts);
962 g_strfreev(change_parts);
963
964 gboolean show_tooltip = *tooltip_text;
965
966 if(show_tooltip)
967 {
968 static GtkWidget *view = NULL;
969 if(!view)
970 {
971 view = gtk_text_view_new();
972 gtk_widget_set_name(view, "history-tooltip");
973 g_signal_connect(G_OBJECT(view), "destroy", G_CALLBACK(gtk_widget_destroyed), &view);
974 }
975
976 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
977 gtk_text_buffer_set_text(buffer, tooltip_text, -1);
978 gtk_tooltip_set_custom(tooltip, view);
979 gtk_widget_map(view); // FIXME: workaround added in order to fix #9908, probably a Gtk issue, remove when fixed upstream
980
981 int count_column1 = 0, count_column2 = 0;
982 for(gchar *line = tooltip_text; *line; )
983 {
984 gchar *endline = g_strstr_len(line, -1, "\n");
985 if(!endline) endline = line + strlen(line);
986
987 gchar *found_tab1 = g_strstr_len(line, endline - line, "\t");
988 if(found_tab1)
989 {
990 if(found_tab1 - line >= count_column1) count_column1 = found_tab1 - line + 1;
991
992 gchar *found_tab2 = g_strstr_len(found_tab1 + 1, endline - found_tab1 - 1, "\t");
993 if(found_tab2 - found_tab1 > count_column2) count_column2 = found_tab2 - found_tab1;
994 }
995
996 line = endline;
997 if(*line) line++;
998 }
999
1000 PangoLayout *layout = gtk_widget_create_pango_layout(view, " ");
1001 int char_width;
1002 pango_layout_get_size(layout, &char_width, NULL);
1003 g_object_unref(layout);
1004 PangoTabArray *tabs = pango_tab_array_new_with_positions(3, FALSE, PANGO_TAB_LEFT, (count_column1) * char_width,
1005 PANGO_TAB_LEFT, (count_column1 + count_column2) * char_width,
1006 PANGO_TAB_LEFT, (count_column1 + count_column2 + 2) * char_width);
1007 gtk_text_view_set_tabs(GTK_TEXT_VIEW(view), tabs);
1008 pango_tab_array_free(tabs);
1009 }
1010
1011 g_free(tooltip_text);
1012
1013 return show_tooltip;
1014 }
1015
_lib_history_change_callback(gpointer instance,gpointer user_data)1016 static void _lib_history_change_callback(gpointer instance, gpointer user_data)
1017 {
1018 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1019 dt_lib_history_t *d = (dt_lib_history_t *)self->data;
1020
1021 /* first destroy all buttons in list */
1022 dt_gui_container_destroy_children(GTK_CONTAINER(d->history_box));
1023
1024 /* add default which always should be */
1025 int num = -1;
1026 GtkWidget *widget =
1027 _lib_history_create_button(self, num, _("original"), FALSE, FALSE, TRUE, darktable.develop->history_end == 0, FALSE);
1028 gtk_box_pack_start(GTK_BOX(d->history_box), widget, FALSE, FALSE, 0);
1029 num++;
1030
1031 d->record_history_level -= 1;
1032
1033 if (d->record_undo == TRUE && (d->record_history_level == 0))
1034 {
1035 /* record undo/redo history snapshot */
1036 dt_undo_history_t *hist = malloc(sizeof(dt_undo_history_t));
1037 hist->before_snapshot = dt_history_duplicate(d->previous_snapshot);
1038 hist->before_end = d->previous_history_end;
1039 hist->before_iop_order_list = dt_ioppr_iop_order_copy_deep(d->previous_iop_order_list);
1040
1041 hist->after_snapshot = dt_history_duplicate(darktable.develop->history);
1042 hist->after_end = darktable.develop->history_end;
1043 hist->after_iop_order_list = dt_ioppr_iop_order_copy_deep(darktable.develop->iop_order_list);
1044
1045 if(darktable.develop->gui_module)
1046 {
1047 hist->mask_edit_mode = dt_masks_get_edit_mode(darktable.develop->gui_module);
1048 hist->request_mask_display = darktable.develop->gui_module->request_mask_display;
1049 }
1050 else
1051 {
1052 hist->mask_edit_mode = DT_MASKS_EDIT_OFF;
1053 hist->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
1054 }
1055
1056 dt_undo_record(darktable.undo, self, DT_UNDO_HISTORY, (dt_undo_data_t)hist,
1057 _pop_undo, _history_undo_data_free);
1058 }
1059 else
1060 d->record_undo = TRUE;
1061
1062 /* lock history mutex */
1063 dt_pthread_mutex_lock(&darktable.develop->history_mutex);
1064
1065 /* iterate over history items and add them to list*/
1066 for(const GList *history = darktable.develop->history; history; history = g_list_next(history))
1067 {
1068 const dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)(history->data);
1069 gchar *label;
1070 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
1071 label = g_strdup_printf("%s", hitem->module->name());
1072 else
1073 label = g_strdup_printf("%s %s", hitem->module->name(), hitem->multi_name);
1074
1075 const gboolean selected = (num == darktable.develop->history_end - 1);
1076 widget =
1077 _lib_history_create_button(self, num, label, (hitem->enabled || (strcmp(hitem->op_name, "mask_manager") == 0)),
1078 hitem->module->default_enabled, hitem->module->hide_enable_button, selected,
1079 hitem->module->flags() & IOP_FLAGS_DEPRECATED);
1080
1081 g_free(label);
1082
1083 gtk_widget_set_has_tooltip(widget, TRUE);
1084 g_signal_connect(G_OBJECT(widget), "query-tooltip", G_CALLBACK(_changes_tooltip_callback), (void *)hitem);
1085
1086 gtk_box_pack_start(GTK_BOX(d->history_box), widget, FALSE, FALSE, 0);
1087 gtk_box_reorder_child(GTK_BOX(d->history_box), widget, 0);
1088 num++;
1089 }
1090
1091 /* show all widgets */
1092 gtk_widget_show_all(d->history_box);
1093
1094 dt_pthread_mutex_unlock(&darktable.develop->history_mutex);
1095 }
1096
_lib_history_truncate(gboolean compress)1097 static void _lib_history_truncate(gboolean compress)
1098 {
1099 const int32_t imgid = darktable.develop->image_storage.id;
1100 if(!imgid) return;
1101
1102 dt_dev_undo_start_record(darktable.develop);
1103
1104 // As dt_history_compress_on_image does *not* use the history stack data at all
1105 // make sure the current stack is in the database
1106 dt_dev_write_history(darktable.develop);
1107
1108 if(compress)
1109 dt_history_compress_on_image(imgid);
1110 else
1111 dt_history_truncate_on_image(imgid, darktable.develop->history_end);
1112
1113 sqlite3_stmt *stmt;
1114
1115 // load new history and write it back to ensure that all history are properly numbered without a gap
1116 dt_dev_reload_history_items(darktable.develop);
1117 dt_dev_write_history(darktable.develop);
1118 dt_image_synch_xmp(imgid);
1119
1120 // then we can get the item to select in the new clean-up history retrieve the position of the module
1121 // corresponding to the history end.
1122 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1123 "SELECT IFNULL(MAX(num)+1, 0)"
1124 " FROM main.history"
1125 " WHERE imgid=?1", -1, &stmt, NULL);
1126 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
1127
1128 if (sqlite3_step(stmt) == SQLITE_ROW)
1129 darktable.develop->history_end = sqlite3_column_int(stmt, 0);
1130 sqlite3_finalize(stmt);
1131
1132 // select the new history end corresponding to the one before the history compression
1133 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1134 "UPDATE main.images SET history_end=?2 WHERE id=?1",
1135 -1, &stmt, NULL);
1136 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
1137 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, darktable.develop->history_end);
1138 sqlite3_step(stmt);
1139 sqlite3_finalize(stmt);
1140
1141 darktable.develop->proxy.chroma_adaptation = NULL;
1142 dt_dev_reload_history_items(darktable.develop);
1143 dt_dev_undo_end_record(darktable.develop);
1144
1145 dt_dev_modulegroups_set(darktable.develop, dt_dev_modulegroups_get(darktable.develop));
1146 }
1147
_lib_compress_stack_accel(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1148 static gboolean _lib_compress_stack_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1149 GdkModifierType modifier, gpointer data)
1150 {
1151 _lib_history_truncate(TRUE);
1152 return TRUE;
1153 }
1154
_lib_truncate_stack_accel(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1155 static gboolean _lib_truncate_stack_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1156 GdkModifierType modifier, gpointer data)
1157 {
1158 _lib_history_truncate(FALSE);
1159 return TRUE;
1160 }
1161
_lib_history_compress_clicked_callback(GtkWidget * widget,GdkEventButton * e,gpointer user_data)1162 static void _lib_history_compress_clicked_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
1163 {
1164 const gboolean compress = !dt_modifier_is(e->state, GDK_CONTROL_MASK);
1165 _lib_history_truncate(compress);
1166 }
1167
_lib_history_button_clicked_callback(GtkWidget * widget,gpointer user_data)1168 static void _lib_history_button_clicked_callback(GtkWidget *widget, gpointer user_data)
1169 {
1170 static int reset = 0;
1171 if(reset) return;
1172 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return;
1173
1174 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1175 dt_lib_history_t *d = (dt_lib_history_t *)self->data;
1176 reset = 1;
1177
1178 /* deactivate all toggle buttons */
1179 GList *children = gtk_container_get_children(GTK_CONTAINER(d->history_box));
1180 for(GList *l = children; l != NULL; l = g_list_next(l))
1181 {
1182 GtkToggleButton *b = GTK_TOGGLE_BUTTON(dt_gui_container_nth_child(GTK_CONTAINER(l->data), HIST_WIDGET_MODULE));
1183 if(b != GTK_TOGGLE_BUTTON(widget))
1184 g_object_set(G_OBJECT(b), "active", FALSE, (gchar *)0);
1185 }
1186 g_list_free(children);
1187
1188 reset = 0;
1189 if(darktable.gui->reset) return;
1190
1191 dt_dev_undo_start_record(darktable.develop);
1192
1193 /* revert to given history item. */
1194 const int num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "history-number"));
1195 dt_dev_pop_history_items(darktable.develop, num);
1196 // set the module list order
1197 dt_dev_reorder_gui_module_list(darktable.develop);
1198
1199 /* signal history changed */
1200 dt_dev_undo_end_record(darktable.develop);
1201
1202 dt_iop_connect_accels_all();
1203 dt_dev_modulegroups_set(darktable.develop, dt_dev_modulegroups_get(darktable.develop));
1204 }
1205
_lib_history_create_style_button_clicked_callback(GtkWidget * widget,gpointer user_data)1206 static void _lib_history_create_style_button_clicked_callback(GtkWidget *widget, gpointer user_data)
1207 {
1208 if(darktable.develop->image_storage.id)
1209 {
1210 dt_dev_write_history(darktable.develop);
1211 dt_gui_styles_dialog_new(darktable.develop->image_storage.id);
1212 }
1213 }
1214
gui_reset(dt_lib_module_t * self)1215 void gui_reset(dt_lib_module_t *self)
1216 {
1217 const int32_t imgid = darktable.develop->image_storage.id;
1218 if(!imgid) return;
1219
1220 gint res = GTK_RESPONSE_YES;
1221
1222 if(dt_conf_get_bool("ask_before_discard"))
1223 {
1224 const GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1225
1226 GtkWidget *dialog = gtk_message_dialog_new(
1227 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1228 _("do you really want to clear history of current image?"));
1229 #ifdef GDK_WINDOWING_QUARTZ
1230 dt_osx_disallow_fullscreen(dialog);
1231 #endif
1232
1233 gtk_window_set_title(GTK_WINDOW(dialog), _("delete image's history?"));
1234 res = gtk_dialog_run(GTK_DIALOG(dialog));
1235 gtk_widget_destroy(dialog);
1236 }
1237
1238 if(res == GTK_RESPONSE_YES)
1239 {
1240 dt_dev_undo_start_record(darktable.develop);
1241
1242 dt_history_delete_on_image_ext(imgid, FALSE);
1243
1244 dt_dev_undo_end_record(darktable.develop);
1245
1246 dt_dev_modulegroups_set(darktable.develop, dt_dev_modulegroups_get(darktable.develop));
1247
1248 dt_control_queue_redraw_center();
1249 }
1250 }
1251
1252 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1253 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1254 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1255