Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/history.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Henrik Andersson.
4 Copyright (C) 2010-2011, 2013-2014 johannes hanika.
5 Copyright (C) 2010 Stuart Henderson.
6 Copyright (C) 2011 Alexandre Prokoudine.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Jérémy Rosen.
9 Copyright (C) 2011 Robert Bieber.
10 Copyright (C) 2011 Rostyslav Pidgornyi.
11 Copyright (C) 2011-2019 Tobias Ellinghaus.
12 Copyright (C) 2012 Frédéric Grollier.
13 Copyright (C) 2012 Richard Wonka.
14 Copyright (C) 2013-2014, 2020-2022 Aldric Renaudin.
15 Copyright (C) 2013 Pascal de Bruijn.
16 Copyright (C) 2013-2017 Roman Lebedev.
17 Copyright (C) 2014-2017, 2019-2022 Pascal Obry.
18 Copyright (C) 2018-2019 Edgardo Hoszowski.
19 Copyright (C) 2018 Maurizio Paglia.
20 Copyright (C) 2018 Peter Budai.
21 Copyright (C) 2018 rawfiner.
22 Copyright (C) 2019-2020, 2023, 2025-2026 Aurélien PIERRE.
23 Copyright (C) 2019 Hanno Schwalm.
24 Copyright (C) 2019-2020 Heiko Bauke.
25 Copyright (C) 2019, 2021 luzpaz.
26 Copyright (C) 2019 Philippe Weyland.
27 Copyright (C) 2020 Chris Elston.
28 Copyright (C) 2020-2022 Diederik Ter Rahe.
29 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
30 Copyright (C) 2020 Hubert Kowalski.
31 Copyright (C) 2020-2021 Marco.
32 Copyright (C) 2021 Marco Carrarini.
33 Copyright (C) 2021 Ralf Brown.
34 Copyright (C) 2022 Martin Bařinka.
35 Copyright (C) 2022 Miloš Komarčević.
36 Copyright (C) 2022 Nicolas Auffray.
37 Copyright (C) 2023 Alynx Zhou.
38
39 darktable is free software: you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation, either version 3 of the License, or
42 (at your option) any later version.
43
44 darktable is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
48
49 You should have received a copy of the GNU General Public License
50 along with darktable. If not, see <http://www.gnu.org/licenses/>.
51*/
52
53#include "common/darktable.h"
54#include "common/debug.h"
55#include "common/styles.h"
56#include "common/undo.h"
57#include "control/conf.h"
58#include "control/control.h"
59#include "develop/develop.h"
60#include "develop/masks.h"
61
62#include "gui/gtk.h"
63#include "gui/styles.h"
64#include "libs/lib.h"
65#include "libs/lib_api.h"
66#include "common/history.h"
67#include <complex.h>
68
69#ifdef GDK_WINDOWING_QUARTZ
70#include "osx/osx.h"
71#endif
72
73DT_MODULE(1)
74
81
83{
84 // This stores the "history end" cursor, i.e.:
85 // - 0 means "original" (raw input, no history item applied),
86 // - N (1..len) means apply the first N history items (dev->history is 0..N-1).
95
96/* compress history stack */
97static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data);
98/* signal callback for history change */
99static void _lib_history_change_callback(gpointer instance, gpointer user_data);
100static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data);
101static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
102 GtkTooltip *tooltip, gpointer user_data);
103static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer,
104 GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
105
106const char *name(struct dt_lib_module_t *self)
107{
108 return _("History of changes");
109}
110
111const char **views(dt_lib_module_t *self)
112{
113 static const char *v[] = {"darkroom", NULL};
114 return v;
115}
116
118{
120}
121
123{
124 return 900;
125}
126
128{
129 /* initialize ui widgets */
130 dt_lib_history_t *d = (dt_lib_history_t *)g_malloc0(sizeof(dt_lib_history_t));
131 self->data = (void *)d;
132
133 d->selection_reset = FALSE;
134
135 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
136 gtk_widget_set_name(self->widget, "history-ui");
137
138 d->history_store = gtk_list_store_new(DT_HISTORY_VIEW_COL_COUNT,
139 G_TYPE_INT, // history_end
140 G_TYPE_STRING, // number
141 G_TYPE_STRING, // label
142 G_TYPE_STRING, // icon-name
143 G_TYPE_BOOLEAN, // enabled
144 G_TYPE_STRING); // tooltip text
145
146 d->history_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(d->history_store));
147 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->history_view), FALSE);
148 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(d->history_view), FALSE);
149
150 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->history_view));
151 gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
152 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_lib_history_view_selection_changed), self);
153 g_signal_connect(G_OBJECT(d->history_view), "button-press-event", G_CALLBACK(_lib_history_view_button_press_callback), self);
154
155 gtk_widget_set_has_tooltip(d->history_view, TRUE);
156 g_signal_connect(G_OBJECT(d->history_view), "query-tooltip", G_CALLBACK(_lib_history_view_query_tooltip), self);
157
158 GtkCellRenderer *renderer_num = gtk_cell_renderer_text_new();
159 g_object_set(G_OBJECT(renderer_num), "xalign", 1.0, "family", "monospace", NULL);
160 GtkTreeViewColumn *col_num = gtk_tree_view_column_new_with_attributes("n", renderer_num,
162 NULL);
163 gtk_tree_view_column_set_cell_data_func(col_num, renderer_num, _lib_history_view_cell_set_foreground, NULL, NULL);
164 gtk_tree_view_column_set_sizing(col_num, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
165 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_num);
166
167 GtkCellRenderer *renderer_label = gtk_cell_renderer_text_new();
168 g_object_set(G_OBJECT(renderer_label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
169 GtkTreeViewColumn *col_label = gtk_tree_view_column_new_with_attributes("label", renderer_label,
171 NULL);
172 gtk_tree_view_column_set_cell_data_func(col_label, renderer_label, _lib_history_view_cell_set_foreground, NULL, NULL);
173 gtk_tree_view_column_set_expand(col_label, TRUE);
174 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_label);
175
176 GtkCellRenderer *renderer_icon = gtk_cell_renderer_pixbuf_new();
177 GtkTreeViewColumn *col_icon = gtk_tree_view_column_new_with_attributes("status", renderer_icon,
179 NULL);
180 gtk_tree_view_column_set_sizing(col_icon, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
181 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_icon);
182
183 /* add history list and buttonbox to widget */
184 GtkWidget *history_sw = dt_ui_scroll_wrap(d->history_view, 1, "plugins/darkroom/history/windowheight",
186 gtk_box_pack_start(GTK_BOX(self->widget), history_sw, FALSE, FALSE, 0);
187
188 gtk_widget_show_all(self->widget);
189
190 /* connect to history change signal for updating the history view */
192 G_CALLBACK(_lib_history_change_callback), self);
193}
194
196{
197 if(IS_NULL_PTR(self->data)) return;
200 if(d && d->history_store) g_object_unref(d->history_store);
201 dt_free(self->data);
202}
203
204static const char *_history_icon_name(const gboolean enabled, const gboolean default_enabled, const gboolean always_on,
205 const gboolean deprecated)
206{
207 if(always_on) return "emblem-readonly";
208 if(deprecated) return "dialog-warning";
209 if(default_enabled) return enabled ? "emblem-ok" : "process-stop";
210 return enabled ? "emblem-ok" : "process-stop";
211}
212
213static gchar *_lib_history_change_text(dt_introspection_field_t *field, const char *d, gpointer params, gpointer oldpar)
214{
215 dt_iop_params_t *p = (dt_iop_params_t *)((uint8_t *)params + field->header.offset);
216 dt_iop_params_t *o = (dt_iop_params_t *)((uint8_t *)oldpar + field->header.offset);
217
218 switch(field->header.type)
219 {
222 {
223 gchar **change_parts = g_malloc0_n(field->Struct.entries + 1, sizeof(char*));
224 int num_parts = 0;
225
226 for(int i = 0; i < field->Struct.entries; i++)
227 {
228 dt_introspection_field_t *entry = field->Struct.fields[i];
229
230 gchar *description = _(*entry->header.description ?
231 entry->header.description :
232 entry->header.field_name);
233
234 if(d) description = g_strdup_printf("%s.%s", d, description);
235
236 if((change_parts[num_parts] = _lib_history_change_text(entry, description, params, oldpar)))
237 num_parts++;
238
239 if(d)
240 {
242 }
243 }
244
245 gchar *struct_text = num_parts ? g_strjoinv("\n", change_parts) : NULL;
246 g_strfreev(change_parts);
247
248 return struct_text;
249 }
250 break;
253 {
254 const gboolean is_valid =
255 g_utf8_validate((char *)o, -1, NULL)
256 && g_utf8_validate((char *)p, -1, NULL);
257
258 if(is_valid && strncmp((char*)o, (char*)p, field->Array.count))
259 return g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", d, (char*)o, (char*)p);
260 }
261 else
262 {
263 const int max_elements = 4;
264 gchar **change_parts = g_malloc0_n(max_elements + 1, sizeof(char*));
265 int num_parts = 0;
266
267 for(int i = 0, item_offset = 0; i < field->Array.count; i++, item_offset += field->Array.field->header.size)
268 {
269 char *description = g_strdup_printf("%s[%d]", d, i);
270 char *element_text = _lib_history_change_text(field->Array.field, description, (uint8_t *)params + item_offset, (uint8_t *)oldpar + item_offset);
272
273 if(element_text && ++num_parts <= max_elements)
274 change_parts[num_parts - 1] = element_text;
275 else
276 dt_free(element_text);
277 }
278
279 gchar *array_text = NULL;
280 if(num_parts > max_elements)
281 array_text = g_strdup_printf("%s\t%d changes", d, num_parts);
282 else if(num_parts > 0)
283 array_text = g_strjoinv("\n", change_parts);
284
285 g_strfreev(change_parts);
286
287 return array_text;
288 }
289 break;
291 if(*(float*)o != *(float*)p && (isfinite(*(float*)o) || isfinite(*(float*)p)))
292 return g_strdup_printf("%s\t%.4f\t\u2192\t%.4f", d, *(float*)o, *(float*)p);
293 break;
295 if(*(int*)o != *(int*)p)
296 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(int*)o, *(int*)p);
297 break;
299 if(*(unsigned int*)o != *(unsigned int*)p)
300 return g_strdup_printf("%s\t%u\t\u2192\t%u", d, *(unsigned int*)o, *(unsigned int*)p);
301 break;
303 if(*(unsigned short int*)o != *(unsigned short int*)p)
304 return g_strdup_printf("%s\t%hu\t\u2192\t%hu", d, *(unsigned short int*)o, *(unsigned short int*)p);
305 break;
307 if(*(uint8_t*)o != *(uint8_t*)p)
308 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(uint8_t*)o, *(uint8_t*)p);
309 break;
311 if(*(char*)o != *(char*)p)
312 return g_strdup_printf("%s\t'%c'\t\u2192\t'%c'", d, *(char *)o, *(char *)p);
313 break;
315 if(*(float complex*)o != *(float complex*)p)
316 return g_strdup_printf("%s\t%.4f + %.4fi\t\u2192\t%.4f + %.4fi", d,
317 creal(*(float complex*)o), cimag(*(float complex*)o),
318 creal(*(float complex*)p), cimag(*(float complex*)p));
319 break;
321 if(*(int*)o != *(int*)p)
322 {
323 const char *old_str = N_("unknown"), *new_str = N_("unknown");
324 for(dt_introspection_type_enum_tuple_t *i = field->Enum.values; i && i->name; i++)
325 {
326 if(i->value == *(int*)o)
327 {
328 old_str = i->description;
329 if(!*old_str) old_str = i->name;
330 }
331 if(i->value == *(int*)p)
332 {
333 new_str = i->description;
334 if(!*new_str) new_str = i->name;
335 }
336 }
337
338 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
339 }
340 break;
342 if(*(gboolean*)o != *(gboolean*)p)
343 {
344 char *old_str = *(gboolean*)o ? "on" : "off";
345 char *new_str = *(gboolean*)p ? "on" : "off";
346 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
347 }
348 break;
350 {
351 // TODO: special case float2
352 }
353 break;
354 default:
355 fprintf(stderr, "unsupported introspection type \"%s\" encountered in _lib_history_change_text (field %s)\n", field->header.type_name, field->header.field_name);
356 break;
357 }
358
359 return NULL;
360}
361
363{
364 // Find the immediate previous history instance matching the current module
365 for(const GList *find_old = g_list_find(darktable.develop->history, hitem);
366 find_old;
367 find_old = g_list_previous(find_old))
368 {
369 dt_dev_history_item_t *hprev = (dt_dev_history_item_t *)(find_old->data);
370 if(hprev == hitem) continue; // first loop run, we start from current hitem
371 if(hprev->module == hitem->module) return hprev;
372 }
373
374 // This is the first history element for this module
375 return hitem;
376}
377
378#define add_blend_history_change(field, format, label) \
379 if((hitem->blend_params->field) != (old_blend->field)) \
380 { \
381 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
382 change_parts[num_parts++] \
383 = g_strdup_printf(full_format, label, (old_blend->field), (hitem->blend_params->field)); \
384 dt_free(full_format); \
385 full_format = NULL; \
386 }
387
388#define add_blend_history_change_enum(field, label, list) \
389 if((hitem->blend_params->field) != (old_blend->field)) \
390 { \
391 const char *old_str = NULL, *new_str = NULL; \
392 for(const dt_develop_name_value_t *i = list; *i->name; i++) \
393 { \
394 if(i->value == (old_blend->field)) old_str = i->name; \
395 if(i->value == (hitem->blend_params->field)) new_str = i->name; \
396 } \
397 \
398 change_parts[num_parts++] \
399 = (!old_str || !new_str) \
400 ? g_strdup_printf("%s\t%d\t\u2192\t%d", label, old_blend->field, hitem->blend_params->field) \
401 : g_strdup_printf("%s\t%s\t\u2192\t%s", label, _(g_dpgettext2(NULL, "blendmode", old_str)), \
402 _(g_dpgettext2(NULL, "blendmode", new_str))); \
403 }
404
405#define add_history_change(field, format, label) \
406 if((hitem->field) != (hprev->field)) \
407 { \
408 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
409 change_parts[num_parts++] = g_strdup_printf(full_format, label, (hprev->field), (hitem->field)); \
410 dt_free(full_format); \
411 full_format = NULL; \
412 }
413
414#define add_history_change_string(field, label) \
415 if(strcmp(hitem->field, hprev->field)) \
416 { \
417 change_parts[num_parts++] \
418 = g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", label, (hprev->field), (hitem->field)); \
419 }
420
421#define add_history_change_boolean(field, label) \
422 if((hitem->field) != (hprev->field)) \
423 { \
424 change_parts[num_parts++] = g_strdup_printf("%s\t%s\t\u2192\t%s", label, (hprev->field) ? _("on") : _("off"), \
425 (hitem->field) ? _("on") : _("off")); \
426 }
427
428
430{
431 if(IS_NULL_PTR(hitem) || !hitem->module) return NULL;
432
434 dt_iop_params_t *old_params
435 = (hprev == hitem || IS_NULL_PTR(hprev)) ? hitem->module->default_params : hprev->module->params;
437 = (hprev == hitem || IS_NULL_PTR(hprev)) ? hitem->module->default_blendop_params : hprev->module->blend_params;
438
439 gchar **change_parts = g_malloc0_n(sizeof(dt_develop_blend_params_t) / (sizeof(float)) + 24, sizeof(char*));
440 int num_parts = 0;
441
442 const gboolean enabled_by_default = (hitem->module->force_enable && hitem->module->force_enable(hitem->module, hitem->enabled))
443 || hitem->module->default_enabled;
444 if(hprev == hitem)
445 {
446 // This is the first history entry for this module.
447 // That means the module was necessarily enabled in this step.
448 if(enabled_by_default)
449 change_parts[num_parts++] = g_strdup_printf(_("mandatory module created automatically"));
450 else
451 change_parts[num_parts++] = g_strdup_printf(_("module created per user request"));
452 }
453 else
454 {
455 // This is not the first history entry for this module.
456 // It can have been disabled.
457 add_history_change_boolean(enabled, _("enabled"));
458 }
459
460 add_history_change(iop_order, "%i", _("pipeline order"));
461 add_history_change_string(multi_name, _("instance name"));
462
463 if(hitem->module->have_introspection)
464 {
465 gchar *introspection_change = _lib_history_change_text(hitem->module->get_introspection()->field, NULL,
466 hitem->params, old_params);
467 if(!IS_NULL_PTR(introspection_change)) change_parts[num_parts++] = introspection_change;
468 }
469
470 if(hitem->module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
471 {
476 add_blend_history_change(blend_parameter, _("%.2f EV"), _("blend fulcrum"));
477 add_blend_history_change(opacity, "%.4f", _("mask opacity"));
479 add_blend_history_change(feathering_radius, "%.4f", _("feathering radius"));
480 add_blend_history_change_enum(feathering_guide, _("feathering guide"), dt_develop_feathering_guide_names);
481 add_blend_history_change(blur_radius, "%.4f", _("mask blur"));
482 add_blend_history_change(contrast, "%.4f", _("mask contrast"));
483 add_blend_history_change(brightness, "%.4f", _("brightness"));
484 add_blend_history_change(raster_mask_instance, "%d", _("raster mask instance"));
485 add_blend_history_change(raster_mask_id, "%d", _("raster mask id"));
486 add_blend_history_change_enum(raster_mask_invert, _("invert mask"), dt_develop_invert_mask_names);
487
488 add_blend_history_change(mask_combine & DEVELOP_COMBINE_MASKS_POS ? '-' : '+', "%c", _("drawn mask polarity"));
489
490 if(hitem->blend_params->mask_id != old_blend->mask_id)
491 change_parts[num_parts++] = old_blend->mask_id == 0
492 ? g_strdup_printf(_("a drawn mask was added"))
493 : hitem->blend_params->mask_id == 0
494 ? g_strdup_printf(_("the drawn mask was removed"))
495 : g_strdup_printf(_("the drawn mask was changed"));
496
497 dt_iop_gui_blend_data_t *bd = hitem->module->blend_data;
498
499 for(int in_out = 1; in_out >= 0; in_out--)
500 {
501 gboolean first = TRUE;
502
503 for(const dt_iop_gui_blendif_channel_t *b = bd ? bd->channel : NULL;
504 b && !IS_NULL_PTR(b->label);
505 b++)
506 {
507 const dt_develop_blendif_channels_t ch = b->param_channels[in_out];
508
509 const int oactive = old_blend->blendif & (1 << ch);
510 const int nactive = hitem->blend_params->blendif & (1 << ch);
511
512 const int opolarity = old_blend->blendif & (1 << (ch + 16));
513 const int npolarity = hitem->blend_params->blendif & (1 << (ch + 16));
514
515 float *of = &old_blend->blendif_parameters[4 * ch];
516 float *nf = &hitem->blend_params->blendif_parameters[4 * ch];
517
518 const float oboost = exp2f(old_blend->blendif_boost_factors[ch]);
519 const float nboost = exp2f(hitem->blend_params->blendif_boost_factors[ch]);
520
521 if((oactive || nactive) && (memcmp(of, nf, sizeof(float) * 4) || opolarity != npolarity))
522 {
523 if(first)
524 {
525 change_parts[num_parts++] = g_strdup(in_out ? _("parametric output mask:") : _("parametric input mask:"));
526 first = FALSE;
527 }
528 char s[4][2][25];
529 for(int k = 0; k < 4; k++)
530 {
531 b->scale_print(of[k], oboost, s[k][0], sizeof(s[k][0]));
532 b->scale_print(nf[k], nboost, s[k][1], sizeof(s[k][1]));
533 }
534
535 char *opol = !oactive ? "" : (opolarity ? "(-)" : "(+)");
536 char *npol = !nactive ? "" : (npolarity ? "(-)" : "(+)");
537
538 change_parts[num_parts++] = g_strdup_printf("%s\t%s| %s- %s| %s%s\t\u2192\t%s| %s- %s| %s%s", _(b->name),
539 s[0][0], s[1][0], s[2][0], s[3][0], opol,
540 s[0][1], s[1][1], s[2][1], s[3][1], npol);
541 }
542 }
543 }
544 }
545
546 gchar *tooltip_text = g_strjoinv("\n", change_parts);
547 g_strfreev(change_parts);
548
549 return tooltip_text;
550}
551
552static gboolean _changes_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
553 GtkTooltip *tooltip)
554{
555 const gchar *tooltip_text = g_object_get_data(G_OBJECT(widget), "tooltip-text");
556 if(IS_NULL_PTR(tooltip_text) || !tooltip_text[0]) return FALSE;
557
558 gtk_tooltip_set_text(tooltip, tooltip_text);
559
560 return TRUE;
561}
562
563static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
564 GtkTooltip *tooltip, gpointer user_data)
565{
566 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
568 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
569 GtkTreeIter iter;
570 GtkTreePath *path = NULL;
571
572 if(keyboard_mode)
573 {
574 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
575 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return FALSE;
576 }
577 else
578 {
579 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL)) return FALSE;
580 if(!gtk_tree_model_get_iter(model, &iter, path))
581 {
582 gtk_tree_path_free(path);
583 return FALSE;
584 }
585 }
586
587 gchar *tooltip_text = NULL;
588 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_TOOLTIP, &tooltip_text, -1);
589 g_object_set_data_full(G_OBJECT(widget), "tooltip-text", tooltip_text, g_free);
590
591 const gboolean ret = _changes_tooltip_callback(widget, x, y, keyboard_mode, tooltip);
592 if(path) gtk_tree_path_free(path);
593 return ret;
594}
595
596static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer,
597 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
598{
599 gboolean enabled = TRUE;
600 gtk_tree_model_get(model, iter, DT_HISTORY_VIEW_COL_ENABLED, &enabled, -1);
601 if(enabled)
602 g_object_set(G_OBJECT(renderer), "foreground-set", FALSE, NULL);
603 else
604 g_object_set(G_OBJECT(renderer), "foreground-set", TRUE, "foreground", "#888", NULL);
605}
606
607static void _history_apply_history_end(const int history_end)
608{
610
612
615 dt_dev_set_history_end_ext(dev, history_end);
618 // Apply the same post-history sync as dt_dev_pop_history_items():
619 // darkroom geometry and the virtual preview pipe must be refreshed only
620 // after releasing history_mutex to avoid lock inversions with GUI users.
623
625
627
628 // We have no way of knowing if moving the history end conceptually added
629 // or removed new pipeline nodes ("modules"), so we need to nuke the pipe all the time
630 // and rebuild from scratch
632
634}
635
636static void _history_show_module_for_end(const int history_end)
637{
638 if(history_end <= 0) return;
639
642 = (dt_dev_history_item_t *)g_list_nth_data(darktable.develop->history, history_end - 1);
643 dt_iop_module_t *module = hist ? hist->module : NULL;
645 if(module)
646 {
647 dt_iop_request_focus(module);
649 }
650}
651
653{
654 GtkTreeIter iter;
655 gtk_list_store_append(d->history_store, &iter);
656 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, 0, DT_HISTORY_VIEW_COL_NUMBER, " 0",
660}
661
663{
664 const gchar *hint = _("Shift+click: show module without changing history");
665
666 gchar *tooltip_text = _create_tooltip_text(hitem);
667 if(tooltip_text && tooltip_text[0])
668 {
669 gchar *tooltip_with_hint = g_strconcat(tooltip_text, "\n\n", hint, NULL);
670 dt_free(tooltip_text);
671 return tooltip_with_hint;
672 }
673
674 dt_free(tooltip_text);
675 return g_strdup(hint);
676}
677
678static void _history_store_prepend_item(dt_lib_history_t *d, const dt_dev_history_item_t *hitem, const int history_end)
679{
680 const gboolean enabled = (hitem->enabled || (strcmp(hitem->op_name, "mask_manager") == 0));
681 if(!hitem->module)
682 {
683 gchar *label = NULL;
684 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
685 label = g_strdup(hitem->op_name);
686 else
687 label = g_strdup_printf("%s %s", hitem->op_name, hitem->multi_name);
688
689 gchar number[10];
690 g_snprintf(number, sizeof(number), "%2d", history_end);
691
692 gchar *tooltip_text = _history_tooltip_with_hint(hitem);
693
694 GtkTreeIter iter;
695 gtk_list_store_insert(d->history_store, &iter, 0);
696 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, history_end,
700 tooltip_text ? tooltip_text : "", -1);
701
702 dt_free(tooltip_text);
703 dt_free(label);
704 return;
705 }
706
707 const gboolean deprecated = (hitem->module->flags() & IOP_FLAGS_DEPRECATED);
708 const char *icon_name = _history_icon_name(enabled, hitem->module->default_enabled, hitem->module->hide_enable_button,
709 deprecated);
710
711 const gboolean enabled_by_default
712 = ((hitem->module->force_enable && hitem->module->force_enable(hitem->module, hitem->enabled))
713 || hitem->module->default_enabled);
714 const gchar *star = (hitem == _find_previous_history_step(hitem) && enabled_by_default) ? " *" : "";
715
716 gchar *clean_name = delete_underscore(hitem->module->name());
717 gchar *label = NULL;
718 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
719 label = g_strdup_printf("%s%s", clean_name, star);
720 else
721 label = g_strdup_printf("%s %s%s", clean_name, hitem->multi_name, star);
722 dt_free(clean_name);
723
724 gchar number[10];
725 g_snprintf(number, sizeof(number), "%2d", history_end);
726
727 gchar *tooltip_text = _history_tooltip_with_hint(hitem);
728
729 GtkTreeIter iter;
730 gtk_list_store_insert(d->history_store, &iter, 0);
731 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, history_end,
734 DT_HISTORY_VIEW_COL_TOOLTIP, tooltip_text ? tooltip_text : "", -1);
735
736 dt_free(tooltip_text);
737 dt_free(label);
738}
739
740static void _history_select_row_for_end(dt_lib_history_t *d, const int history_end)
741{
742 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->history_view));
743 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
744 GtkTreeIter iter;
745
746 for(gboolean valid = gtk_tree_model_get_iter_first(model, &iter); valid; valid = gtk_tree_model_iter_next(model, &iter))
747 {
748 int row_history_end = 0;
749 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &row_history_end, -1);
750 if(row_history_end == history_end)
751 {
752 gtk_tree_selection_select_iter(selection, &iter);
753 return;
754 }
755 }
756}
757
758static void _lib_history_change_callback(gpointer instance, gpointer user_data)
759{
760 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
762
763 d->selection_reset = TRUE;
764 gtk_list_store_clear(d->history_store);
765
766 // Read-only access: don't take a write lock here. This callback can run
767 // while the pixelpipe holds a read lock, and a write lock would deadlock
768 // the UI thread when history change signals are emitted.
770
772
773 int history_pos = 0;
774 for(const GList *history = darktable.develop->history; history; history = g_list_next(history), history_pos++)
775 _history_store_prepend_item(d, (const dt_dev_history_item_t *)history->data, history_pos + 1);
776
777 const int history_end = dt_dev_get_history_end_ext(darktable.develop);
779
780 _history_select_row_for_end(d, history_end);
781 d->selection_reset = FALSE;
782}
783
784static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data)
785{
786 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
788 if(IS_NULL_PTR(d) || d->selection_reset || darktable.gui->reset) return;
789
790 GtkTreeModel *model = NULL;
791 GtkTreeIter iter;
792 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
793
794 int history_end = 0;
795 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &history_end, -1);
796 if(history_end == dt_dev_get_history_end_ext(darktable.develop)) return;
797
798 _history_apply_history_end(history_end);
799}
800
801static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
802{
803 // Ctrl-click just shows the corresponding module in modulegroups
804 if(e->button == 1 && dt_modifier_is(e->state, GDK_CONTROL_MASK))
805 {
806 GtkTreePath *path = NULL;
807 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), (gint)e->x, (gint)e->y, &path, NULL, NULL, NULL))
808 {
809 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
811 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
812 GtkTreeIter iter;
813 if(gtk_tree_model_get_iter(model, &iter, path))
814 {
815 int history_end = 0;
816 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &history_end, -1);
817 _history_show_module_for_end(history_end);
818 }
819 gtk_tree_path_free(path);
820 return TRUE;
821 }
822 }
823
824 return FALSE;
825}
826
828{
829 const int32_t imgid = darktable.develop->image_storage.id;
830 if(!imgid) return;
831
832 gint res = GTK_RESPONSE_YES;
833
834 if(dt_conf_get_bool("ask_before_discard"))
835 {
837
838 GtkWidget *dialog = gtk_message_dialog_new(
839 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
840 _("do you really want to clear history of current image?"));
841#ifdef GDK_WINDOWING_QUARTZ
843#endif
844
845 gtk_window_set_title(GTK_WINDOW(dialog), _("delete image's history?"));
846 res = gtk_dialog_run(GTK_DIALOG(dialog));
847 gtk_widget_destroy(dialog);
848 }
849
850 if(res == GTK_RESPONSE_YES)
851 {
853
855
857
859 }
860}
861
862// clang-format off
863// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
864// vim: shiftwidth=2 expandtab tabstop=2 cindent
865// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
866// clang-format on
const char ** description(struct dt_iop_module_t *self)
Definition ashift.c:160
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const dt_develop_name_value_t dt_develop_invert_mask_names[]
Definition blend_gui.c:160
const dt_develop_name_value_t dt_develop_mask_mode_names[]
Definition blend_gui.c:137
@ DEVELOP_COMBINE_INV
Definition blend.h:125
@ DEVELOP_COMBINE_INCL
Definition blend.h:127
@ DEVELOP_COMBINE_MASKS_POS
Definition blend.h:128
const dt_develop_name_value_t dt_develop_blend_colorspace_names[]
Definition blend_gui.c:129
dt_develop_blendif_channels_t
Definition blend.h:144
const dt_develop_name_value_t dt_develop_blend_mode_flag_names[]
Definition blend_gui.c:124
@ DEVELOP_BLEND_MODE_MASK
Definition blend.h:109
@ DEVELOP_BLEND_REVERSE
Definition blend.h:108
const dt_develop_name_value_t dt_develop_combine_masks_names[]
Definition blend_gui.c:146
const dt_develop_name_value_t dt_develop_blend_mode_names[]
Definition blend_gui.c:82
const dt_develop_name_value_t dt_develop_feathering_guide_names[]
Definition blend_gui.c:153
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_history_delete_on_image_ext(int32_t imgid, gboolean undo)
char * name
int dt_conf_get_bool(const char *name)
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
static gchar * delete_underscore(const char *s)
Definition darktable.h:1083
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
void dt_dev_pop_history_items_ext(dt_develop_t *dev)
Apply history items to module params up to dev->history_end.
void dt_dev_write_history(dt_develop_t *dev, gboolean async)
Thread-safe wrapper around dt_dev_write_history_ext() for dev->image_storage.id.
void dt_dev_history_pixelpipe_update(dt_develop_t *dev, gboolean rebuild)
Rebuild or resync pixelpipes after backend history changes.
void dt_dev_history_gui_update(dt_develop_t *dev)
Apply history-loaded params to module GUIs.
void dt_dev_set_history_end_ext(struct dt_develop_t *dev, const uint32_t index)
Set the history end index (GUI perspective).
Definition develop.c:1665
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1659
void dt_iop_params_t
Definition dev_history.h:41
#define dt_dev_pixelpipe_resync_history_all(dev)
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:309
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1614
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1625
#define dt_pthread_rwlock_wrlock
Definition dtpthread.h:394
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
#define dt_pthread_rwlock_rdlock
Definition dtpthread.h:393
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2713
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
const char * tooltip
Definition image.h:251
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
Definition imageop.c:2321
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ DT_INTROSPECTION_TYPE_BOOL
@ DT_INTROSPECTION_TYPE_ENUM
@ DT_INTROSPECTION_TYPE_OPAQUE
@ DT_INTROSPECTION_TYPE_ARRAY
@ DT_INTROSPECTION_TYPE_CHAR
@ DT_INTROSPECTION_TYPE_FLOAT
@ DT_INTROSPECTION_TYPE_UNION
@ DT_INTROSPECTION_TYPE_UINT
@ DT_INTROSPECTION_TYPE_USHORT
@ DT_INTROSPECTION_TYPE_INT8
@ DT_INTROSPECTION_TYPE_STRUCT
@ DT_INTROSPECTION_TYPE_FLOATCOMPLEX
@ DT_INTROSPECTION_TYPE_INT
const char * model
static const float x
const float v
void gui_reset(dt_lib_module_t *self)
#define add_blend_history_change_enum(field, label, list)
#define add_history_change_boolean(field, label)
static const char * _history_icon_name(const gboolean enabled, const gboolean default_enabled, const gboolean always_on, const gboolean deprecated)
static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
static void _lib_history_change_callback(gpointer instance, gpointer user_data)
#define add_history_change(field, format, label)
static gboolean _changes_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip)
static gchar * _lib_history_change_text(dt_introspection_field_t *field, const char *d, gpointer params, gpointer oldpar)
void gui_cleanup(dt_lib_module_t *self)
static void _history_show_module_for_end(const int history_end)
static void _history_store_add_original(dt_lib_history_t *d)
dt_history_view_column_t
@ DT_HISTORY_VIEW_COL_HISTORY_END
@ DT_HISTORY_VIEW_COL_TOOLTIP
@ DT_HISTORY_VIEW_COL_COUNT
@ DT_HISTORY_VIEW_COL_LABEL
@ DT_HISTORY_VIEW_COL_ICON_NAME
@ DT_HISTORY_VIEW_COL_ENABLED
@ DT_HISTORY_VIEW_COL_NUMBER
static void _history_select_row_for_end(dt_lib_history_t *d, const int history_end)
uint32_t container(dt_lib_module_t *self)
static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
static gchar * _create_tooltip_text(const dt_dev_history_item_t *hitem)
void gui_init(dt_lib_module_t *self)
int position()
const char ** views(dt_lib_module_t *self)
static void _history_apply_history_end(const int history_end)
static void _history_store_prepend_item(dt_lib_history_t *d, const dt_dev_history_item_t *hitem, const int history_end)
#define add_blend_history_change(field, format, label)
#define add_history_change_string(field, label)
static const dt_dev_history_item_t * _find_previous_history_step(const dt_dev_history_item_t *hitem)
static gchar * _history_tooltip_with_hint(const dt_dev_history_item_t *hitem)
static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data)
static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:204
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_develop_t * develop
Definition darktable.h:770
dt_iop_params_t * params
Definition dev_history.h:52
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:55
struct dt_iop_module_t *gboolean enabled
Definition dev_history.h:50
float blendif_parameters[4 *DEVELOP_BLENDIF_SIZE]
Definition blend.h:233
float blendif_boost_factors[DEVELOP_BLENDIF_SIZE]
Definition blend.h:234
int32_t gui_attached
Definition develop.h:162
dt_image_t image_storage
Definition develop.h:259
dt_pthread_rwlock_t history_mutex
Definition develop.h:263
GList * history
Definition develop.h:275
int32_t reset
Definition gtk.h:172
dt_ui_t * ui
Definition gtk.h:164
int32_t id
Definition image.h:319
union dt_introspection_field_t * field
dt_introspection_type_t type
dt_introspection_type_enum_tuple_t * values
dt_introspection_type_t type
union dt_introspection_field_t ** fields
const dt_iop_gui_blendif_channel_t * channel
Definition blend.h:357
GtkWidget * history_view
gboolean selection_reset
GtkListStore * history_store
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
dt_introspection_type_header_t header
dt_introspection_type_array_t Array
dt_introspection_type_enum_t Enum
dt_introspection_type_struct_t Struct
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER