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"
56#include "common/styles.h"
57#include "common/undo.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 <complex.h>
67
68DT_MODULE(1)
69
76
78{
79 // This stores the "history end" cursor, i.e.:
80 // - 0 means "original" (raw input, no history item applied),
81 // - N (1..len) means apply the first N history items (dev->history is 0..N-1).
90
91/* compress history stack */
92static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data);
93/* signal callback for history change */
94static void _lib_history_change_callback(gpointer instance, gpointer user_data);
95static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data);
96static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
97 GtkTooltip *tooltip, gpointer user_data);
98static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer,
99 GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
100
101const char *name(struct dt_lib_module_t *self)
102{
103 return _("History of changes");
104}
105
106const char **views(dt_lib_module_t *self)
107{
108 static const char *v[] = {"darkroom", NULL};
109 return v;
110}
111
113{
115}
116
118{
119 return 900;
120}
121
123{
124 /* initialize ui widgets */
125 dt_lib_history_t *d = (dt_lib_history_t *)g_malloc0(sizeof(dt_lib_history_t));
126 self->data = (void *)d;
127
128 d->selection_reset = FALSE;
129
130 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
131 gtk_widget_set_name(self->widget, "history-ui");
132
133 d->history_store = gtk_list_store_new(DT_HISTORY_VIEW_COL_COUNT,
134 G_TYPE_INT, // history_end
135 G_TYPE_STRING, // number
136 G_TYPE_STRING, // label
137 G_TYPE_STRING, // icon-name
138 G_TYPE_BOOLEAN, // enabled
139 G_TYPE_STRING); // tooltip text
140
141 d->history_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(d->history_store));
142 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->history_view), FALSE);
143 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(d->history_view), FALSE);
144
145 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->history_view));
146 gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
147 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_lib_history_view_selection_changed), self);
148 g_signal_connect(G_OBJECT(d->history_view), "button-press-event", G_CALLBACK(_lib_history_view_button_press_callback), self);
149
150 gtk_widget_set_has_tooltip(d->history_view, TRUE);
151 g_signal_connect(G_OBJECT(d->history_view), "query-tooltip", G_CALLBACK(_lib_history_view_query_tooltip), self);
152
153 GtkCellRenderer *renderer_num = gtk_cell_renderer_text_new();
154 g_object_set(G_OBJECT(renderer_num), "xalign", 1.0, "family", "monospace", NULL);
155 GtkTreeViewColumn *col_num = gtk_tree_view_column_new_with_attributes("n", renderer_num,
157 NULL);
158 gtk_tree_view_column_set_cell_data_func(col_num, renderer_num, _lib_history_view_cell_set_foreground, NULL, NULL);
159 gtk_tree_view_column_set_sizing(col_num, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
160 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_num);
161
162 GtkCellRenderer *renderer_label = gtk_cell_renderer_text_new();
163 g_object_set(G_OBJECT(renderer_label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
164 GtkTreeViewColumn *col_label = gtk_tree_view_column_new_with_attributes("label", renderer_label,
166 NULL);
167 gtk_tree_view_column_set_cell_data_func(col_label, renderer_label, _lib_history_view_cell_set_foreground, NULL, NULL);
168 gtk_tree_view_column_set_expand(col_label, TRUE);
169 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_label);
170
171 GtkCellRenderer *renderer_icon = gtk_cell_renderer_pixbuf_new();
172 GtkTreeViewColumn *col_icon = gtk_tree_view_column_new_with_attributes("status", renderer_icon,
174 NULL);
175 gtk_tree_view_column_set_sizing(col_icon, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
176 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_icon);
177
178 /* add history list and buttonbox to widget */
179 GtkWidget *history_sw = dt_ui_scroll_wrap(d->history_view, 1, "plugins/darkroom/history/windowheight",
181 gtk_box_pack_start(GTK_BOX(self->widget), history_sw, FALSE, FALSE, 0);
182
183 gtk_widget_show_all(self->widget);
184
185 /* connect to history change signal for updating the history view */
187 G_CALLBACK(_lib_history_change_callback), self);
188}
189
191{
192 if(IS_NULL_PTR(self->data)) return;
195 if(d && d->history_store) g_object_unref(d->history_store);
196 dt_free(self->data);
197}
198
199static const char *_history_icon_name(const gboolean enabled, const gboolean default_enabled, const gboolean always_on,
200 const gboolean deprecated)
201{
202 if(always_on) return "emblem-readonly";
203 if(deprecated) return "dialog-warning";
204 if(default_enabled) return enabled ? "emblem-ok" : "process-stop";
205 return enabled ? "emblem-ok" : "process-stop";
206}
207
208static gchar *_lib_history_change_text(dt_introspection_field_t *field, const char *d, gpointer params, gpointer oldpar)
209{
210 dt_iop_params_t *p = (dt_iop_params_t *)((uint8_t *)params + field->header.offset);
211 dt_iop_params_t *o = (dt_iop_params_t *)((uint8_t *)oldpar + field->header.offset);
212
213 switch(field->header.type)
214 {
217 {
218 gchar **change_parts = g_malloc0_n(field->Struct.entries + 1, sizeof(char*));
219 int num_parts = 0;
220
221 for(int i = 0; i < field->Struct.entries; i++)
222 {
223 dt_introspection_field_t *entry = field->Struct.fields[i];
224
225 gchar *description = _(*entry->header.description ?
226 entry->header.description :
227 entry->header.field_name);
228
229 if(d) description = g_strdup_printf("%s.%s", d, description);
230
231 if((change_parts[num_parts] = _lib_history_change_text(entry, description, params, oldpar)))
232 num_parts++;
233
234 if(d)
235 {
237 }
238 }
239
240 gchar *struct_text = num_parts ? g_strjoinv("\n", change_parts) : NULL;
241 g_strfreev(change_parts);
242
243 return struct_text;
244 }
245 break;
248 {
249 const gboolean is_valid =
250 g_utf8_validate((char *)o, -1, NULL)
251 && g_utf8_validate((char *)p, -1, NULL);
252
253 if(is_valid && strncmp((char*)o, (char*)p, field->Array.count))
254 return g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", d, (char*)o, (char*)p);
255 }
256 else
257 {
258 const int max_elements = 4;
259 gchar **change_parts = g_malloc0_n(max_elements + 1, sizeof(char*));
260 int num_parts = 0;
261
262 for(int i = 0, item_offset = 0; i < field->Array.count; i++, item_offset += field->Array.field->header.size)
263 {
264 char *description = g_strdup_printf("%s[%d]", d, i);
265 char *element_text = _lib_history_change_text(field->Array.field, description, (uint8_t *)params + item_offset, (uint8_t *)oldpar + item_offset);
267
268 if(element_text && ++num_parts <= max_elements)
269 change_parts[num_parts - 1] = element_text;
270 else
271 dt_free(element_text);
272 }
273
274 gchar *array_text = NULL;
275 if(num_parts > max_elements)
276 array_text = g_strdup_printf("%s\t%d changes", d, num_parts);
277 else if(num_parts > 0)
278 array_text = g_strjoinv("\n", change_parts);
279
280 g_strfreev(change_parts);
281
282 return array_text;
283 }
284 break;
286 if(*(float*)o != *(float*)p && (isfinite(*(float*)o) || isfinite(*(float*)p)))
287 return g_strdup_printf("%s\t%.4f\t\u2192\t%.4f", d, *(float*)o, *(float*)p);
288 break;
290 if(*(int*)o != *(int*)p)
291 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(int*)o, *(int*)p);
292 break;
294 if(*(unsigned int*)o != *(unsigned int*)p)
295 return g_strdup_printf("%s\t%u\t\u2192\t%u", d, *(unsigned int*)o, *(unsigned int*)p);
296 break;
298 if(*(unsigned short int*)o != *(unsigned short int*)p)
299 return g_strdup_printf("%s\t%hu\t\u2192\t%hu", d, *(unsigned short int*)o, *(unsigned short int*)p);
300 break;
302 if(*(uint8_t*)o != *(uint8_t*)p)
303 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(uint8_t*)o, *(uint8_t*)p);
304 break;
306 if(*(char*)o != *(char*)p)
307 return g_strdup_printf("%s\t'%c'\t\u2192\t'%c'", d, *(char *)o, *(char *)p);
308 break;
310 if(*(float complex*)o != *(float complex*)p)
311 return g_strdup_printf("%s\t%.4f + %.4fi\t\u2192\t%.4f + %.4fi", d,
312 creal(*(float complex*)o), cimag(*(float complex*)o),
313 creal(*(float complex*)p), cimag(*(float complex*)p));
314 break;
316 if(*(int*)o != *(int*)p)
317 {
318 const char *old_str = N_("unknown"), *new_str = N_("unknown");
319 for(dt_introspection_type_enum_tuple_t *i = field->Enum.values; i && i->name; i++)
320 {
321 if(i->value == *(int*)o)
322 {
323 old_str = i->description;
324 if(!*old_str) old_str = i->name;
325 }
326 if(i->value == *(int*)p)
327 {
328 new_str = i->description;
329 if(!*new_str) new_str = i->name;
330 }
331 }
332
333 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
334 }
335 break;
337 if(*(gboolean*)o != *(gboolean*)p)
338 {
339 char *old_str = *(gboolean*)o ? "on" : "off";
340 char *new_str = *(gboolean*)p ? "on" : "off";
341 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
342 }
343 break;
345 {
346 // TODO: special case float2
347 }
348 break;
349 default:
350 fprintf(stderr, "unsupported introspection type \"%s\" encountered in _lib_history_change_text (field %s)\n", field->header.type_name, field->header.field_name);
351 break;
352 }
353
354 return NULL;
355}
356
358{
359 // Find the immediate previous history instance matching the current module
360 for(const GList *find_old = g_list_find(darktable.develop->history, hitem);
361 find_old;
362 find_old = g_list_previous(find_old))
363 {
364 dt_dev_history_item_t *hprev = (dt_dev_history_item_t *)(find_old->data);
365 if(hprev == hitem) continue; // first loop run, we start from current hitem
366 if(hprev->module == hitem->module) return hprev;
367 }
368
369 // This is the first history element for this module
370 return hitem;
371}
372
373#define add_blend_history_change(field, format, label) \
374 if((hitem->blend_params->field) != (old_blend->field)) \
375 { \
376 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
377 change_parts[num_parts++] \
378 = g_strdup_printf(full_format, label, (old_blend->field), (hitem->blend_params->field)); \
379 dt_free(full_format); \
380 full_format = NULL; \
381 }
382
383#define add_blend_history_change_enum(field, label, list) \
384 if((hitem->blend_params->field) != (old_blend->field)) \
385 { \
386 const char *old_str = NULL, *new_str = NULL; \
387 for(const dt_develop_name_value_t *i = list; *i->name; i++) \
388 { \
389 if(i->value == (old_blend->field)) old_str = i->name; \
390 if(i->value == (hitem->blend_params->field)) new_str = i->name; \
391 } \
392 \
393 change_parts[num_parts++] \
394 = (!old_str || !new_str) \
395 ? g_strdup_printf("%s\t%d\t\u2192\t%d", label, old_blend->field, hitem->blend_params->field) \
396 : g_strdup_printf("%s\t%s\t\u2192\t%s", label, _(g_dpgettext2(NULL, "blendmode", old_str)), \
397 _(g_dpgettext2(NULL, "blendmode", new_str))); \
398 }
399
400#define add_history_change(field, format, label) \
401 if((hitem->field) != (hprev->field)) \
402 { \
403 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
404 change_parts[num_parts++] = g_strdup_printf(full_format, label, (hprev->field), (hitem->field)); \
405 dt_free(full_format); \
406 full_format = NULL; \
407 }
408
409#define add_history_change_string(field, label) \
410 if(strcmp(hitem->field, hprev->field)) \
411 { \
412 change_parts[num_parts++] \
413 = g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", label, (hprev->field), (hitem->field)); \
414 }
415
416#define add_history_change_boolean(field, label) \
417 if((hitem->field) != (hprev->field)) \
418 { \
419 change_parts[num_parts++] = g_strdup_printf("%s\t%s\t\u2192\t%s", label, (hprev->field) ? _("on") : _("off"), \
420 (hitem->field) ? _("on") : _("off")); \
421 }
422
423
425{
426 if(IS_NULL_PTR(hitem) || !hitem->module) return NULL;
427
429 dt_iop_params_t *old_params
430 = (hprev == hitem || IS_NULL_PTR(hprev)) ? hitem->module->default_params : hprev->module->params;
432 = (hprev == hitem || IS_NULL_PTR(hprev)) ? hitem->module->default_blendop_params : hprev->module->blend_params;
433
434 gchar **change_parts = g_malloc0_n(sizeof(dt_develop_blend_params_t) / (sizeof(float)) + 24, sizeof(char*));
435 int num_parts = 0;
436
437 const gboolean enabled_by_default = (hitem->module->force_enable && hitem->module->force_enable(hitem->module, hitem->enabled))
438 || hitem->module->default_enabled;
439 if(hprev == hitem)
440 {
441 // This is the first history entry for this module.
442 // That means the module was necessarily enabled in this step.
443 if(enabled_by_default)
444 change_parts[num_parts++] = g_strdup_printf(_("mandatory module created automatically"));
445 else
446 change_parts[num_parts++] = g_strdup_printf(_("module created per user request"));
447 }
448 else
449 {
450 // This is not the first history entry for this module.
451 // It can have been disabled.
452 add_history_change_boolean(enabled, _("enabled"));
453 }
454
455 add_history_change(iop_order, "%i", _("pipeline order"));
456 add_history_change_string(multi_name, _("instance name"));
457
458 if(hitem->module->have_introspection)
459 {
460 gchar *introspection_change = _lib_history_change_text(hitem->module->get_introspection()->field, NULL,
461 hitem->params, old_params);
462 if(!IS_NULL_PTR(introspection_change)) change_parts[num_parts++] = introspection_change;
463 }
464
465 if(hitem->module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
466 {
471 add_blend_history_change(blend_parameter, _("%.2f EV"), _("blend fulcrum"));
472 add_blend_history_change(opacity, "%.4f", _("mask opacity"));
474 add_blend_history_change(feathering_radius, "%.4f", _("feathering radius"));
475 add_blend_history_change_enum(feathering_guide, _("feathering guide"), dt_develop_feathering_guide_names);
476 add_blend_history_change(blur_radius, "%.4f", _("mask blur"));
477 add_blend_history_change(contrast, "%.4f", _("mask contrast"));
478 add_blend_history_change(brightness, "%.4f", _("brightness"));
479 add_blend_history_change(raster_mask_instance, "%d", _("raster mask instance"));
480 add_blend_history_change(raster_mask_id, "%d", _("raster mask id"));
481 add_blend_history_change_enum(raster_mask_invert, _("invert mask"), dt_develop_invert_mask_names);
482
483 add_blend_history_change(mask_combine & DEVELOP_COMBINE_MASKS_POS ? '-' : '+', "%c", _("drawn mask polarity"));
484
485 if(hitem->blend_params->mask_id != old_blend->mask_id)
486 change_parts[num_parts++] = old_blend->mask_id == 0
487 ? g_strdup_printf(_("a drawn mask was added"))
488 : hitem->blend_params->mask_id == 0
489 ? g_strdup_printf(_("the drawn mask was removed"))
490 : g_strdup_printf(_("the drawn mask was changed"));
491
492 dt_iop_gui_blend_data_t *bd = hitem->module->blend_data;
493
494 for(int in_out = 1; in_out >= 0; in_out--)
495 {
496 gboolean first = TRUE;
497
498 for(const dt_iop_gui_blendif_channel_t *b = bd ? bd->channel : NULL;
499 b && !IS_NULL_PTR(b->label);
500 b++)
501 {
502 const dt_develop_blendif_channels_t ch = b->param_channels[in_out];
503
504 const int oactive = old_blend->blendif & (1 << ch);
505 const int nactive = hitem->blend_params->blendif & (1 << ch);
506
507 const int opolarity = old_blend->blendif & (1 << (ch + 16));
508 const int npolarity = hitem->blend_params->blendif & (1 << (ch + 16));
509
510 float *of = &old_blend->blendif_parameters[4 * ch];
511 float *nf = &hitem->blend_params->blendif_parameters[4 * ch];
512
513 const float oboost = exp2f(old_blend->blendif_boost_factors[ch]);
514 const float nboost = exp2f(hitem->blend_params->blendif_boost_factors[ch]);
515
516 if((oactive || nactive) && (memcmp(of, nf, sizeof(float) * 4) || opolarity != npolarity))
517 {
518 if(first)
519 {
520 change_parts[num_parts++] = g_strdup(in_out ? _("parametric output mask:") : _("parametric input mask:"));
521 first = FALSE;
522 }
523 char s[4][2][25];
524 for(int k = 0; k < 4; k++)
525 {
526 b->scale_print(of[k], oboost, s[k][0], sizeof(s[k][0]));
527 b->scale_print(nf[k], nboost, s[k][1], sizeof(s[k][1]));
528 }
529
530 char *opol = !oactive ? "" : (opolarity ? "(-)" : "(+)");
531 char *npol = !nactive ? "" : (npolarity ? "(-)" : "(+)");
532
533 change_parts[num_parts++] = g_strdup_printf("%s\t%s| %s- %s| %s%s\t\u2192\t%s| %s- %s| %s%s", _(b->name),
534 s[0][0], s[1][0], s[2][0], s[3][0], opol,
535 s[0][1], s[1][1], s[2][1], s[3][1], npol);
536 }
537 }
538 }
539 }
540
541 gchar *tooltip_text = g_strjoinv("\n", change_parts);
542 g_strfreev(change_parts);
543
544 return tooltip_text;
545}
546
547static gboolean _changes_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
548 GtkTooltip *tooltip)
549{
550 const gchar *tooltip_text = g_object_get_data(G_OBJECT(widget), "tooltip-text");
551 if(IS_NULL_PTR(tooltip_text) || !tooltip_text[0]) return FALSE;
552
553 gtk_tooltip_set_text(tooltip, tooltip_text);
554
555 return TRUE;
556}
557
558static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
559 GtkTooltip *tooltip, gpointer user_data)
560{
561 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
563 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
564 GtkTreeIter iter;
565 GtkTreePath *path = NULL;
566
567 if(keyboard_mode)
568 {
569 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
570 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return FALSE;
571 }
572 else
573 {
574 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL)) return FALSE;
575 if(!gtk_tree_model_get_iter(model, &iter, path))
576 {
577 gtk_tree_path_free(path);
578 return FALSE;
579 }
580 }
581
582 gchar *tooltip_text = NULL;
583 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_TOOLTIP, &tooltip_text, -1);
584 g_object_set_data_full(G_OBJECT(widget), "tooltip-text", tooltip_text, g_free);
585
586 const gboolean ret = _changes_tooltip_callback(widget, x, y, keyboard_mode, tooltip);
587 if(path) gtk_tree_path_free(path);
588 return ret;
589}
590
591static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer,
592 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
593{
594 gboolean enabled = TRUE;
595 gtk_tree_model_get(model, iter, DT_HISTORY_VIEW_COL_ENABLED, &enabled, -1);
596 if(enabled)
597 g_object_set(G_OBJECT(renderer), "foreground-set", FALSE, NULL);
598 else
599 g_object_set(G_OBJECT(renderer), "foreground-set", TRUE, "foreground", "#888", NULL);
600}
601
602static void _history_apply_history_end(const int history_end)
603{
605
607
610 dt_dev_set_history_end_ext(dev, history_end);
613 // Apply the same post-history sync as dt_dev_pop_history_items():
614 // darkroom geometry and the virtual preview pipe must be refreshed only
615 // after releasing history_mutex to avoid lock inversions with GUI users.
618
620
622
623 // We have no way of knowing if moving the history end conceptually added
624 // or removed new pipeline nodes ("modules"), so we need to nuke the pipe all the time
625 // and rebuild from scratch
627
629}
630
631static void _history_show_module_for_end(const int history_end)
632{
633 if(history_end <= 0) return;
634
637 = (dt_dev_history_item_t *)g_list_nth_data(darktable.develop->history, history_end - 1);
638 dt_iop_module_t *module = hist ? hist->module : NULL;
640 if(module)
641 {
642 dt_iop_request_focus(module);
644 }
645}
646
648{
649 GtkTreeIter iter;
650 gtk_list_store_append(d->history_store, &iter);
651 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, 0, DT_HISTORY_VIEW_COL_NUMBER, " 0",
655}
656
658{
659 const gchar *hint = _("Shift+click: show module without changing history");
660
661 gchar *tooltip_text = _create_tooltip_text(hitem);
662 if(tooltip_text && tooltip_text[0])
663 {
664 gchar *tooltip_with_hint = g_strconcat(tooltip_text, "\n\n", hint, NULL);
665 dt_free(tooltip_text);
666 return tooltip_with_hint;
667 }
668
669 dt_free(tooltip_text);
670 return g_strdup(hint);
671}
672
673static void _history_store_prepend_item(dt_lib_history_t *d, const dt_dev_history_item_t *hitem, const int history_end)
674{
675 const gboolean enabled = (hitem->enabled || (strcmp(hitem->op_name, "mask_manager") == 0));
676 if(!hitem->module)
677 {
678 gchar *label = NULL;
679 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
680 label = g_strdup(hitem->op_name);
681 else
682 label = g_strdup_printf("%s %s", hitem->op_name, hitem->multi_name);
683
684 gchar number[10];
685 g_snprintf(number, sizeof(number), "%2d", history_end);
686
687 gchar *tooltip_text = _history_tooltip_with_hint(hitem);
688
689 GtkTreeIter iter;
690 gtk_list_store_insert(d->history_store, &iter, 0);
691 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, history_end,
695 tooltip_text ? tooltip_text : "", -1);
696
697 dt_free(tooltip_text);
698 dt_free(label);
699 return;
700 }
701
702 const gboolean deprecated = (hitem->module->flags() & IOP_FLAGS_DEPRECATED);
703 const char *icon_name = _history_icon_name(enabled, hitem->module->default_enabled, hitem->module->hide_enable_button,
704 deprecated);
705
706 const gboolean enabled_by_default
707 = ((hitem->module->force_enable && hitem->module->force_enable(hitem->module, hitem->enabled))
708 || hitem->module->default_enabled);
709 const gchar *star = (hitem == _find_previous_history_step(hitem) && enabled_by_default) ? " *" : "";
710
711 gchar *clean_name = delete_underscore(hitem->module->name());
712 gchar *label = NULL;
713 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
714 label = g_strdup_printf("%s%s", clean_name, star);
715 else
716 label = g_strdup_printf("%s %s%s", clean_name, hitem->multi_name, star);
717 dt_free(clean_name);
718
719 gchar number[10];
720 g_snprintf(number, sizeof(number), "%2d", history_end);
721
722 gchar *tooltip_text = _history_tooltip_with_hint(hitem);
723
724 GtkTreeIter iter;
725 gtk_list_store_insert(d->history_store, &iter, 0);
726 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, history_end,
729 DT_HISTORY_VIEW_COL_TOOLTIP, tooltip_text ? tooltip_text : "", -1);
730
731 dt_free(tooltip_text);
732 dt_free(label);
733}
734
735static void _history_select_row_for_end(dt_lib_history_t *d, const int history_end)
736{
737 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->history_view));
738 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
739 GtkTreeIter iter;
740
741 for(gboolean valid = gtk_tree_model_get_iter_first(model, &iter); valid; valid = gtk_tree_model_iter_next(model, &iter))
742 {
743 int row_history_end = 0;
744 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &row_history_end, -1);
745 if(row_history_end == history_end)
746 {
747 gtk_tree_selection_select_iter(selection, &iter);
748 return;
749 }
750 }
751}
752
753static void _lib_history_change_callback(gpointer instance, gpointer user_data)
754{
755 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
757
758 d->selection_reset = TRUE;
759 gtk_list_store_clear(d->history_store);
760
761 // Read-only access: don't take a write lock here. This callback can run
762 // while the pixelpipe holds a read lock, and a write lock would deadlock
763 // the UI thread when history change signals are emitted.
765
767
768 int history_pos = 0;
769 for(const GList *history = darktable.develop->history; history; history = g_list_next(history), history_pos++)
770 _history_store_prepend_item(d, (const dt_dev_history_item_t *)history->data, history_pos + 1);
771
772 const int history_end = dt_dev_get_history_end_ext(darktable.develop);
774
775 _history_select_row_for_end(d, history_end);
776 d->selection_reset = FALSE;
777}
778
779static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data)
780{
781 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
783 if(IS_NULL_PTR(d) || d->selection_reset || dt_gui_widgets_suppressed()) return;
784
785 GtkTreeModel *model = NULL;
786 GtkTreeIter iter;
787 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
788
789 int history_end = 0;
790 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &history_end, -1);
791 if(history_end == dt_dev_get_history_end_ext(darktable.develop)) return;
792
793 _history_apply_history_end(history_end);
794}
795
796static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
797{
798 // Ctrl-click just shows the corresponding module in modulegroups
799 if(e->button == 1 && dt_modifier_is(e->state, GDK_CONTROL_MASK))
800 {
801 GtkTreePath *path = NULL;
802 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), (gint)e->x, (gint)e->y, &path, NULL, NULL, NULL))
803 {
804 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
806 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
807 GtkTreeIter iter;
808 if(gtk_tree_model_get_iter(model, &iter, path))
809 {
810 int history_end = 0;
811 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &history_end, -1);
812 _history_show_module_for_end(history_end);
813 }
814 gtk_tree_path_free(path);
815 return TRUE;
816 }
817 }
818
819 return FALSE;
820}
821
823{
824 // Same action as "Delete history" in the Edit menu -- single implementation,
825 // shared instead of duplicated (see gui/actions/edit.c).
826 delete_history_callback(NULL, NULL, 0, 0, NULL);
827}
828
829// clang-format off
830// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
831// vim: shiftwidth=2 expandtab tabstop=2 cindent
832// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
833// 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
char * name
darktable_t darktable
Definition darktable.c:183
#define dt_gui_freeze_begin()
Definition darktable.h:900
#define dt_gui_freeze_end()
Definition darktable.h:901
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
static gchar * delete_underscore(const char *s)
Definition darktable.h:1210
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#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:293
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:1729
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1723
void dt_iop_params_t
Definition dev_history.h:42
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:356
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1678
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1689
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:502
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:534
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:2945
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:266
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
gboolean delete_history_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
const char * tooltip
Definition image.h:251
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2221
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
Definition imageop.c:2381
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:198
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ 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
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
@ 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:376
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_develop_t * develop
Definition darktable.h:798
dt_iop_params_t * params
Definition dev_history.h:53
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:56
struct dt_iop_module_t *gboolean enabled
Definition dev_history.h:51
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_pthread_rwlock_t history_mutex
Definition develop.h:263
GList * history
Definition develop.h:275
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