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