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 Copyright (C) 2026 Guillaume Stutin.
39
40 darktable is free software: you can redistribute it and/or modify
41 it under the terms of the GNU General Public License as published by
42 the Free Software Foundation, either version 3 of the License, or
43 (at your option) any later version.
44
45 darktable is distributed in the hope that it will be useful,
46 but WITHOUT ANY WARRANTY; without even the implied warranty of
47 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 GNU General Public License for more details.
49
50 You should have received a copy of the GNU General Public License
51 along with darktable. If not, see <http://www.gnu.org/licenses/>.
52*/
53
54#include "develop/imageop_gui.h"
59#include "system/macros.h"
61#include "common/styles.h"
62#include "develop/develop.h"
63#include "develop/masks.h"
64#include "develop/blend_gui.h"
65
66#include "gui/application.h"
67#include "libs/lib.h"
68#include "libs/lib_api.h"
69#include <complex.h>
70#include "widgets/label.h"
71#include "widgets/scroll_wrap.h"
72#include "control/signal.h"
73
74DT_MODULE(1)
75
82
84{
85 // This stores the "history end" cursor, i.e.:
86 // - 0 means "original" (raw input, no history item applied),
87 // - N (1..len) means apply the first N history items (dev->history is 0..N-1).
96
97/* compress history stack */
98static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data);
99/* signal callback for history change */
100static void _lib_history_change_callback(gpointer instance, gpointer user_data);
101static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data);
102static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
103 GtkTooltip *tooltip, gpointer user_data);
104static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer,
105 GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
106
107const char *name(struct dt_lib_module_t *self)
108{
109 return _("History of changes");
110}
111
112const char **views(dt_lib_module_t *self)
113{
114 static const char *v[] = {"darkroom", NULL};
115 return v;
116}
117
119{
121}
122
124{
125 return 900;
126}
127
129{
130 /* initialize ui widgets */
131 dt_lib_history_t *d = (dt_lib_history_t *)g_malloc0(sizeof(dt_lib_history_t));
132 self->data = (void *)d;
133
134 d->selection_reset = FALSE;
135
136 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
137 gtk_widget_set_name(self->widget, "history-ui");
138
139 d->history_store = gtk_list_store_new(DT_HISTORY_VIEW_COL_COUNT,
140 G_TYPE_INT, // history_end
141 G_TYPE_STRING, // number
142 G_TYPE_STRING, // label
143 G_TYPE_STRING, // icon-name
144 G_TYPE_BOOLEAN, // enabled
145 G_TYPE_STRING); // tooltip text
146
147 d->history_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(d->history_store));
148 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->history_view), FALSE);
149 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(d->history_view), FALSE);
150
151 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->history_view));
152 gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
153 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_lib_history_view_selection_changed), self);
154 g_signal_connect(G_OBJECT(d->history_view), "button-press-event", G_CALLBACK(_lib_history_view_button_press_callback), self);
155
156 gtk_widget_set_has_tooltip(d->history_view, TRUE);
157 g_signal_connect(G_OBJECT(d->history_view), "query-tooltip", G_CALLBACK(_lib_history_view_query_tooltip), self);
158
159 GtkCellRenderer *renderer_num = gtk_cell_renderer_text_new();
160 g_object_set(G_OBJECT(renderer_num), "xalign", 1.0, "family", "monospace", NULL);
161 GtkTreeViewColumn *col_num = gtk_tree_view_column_new_with_attributes("n", renderer_num,
163 NULL);
164 gtk_tree_view_column_set_cell_data_func(col_num, renderer_num, _lib_history_view_cell_set_foreground, NULL, NULL);
165 gtk_tree_view_column_set_sizing(col_num, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
166 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_num);
167
168 GtkCellRenderer *renderer_label = gtk_cell_renderer_text_new();
169 g_object_set(G_OBJECT(renderer_label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
170 GtkTreeViewColumn *col_label = gtk_tree_view_column_new_with_attributes("label", renderer_label,
172 NULL);
173 gtk_tree_view_column_set_cell_data_func(col_label, renderer_label, _lib_history_view_cell_set_foreground, NULL, NULL);
174 gtk_tree_view_column_set_expand(col_label, TRUE);
175 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_label);
176
177 GtkCellRenderer *renderer_icon = gtk_cell_renderer_pixbuf_new();
178 GtkTreeViewColumn *col_icon = gtk_tree_view_column_new_with_attributes("status", renderer_icon,
180 NULL);
181 gtk_tree_view_column_set_sizing(col_icon, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
182 gtk_tree_view_append_column(GTK_TREE_VIEW(d->history_view), col_icon);
183
184 /* add history list and buttonbox to widget */
185 GtkWidget *history_sw = dt_ui_scroll_wrap(d->history_view, 1, "plugins/darkroom/history/windowheight",
187 gtk_box_pack_start(GTK_BOX(self->widget), history_sw, FALSE, FALSE, 0);
188
189 gtk_widget_show_all(self->widget);
190
191 /* connect to history change signal for updating the history view */
193 G_CALLBACK(_lib_history_change_callback), self);
194}
195
197{
198 if(IS_NULL_PTR(self->data)) return;
201 if(d && d->history_store) g_object_unref(d->history_store);
202 dt_free(self->data);
203}
204
205static const char *_history_icon_name(const gboolean enabled, const gboolean default_enabled, const gboolean always_on,
206 const gboolean deprecated)
207{
208 if(always_on) return "emblem-readonly";
209 if(deprecated) return "dialog-warning";
210 // "object-select-symbolic" is Adwaita's check mark. "emblem-ok" exists in no theme: GTK's
211 // "-symbolic" fallback turns it into "emblem-ok-symbolic", missing from the Adwaita bundled on Windows.
212 if(default_enabled) return enabled ? "object-select-symbolic" : "process-stop";
213 return enabled ? "object-select-symbolic" : "process-stop";
214}
215
216static gchar *_lib_history_change_text(dt_introspection_field_t *field, const char *d, gpointer params, gpointer oldpar)
217{
218 dt_iop_params_t *p = (dt_iop_params_t *)((uint8_t *)params + field->header.offset);
219 dt_iop_params_t *o = (dt_iop_params_t *)((uint8_t *)oldpar + field->header.offset);
220
221 switch(field->header.type)
222 {
225 {
226 gchar **change_parts = g_malloc0_n(field->Struct.entries + 1, sizeof(char*));
227 int num_parts = 0;
228
229 for(int i = 0; i < field->Struct.entries; i++)
230 {
231 dt_introspection_field_t *entry = field->Struct.fields[i];
232
233 gchar *description = _(*entry->header.description ?
234 entry->header.description :
235 entry->header.field_name);
236
237 if(d) description = g_strdup_printf("%s.%s", d, description);
238
239 if((change_parts[num_parts] = _lib_history_change_text(entry, description, params, oldpar)))
240 num_parts++;
241
242 if(d)
243 {
245 }
246 }
247
248 gchar *struct_text = num_parts ? g_strjoinv("\n", change_parts) : NULL;
249 g_strfreev(change_parts);
250
251 return struct_text;
252 }
253 break;
256 {
257 const gboolean is_valid =
258 g_utf8_validate((char *)o, -1, NULL)
259 && g_utf8_validate((char *)p, -1, NULL);
260
261 if(is_valid && strncmp((char*)o, (char*)p, field->Array.count))
262 return g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", d, (char*)o, (char*)p);
263 }
264 else
265 {
266 const int max_elements = 4;
267 gchar **change_parts = g_malloc0_n(max_elements + 1, sizeof(char*));
268 int num_parts = 0;
269
270 for(int i = 0, item_offset = 0; i < field->Array.count; i++, item_offset += field->Array.field->header.size)
271 {
272 char *description = g_strdup_printf("%s[%d]", d, i);
273 char *element_text = _lib_history_change_text(field->Array.field, description, (uint8_t *)params + item_offset, (uint8_t *)oldpar + item_offset);
275
276 if(element_text && ++num_parts <= max_elements)
277 change_parts[num_parts - 1] = element_text;
278 else
279 dt_free(element_text);
280 }
281
282 gchar *array_text = NULL;
283 if(num_parts > max_elements)
284 array_text = g_strdup_printf("%s\t%d changes", d, num_parts);
285 else if(num_parts > 0)
286 array_text = g_strjoinv("\n", change_parts);
287
288 g_strfreev(change_parts);
289
290 return array_text;
291 }
292 break;
294 if(*(float*)o != *(float*)p && (isfinite(*(float*)o) || isfinite(*(float*)p)))
295 return g_strdup_printf("%s\t%.4f\t\u2192\t%.4f", d, *(float*)o, *(float*)p);
296 break;
298 if(*(int*)o != *(int*)p)
299 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(int*)o, *(int*)p);
300 break;
302 if(*(unsigned int*)o != *(unsigned int*)p)
303 return g_strdup_printf("%s\t%u\t\u2192\t%u", d, *(unsigned int*)o, *(unsigned int*)p);
304 break;
306 if(*(unsigned short int*)o != *(unsigned short int*)p)
307 return g_strdup_printf("%s\t%hu\t\u2192\t%hu", d, *(unsigned short int*)o, *(unsigned short int*)p);
308 break;
310 if(*(uint8_t*)o != *(uint8_t*)p)
311 return g_strdup_printf("%s\t%d\t\u2192\t%d", d, *(uint8_t*)o, *(uint8_t*)p);
312 break;
314 if(*(char*)o != *(char*)p)
315 return g_strdup_printf("%s\t'%c'\t\u2192\t'%c'", d, *(char *)o, *(char *)p);
316 break;
318 if(*(float complex*)o != *(float complex*)p)
319 return g_strdup_printf("%s\t%.4f + %.4fi\t\u2192\t%.4f + %.4fi", d,
320 creal(*(float complex*)o), cimag(*(float complex*)o),
321 creal(*(float complex*)p), cimag(*(float complex*)p));
322 break;
324 if(*(int*)o != *(int*)p)
325 {
326 const char *old_str = N_("unknown"), *new_str = N_("unknown");
327 for(dt_introspection_type_enum_tuple_t *i = field->Enum.values; i && i->name; i++)
328 {
329 if(i->value == *(int*)o)
330 {
331 old_str = i->description;
332 if(!*old_str) old_str = i->name;
333 }
334 if(i->value == *(int*)p)
335 {
336 new_str = i->description;
337 if(!*new_str) new_str = i->name;
338 }
339 }
340
341 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
342 }
343 break;
345 if(*(gboolean*)o != *(gboolean*)p)
346 {
347 char *old_str = *(gboolean*)o ? "on" : "off";
348 char *new_str = *(gboolean*)p ? "on" : "off";
349 return g_strdup_printf("%s\t%s\t\u2192\t%s", d, _(old_str), _(new_str));
350 }
351 break;
353 {
354 // TODO: special case float2
355 }
356 break;
357 default:
358 fprintf(stderr, "unsupported introspection type \"%s\" encountered in _lib_history_change_text (field %s)\n", field->header.type_name, field->header.field_name);
359 break;
360 }
361
362 return NULL;
363}
364
366{
367 // Find the immediate previous history instance matching the current module
368 for(const GList *find_old = g_list_find(dt_dev_get_global()->history, hitem);
369 find_old;
370 find_old = g_list_previous(find_old))
371 {
372 dt_dev_history_item_t *hprev = (dt_dev_history_item_t *)(find_old->data);
373 if(hprev == hitem) continue; // first loop run, we start from current hitem
374 if(hprev->module == hitem->module) return hprev;
375 }
376
377 // This is the first history element for this module
378 return hitem;
379}
380
381#define add_blend_history_change(field, format, label) \
382 if((hitem->blend_params->field) != (old_blend->field)) \
383 { \
384 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
385 change_parts[num_parts++] \
386 = g_strdup_printf(full_format, label, (old_blend->field), (hitem->blend_params->field)); \
387 dt_free(full_format); \
388 full_format = NULL; \
389 }
390
391#define add_blend_history_change_enum(field, label, list) \
392 if((hitem->blend_params->field) != (old_blend->field)) \
393 { \
394 const char *old_str = NULL, *new_str = NULL; \
395 for(const dt_develop_name_value_t *i = list; *i->name; i++) \
396 { \
397 if(i->value == (old_blend->field)) old_str = i->name; \
398 if(i->value == (hitem->blend_params->field)) new_str = i->name; \
399 } \
400 \
401 change_parts[num_parts++] \
402 = (!old_str || !new_str) \
403 ? g_strdup_printf("%s\t%d\t\u2192\t%d", label, old_blend->field, hitem->blend_params->field) \
404 : g_strdup_printf("%s\t%s\t\u2192\t%s", label, _(g_dpgettext2(NULL, "blendmode", old_str)), \
405 _(g_dpgettext2(NULL, "blendmode", new_str))); \
406 }
407
408#define add_history_change(field, format, label) \
409 if((hitem->field) != (hprev->field)) \
410 { \
411 gchar *full_format = g_strconcat("%s\t", format, "\t\u2192\t", format, NULL); \
412 change_parts[num_parts++] = g_strdup_printf(full_format, label, (hprev->field), (hitem->field)); \
413 dt_free(full_format); \
414 full_format = NULL; \
415 }
416
417#define add_history_change_string(field, label) \
418 if(strcmp(hitem->field, hprev->field)) \
419 { \
420 change_parts[num_parts++] \
421 = g_strdup_printf("%s\t\"%s\"\t\u2192\t\"%s\"", label, (hprev->field), (hitem->field)); \
422 }
423
424#define add_history_change_boolean(field, label) \
425 if((hitem->field) != (hprev->field)) \
426 { \
427 change_parts[num_parts++] = g_strdup_printf("%s\t%s\t\u2192\t%s", label, (hprev->field) ? _("on") : _("off"), \
428 (hitem->field) ? _("on") : _("off")); \
429 }
430
431
433{
434 if(IS_NULL_PTR(hitem) || !hitem->module) return NULL;
435
437 dt_iop_params_t *old_params
438 = (hprev == hitem || IS_NULL_PTR(hprev)) ? hitem->module->default_params : hprev->module->params;
440 = (hprev == hitem || IS_NULL_PTR(hprev)) ? hitem->module->default_blendop_params : hprev->module->blend_params;
441
442 gchar **change_parts = g_malloc0_n(sizeof(dt_develop_blend_params_t) / (sizeof(float)) + 24, sizeof(char*));
443 int num_parts = 0;
444
445 const gboolean enabled_by_default = (hitem->module->force_enable && hitem->module->force_enable(hitem->module, hitem->enabled))
446 || hitem->module->default_enabled;
447 if(hprev == hitem)
448 {
449 // This is the first history entry for this module.
450 // That means the module was necessarily enabled in this step.
451 if(enabled_by_default)
452 change_parts[num_parts++] = g_strdup_printf(_("mandatory module created automatically"));
453 else
454 change_parts[num_parts++] = g_strdup_printf(_("module created per user request"));
455 }
456 else
457 {
458 // This is not the first history entry for this module.
459 // It can have been disabled.
460 add_history_change_boolean(enabled, _("enabled"));
461 }
462
463 add_history_change(iop_order, "%i", _("pipeline order"));
464 add_history_change_string(multi_name, _("instance name"));
465
466 if(hitem->module->have_introspection)
467 {
468 gchar *introspection_change = _lib_history_change_text(hitem->module->get_introspection()->field, NULL,
469 hitem->params, old_params);
470 if(!IS_NULL_PTR(introspection_change)) change_parts[num_parts++] = introspection_change;
471 }
472
473 if(hitem->module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
474 {
479 add_blend_history_change(blend_parameter, _("%.2f EV"), _("blend fulcrum"));
480 add_blend_history_change(opacity, "%.4f", _("mask opacity"));
482 add_blend_history_change(feathering_radius, "%.4f", _("feathering radius"));
483 add_blend_history_change_enum(feathering_guide, _("feathering guide"), dt_develop_feathering_guide_names);
484 add_blend_history_change(blur_radius, "%.4f", _("mask blur"));
485 add_blend_history_change(contrast, "%.4f", _("mask contrast"));
486 add_blend_history_change(brightness, "%.4f", _("brightness"));
487 add_blend_history_change(raster_mask_instance, "%d", _("raster mask instance"));
488 add_blend_history_change(raster_mask_id, "%d", _("raster mask id"));
489 add_blend_history_change_enum(raster_mask_invert, _("invert mask"), dt_develop_invert_mask_names);
490
491 add_blend_history_change(mask_combine & DEVELOP_COMBINE_MASKS_POS ? '-' : '+', "%c", _("drawn mask polarity"));
492
493 if(hitem->blend_params->mask_id != old_blend->mask_id)
494 change_parts[num_parts++] = old_blend->mask_id == 0
495 ? g_strdup_printf(_("a drawn mask was added"))
496 : hitem->blend_params->mask_id == 0
497 ? g_strdup_printf(_("the drawn mask was removed"))
498 : g_strdup_printf(_("the drawn mask was changed"));
499
500 dt_iop_gui_blend_data_t *bd = hitem->module->gui ? hitem->module->gui->blend_data : NULL;
501
502 for(int in_out = 1; in_out >= 0; in_out--)
503 {
504 gboolean first = TRUE;
505
506 for(const dt_iop_gui_blendif_channel_t *b = bd ? bd->channel : NULL;
507 b && !IS_NULL_PTR(b->label);
508 b++)
509 {
510 const dt_develop_blendif_channels_t ch = b->param_channels[in_out];
511
512 const int oactive = old_blend->blendif & (1 << ch);
513 const int nactive = hitem->blend_params->blendif & (1 << ch);
514
515 const int opolarity = old_blend->blendif & (1 << (ch + 16));
516 const int npolarity = hitem->blend_params->blendif & (1 << (ch + 16));
517
518 float *of = &old_blend->blendif_parameters[4 * ch];
519 float *nf = &hitem->blend_params->blendif_parameters[4 * ch];
520
521 const float oboost = exp2f(old_blend->blendif_boost_factors[ch]);
522 const float nboost = exp2f(hitem->blend_params->blendif_boost_factors[ch]);
523
524 if((oactive || nactive) && (memcmp(of, nf, sizeof(float) * 4) || opolarity != npolarity))
525 {
526 if(first)
527 {
528 change_parts[num_parts++] = g_strdup(in_out ? _("parametric output mask:") : _("parametric input mask:"));
529 first = FALSE;
530 }
531 char s[4][2][25];
532 for(int k = 0; k < 4; k++)
533 {
534 b->scale_print(of[k], oboost, s[k][0], sizeof(s[k][0]));
535 b->scale_print(nf[k], nboost, s[k][1], sizeof(s[k][1]));
536 }
537
538 char *opol = !oactive ? "" : (opolarity ? "(-)" : "(+)");
539 char *npol = !nactive ? "" : (npolarity ? "(-)" : "(+)");
540
541 change_parts[num_parts++] = g_strdup_printf("%s\t%s| %s- %s| %s%s\t\u2192\t%s| %s- %s| %s%s", _(b->name),
542 s[0][0], s[1][0], s[2][0], s[3][0], opol,
543 s[0][1], s[1][1], s[2][1], s[3][1], npol);
544 }
545 }
546 }
547 }
548
549 gchar *tooltip_text = g_strjoinv("\n", change_parts);
550 g_strfreev(change_parts);
551
552 return tooltip_text;
553}
554
555static gboolean _changes_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
556 GtkTooltip *tooltip)
557{
558 const gchar *tooltip_text = g_object_get_data(G_OBJECT(widget), "tooltip-text");
559 if(IS_NULL_PTR(tooltip_text) || !tooltip_text[0]) return FALSE;
560
561 gtk_tooltip_set_text(tooltip, tooltip_text);
562
563 return TRUE;
564}
565
566static gboolean _lib_history_view_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
567 GtkTooltip *tooltip, gpointer user_data)
568{
569 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
571 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
572 GtkTreeIter iter;
573 GtkTreePath *path = NULL;
574
575 if(keyboard_mode)
576 {
577 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
578 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return FALSE;
579 }
580 else
581 {
582 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL)) return FALSE;
583 if(!gtk_tree_model_get_iter(model, &iter, path))
584 {
585 gtk_tree_path_free(path);
586 return FALSE;
587 }
588 }
589
590 gchar *tooltip_text = NULL;
591 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_TOOLTIP, &tooltip_text, -1);
592 g_object_set_data_full(G_OBJECT(widget), "tooltip-text", tooltip_text, g_free);
593
594 const gboolean ret = _changes_tooltip_callback(widget, x, y, keyboard_mode, tooltip);
595 if(path) gtk_tree_path_free(path);
596 return ret;
597}
598
599static void _lib_history_view_cell_set_foreground(GtkTreeViewColumn *column, GtkCellRenderer *renderer,
600 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
601{
602 gboolean enabled = TRUE;
603 gtk_tree_model_get(model, iter, DT_HISTORY_VIEW_COL_ENABLED, &enabled, -1);
604 if(enabled)
605 g_object_set(G_OBJECT(renderer), "foreground-set", FALSE, NULL);
606 else
607 g_object_set(G_OBJECT(renderer), "foreground-set", TRUE, "foreground", "#888", NULL);
608}
609
610static void _history_apply_history_end(const int history_end)
611{
613
615
618 dt_dev_set_history_end_ext(dev, history_end);
621 // Apply the same post-history sync as dt_dev_pop_history_items():
622 // darkroom geometry and the virtual preview pipe must be refreshed only
623 // after releasing history_mutex to avoid lock inversions with GUI users.
626
628
630
631 // We have no way of knowing if moving the history end conceptually added
632 // or removed new pipeline nodes ("modules"), so we need to nuke the pipe all the time
633 // and rebuild from scratch
635
637}
638
639static void _history_show_module_for_end(const int history_end)
640{
641 if(history_end <= 0) return;
642
645 = (dt_dev_history_item_t *)g_list_nth_data(dt_dev_get_global()->history, history_end - 1);
646 dt_iop_module_t *module = hist ? hist->module : NULL;
648 if(module)
649 {
650 dt_iop_request_focus(module);
652 }
653}
654
656{
657 GtkTreeIter iter;
658 gtk_list_store_append(d->history_store, &iter);
659 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, 0, DT_HISTORY_VIEW_COL_NUMBER, " 0",
663}
664
666{
667 const gchar *hint = _("Shift+click: show module without changing history");
668
669 gchar *tooltip_text = _create_tooltip_text(hitem);
670 if(tooltip_text && tooltip_text[0])
671 {
672 gchar *tooltip_with_hint = g_strconcat(tooltip_text, "\n\n", hint, NULL);
673 dt_free(tooltip_text);
674 return tooltip_with_hint;
675 }
676
677 dt_free(tooltip_text);
678 return g_strdup(hint);
679}
680
681static void _history_store_prepend_item(dt_lib_history_t *d, const dt_dev_history_item_t *hitem, const int history_end)
682{
683 const gboolean enabled = (hitem->enabled || (strcmp(hitem->op_name, "mask_manager") == 0));
684 if(!hitem->module)
685 {
686 gchar *label = NULL;
687 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
688 label = g_strdup(hitem->op_name);
689 else
690 label = g_strdup_printf("%s %s", hitem->op_name, hitem->multi_name);
691
692 gchar number[10];
693 g_snprintf(number, sizeof(number), "%2d", history_end);
694
695 gchar *tooltip_text = _history_tooltip_with_hint(hitem);
696
697 GtkTreeIter iter;
698 gtk_list_store_insert(d->history_store, &iter, 0);
699 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, history_end,
703 tooltip_text ? tooltip_text : "", -1);
704
705 dt_free(tooltip_text);
706 dt_free(label);
707 return;
708 }
709
710 const gboolean deprecated = (hitem->module->flags() & IOP_FLAGS_DEPRECATED);
711 const char *icon_name = _history_icon_name(enabled, hitem->module->default_enabled, hitem->module->hide_enable_button,
712 deprecated);
713
714 const gboolean enabled_by_default
715 = ((hitem->module->force_enable && hitem->module->force_enable(hitem->module, hitem->enabled))
716 || hitem->module->default_enabled);
717 const gchar *star = (hitem == _find_previous_history_step(hitem) && enabled_by_default) ? " *" : "";
718
719 gchar *clean_name = delete_underscore(hitem->module->name());
720 gchar *label = NULL;
721 if(!hitem->multi_name[0] || strcmp(hitem->multi_name, "0") == 0)
722 label = g_strdup_printf("%s%s", clean_name, star);
723 else
724 label = g_strdup_printf("%s %s%s", clean_name, hitem->multi_name, star);
725 dt_free(clean_name);
726
727 gchar number[10];
728 g_snprintf(number, sizeof(number), "%2d", history_end);
729
730 gchar *tooltip_text = _history_tooltip_with_hint(hitem);
731
732 GtkTreeIter iter;
733 gtk_list_store_insert(d->history_store, &iter, 0);
734 gtk_list_store_set(d->history_store, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, history_end,
737 DT_HISTORY_VIEW_COL_TOOLTIP, tooltip_text ? tooltip_text : "", -1);
738
739 dt_free(tooltip_text);
740 dt_free(label);
741}
742
743static void _history_select_row_for_end(dt_lib_history_t *d, const int history_end)
744{
745 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->history_view));
746 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
747 GtkTreeIter iter;
748
749 for(gboolean valid = gtk_tree_model_get_iter_first(model, &iter); valid; valid = gtk_tree_model_iter_next(model, &iter))
750 {
751 int row_history_end = 0;
752 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &row_history_end, -1);
753 if(row_history_end == history_end)
754 {
755 gtk_tree_selection_select_iter(selection, &iter);
756 return;
757 }
758 }
759}
760
761static void _lib_history_change_callback(gpointer instance, gpointer user_data)
762{
763 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
765
766 d->selection_reset = TRUE;
767 gtk_list_store_clear(d->history_store);
768
769 // Read-only access: don't take a write lock here. This callback can run
770 // while the pixelpipe holds a read lock, and a write lock would deadlock
771 // the UI thread when history change signals are emitted.
773
775
776 int history_pos = 0;
777 for(const GList *history = dt_dev_get_global()->history; history; history = g_list_next(history), history_pos++)
778 _history_store_prepend_item(d, (const dt_dev_history_item_t *)history->data, history_pos + 1);
779
780 const int history_end = dt_dev_get_history_end_ext(dt_dev_get_global());
782
783 _history_select_row_for_end(d, history_end);
784 d->selection_reset = FALSE;
785}
786
787static void _lib_history_view_selection_changed(GtkTreeSelection *selection, gpointer user_data)
788{
789 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
791 if(IS_NULL_PTR(d) || d->selection_reset || dt_gui_widgets_suppressed()) return;
792
793 GtkTreeModel *model = NULL;
794 GtkTreeIter iter;
795 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
796
797 int history_end = 0;
798 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &history_end, -1);
799 if(history_end == dt_dev_get_history_end_ext(dt_dev_get_global())) return;
800
801 _history_apply_history_end(history_end);
802}
803
804static gboolean _lib_history_view_button_press_callback(GtkWidget *widget, GdkEventButton *e, gpointer user_data)
805{
806 // Ctrl-click just shows the corresponding module in modulegroups
807 if(e->button == 1 && dt_modifier_is(e->state, DT_PRIMARY_MASK))
808 {
809 GtkTreePath *path = NULL;
810 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), (gint)e->x, (gint)e->y, &path, NULL, NULL, NULL))
811 {
812 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
814 GtkTreeModel *model = GTK_TREE_MODEL(d->history_store);
815 GtkTreeIter iter;
816 if(gtk_tree_model_get_iter(model, &iter, path))
817 {
818 int history_end = 0;
819 gtk_tree_model_get(model, &iter, DT_HISTORY_VIEW_COL_HISTORY_END, &history_end, -1);
820 _history_show_module_for_end(history_end);
821 }
822 gtk_tree_path_free(path);
823 return TRUE;
824 }
825 }
826
827 return FALSE;
828}
829
831{
832 // Same action as "Delete history" in the Edit menu -- single implementation,
833 // shared instead of duplicated (see gui/actions/edit.c).
834 delete_history_callback(NULL, NULL, 0, 0, NULL);
835}
836
837// clang-format off
838// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
839// vim: shiftwidth=2 expandtab tabstop=2 cindent
840// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
841// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
const char ** description(struct dt_iop_module_t *self)
Definition ashift.c:168
#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.c:2386
const dt_develop_name_value_t dt_develop_mask_mode_names[]
Definition blend.c:2363
const dt_develop_name_value_t dt_develop_blend_colorspace_names[]
Definition blend.c:2355
const dt_develop_name_value_t dt_develop_blend_mode_flag_names[]
Definition blend.c:2350
const dt_develop_name_value_t dt_develop_combine_masks_names[]
Definition blend.c:2372
const dt_develop_name_value_t dt_develop_blend_mode_names[]
Definition blend.c:2308
const dt_develop_name_value_t dt_develop_feathering_guide_names[]
Definition blend.c:2379
@ DEVELOP_COMBINE_INV
Definition blend.h:123
@ DEVELOP_COMBINE_INCL
Definition blend.h:125
@ DEVELOP_COMBINE_MASKS_POS
Definition blend.h:126
dt_develop_blendif_channels_t
Definition blend.h:142
@ DEVELOP_BLEND_MODE_MASK
Definition blend.h:107
@ DEVELOP_BLEND_REVERSE
Definition blend.h:106
The blending panel's GUI state and API, cut out of develop/blend.h.
static const float x
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:531
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
void dt_dev_pop_history_items_ext(dt_develop_t *dev) REQUIRES_SHARED(dev -> history_mutex)
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_set_history_end_ext(struct dt_develop_t *dev, const uint32_t index)
Set the history end index (GUI perspective).
Definition develop.c:1972
void dt_dev_history_gui_update(struct dt_develop_t *dev)
Apply history-loaded params to module GUIs.
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1966
void dt_iop_params_t
Definition dev_history.h:43
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:1921
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1932
gboolean enabled
–doc was passed on the command line
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
gboolean delete_history_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
const char * tooltip
Definition image.h:319
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
@ 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
gchar * delete_underscore(const char *s)
Definition label.c:75
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
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 IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define o
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
const char * name
Definition pdf.h:90
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:43
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:208
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
dt_iop_params_t * params
Definition dev_history.h:54
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:57
struct dt_iop_module_t *gboolean enabled
Definition dev_history.h:52
float blendif_parameters[4 *DEVELOP_BLENDIF_SIZE]
Definition blend.h:231
float blendif_boost_factors[DEVELOP_BLENDIF_SIZE]
Definition blend.h:232
int32_t gui_attached
Definition develop.h:167
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
GList * history
Definition develop.h:259
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_gui.h:127
GtkWidget * history_view
gboolean selection_reset
GtkListStore * history_store
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
dt_introspection_type_header_t header
dt_introspection_type_array_t Array
dt_introspection_type_enum_t Enum
dt_introspection_type_struct_t Struct
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER