Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
imageop_gui.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2022 Diederik Ter Rahe.
4 Copyright (C) 2020-2022 Pascal Obry.
5 Copyright (C) 2022 Aldric Renaudin.
6 Copyright (C) 2022 Martin Bařinka.
7 Copyright (C) 2022 Miloš Komarčević.
8 Copyright (C) 2025 Aurélien PIERRE.
9
10 darktable is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 darktable is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with darktable. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#include "widgets/container.h"
25#include "common/telemetry.h"
26#include "common/sentry.h"
27#include "widgets/gdkkeys.h"
28#include "widgets/popup.h"
29#include "gui/application.h"
31#include "common/collection.h"
32#include "common/hash.h"
33#include "common/module.h"
36#include "control/control.h"
37#include "control/signal.h"
40#include "develop/blend.h"
41#include "develop/blend_gui.h"
43#include "develop/masks_gui.h"
45#include "gui/presets.h"
46#include "widgets/expander.h"
47#include "widgets/label.h"
50#include "history/notify.h"
51#include "develop/imageop_gui.h"
52#define DT_IOP_HEADER_MENU_OPEN "dt-module-header-menu-open"
53#define DT_IOP_HEADER_MENU_DISMISS_CLICK "dt-module-header-menu-dismiss-click"
54
55#define DT_IOP_HEADER_IGNORE_RELEASE "dt-module-header-ignore-release"
56static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded);
57static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data);
58static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
59 GdkModifierType modifier, gpointer data);
60static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
61 GdkModifierType modifier, gpointer data);
62#include "develop/dev_history.h"
63#include "develop/develop.h"
64#include "widgets/resetlabel.h"
65#include "common/conf.h"
66#include "develop/imageop.h"
67#include "widgets/bauhaus.h"
68#include "widgets/button.h"
69#include "system/macros.h"
70#include "system/mem_alloc.h"
71#include "common/utility.h"
72
73
74#ifdef GDK_WINDOWING_QUARTZ
75#endif
76
77#include <assert.h>
78#include <math.h>
79#include <stdlib.h>
80#include <string.h>
81#include <time.h>
83
84typedef struct dt_module_param_t
85{
86 dt_iop_module_t *module;
87 void *param;
89
91{
92 if(dt_gui_widgets_suppressed()) return;
93
94 dt_iop_module_t *self = data->module;
95 gboolean *field = (gboolean*)(data->param);
96
97 gboolean previous = *field;
99
100 if(*field != previous)
101 {
102 dt_iop_gui_changed(self, togglebutton, &previous);
103 }
104}
105
106// Add to the module internal list of widgets for incremental browsing
107// Note: Bauhaus widgets do it internally upon setting label
109{
110 if(!IS_NULL_PTR(self) && !IS_NULL_PTR(widget))
111 {
113 mod->widget_list = g_list_append(mod->widget_list, widget);
114 }
115}
116
118{
121
122 size_t param_index = 0;
123 gboolean skip_label = FALSE;
124
125 const size_t param_length = strlen(param) + 1;
128 if(sscanf(param, "%[^[][%" G_GSIZE_FORMAT "]", base_name, &param_index) == 2)
129 {
130 sprintf(param_name, "%s[0]", base_name);
132 }
133 else
134 {
136 }
138
139 const dt_introspection_field_t *f = self->so->get_f(param_name);
140
141 GtkWidget *slider = NULL;
142 size_t offset = 0;
143
144 if(!IS_NULL_PTR(f))
145 {
146 if(f->header.type == DT_INTROSPECTION_TYPE_FLOAT)
147 {
148 const float min = f->Float.Min;
149 const float max = f->Float.Max;
150 offset = f->header.offset + param_index * sizeof(float);
151 const float defval = *(float*)((uint8_t *)d + offset);
152
153 const float top = fminf(max-min, fmaxf(fabsf(min), fabsf(max)));
154 const int digits = MAX(2, -floorf(log10f(top/100)+.1));
155
157 }
158 else if(f->header.type == DT_INTROSPECTION_TYPE_INT)
159 {
160 const int min = f->Int.Min;
161 const int max = f->Int.Max;
162 offset = f->header.offset + param_index * sizeof(int);
163 const int defval = *(int*)((uint8_t *)d + offset);
164
166 }
167 else if(f->header.type == DT_INTROSPECTION_TYPE_USHORT)
168 {
169 const unsigned short min = f->UShort.Min;
170 const unsigned short max = f->UShort.Max;
171 offset = f->header.offset + param_index * sizeof(unsigned short);
172 const unsigned short defval = *(unsigned short*)((uint8_t *)d + offset);
173
175 }
176 else f = NULL;
177 }
178
179 if(!IS_NULL_PTR(f))
180 {
181 dt_bauhaus_widget_set_field(slider, (uint8_t *)p + offset, f->header.type);
182
183 if(!skip_label)
184 {
185 if (*f->header.description)
186 {
187 // we do not want to support a context as it break all translations see #5498
188 // dt_bauhaus_widget_set_label(slider, g_dpgettext2(NULL, "introspection description", f->header.description));
189 dt_bauhaus_widget_set_label(slider, f->header.description);
190 }
191 else
192 {
193 gchar *str = dt_util_str_replace(f->header.field_name, "_", " ");
194
196
197 dt_free(str);
198 }
199 }
200 }
201 else
202 {
203 gchar *str = g_strdup_printf("'%s' is not a float/int/unsigned short/slider parameter", param_name);
204
207
208 dt_free(str);
209 }
210
212 gtk_box_pack_start(GTK_BOX(self->gui->widget), slider, FALSE, FALSE, 0);
213
216
218
219 return slider;
220}
221
223{
225 dt_introspection_field_t *f = self->so->get_f(param);
226
228 gchar *str = NULL;
229
230 if (!IS_NULL_PTR(f) && (f->header.type == DT_INTROSPECTION_TYPE_ENUM ||
231 f->header.type == DT_INTROSPECTION_TYPE_INT ||
232 f->header.type == DT_INTROSPECTION_TYPE_UINT ||
233 f->header.type == DT_INTROSPECTION_TYPE_BOOL ))
234 {
235 dt_bauhaus_widget_set_field(combobox, (uint8_t *)p + f->header.offset, f->header.type);
236
237 if (*f->header.description)
238 {
239 // we do not want to support a context as it break all translations see #5498
240 // dt_bauhaus_widget_set_label(combobox, g_dpgettext2(NULL, "introspection description", f->header.description));
241 dt_bauhaus_widget_set_label(combobox, f->header.description);
242 }
243 else
244 {
245 str = dt_util_str_replace(f->header.field_name, "_", " ");
246
248
249 dt_free(str);
250 }
251
252 if(f->header.type == DT_INTROSPECTION_TYPE_BOOL)
253 {
254 dt_bauhaus_combobox_add(combobox, _("no"));
255 dt_bauhaus_combobox_add(combobox, _("yes"));
256 }
257 else if(f->header.type == DT_INTROSPECTION_TYPE_ENUM)
258 {
259 for(dt_introspection_type_enum_tuple_t *iter = f->Enum.values; iter && iter->name; iter++)
260 {
261 // we do not want to support a context as it break all translations see #5498
262 // dt_bauhaus_combobox_add_full(combobox, g_dpgettext2(NULL, "introspection description", iter->description), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, GINT_TO_POINTER(iter->value), NULL, TRUE);
263 if(*iter->description)
265 }
266 }
267 }
268 else
269 {
270 str = g_strdup_printf("'%s' is not an enum/int/bool/combobox parameter", param);
271
273
274 dt_free(str);
275 }
276
278 gtk_box_pack_start(GTK_BOX(self->gui->widget), combobox, FALSE, FALSE, 0);
279
282
283 return combobox;
284}
285
287{
289 dt_introspection_field_t *f = self->so->get_f(param);
290
291 GtkWidget *button = NULL;
292 gchar *str = NULL;
293
294 if(!IS_NULL_PTR(f) && f->header.type == DT_INTROSPECTION_TYPE_BOOL)
295 {
296 // we do not want to support a context as it break all translations see #5498
297 // button = gtk_check_button_new_with_label(g_dpgettext2(NULL, "introspection description", f->header.description));
298 str = *f->header.description
299 ? g_strdup(f->header.description)
300 : dt_util_str_replace(f->header.field_name, "_", " ");
301
302 GtkWidget *label = gtk_label_new(_(str));
304 button = gtk_check_button_new();
305 gtk_container_add(GTK_CONTAINER(button), label);
307 module_param->module = self;
308 module_param->param = (uint8_t *)p + f->header.offset;
310 }
311 else
312 {
313 str = g_strdup_printf("'%s' is not a bool/togglebutton parameter", param);
314
316 }
317
318 _add_widget_to_module_list(self, button);
319
320 dt_free(str);
322 gtk_box_pack_start(GTK_BOX(self->gui->widget), button, FALSE, FALSE, 0);
323
324 return button;
325}
326
327GtkWidget *dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label,
328 GCallback callback, gboolean local, guint accel_key, GdkModifierType mods,
330{
331 GtkWidget *w = dtgtk_togglebutton_new(paint, 0, NULL);
332 g_signal_connect(G_OBJECT(w), "button-press-event", callback, self);
333
334 if(IS_NULL_PTR(ctrl_label))
336 else
337 {
338 gchar *tooltip = g_strdup_printf(_("%s\nctrl+click to %s"), _(label), _(ctrl_label));
341 }
342
344
346 if(GTK_IS_BOX(box)) gtk_box_pack_end(GTK_BOX(box), w, FALSE, FALSE, 0);
347
348 return w;
349}
350
351GtkWidget *dt_iop_togglebutton_new_no_register(dt_iop_module_t *self, const char *section, const gchar *label,
352 const gchar *ctrl_label, GCallback callback, gboolean local,
353 guint accel_key, GdkModifierType mods,
355{
356 GtkWidget *w = dtgtk_togglebutton_new(paint, 0, NULL);
357 g_signal_connect(G_OBJECT(w), "button-press-event", callback, self);
358
359 if(IS_NULL_PTR(ctrl_label))
361 else
362 {
363 gchar *tooltip = g_strdup_printf(_("%s\nctrl+click to %s"), _(label), _(ctrl_label));
366 }
367
369 if(GTK_IS_BOX(box)) gtk_box_pack_end(GTK_BOX(box), w, FALSE, FALSE, 0);
370
371 return w;
372}
373
374GtkWidget *dt_iop_button_new(dt_iop_module_t *self, const gchar *label,
375 GCallback callback, gboolean local, guint accel_key, GdkModifierType mods,
377{
378 GtkWidget *button = NULL;
379
380 if(paint)
381 {
382 button = dtgtk_button_new(paint, paintflags, NULL);
383 gtk_widget_set_tooltip_text(button, _(label));
384 }
385 else
386 {
387 button = gtk_button_new_with_label(_(label));
389 }
390 _add_widget_to_module_list(self, button);
391
392 g_signal_connect(G_OBJECT(button), "clicked", callback, (gpointer)self);
393
394 if(GTK_IS_BOX(box)) gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
395
396 return button;
397}
398
400{
401 const gboolean mask_down = dt_conf_get_bool("masks_scroll_down_increases");
402 return up ? !mask_down : mask_down;
403}
404
405/* ------------------------------------------------------------------------------------
406 * Everything below was moved verbatim from imageop.c: the module GUI -- header and
407 * expander construction, enable button, focus, expansion, rename/duplicate flows,
408 * tooltips, mask indicator, the bauhaus bridge and gui_init/update/cleanup. The module
409 * lifecycle, params/history core and pipeline defaults stayed behind.
410 * ------------------------------------------------------------------------------------ */
411
412
413static void _iop_color_picker_data_ready_callback(gpointer instance, gpointer user_data)
414{
415 dt_iop_module_t *const module = user_data;
416 GtkWidget *picker = NULL;
417 dt_dev_pixelpipe_t *pipe = NULL;
418 const dt_dev_pixelpipe_iop_t *piece = NULL;
419 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->color_picker_apply)) return;
420 if(dt_iop_color_picker_get_ready_data(module, &picker, &pipe, &piece)) return;
421
422 dt_print(DT_DEBUG_DEV, "[picker] dispatch module=%s picker=%p pipe=%p hash=%" PRIu64 "\n",
423 module->op, (void *)picker, (void *)pipe, piece ? piece->global_hash : 0);
424
425 if(!module->gui->blend_data || !blend_color_picker_apply(module, picker, pipe, (dt_dev_pixelpipe_iop_t *)piece))
426 module->color_picker_apply(module, picker, pipe, (dt_dev_pixelpipe_iop_t *)piece);
427}
428
429static void _gui_delete_callback(GtkButton *button, dt_iop_module_t *module)
430{
431 dt_develop_t *dev = module->dev;
432
433 // we search another module with the same base
434 // we want the next module if any or the previous one
435 GList *modules = module->dev->iop;
436 dt_iop_module_t *next = NULL;
437 int find = 0;
438 while(modules)
439 {
441 if(mod == module)
442 {
443 find = 1;
444 if(next) break;
445 }
446 else if(mod->instance == module->instance)
447 {
448 next = mod;
449 if(find) break;
450 }
452 }
453 if(IS_NULL_PTR(next)) return; // what happened ???
454
455 if(module->module_will_remove && !module->module_will_remove(module)) return;
456
458
459 // we must pay attention if priority is 0
460 const gboolean is_zero = (module->multi_priority == 0);
461
462 // We are about to destroy this module GUI. Drop darkroom focus first so the
463 // center expose callback cannot call module->gui_post_expose() with a stale
464 // module->gui_data pointer during teardown-triggered redraws.
466
468
469 // we remove the plugin effectively
471 {
472 // we just hide the module to avoid lots of gtk critical warnings
473 gtk_widget_hide(module->gui->expander);
474
477 }
478
479 // we remove all references in the history stack and dev->iop
480 // this will inform that a module has been removed from history
481 // we do it here so we have the multi_priorities to reconstruct
482 // de deleted module if the user undo it
484
485 // if module was priority 0, then we set next to priority 0
486 if(is_zero)
487 {
488 // we want the first one in history
489 dt_iop_module_t *first = NULL;
490 GList *history = dev->history;
491 while(history)
492 {
493 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
494 if(!hist || !hist->module)
495 {
496 history = g_list_next(history);
497 continue;
498 }
499 if(hist->module->instance == module->instance && hist->module != module)
500 {
501 first = hist->module;
502 break;
503 }
504 history = g_list_next(history);
505 }
506 if(IS_NULL_PTR(first)) first = next;
507
508 // we set priority of first to 0
510
511 // we change this in the history stack too
512 for(history = dev->history; history; history = g_list_next(history))
513 {
514 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
515 // the loop above guards NULL entries in this same list; this one must too
516 if(hist && hist->module == first) hist->multi_priority = 0;
517 }
518 }
519
520 // Commit undo snapshot for the whole delete operation (module removal + multi_priority adjustments).
522
523 // Save history
525
526 // don't delete the module, a pipe may still need it
527 dev->alliop = g_list_append(dev->alliop, module);
528
529 /* redraw */
532
534}
535
537{
538 // callers walk the full dev->iop list: hidden modules (gamma, basebuffer, ...) never get
539 // gui_init() and keep module->gui == NULL -- they are simply not visible
540 GtkWidget *expander = module->gui ? module->gui->expander : NULL;
541 return (expander && gtk_widget_is_visible(expander) && !dt_iop_is_hidden(module));
542}
543
545{
546 dt_iop_module_t *prev = NULL;
548 {
550 if(mod == module)
551 break;
553 prev = mod;
554 }
555 return prev;
556}
557
559{
560 dt_iop_module_t *next = NULL;
562 {
564 if(mod == module)
565 break;
567 next = mod;
568 }
569 return next;
570}
571
573{
574 // make sure the duplicated module appears in the history
575 dt_dev_add_history_item(base->dev, base, FALSE, FALSE);
576
577 // first we create the new module
579 dt_iop_module_t *module = dt_dev_module_duplicate(base->dev, base);
581 if(IS_NULL_PTR(module)) return NULL;
582
583 // we set the gui part of it
584 /* initialize gui if iop have one defined */
586 {
587 // make sure gui_init and reload defaults is called safely
589
590 /* add module to right panel */
592 dt_gui_get_global()->scroll_to_header_once = module->gui->expander;
593
594 dt_iop_reload_defaults(module); // some modules like profiled denoise update the gui in reload_defaults
595
596 if(copy_params)
597 {
598 memcpy(module->params, base->params, module->params_size);
600 {
602 if(base->blend_params->mask_id > 0)
603 {
604 module->blend_params->mask_id = 0;
606 }
607 }
608 }
609
612 if(base != module && !IS_NULL_PTR(base->gui->expander)) _gui_set_single_expanded(base, FALSE);
614
615 if(module->dev->gui_attached)
617
618 // we save the new instance creation
620
621 // ...and run it NOW, before returning to the main loop.
622 //
623 // dt_dev_add_history_item() only QUEUES: dev_history_gui.c holds the request behind the
624 // "Pipe recompute timeout" preference (processing/timeout, 200 ms by default) so that a
625 // slider drag does not commit once per step. Creating an instance is not a drag -- there
626 // is nothing to coalesce, and the delay is load-bearing here in a way it is not anywhere
627 // else, because two things read the module's state before the timeout fires:
628 //
629 // - libs/modulegroups.c shows an extra instance only when it is in history
630 // (`in_history || multi_priority == 0`), and its refresh is a g_idle queued by the
631 // dt_iop_request_focus() above -- so it runs a few ms from now, long before 200 ms;
632 // - that module deliberately does NOT listen to history changes (expander reparenting
633 // stole keyboard focus from Bauhaus widgets), so nothing refreshes the panel again
634 // when the commit finally lands.
635 //
636 // The new instance was therefore judged "not in history yet", hidden, and stayed hidden
637 // until an unrelated event queued another refresh -- clicking the base module's header,
638 // which is exactly what users reported doing to make their new copy appear (#1265).
639 // Measured with -d dev: idle at +100 ms saw in_history=0 enabled=0 -> HIDE.
641 }
642
643 /* update ui to new parameters */
645
646 return module;
647}
648
650
659static gboolean _rename_module_idle(gpointer user_data)
660{
661 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
663
664 return G_SOURCE_REMOVE;
665}
666
667static void _gui_copy_callback(GtkButton *button, gpointer user_data)
668{
669 dt_iop_module_t *module = dt_iop_gui_duplicate(user_data, FALSE);
670 if(IS_NULL_PTR(module)) return;
671
673}
674
675static void _gui_duplicate_callback(GtkButton *button, gpointer user_data)
676{
677 dt_iop_module_t *module = dt_iop_gui_duplicate(user_data, TRUE);
678 if(IS_NULL_PTR(module)) return;
679
681}
682
684{
685 int ended = 0;
686 guint key = dt_keys_mainpad_alternatives(event->keyval);
687
688 if(event->type == GDK_FOCUS_CHANGE || key == GDK_KEY_Return)
689 {
691 {
692 // name is not empty, set new multi_name
693
694 const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry));
695
696 // restore saved 1st character of instance name (without it the same name wouls still produce unnecessary copy + add history item)
697 module->multi_name[0] = module->multi_name[sizeof(module->multi_name) - 1];
698 module->multi_name[sizeof(module->multi_name) - 1] = 0;
699
700 if(g_strcmp0(module->multi_name, name) != 0)
701 {
702 g_strlcpy(module->multi_name, name, sizeof(module->multi_name));
704 }
705 }
706 else
707 {
708 // clear out multi-name (set 1st char to 0)
709 module->multi_name[0] = 0;
711 }
712
713 ended = 1;
714 }
715 else if(key == GDK_KEY_Escape)
716 {
717 // restore saved 1st character of instance name
718 module->multi_name[0] = module->multi_name[sizeof(module->multi_name) - 1];
719 module->multi_name[sizeof(module->multi_name) - 1] = 0;
720
721 ended = 1;
722 }
723
724 if(ended)
725 {
727 gtk_widget_destroy(entry);
730 return TRUE;
731 }
732
733 return FALSE; /* event not handled */
734}
735
737{
738 int width = 0;
739 GtkBorder padding;
740
744 &padding);
745 gtk_widget_set_size_request(entry, width + padding.left + padding.right + 1, -1);
746
747 return TRUE;
748}
749
751{
752 // dt_iop_gui_duplicate() returns NULL when the instance could not be created
753 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->header)) return;
755 if(focused && GTK_IS_ENTRY(focused)) return;
756
757 GtkWidget *entry = gtk_entry_new();
759
760 gtk_widget_set_name(entry, "iop-panel-label");
762 gtk_entry_set_max_length(GTK_ENTRY(entry), sizeof(module->multi_name) - 1);
763 gtk_entry_set_text(GTK_ENTRY(entry), module->multi_name);
764
765 // remove instance name but save 1st character in case of escape
766 module->multi_name[sizeof(module->multi_name) - 1] = module->multi_name[0];
767 module->multi_name[0] = 0;
769
771 g_signal_connect(entry, "key-press-event", G_CALLBACK(_rename_module_key_press), module);
772 g_signal_connect(entry, "focus-out-event", G_CALLBACK(_rename_module_key_press), module);
773 g_signal_connect(entry, "style-updated", G_CALLBACK(_rename_module_resize), module);
775
776 gtk_box_pack_start(GTK_BOX(module->gui->header), entry, TRUE, TRUE, 0);
777 gtk_widget_show(entry);
779}
780
781static void _gui_rename_callback(GtkButton *button, dt_iop_module_t *module)
782{
784}
785
786static gboolean _iop_plugin_header_menu_dismiss_idle(gpointer user_data)
787{
788 GtkWidget *expander = GTK_WIDGET(user_data);
789 if(GTK_IS_WIDGET(expander))
791
792 g_object_unref(expander);
793 return G_SOURCE_REMOVE;
794}
795
796static void _iop_plugin_header_menu_deactivate(GtkWidget *menu, gpointer user_data)
797{
798 GtkWidget *expander = GTK_WIDGET(user_data);
799 if(!GTK_IS_WIDGET(expander)) return;
800
810}
811
812static gboolean _gui_multiinstance_callback(GtkButton *button, GdkEventButton *event, gpointer user_data)
813{
814 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
815
816 if(!IS_NULL_PTR(event) && event->button == 3)
817 {
818 if(!(module->flags() & IOP_FLAGS_ONE_INSTANCE)) _gui_copy_callback(button, user_data);
819 return TRUE;
820 }
821 else if(!IS_NULL_PTR(event) && event->button == 2)
822 {
823 return FALSE;
824 }
825
827 GtkWidget *item;
828
829 item = gtk_menu_item_new_with_label(_("new instance"));
830 // gtk_widget_set_tooltip_text(item, _("add a new instance of this module to the pipe"));
832 gtk_widget_set_sensitive(item, module->gui->multi_show_new);
833 gtk_menu_shell_append(menu, item);
834
835 item = gtk_menu_item_new_with_label(_("duplicate instance"));
836 // gtk_widget_set_tooltip_text(item, _("add a copy of this instance to the pipe"));
838 gtk_widget_set_sensitive(item, module->gui->multi_show_new);
839 gtk_menu_shell_append(menu, item);
840
841 item = gtk_menu_item_new_with_label(_("delete"));
842 // gtk_widget_set_tooltip_text(item, _("delete this instance"));
844 gtk_widget_set_sensitive(item, module->gui->multi_show_close);
845 gtk_menu_shell_append(menu, item);
846
848
849 item = gtk_menu_item_new_with_label(_("rename"));
851 gtk_menu_shell_append(menu, item);
852
853 if(!IS_NULL_PTR(module->gui->expander))
854 {
856 g_signal_connect_data(G_OBJECT(menu), "deactivate",
858 g_object_ref(module->gui->expander), (GClosureNotify)g_object_unref, 0);
859 }
860
862
863 // make sure the button is deactivated now that the menu is opened
864 if(button) dtgtk_button_set_active(DTGTK_BUTTON(button), FALSE);
865 return TRUE;
866}
867
868static void _gui_off_callback(GtkToggleButton *togglebutton, gpointer user_data)
869{
870 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
871
873 {
875 {
876 module->enabled = 1;
880 }
881 else
882 {
883 module->enabled = 0;
884
885 // if current module is set as the CAT instance, remove that setting
886 if(module->dev->proxy.chroma_adaptation == module)
887 module->dev->proxy.chroma_adaptation = NULL;
888
890 }
891 }
892
893 char tooltip[512];
895 snprintf(tooltip, sizeof(tooltip), module->enabled ? _("%s is switched on") : _("%s is switched off"),
900}
901
903{
904 // module->gui is NULL for hidden modules, and for visible ones between dev->iop
905 // construction and gui_init() during darkroom entry
906 return !dt_iop_is_hidden(module) && !IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->expander)
907 && gtk_widget_is_visible(module->gui->expander);
908}
909
911{
913 lab = gtk_bin_get_child(GTK_BIN(lab));
914 gtk_widget_set_name(lab, "iop-panel-label");
915
916 char *module_name = dt_history_item_get_label(module);
917 dt_capitalize_label(module_name);
919 dt_free(module_name);
920
921 // Module name hasn't changed or no instance name: abort now
922 if(!g_strcmp0(module_name, gtk_label_get_text(GTK_LABEL(lab))) || module->multi_name[0] == '\0')
923 return;
924
926 if(mod->instance_name)
927 {
928 char *instance_path = dt_accels_build_path(_("Darkroom/Modules/Instances"), mod->instance_name);
931 dt_free(mod->instance_name);
932 }
933
934 gchar *clean_name = delete_underscore(module->name());
936
937 mod->instance_name
938 = g_strdup_printf("%s/%s", clean_name, (module->multi_name[0] != '\0') ? module->multi_name : "0");
939
941 dt_gui_get_accels()->darkroom_accels, _("Darkroom/Modules/Instances"),
942 mod->instance_name);
943
945
947 g_object_set(G_OBJECT(lab), "xalign", 0.0, (gchar *)0);
948}
949
951{
952 if (IS_NULL_PTR(module->gui)) return; /* module has no GUI half at all */
953 if (IS_NULL_PTR(module->gui->header)) /* some modules such as overexposed don't actually have a header */
954 return;
955
956 // set panel name to display correct multi-instance
960}
961
963{
964 // set on/off icon
965 if(module->default_enabled && module->hide_enable_button)
966 {
968 }
969 else if(!module->default_enabled && module->hide_enable_button)
970 {
972 }
973 else
974 {
976 }
977}
978
980{
981 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui)) return;
982
983 if(module->gui->off)
984 {
986 if(module->hide_enable_button)
988 else
990
991 dt_gui_remove_class(GTK_WIDGET(module->gui->off), "dt_iop_enable_forced_on");
992 dt_gui_remove_class(GTK_WIDGET(module->gui->off), "dt_iop_enable_forced_off");
993 if(module->hide_enable_button)
994 {
996 module->enabled ? "dt_iop_enable_forced_on" : "dt_iop_enable_forced_off");
997 }
998
1000 }
1001}
1002
1004{
1005 // The module's interactive half exists from here until dt_iop_gui_cleanup_module();
1006 // its absence IS the headless flag (see dt_iop_module_gui_t in imageop_gui.h).
1007 if(IS_NULL_PTR(module->gui)) module->gui = (dt_iop_module_gui_t *)calloc(1, sizeof(dt_iop_module_gui_t));
1008
1009 // Suppress widget value-changed callbacks for the whole GUI build. Setting a slider's soft
1010 // range (etc.) in gui_init emits "value-changed", which would re-enter the module's
1011 // gui_changed handler before its sibling widgets exist and crash on dt_bauhaus_*(NULL)
1012 // (Sentry #129494618, #129578628). The scope guard releases the freeze automatically on every
1013 // exit path, and the central depth is GUI-thread-only and self-healing, so it cannot drift.
1015
1016 // Add the accelerators
1017 if(!dt_iop_is_hidden(module) && !(module->flags() & IOP_FLAGS_DEPRECATED))
1018 {
1019 gchar *clean_name = delete_underscore(module->name());
1021
1022 // slash is not allowed in module names because that makes accel pathes fail
1023 assert(g_strrstr(clean_name, "/") == NULL);
1024
1026 const gchar *const main_scope = _("Darkroom/Modules");
1028
1030
1031 // NOTE: we should enable the accel only if the module is disable-able, but this property is set at runtime
1032 // in reload_defaults(), which depends on the image metadata for each pipeline.
1033 // We have no way of knowing here at init time.
1034 // if(!module->hide_enable_button)
1035 dt_accels_new_darkroom_action(_iop_plugin_enable_accel, module, mod->accel_path, _("Enable"), 0, 0, _("Enables the module"));
1036
1038 }
1039
1040 // We absolutely need to init the module controls after the module object
1041 if(module->gui_init) module->gui_init(module);
1042 if(module->color_picker_apply)
1043 {
1046 }
1047 // the freeze ends here as the scope guard goes out of scope
1048}
1049
1056static void _iop_gui_widget_gone(gpointer user_data, GObject *where_the_object_was)
1057{
1058 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1059 if(IS_NULL_PTR(module)) return;
1060
1061 if(!IS_NULL_PTR(module->gui))
1062 {
1063 if(module->gui->header == (GtkWidget *)where_the_object_was) module->gui->header = NULL;
1064 if(module->gui->expander == (GtkWidget *)where_the_object_was) module->gui->expander = NULL;
1065 }
1066
1067 if(IS_NULL_PTR(dt_gui_get_global())) return;
1068
1072}
1073
1075{
1076 if(IS_NULL_PTR(module)) return;
1077 // backend-only modules (hidden, or loaded without gui_init -- see studio-capture.md)
1078 // have nothing to clean up here
1079 if(IS_NULL_PTR(module->gui)) return;
1081 if(!IS_NULL_PTR(module->dev) && module->dev->gui_module == module) module->dev->gui_module = NULL;
1082
1083 // remove multiple delayed gtk_widget_queue_draw triggers
1084 if(module->gui->widget)
1085 while(g_idle_remove_by_data(module->gui->widget));
1086
1087 // Detach accels. accel_path is only ever set by dt_iop_gui_init() (never for a module that was
1088 // only backend-loaded, e.g. studio_capture's dev->iop -- see studio-capture.md): a NULL path
1089 // here means there is nothing registered to remove, and dt_accels_remove_accel() would
1090 // otherwise g_strrstr() every entry in accels->acceleratables against a NULL needle, tripping
1091 // a GLib-CRITICAL for each one.
1092 if(!dt_iop_is_hidden(module) && !(module->flags() & IOP_FLAGS_DEPRECATED) && !IS_NULL_PTR(mod->accel_path))
1093 {
1095 dt_free(mod->accel_path);
1096 }
1097
1098 if(mod->instance_name)
1099 {
1100 char *instance_path = dt_accels_build_path(_("Darkroom/Modules/Instances"), mod->instance_name);
1103 }
1104
1105 dt_free(mod->instance_name);
1106
1107 // widget_list doesn't own the widget referenced, so don't deep_free
1109 g_list_free(m->widget_list);
1110 m->widget_list = NULL;
1111 g_list_free(m->widget_list_bh);
1112 m->widget_list_bh = NULL;
1113 dt_free(m->name);
1114 m->name = NULL;
1115 dt_free(m->view);
1116 m->view = NULL;
1117
1118 if(module->color_picker_apply)
1119 {
1121 }
1122 // History refresh can delete pipeline-only modules created for ordering/history
1123 // resolution. They have a module GUI cleanup callback but no module GUI data.
1124 if(module->gui_cleanup && !IS_NULL_PTR(dt_iop_gui_data(module)))
1125 module->gui_cleanup(module);
1127
1128 // size-allocate callbacks can still read scroll targets while GTK tears down widgets
1130 {
1131 if(dt_gui_get_global()->scroll_to[0] == module->gui->header || dt_gui_get_global()->scroll_to[0] == module->gui->expander)
1133 if(dt_gui_get_global()->scroll_to[1] == module->gui->header || dt_gui_get_global()->scroll_to[1] == module->gui->expander)
1135 if(dt_gui_get_global()->scroll_to_header_once == module->gui->expander)
1137 }
1138
1139 /* Release the transient widget tree explicitly. In normal GUI lifetime, these
1140 * widgets are parented and get destroyed by container teardown. During module
1141 * probe/init paths, they can stay unparented and would otherwise leak. */
1142 if(!IS_NULL_PTR(module->gui->expander) && GTK_IS_WIDGET(module->gui->expander))
1143 {
1144 GtkWidget *expander = module->gui->expander;
1145 g_object_ref_sink(expander);
1146 gtk_widget_destroy(expander);
1147 g_object_unref(expander);
1148 }
1149 else
1150 {
1151 if(!IS_NULL_PTR(module->gui->header) && GTK_IS_WIDGET(module->gui->header))
1152 {
1153 GtkWidget *header = module->gui->header;
1154 g_object_ref_sink(header);
1155 gtk_widget_destroy(header);
1156 g_object_unref(header);
1157 }
1158
1159 if(!IS_NULL_PTR(module->gui->widget) && GTK_IS_WIDGET(module->gui->widget))
1160 {
1161 GtkWidget *widget = module->gui->widget;
1162 g_object_ref_sink(widget);
1163 gtk_widget_destroy(widget);
1164 g_object_unref(widget);
1165 }
1166 }
1167
1168 module->gui->widget = NULL;
1169 module->gui->header = NULL;
1170 module->gui->expander = NULL;
1171 module->gui->off = NULL;
1172
1173 dt_free(module->gui);
1174 module->gui = NULL;
1175}
1176
1178{
1181 {
1183 {
1185
1186 if(module->params && module->gui_update)
1187 module->gui_update(module);
1188
1191 }
1193 }
1195}
1196
1197static void _gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
1198{
1199 // never use the callback if module is always disabled
1200 const gboolean disabled = !module->default_enabled && module->hide_enable_button;
1201 if(disabled) return;
1202
1203 //Ctrl is used to apply any auto-presets to the current module
1204 //If Ctrl was not pressed, or no auto-presets were applied, reset the module parameters
1205 // FIXME: can we stop with all the easter-eggs key modifiers doing undocumented stuff all along ?
1207 {
1208 /* Resetting a module's parameters does not change GUI focus, so the
1209 focus-loss cleanup in dt_iop_request_focus() never runs here: an active
1210 eye-dropper (this module's own, or its blending/masking parametric-mask
1211 picker -- both share dev->color_picker) and any live mask-shape editing
1212 state (edit mode, shape buttons, mask/channel display) must be turned
1213 off explicitly, or they keep sampling/drawing on the image against
1214 parameters that no longer exist. */
1217
1218 // if a drawn mask is set, remove it from the list
1219 if(module->blend_params->mask_id > 0)
1220 {
1221 dt_masks_form_t *grp = dt_masks_get_from_id(module->dev, module->blend_params->mask_id);
1222 // FIXME: ask the user if he wants to delete the mask, or just unlink them.
1225 }
1226 /* reset to default params */
1228 dt_iop_commit_blend_params(module, module->default_blendop_params);
1229
1230 /* reset ui to its defaults */
1232
1233 /* update ui to default params*/
1235
1237 }
1238}
1239
1240static void _presets_popup_callback(GtkButton *button, dt_iop_module_t *module)
1241{
1242 const gboolean disabled = !module->default_enabled && module->hide_enable_button;
1243 if(disabled) return;
1244
1246
1247 if(!IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->expander)
1248 && !IS_NULL_PTR(dt_gui_get_global()->presets_popup_menu))
1249 {
1251 g_signal_connect_data(G_OBJECT(dt_gui_get_global()->presets_popup_menu), "deactivate",
1253 g_object_ref(module->gui->expander), (GClosureNotify)g_object_unref, 0);
1254 }
1255
1257}
1258
1260{
1261 dt_develop_t *const dev = dt_dev_get_global();
1263
1265
1266 dev->gui_module = module;
1267 if(!IS_NULL_PTR(module) && !IS_NULL_PTR(module->gui))
1268 {
1269 const gboolean scroll_new_instance_to_header
1270 = (dt_gui_get_global()->scroll_to_header_once == module->gui->expander
1271 && !IS_NULL_PTR(module->gui->header) && GTK_IS_WIDGET(module->gui->header));
1272 dt_gui_get_global()->scroll_to[1] = scroll_new_instance_to_header ? module->gui->header : module->gui->expander;
1273 }
1274
1275 /* lets lose the focus of previous focus module*/
1277 {
1281 {
1284 }
1285
1286 if(out_focus_module->gui_focus)
1288
1289 /* A module that loses focus (switching to another module, or collapsing the
1290 focused module, which calls dt_iop_request_focus(NULL)) must not leave an
1291 active picker sampling/drawn on the image behind it: the picker's own
1292 "must have GUI focus" invariant only hides its on-screen overlay, it does
1293 not stop it or reset the toggle button, so a stale enabled picker can
1294 resurface unexpectedly (e.g. when the module's GUI is rebuilt). A real
1295 reset (keep = FALSE) is required here, not the "preserve across a soft
1296 refresh" keep = TRUE used by gui_update()/gui_changed() call sites. */
1299
1301
1302 /* reset mask view */
1304
1305 /* do stuff needed in the blending gui */
1307
1308 /* redraw the expander */
1310
1311 /* and finally collection restore hinter messages */
1313
1314 // we also remove the focus css class
1316 dt_gui_remove_class(iop_w, "dt_module_focus");
1317 }
1318
1319 /* set the focus on module */
1320 if(!IS_NULL_PTR(module))
1321 {
1322 // In case we tried giving focus to a module that is not in the visible tab
1324
1326
1327 if(module->gui_focus) module->gui_focus(module, TRUE);
1328
1329 /* redraw the expander */
1330 if(!IS_NULL_PTR(module->gui))
1331 {
1332 gtk_widget_queue_draw(module->gui->expander);
1333 gtk_widget_grab_focus(module->gui->expander);
1334 }
1335
1336 /* set the focus on the first child to enable arrow-key navigation and accessibility stuff */
1337 GList *widget_list = ((dt_gui_module_t *)module)->widget_list;
1338 if(widget_list)
1339 {
1340 GList *first_child = g_list_first(widget_list);
1341 if(first_child)
1342 {
1343 GtkWidget *widget = (GtkWidget *)first_child->data;
1344 if(widget) gtk_widget_grab_focus(widget);
1345 }
1346 }
1347
1348 // we also add the focus css class
1350 dt_gui_add_class(iop_w, "dt_module_focus");
1351 }
1352
1356}
1357
1358/*
1359 * NEW EXPANDER
1360 */
1361
1362static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded)
1363{
1364 // reached for every module in dev->iop via the collapse_others fan-out: gui-less
1365 // (hidden / not-yet-inited) modules have nothing to collapse
1366 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return;
1367
1368 /* update expander arrow state */
1369 dtgtk_expander_set_expanded(DTGTK_EXPANDER(module->gui->expander), expanded);
1370
1371 /* store expanded state of module.
1372 * we do that first, so update_expanded won't think it should be visible
1373 * and undo our changes right away. */
1374 module->gui->expanded = expanded;
1375
1376 /* show / hide plugin widget */
1377 if(expanded)
1378 {
1379 /* set this module to receive focus / draw events*/
1381
1382 /* focus the current module */
1383 for(int k = 0; k < DT_UI_CONTAINER_SIZE; k++)
1385
1386 /* redraw center, iop might have post expose */
1388 }
1389 else
1390 {
1391 if(module->dev->gui_module == module)
1392 {
1395 }
1396 }
1397
1398 if(expanded)
1399 dt_gui_add_class(module->gui->expander, "expanded");
1400 else
1401 dt_gui_remove_class(module->gui->expander, "expanded");
1402
1403 char var[1024];
1404 snprintf(var, sizeof(var), "plugins/darkroom/%s/expanded", module->op);
1405 dt_conf_set_bool(var, expanded);
1406}
1407
1410{
1411 for(GList *iop = g_list_first(dt_dev_get_global()->iop); iop; iop = g_list_next(iop))
1412 {
1413 dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
1414
1415 // Handle invisible modules
1416 if(IS_NULL_PTR(m) || !m->gui || !m->gui->expander) continue;
1417
1418 if(dim && m != module)
1420 else
1422 }
1423}
1424
1426{
1427 if(IS_NULL_PTR(module) || !module->gui || !module->gui->expander) return;
1428 if(collapse_others)
1429 {
1430 for(GList *iop = g_list_first(module->dev->iop); iop; iop = g_list_next(iop))
1431 {
1432 dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
1434 }
1435 }
1436
1438 _iop_dim_all_but((expanded) ? module : NULL, expanded);
1439 gtk_widget_queue_draw(module->gui->widget);
1440}
1441
1443{
1444 if(IS_NULL_PTR(module->gui->expander)) return;
1445
1446 const gboolean expanded = module->gui->expanded;
1447
1448 dtgtk_expander_set_expanded(DTGTK_EXPANDER(module->gui->expander), expanded);
1449}
1450
1451static gboolean _iop_plugin_body_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1452{
1453 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1454
1455 /* Reset the scrolling focus. If the click happened on any bauhaus element,
1456 * its internal button_press method will set it for itself */
1458
1459 gboolean handled = FALSE;
1460
1461 if(e->button == 1)
1462 {
1464 handled = TRUE;
1465 }
1466 else if(e->button == 3)
1467 {
1469 handled = TRUE;
1470 }
1471 return handled;
1472}
1473
1474static gboolean _iop_plugin_header_activate(GtkWidget* self, gboolean group_cycling, gpointer user_data)
1475{
1476 dt_gui_module_t *module = (dt_gui_module_t *)user_data;
1477 if(IS_NULL_PTR(module) || !module->focus) return FALSE;
1478 return module->focus(module, TRUE);
1479}
1480
1481static gboolean _iop_plugin_header_child_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1482{
1483 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1484 if(module && module->gui->expander)
1485 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click", GINT_TO_POINTER(TRUE));
1486
1487 return FALSE;
1488}
1489
1491 GdkModifierType modifier, gpointer data)
1492{
1493 dt_gui_module_t *module = (dt_gui_module_t *)data;
1494 dt_iop_module_t *iop = (dt_iop_module_t *)data;
1495 if(IS_NULL_PTR(module) || !module->focus) return FALSE;
1496
1497 // Accel search explicitly targets a module, so allow modulegroups to leave
1498 // the Pipeline tab once for this focus request.
1499 if(iop->gui->expander)
1500 g_object_set_data(G_OBJECT(iop->gui->expander), "dt-modulegroups-switch-from-active-once",
1502
1503 return module->focus(module, FALSE);
1504}
1505
1507 GdkModifierType modifier, gpointer data)
1508{
1509 dt_iop_module_t *module = (dt_iop_module_t *)data;
1510 if(IS_NULL_PTR(module)) return FALSE;
1511
1512 // Direct actions from accel search should prioritize Pipeline when they focus
1513 // the edited module right after applying the change.
1514 if(!IS_NULL_PTR(module->gui->expander))
1515 g_object_set_data(G_OBJECT(module->gui->expander), "dt-modulegroups-prefer-active-once",
1517
1518 // Kind of ugly to go through history to change module GUI state
1519 // FIXME: we should have a GUI callback that enables module and dispatches history instead,
1520 // history should not care about module GUI. This is a wrong, reversed inheritance.
1521 if(!module->hide_enable_button)
1522 {
1523 module->enabled = TRUE;
1525 }
1526
1529 return TRUE;
1530}
1531
1532static gboolean _iop_plugin_header_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1533{
1534 if(e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) return TRUE;
1535
1536 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1537 if(IS_NULL_PTR(module)) return FALSE;
1538
1539 if(!IS_NULL_PTR(module->gui->expander)
1542 {
1544 return TRUE;
1545 }
1546
1547 /* Reset the scrolling focus. If the click happened on any bauhaus element,
1548 * its internal button_press method will set it for itself */
1550
1551 if(e->button == 1)
1552 {
1553 if(module->gui->expander) g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-dragged", NULL);
1554
1555 if(!dt_modifier_is(e->state, DT_PRIMARY_MASK))
1556 {
1557 return FALSE;
1558 }
1559 else
1560 {
1561 if(module->gui->expander)
1564 return TRUE;
1565 }
1566 }
1567 else if(e->button == 3)
1568 {
1570
1571 return TRUE;
1572 }
1573 return FALSE;
1574}
1575
1576static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1577{
1578 if(e->button != 1 || e->type != GDK_BUTTON_RELEASE) return FALSE;
1579
1580 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1581 if(IS_NULL_PTR(module) || !module->gui->expander) return FALSE;
1582
1584 {
1586 return TRUE;
1587 }
1588
1591 {
1592 return TRUE;
1593 }
1594
1595 if(g_object_get_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click"))
1596 {
1597 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click", NULL);
1598 return FALSE;
1599 }
1600
1601 if(g_object_get_data(G_OBJECT(module->gui->expander), "dt-module-dragged"))
1602 {
1603 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-dragged", NULL);
1604 return TRUE;
1605 }
1606
1607 // make gtk scroll to the module once it updated its allocation size
1608 const gboolean collapse_others = dt_modifier_is(e->state, GDK_SHIFT_MASK) ? FALSE : TRUE;
1611
1612 return TRUE;
1613}
1614
1616{
1617 if(dt_gui_widgets_suppressed()) return;
1618
1620 const dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->gui->blend_data;
1621
1622 module->request_mask_display
1623 &= ~(DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
1624 | DT_DEV_PIXELPIPE_DISPLAY_ANY | DT_DEV_PIXELPIPE_DISPLAY_STICKY);
1625
1626 if(is_active)
1627 module->request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_MASK;
1628
1630
1631 // set the module show mask button too
1632 if(bd->showmask)
1634
1636 if(GTK_IS_TOGGLE_BUTTON(bd->filter[0].channel_display))
1637 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->filter[0].channel_display), FALSE);
1638 if(GTK_IS_TOGGLE_BUTTON(bd->filter[1].channel_display))
1639 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->filter[1].channel_display), FALSE);
1641
1644}
1645
1647 gboolean *drawn_used, gboolean *parametric_used)
1648{
1653
1654 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->blend_params)) return;
1655
1656 const dt_iop_gui_blend_data_t *bd = (const dt_iop_gui_blend_data_t *)module->gui->blend_data;
1657 gboolean top = FALSE;
1658 gboolean raster = FALSE;
1659 gboolean drawn = FALSE;
1660 gboolean parametric = FALSE;
1662
1663 // look if the user disabled masks modes
1664
1665 // raster mask must be enabled and a raster mask must be selected in the combo
1666 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->raster_enable)
1668 raster = FALSE;
1669
1670 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->masks_enable)
1672 drawn = FALSE;
1673
1674 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->blendif_enable)
1676 parametric = FALSE;
1677
1682}
1683
1684static gboolean _mask_indicator_tooltip(GtkWidget *treeview, gint x, gint y, gboolean kb_mode,
1686{
1687 (void)treeview;
1688 (void)x;
1689 (void)y;
1690 (void)kb_mode;
1691
1692 gboolean res = FALSE;
1693 if(module->gui->mask_indicator)
1694 {
1695 gchar *type = _("unknown mask");
1696 gchar *text;
1699 if(!top_enabled) return FALSE;
1700
1702 type=_("drawn + parametric mask");
1703 else if(drawn_used)
1704 type=_("drawn mask");
1705 else if(parametric_used)
1706 type=_("parametric mask");
1707 else if(raster_used)
1708 type=_("raster mask");
1709 else
1710 return FALSE;
1711 gchar *part1 = g_strdup_printf(_("this module has a '%s'"), type);
1712 gchar *part2 = NULL;
1713 if(raster_used && module->raster_mask.sink.source)
1714 {
1715 gchar *source = dt_history_item_get_name(module->raster_mask.sink.source);
1716 part2 = g_strdup_printf(_("taken from module %s"), source);
1717 dt_free(source);
1718 }
1719
1720 if(part2)
1721 {
1722 gchar *details = g_strdup_printf("%s\n%s", part2, _("click to display (module must be activated first)"));
1723 dt_free(part2);
1724 part2 = details;
1725 }
1726 else
1727 {
1728 part2 = g_strdup(_("click to display (module must be activated first)"));
1729 }
1730
1731 if(part2)
1732 text = g_strconcat(part1, "\n", part2, NULL);
1733 else
1734 text = g_strdup(part1);
1735
1737 res = TRUE;
1738 dt_free(part1);
1739 dt_free(part2);
1740 dt_free(text);
1741 }
1742 return res;
1743}
1744
1746{
1747 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->mask_indicator)) return;
1748
1749 const gboolean support_blending = (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) == IOP_FLAGS_SUPPORTS_BLENDING;
1750
1751 if(!support_blending || !module->blend_params)
1752 {
1753 gtk_widget_set_visible(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1754 gtk_widget_set_has_tooltip(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1755 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1756 return;
1757 }
1758
1761
1762 const gboolean use_masks = top_enabled && (raster_used || drawn_used || parametric_used);
1763
1764 gtk_widget_set_visible(GTK_WIDGET(module->gui->mask_indicator), use_masks);
1765 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->mask_indicator), module->enabled);
1766 gtk_widget_set_has_tooltip(GTK_WIDGET(module->gui->mask_indicator), use_masks);
1767}
1768
1769gboolean _iop_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
1770 GtkTooltip *tooltip, gpointer user_data)
1771{
1772 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1773
1774 const char **des = module->description(module);
1775
1776 if(IS_NULL_PTR(des)) return FALSE;
1777
1779 GtkWidget *grid = gtk_grid_new();
1783
1784 GtkWidget *label = gtk_label_new(des[0] ? des[0] : "");
1788 // if there is no more description, do not add a separator
1789 if(des[1]) dt_gui_add_class(label, "dt_section_label");
1790 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1791
1795
1796 const char *icon_purpose = "\342\237\263";
1797 const char *icon_input = "\342\207\245";
1798 const char *icon_process = "\342\237\264";
1799 const char *icon_output = "\342\206\246";
1800
1802 const char *ilabs[4] = {_("Purpose"), _("Input"), _("Process"), _("Output")};
1803
1804 for(int k=1; k<5; k++)
1805 {
1806 if(des[k])
1807 {
1808 label = gtk_label_new(icons[k-1]);
1810 gtk_grid_attach(GTK_GRID(grid), label, 0, k, 1, 1);
1812
1813 label = gtk_label_new(ilabs[k-1]);
1815 gtk_grid_attach(GTK_GRID(grid), label, 1, k, 1, 1);
1817
1818 label = gtk_label_new(":");
1820 gtk_grid_attach(GTK_GRID(grid), label, 2, k, 1, 1);
1822
1823 label = gtk_label_new(des[k]);
1825 gtk_grid_attach(GTK_GRID(grid), label, 3, k, 1, 1);
1827 }
1828 }
1829
1830 gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 0);
1831 gtk_widget_show_all(vbox);
1833
1834 return TRUE;
1835}
1836
1838{
1840 gtk_widget_set_name(GTK_WIDGET(header), "module-header");
1841
1843 GtkWidget *expander = dtgtk_expander_new(header, iopw);
1844 dt_gui_add_class(expander, "dt_module_frame");
1845 dt_gui_add_class(expander, "dt_iop_module");
1846
1850
1851 dt_gui_add_class(pluginui_frame, "dt_plugin_ui");
1852
1853 module->gui->header = header;
1854
1855 /* setup the header box */
1856 g_signal_connect(G_OBJECT(header_evb), "button-press-event", G_CALLBACK(_iop_plugin_header_button_press), module);
1857 g_signal_connect(G_OBJECT(header_evb), "button-release-event", G_CALLBACK(_iop_plugin_header_button_release), module);
1858 g_signal_connect(G_OBJECT(header_evb), "mnemonic-activate", G_CALLBACK(_iop_plugin_header_activate), module);
1860
1861 /* connect mouse button callbacks for focus and presets */
1862 g_signal_connect(G_OBJECT(body_evb), "button-press-event", G_CALLBACK(_iop_plugin_body_button_press), module);
1864
1865 /*
1866 * initialize the header widgets
1867 */
1869
1870 /* init empty place for icon, this is then set in CSS if needed */
1871 char w_name[256] = { 0 };
1872 snprintf(w_name, sizeof(w_name), "iop-panel-icon-%s", module->op);
1875
1876 /* add module label */
1880 gtk_container_add(GTK_CONTAINER(lab), label);
1881 gtk_label_set_mnemonic_widget(GTK_LABEL(label), header_evb);
1882
1883 if((module->flags() & IOP_FLAGS_DEPRECATED) && module->deprecated_msg())
1884 gtk_widget_set_tooltip_text(lab, module->deprecated_msg());
1885 else
1886 {
1887 gtk_widget_set_name(lab, "iop_description");
1889 }
1890
1891 /* add mask preview button */
1893
1896 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "button-press-event",
1898 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "query-tooltip",
1900 module->gui->mask_indicator = hw[IOP_MODULE_MASK];
1901
1902 /* add multi instances menu button */
1904 module->gui->multimenu_button = GTK_WIDGET(hw[IOP_MODULE_INSTANCE]);
1906 _("multiple instance actions\nright-click creates new instance"));
1907 g_signal_connect(G_OBJECT(hw[IOP_MODULE_INSTANCE]), "button-press-event",
1910 module);
1911
1913
1914 /* add reset button */
1916 module->gui->reset_button = GTK_WIDGET(hw[IOP_MODULE_RESET]);
1917 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_RESET]), _("reset parameters\nctrl+click to reapply any automatic presets"));
1918 g_signal_connect(G_OBJECT(hw[IOP_MODULE_RESET]), "button-press-event",
1921
1922 /* add preset button if module has implementation */
1924 module->gui->presets_button = GTK_WIDGET(hw[IOP_MODULE_PRESETS]);
1925 if(!(module->flags() & IOP_FLAGS_ONE_INSTANCE))
1926 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_PRESETS]), _("presets\nright-click to apply on new instance"));
1927 g_signal_connect(G_OBJECT(hw[IOP_MODULE_PRESETS]), "button-press-event",
1930
1931 /* add enabled button */
1933
1934 dt_gui_add_class(switch_button, "dt_iop_enable_button");
1937 g_signal_connect(G_OBJECT(switch_button), "button-press-event",
1940
1941 module->gui->off = switch_button;
1943
1944 /* Wrap the switch in a plain box so the CSS spacing trick that visually tucks it
1945 against the header edge (negative margin, see ansel.css) lives on the wrapper,
1946 not on the button itself. dtgtk_togglebutton's custom draw() reinterprets its own
1947 CSS margin a second time (as a paint-only offset) to size its icon -- if that same
1948 margin also carries a position nudge, the icon gets painted outside the button's
1949 real GTK allocation, which is what drives hover/click hit-testing. Keeping the
1950 button's own margin at 0 keeps its drawing and its hit-region intrinsically in
1951 sync; the wrapper absorbs the nudge at the layout level instead. */
1953 dt_gui_add_class(hw[IOP_MODULE_SWITCH], "dt_iop_enable_button_box");
1955
1956 /* reorder header, for now, iop are always in the right panel */
1957 for(int i = 0; i <= IOP_MODULE_LABEL; i++)
1958 {
1959 if(hw[i])
1960 gtk_box_pack_start(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
1961 }
1962 for(int i = IOP_MODULE_LAST - 1; i > IOP_MODULE_LABEL; i--)
1963 {
1964 if(hw[i])
1965 gtk_box_pack_end(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
1966 }
1967
1968 dt_gui_add_help_link(header, dt_get_help_url("module_header"));
1969 // for the module label, point to module specific help page
1971
1974
1975 // show deprecated message if any
1976 if(module->deprecated_msg())
1977 {
1978 GtkWidget *lb = gtk_label_new(module->deprecated_msg());
1981 dt_gui_add_class(lb, "dt_warning");
1983 gtk_widget_show(lb);
1984 }
1985
1986 /* initialize blending state if supported; the detached widget is hosted by the masks lib */
1987 gtk_box_pack_start(GTK_BOX(iopw), module->gui->widget, TRUE, TRUE, 0);
1989 dt_gui_add_class(module->gui->widget, "dt_plugin_ui_main");
1992
1993 module->gui->expander = expander;
1996
1997 /* update header */
1999
2000 gtk_widget_set_hexpand(module->gui->widget, FALSE);
2001 gtk_widget_set_vexpand(module->gui->widget, FALSE);
2002
2004}
2005
2007{
2008 // NULL for a module with no GUI half, the way the flat module->expander was NULL before
2009 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return NULL;
2010 return dtgtk_expander_get_body(DTGTK_EXPANDER(module->gui->expander));
2011}
2012
2014{
2015 // return gtkframe (pluginui_frame)
2016 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return NULL;
2017 return dtgtk_expander_get_frame(DTGTK_EXPANDER(module->gui->expander));
2018}
2019
2021{
2022 if(IS_NULL_PTR(action)) return;
2023 dt_iop_module_t *module = (dt_iop_module_t *)action;
2024
2025 if(module->gui_changed) module->gui_changed(module, widget, data);
2026
2028
2030
2031 if(!IS_NULL_PTR(widget) && g_object_get_data(G_OBJECT(widget), "dt-blendop-header-update"))
2033 else
2035}
2036
2037
2039{
2041
2042 for(GList *w = g_list_first(m->widget_list_bh); w; w = g_list_next(w))
2043 {
2044 GtkWidget *widget = (GtkWidget *)w->data;
2045 struct dt_bauhaus_widget_t *bhw = DT_BAUHAUS_WIDGET(widget);
2046 if(IS_NULL_PTR(bhw)) continue;
2047
2048 switch(bhw->type)
2049 {
2050 case DT_BAUHAUS_SLIDER:
2051 switch(bhw->field_type)
2052 {
2054 dt_bauhaus_slider_set(widget, *(float *)bhw->field);
2055 break;
2057 dt_bauhaus_slider_set(widget, *(int *)bhw->field);
2058 break;
2060 dt_bauhaus_slider_set(widget, *(unsigned short *)bhw->field);
2061 break;
2062 default:
2063 fprintf(stderr, "[dt_bauhaus_update_module] unsupported slider data type\n");
2064 }
2065 break;
2067 switch(bhw->field_type)
2068 {
2070 dt_bauhaus_combobox_set_from_value(widget, *(int *)bhw->field);
2071 break;
2073 dt_bauhaus_combobox_set(widget, *(int *)bhw->field);
2074 break;
2076 dt_bauhaus_combobox_set(widget, *(unsigned int *)bhw->field);
2077 break;
2079 dt_bauhaus_combobox_set(widget, *(gboolean *)bhw->field);
2080 break;
2081 default:
2082 fprintf(stderr, "[dt_bauhaus_update_module] unsupported combo data type\n");
2083 }
2084 break;
2085 default:
2086 fprintf(stderr, "[dt_bauhaus_update_module] invalid bauhaus widget type encountered\n");
2087 }
2088 }
2089}
2090
2092{
2094 dt_iop_module_t *module = (dt_iop_module_t *)w->module;
2095 if(IS_NULL_PTR(w->field) || IS_NULL_PTR(module)) return;
2096
2097 switch(w->type)
2098 {
2099 case DT_BAUHAUS_SLIDER:
2100 {
2101 float val = dt_bauhaus_slider_get(widget);
2102 switch(w->field_type)
2103 {
2105 {
2106 float *f = w->field, prevf = *f; *f = val;
2107 if(*f != prevf) dt_iop_gui_changed(module, widget, &prevf);
2108 break;
2109 }
2111 {
2112 int *i = w->field, previ = *i; *i = val;
2113 if(*i != previ) dt_iop_gui_changed(module, widget, &previ);
2114 break;
2115 }
2117 {
2118 unsigned short *s = w->field, prevs = *s; *s = val;
2119 if(*s != prevs) dt_iop_gui_changed(module, widget, &prevs);
2120 break;
2121 }
2122 default:
2123 fprintf(stderr, "[_bauhaus_slider_value_change] unsupported slider data type\n");
2124 }
2125 break;
2126 }
2128 {
2130 switch(w->field_type)
2131 {
2133 {
2134 if(d->active >= 0)
2135 {
2136 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
2137 int *e = w->field, preve = *e; *e = GPOINTER_TO_INT(entry->data);
2138 if(*e != preve) dt_iop_gui_changed(module, widget, &preve);
2139 }
2140 break;
2141 }
2143 {
2144 int *i = w->field, previ = *i; *i = d->active;
2145 if(*i != previ) dt_iop_gui_changed(module, widget, &previ);
2146 break;
2147 }
2149 {
2150 unsigned int *u = w->field, prevu = *u; *u = d->active;
2151 if(*u != prevu) dt_iop_gui_changed(module, widget, &prevu);
2152 break;
2153 }
2155 {
2156 gboolean *b = w->field, prevb = *b; *b = d->active;
2157 if(*b != prevb) dt_iop_gui_changed(module, widget, &prevb);
2158 break;
2159 }
2160 default:
2161 fprintf(stderr, "[_bauhaus_combobox_set] unsupported combo data type\n");
2162 }
2163 break;
2164 }
2165 default:
2166 fprintf(stderr, "[dt_bauhaus_value_changed_default_callback] invalid bauhaus widget type encountered for %s %s: %i\n", w->label, w->module->name, w->type);
2167 }
2168}
2170{
2171 // conditional on module->gui and paired with a dt_iop_gui_leave_critical_section() call at
2172 // an unrelated call site, which the thread-safety analyzer can't model -- same reason
2173 // dt_opencl_reserve_device_by_id()/dt_opencl_release_device() use the BAD variants.
2174 if(module->gui) dt_pthread_mutex_BAD_lock(&module->gui->gui_lock);
2175}
2176
2181
2183{
2184 return module && module->gui ? module->gui->off : NULL;
2185}
2186
2188{
2189 return module->gui && (module->gui->expander == (const void *)target || module->gui->header == (const void *)target);
2190}
2191
2192// clang-format off
2193// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2194// vim: shiftwidth=2 expandtab tabstop=2 cindent
2195// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2196// clang-format on
2197
2198/* Attached to each reset label; freed with the widget. */
2200{
2201 dt_iop_module_t *module;
2202 int offset; // offset of the parameter inside module->params
2203 int size; // its size in bytes
2205
2206static void _iop_reset_label_reset(GtkWidget *widget, gpointer user_data)
2207{
2209 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->module)) return;
2210
2211 memcpy(((char *)d->module->params) + d->offset, ((char *)d->module->default_params) + d->offset, d->size);
2212 if(d->module->gui_update) d->module->gui_update(d->module);
2214}
2215
2217 int param_size)
2218{
2219 GtkWidget *w = dtgtk_reset_label_new(label);
2220
2222 d->module = module;
2223 d->offset = param - (void *)module->params;
2224 d->size = param_size;
2225
2228 return w;
2229}
void dt_accels_remove_shortcut(dt_accels_t *accels, const char *path)
Remove the shortcut object identified by path and all its accels.
void dt_accels_new_virtual_instance_shortcut(dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name)
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
void dt_accels_remove_accel(dt_accels_t *accels, const char *path, gpointer data)
Recursively remove all accels for all shortcuts containing path. This is unneeded for accels attached...
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnect accels while a text or search entry has the focus, and reconnect them when it loses it....
Handle default and user-set shortcuts (accelerators)
#define dt_accels_new_darkroom_action(a, b, c, d, e, f, g)
#define DT_PRIMARY_MASK
struct dt_accels_t * dt_gui_get_accels(void)
struct dt_ui_t * dt_gui_get_ui(void)
void dt_gui_add_help_link(GtkWidget *widget, char *link)
void dt_gui_refocus_center()
void dt_ui_container_focus_widget(dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w)
gives a widget focus in the container
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
GtkWidget * dt_bauhaus_slider_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1626
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3275
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2114
GtkWidget * dt_bauhaus_slider_new_with_range_and_feedback(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits, int feedback)
Definition bauhaus.c:1637
void dt_bauhaus_widget_set_field(GtkWidget *widget, gpointer field, dt_introspection_type_t field_type)
Definition bauhaus.c:1561
void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(free_func)(void *data), gboolean sensitive)
Definition bauhaus.c:1841
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3326
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2085
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1693
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1819
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
Definition bauhaus.h:127
@ DT_BAUHAUS_COMBOBOX
Definition bauhaus.h:89
@ DT_BAUHAUS_SLIDER
Definition bauhaus.h:88
#define DT_BAUHAUS_WIDGET(obj)
Definition bauhaus.h:62
void dt_develop_blend_get_mask_usage(const dt_iop_module_t *module, const dt_develop_blend_params_t *params, gboolean *top_enabled, gboolean *raster_used, gboolean *drawn_used, gboolean *parametric_used)
Definition blend.c:263
void dt_iop_gui_init_blending(dt_iop_module_t *module)
Definition blend_gui.c:5162
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4459
void dt_iop_gui_blending_lose_focus(dt_iop_module_t *module)
Definition blend_gui.c:4756
void dt_iop_gui_cleanup_blending(dt_iop_module_t *module)
Definition blend_gui.c:4308
The blending panel's GUI state and API, cut out of develop/blend.h.
gboolean blend_color_picker_apply(dt_iop_module_t *module, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void dtgtk_button_set_active(GtkDarktableButton *button, gboolean active)
Definition button.c:177
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:135
#define DTGTK_BUTTON(obj)
Definition button.h:40
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
void dt_collection_hint_message(const dt_collection_t *collection)
Definition collection.c:912
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
int dt_iop_color_picker_get_ready_data(const dt_iop_module_t *module, GtkWidget **picker, dt_dev_pixelpipe_t **pipe, const dt_dev_pixelpipe_iop_t **piece)
static const float x
const float f
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
const float top
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
GtkWidget * dt_gui_container_nth_child(GtkContainer *container, int which)
Definition container.c:53
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:928
#define dt_control_queue_cursor(cursor)
Definition control.h:139
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:527
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:522
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:645
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.
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
void dt_dev_history_flush_pending_commits(struct dt_develop_t *dev)
Run every history commit still queued for dev, now.
#define dt_dev_pixelpipe_rebuild_all(dev)
#define dt_dev_pixelpipe_update_history_main(dev)
void dt_dev_modulegroups_switch_tab(dt_develop_t *dev, dt_iop_module_t *module)
Definition develop.c:1328
void dt_dev_module_remove(dt_develop_t *dev, dt_iop_module_t *module)
Definition develop.c:1436
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1862
gchar * dt_history_item_get_label(const struct dt_iop_module_t *module)
Definition develop.c:1607
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1873
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1653
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
static int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:367
static int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:377
const int res
Definition dtpthread.h:351
GtkWidget * dtgtk_expander_get_header_event_box(GtkDarktableExpander *expander)
Definition expander.c:49
GtkWidget * dtgtk_expander_get_body_event_box(GtkDarktableExpander *expander)
Definition expander.c:63
GtkWidget * dtgtk_expander_get_frame(GtkDarktableExpander *expander)
Definition expander.c:35
GtkWidget * dtgtk_expander_get_body(GtkDarktableExpander *expander)
Definition expander.c:56
void dtgtk_expander_set_expanded(GtkDarktableExpander *expander, gboolean expanded)
Definition expander.c:70
GtkWidget * dtgtk_expander_new(GtkWidget *header, GtkWidget *body)
Definition expander.c:101
#define DTGTK_EXPANDER(obj)
Definition expander.h:31
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
gboolean dt_gui_presets_autoapply_for_module(dt_iop_module_t *module)
void dt_gui_presets_popup_menu_show_for_module(dt_iop_module_t *module)
#define DT_GUI_MODULE(x)
Everything the history, styles and presets code says to the outside world: messages for the user,...
const char * tooltip
Definition image.h:310
void dt_iop_reload_defaults(dt_iop_module_t *module)
Definition imageop.c:740
void dt_iop_commit_blend_params(dt_iop_module_t *module, const dt_develop_blend_params_t *blendop_params)
Definition imageop.c:1131
void dt_iop_gui_reset(dt_iop_module_t *module)
Definition imageop.c:1540
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:735
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1563
void dt_iop_update_multi_priority(dt_iop_module_t *module, int new_priority)
Definition imageop.c:1695
@ IOP_MODULE_ICON
Definition imageop.h:95
@ IOP_MODULE_PRESETS
Definition imageop.h:100
@ IOP_MODULE_RESET
Definition imageop.h:99
@ IOP_MODULE_LABEL
Definition imageop.h:96
@ IOP_MODULE_MASK
Definition imageop.h:97
@ IOP_MODULE_LAST
Definition imageop.h:101
@ IOP_MODULE_SWITCH
Definition imageop.h:94
@ IOP_MODULE_INSTANCE
Definition imageop.h:98
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
GtkWidget * dt_iop_togglebutton_new_no_register(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
static void _gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
static gboolean _gui_multiinstance_callback(GtkButton *button, GdkEventButton *event, gpointer user_data)
void dt_iop_gui_cleanup_module(dt_iop_module_t *module)
static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data)
GtkWidget * dt_iop_gui_get_pluginui(dt_iop_module_t *module)
static void _iop_plugin_header_menu_deactivate(GtkWidget *menu, gpointer user_data)
void dt_iop_add_remove_mask_indicator(dt_iop_module_t *module)
GtkWidget * dt_iop_button_new(dt_iop_module_t *self, const gchar *label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, gint paintflags, GtkWidget *box)
void dt_iop_gui_set_expander(dt_iop_module_t *module)
dt_iop_module_t * dt_iop_gui_get_next_visible_module(dt_iop_module_t *module)
static gboolean _rename_module_key_press(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
static gboolean _iop_plugin_header_activate(GtkWidget *self, gboolean group_cycling, gpointer user_data)
static void _iop_panel_label(dt_iop_module_t *module)
void dt_iop_gui_init(dt_iop_module_t *module)
static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
void dt_iop_gui_update(dt_iop_module_t *module)
void dt_bauhaus_update_module(dt_iop_module_t *self)
GtkWidget * dt_iop_gui_reset_label_new(const gchar *label, dt_iop_module_t *module, void *param, int param_size)
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
static void _display_mask_indicator_callback(GtkToggleButton *bt, dt_iop_module_t *module)
static void _iop_toggle_callback(GtkWidget *togglebutton, dt_module_param_t *data)
Definition imageop_gui.c:90
void _iop_dim_all_but(dt_iop_module_t *module, gboolean dim)
void dt_iop_gui_update_expanded(dt_iop_module_t *module)
void dt_iop_gui_update_header(dt_iop_module_t *module)
static gboolean _iop_plugin_header_menu_dismiss_idle(gpointer user_data)
void dt_iop_gui_set_enable_button(dt_iop_module_t *module)
static void _iop_reset_label_reset(GtkWidget *widget, gpointer user_data)
static void _gui_copy_callback(GtkButton *button, gpointer user_data)
gboolean dt_iop_is_visible(dt_iop_module_t *module)
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
dt_iop_module_t * dt_iop_gui_get_previous_visible_module(dt_iop_module_t *module)
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
static gboolean _iop_plugin_body_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_iop_gui_get_widget(dt_iop_module_t *module)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded)
void dt_iop_gui_changed(dt_iop_module_t *action, GtkWidget *widget, gpointer data)
static void _iop_color_picker_data_ready_callback(gpointer instance, gpointer user_data)
gboolean dt_iop_gui_owns_widget(const dt_iop_module_t *module, const GtkWidget *target)
static void _mask_indicator_get_usage(dt_iop_module_t *module, gboolean *top_enabled, gboolean *raster_used, gboolean *drawn_used, gboolean *parametric_used)
static gboolean _iop_plugin_header_child_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static gboolean _mask_indicator_tooltip(GtkWidget *treeview, gint x, gint y, gboolean kb_mode, GtkTooltip *tooltip, dt_iop_module_t *module)
void dt_iop_gui_set_enable_button_icon(GtkWidget *w, dt_iop_module_t *module)
void dt_bauhaus_value_changed_default_callback(GtkWidget *widget)
static void _add_widget_to_module_list(dt_iop_module_t *self, GtkWidget *widget)
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
#define DT_IOP_HEADER_IGNORE_RELEASE
Definition imageop_gui.c:55
static void _presets_popup_callback(GtkButton *button, dt_iop_module_t *module)
#define DT_IOP_HEADER_MENU_DISMISS_CLICK
Definition imageop_gui.c:53
static void _iop_gui_widget_gone(gpointer user_data, GObject *where_the_object_was)
Clear GUI pointers that still reference one iop widget being finalized.
static gboolean _rename_module_resize(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
static void _gui_delete_callback(GtkButton *button, dt_iop_module_t *module)
static gboolean _rename_module_idle(gpointer user_data)
static gboolean _iop_plugin_header_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
gboolean dt_mask_scroll_increases(int up)
gboolean _iop_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void dt_iop_gui_rename_module(dt_iop_module_t *module)
dt_iop_module_t * dt_iop_gui_duplicate(dt_iop_module_t *base, gboolean copy_params)
Create another instance of base, insert it into dev->iop and give it a GUI.
gboolean dt_iop_gui_module_is_visible(dt_iop_module_t *module)
GtkWidget * dt_iop_gui_get_off(dt_iop_module_t *module)
static void _gui_duplicate_callback(GtkButton *button, gpointer user_data)
static void _gui_off_callback(GtkToggleButton *togglebutton, gpointer user_data)
static void _gui_rename_callback(GtkButton *button, dt_iop_module_t *module)
#define DT_IOP_HEADER_MENU_OPEN
Definition imageop_gui.c:52
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
@ DT_INTROSPECTION_TYPE_BOOL
@ DT_INTROSPECTION_TYPE_ENUM
@ DT_INTROSPECTION_TYPE_FLOAT
@ DT_INTROSPECTION_TYPE_UINT
@ DT_INTROSPECTION_TYPE_USHORT
@ DT_INTROSPECTION_TYPE_INT
gchar * delete_underscore(const char *s)
Definition label.c:62
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_DEV
Definition logging.h:53
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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
void dt_masks_iop_use_same_as(struct dt_iop_module_t *module, struct dt_iop_module_t *src)
@ DT_MASKS_EVENT_RESET
Definition masks.h:150
void dt_masks_form_delete(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *grp, dt_masks_form_t *form)
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
void dt_masks_reset_form_gui(dt_develop_t *dev)
Definition masks_gui.c:4020
void dt_masks_group_update_name(dt_iop_module_t *module)
Definition masks_gui.c:4094
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
const char * name
Definition pdf.h:90
void dt_gui_menu_popup(GtkMenu *menu, GtkWidget *button, GdkGravity widget_anchor, GdkGravity menu_anchor)
Definition popup.c:53
data.presets: a module's saved parameters, with the conditions under which it auto-applies.
GtkWidget * dtgtk_reset_label_new(const gchar *text)
Definition resetlabel.c:60
void dt_sentry_record_module_usage(const char *category, const char *name)
Definition sentry.c:692
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:403
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:382
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:615
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:315
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:319
@ DT_SIGNAL_CONTROL_PICKERDATA_READY
This signal is raised when new color picker data are available in darkroom. no param,...
Definition signal.h:296
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:392
const float const float param
Definition bauhaus.h:132
gpointer data
Definition bauhaus.h:139
char label[256]
Definition bauhaus.h:186
dt_bauhaus_data_t data
Definition bauhaus.h:218
gboolean use_default_callback
Definition bauhaus.h:215
dt_gui_module_t *gpointer field
Definition bauhaus.h:181
dt_introspection_type_t field_type
Definition bauhaus.h:183
dt_bauhaus_type_t type
Definition bauhaus.h:177
int32_t gui_attached
Definition develop.h:167
struct dt_iop_module_t * gui_module
Definition develop.h:170
GList * history
Definition develop.h:259
GList * alliop
Definition develop.h:271
GtkWidget * scroll_to[2]
GtkWidget * scroll_to_header_once
The dt_gui_module_t type is the intersection between a dt_lib_module_t and a dt_iop_module_t structur...
GtkWidget * expander
Definition imageop_gui.h:57
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_module_so_t * so
Definition imageop.h:376
dt_iop_params_t * params
Definition imageop.h:333
dt_iop_module_t *int offset
dt_iop_module_t *void * param
Definition imageop_gui.c:87
void dt_telemetry_record_module_usage(const char *category, const char *name)
Definition telemetry.c:460
#define MAX(a, b)
Definition thinplate.c:29
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)
dt_bauhaus_combobox_data_t combobox
Definition bauhaus.h:162
char * dt_get_help_url(char *name)
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:140
GtkWidget * dt_widget_scroll_focus(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
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_widget_freeze()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
void dt_capitalize_label(gchar *text)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_module_switch_on(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_presets(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_multiinstance(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_module_switch(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_reset(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dt_ui_container_add_widget(dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w)
@ DT_UI_CONTAINER_SIZE
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER