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 Copyright (C) 2026 Guillaume Stutin.
10
11 darktable is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
15
16 darktable is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with darktable. If not, see <http://www.gnu.org/licenses/>.
23*/
24
25#include "widgets/container.h"
26#include "common/telemetry.h"
27#include "common/sentry.h"
28#include "widgets/gdkkeys.h"
29#include "widgets/popup.h"
30#include "gui/application.h"
32#include "common/collection.h"
33#include "common/hash.h"
34#include "common/module.h"
37#include "control/control.h"
38#include "control/signal.h"
41#include "develop/blend.h"
42#include "develop/blend_gui.h"
44#include "develop/masks_gui.h"
46#include "gui/presets.h"
47#include "widgets/expander.h"
48#include "widgets/label.h"
51#include "history/notify.h"
52#include "develop/imageop_gui.h"
53#define DT_IOP_HEADER_MENU_OPEN "dt-module-header-menu-open"
54#define DT_IOP_HEADER_MENU_DISMISS_CLICK "dt-module-header-menu-dismiss-click"
55
56#define DT_IOP_HEADER_IGNORE_RELEASE "dt-module-header-ignore-release"
57static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded);
58static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data);
59static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
60 GdkModifierType modifier, gpointer data);
61static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
62 GdkModifierType modifier, gpointer data);
63#include "develop/dev_history.h"
64#include "develop/develop.h"
65#include "widgets/resetlabel.h"
66#include "common/conf.h"
67#include "develop/imageop.h"
68#include "widgets/bauhaus.h"
69#include "widgets/button.h"
70#include "system/macros.h"
71#include "system/mem_alloc.h"
72#include "common/utility.h"
73
74
75#ifdef GDK_WINDOWING_QUARTZ
76#endif
77
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
90static void _iop_toggle_callback(GtkWidget *togglebutton, dt_module_param_t *data)
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;
98 *field = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton));
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 {
112 dt_gui_module_t *mod = (dt_gui_module_t *)self;
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;
126 char *param_name = g_malloc(param_length);
127 char *base_name = g_malloc(param_length);
128 if(sscanf(param, "%[^[][%" G_GSIZE_FORMAT "]", base_name, &param_index) == 2)
129 {
130 sprintf(param_name, "%s[0]", base_name);
131 skip_label = TRUE;
132 }
133 else
134 {
135 memcpy(param_name, param, param_length);
136 }
137 dt_free(base_name);
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
195 dt_bauhaus_widget_set_label(slider, str);
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
206 dt_bauhaus_widget_set_label(slider, str);
207
208 dt_free(str);
209 }
210
211 if(!self->gui->widget) self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
212 gtk_box_pack_start(GTK_BOX(self->gui->widget), slider, FALSE, FALSE, 0);
213
216
217 dt_free(param_name);
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
247 dt_bauhaus_widget_set_label(combobox, str);
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)
264 dt_bauhaus_combobox_add_full(combobox, gettext(iter->description), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, GINT_TO_POINTER(iter->value), NULL, TRUE);
265 }
266 }
267 }
268 else
269 {
270 str = g_strdup_printf("'%s' is not an enum/int/bool/combobox parameter", param);
271
272 dt_bauhaus_widget_set_label(combobox, str);
273
274 dt_free(str);
275 }
276
277 if(!self->gui->widget) self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
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));
303 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
304 button = gtk_check_button_new();
305 gtk_container_add(GTK_CONTAINER(button), label);
306 dt_module_param_t *module_param = (dt_module_param_t *)g_malloc(sizeof(dt_module_param_t));
307 module_param->module = self;
308 module_param->param = (uint8_t *)p + f->header.offset;
309 g_signal_connect_data(G_OBJECT(button), "toggled", G_CALLBACK(_iop_toggle_callback), module_param, (GClosureNotify)g_free, 0);
310 }
311 else
312 {
313 str = g_strdup_printf("'%s' is not a bool/togglebutton parameter", param);
314
315 button = gtk_check_button_new_with_label(str);
316 }
317
318 _add_widget_to_module_list(self, button);
319
320 dt_free(str);
321 if(!self->gui->widget) self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
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))
335 gtk_widget_set_tooltip_text(w, _(label));
336 else
337 {
338 gchar *tooltip = g_strdup_printf(_("%s\nctrl+click to %s"), _(label), _(ctrl_label));
339 gtk_widget_set_tooltip_text(w, tooltip);
341 }
342
344
345 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), FALSE);
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))
360 gtk_widget_set_tooltip_text(w, _(label));
361 else
362 {
363 gchar *tooltip = g_strdup_printf(_("%s\nctrl+click to %s"), _(label), _(ctrl_label));
364 gtk_widget_set_tooltip_text(w, tooltip);
366 }
367
368 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), FALSE);
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,
376 DTGTKCairoPaintIconFunc paint, gint paintflags, GtkWidget *box)
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));
388 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(button))), PANGO_ELLIPSIZE_END);
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)) 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)
427 module->color_picker_apply(module, picker, pipe, (dt_dev_pixelpipe_iop_t *)piece);
428}
429
430/* The pickers of the blending panel are fed by the same signal as a module's own. A module with no
431 * picker of its own still has those as soon as it blends, so both kinds subscribe: gating on
432 * color_picker_apply alone left every such module's blend picker updated only when (re)activated. */
433static gboolean _iop_listens_to_picker_data(const dt_iop_module_t *module)
434{
435 return module->color_picker_apply || (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING);
436}
437
438static void _gui_delete_callback(GtkButton *button, dt_iop_module_t *module)
439{
440 dt_develop_t *dev = module->dev;
441
442 // we search another module with the same base
443 // we want the next module if any or the previous one
444 GList *modules = module->dev->iop;
445 dt_iop_module_t *next = NULL;
446 int find = 0;
447 while(modules)
448 {
449 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
450 if(mod == module)
451 {
452 find = 1;
453 if(next) break;
454 }
455 else if(mod->instance == module->instance)
456 {
457 next = mod;
458 if(find) break;
459 }
460 modules = g_list_next(modules);
461 }
462 if(IS_NULL_PTR(next)) return; // what happened ???
463
464 if(module->module_will_remove && !module->module_will_remove(module)) return;
465
467
468 // we must pay attention if priority is 0
469 const gboolean is_zero = (module->multi_priority == 0);
470
471 // We are about to destroy this module GUI. Drop darkroom focus first so the
472 // center expose callback cannot call module->gui_post_expose() with a stale
473 // module->gui_data pointer during teardown-triggered redraws.
474 if(dev->gui_module == module) dt_iop_request_focus(NULL);
475
477
478 // we remove the plugin effectively
479 if(!dt_iop_is_hidden(module))
480 {
481 // we just hide the module to avoid lots of gtk critical warnings
482 gtk_widget_hide(module->gui->expander);
483
486 }
487
488 // we remove all references in the history stack and dev->iop
489 // this will inform that a module has been removed from history
490 // we do it here so we have the multi_priorities to reconstruct
491 // de deleted module if the user undo it
492 dt_dev_module_remove(dev, module);
493
494 // if module was priority 0, then we set next to priority 0
495 if(is_zero)
496 {
497 // we want the first one in history
498 dt_iop_module_t *first = NULL;
499 GList *history = dev->history;
500 while(history)
501 {
502 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
503 if(!hist || !hist->module)
504 {
505 history = g_list_next(history);
506 continue;
507 }
508 if(hist->module->instance == module->instance && hist->module != module)
509 {
510 first = hist->module;
511 break;
512 }
513 history = g_list_next(history);
514 }
515 if(IS_NULL_PTR(first)) first = next;
516
517 // we set priority of first to 0
519
520 // we change this in the history stack too
521 for(history = dev->history; history; history = g_list_next(history))
522 {
523 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
524 // the loop above guards NULL entries in this same list; this one must too
525 if(hist && hist->module == first) hist->multi_priority = 0;
526 }
527 }
528
529 // Commit undo snapshot for the whole delete operation (module removal + multi_priority adjustments).
531
532 // Save history
534
535 // don't delete the module, a pipe may still need it
536 dev->alliop = g_list_append(dev->alliop, module);
537
538 /* redraw */
541
543}
544
546{
547 // callers walk the full dev->iop list: hidden modules (gamma, basebuffer, ...) never get
548 // gui_init() and keep module->gui == NULL -- they are simply not visible
549 GtkWidget *expander = module->gui ? module->gui->expander : NULL;
550 return (expander && gtk_widget_is_visible(expander) && !dt_iop_is_hidden(module));
551}
552
554{
555 dt_iop_module_t *prev = NULL;
556 for(GList *modules = g_list_first(module->dev->iop); modules; modules = g_list_next(modules))
557 {
558 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
559 if(mod == module)
560 break;
561 else if(dt_iop_gui_module_is_visible(mod))
562 prev = mod;
563 }
564 return prev;
565}
566
568{
569 dt_iop_module_t *next = NULL;
570 for(GList *modules = g_list_last(module->dev->iop); modules; modules = g_list_previous(modules))
571 {
572 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
573 if(mod == module)
574 break;
575 else if(dt_iop_gui_module_is_visible(mod))
576 next = mod;
577 }
578 return next;
579}
580
582{
583 // make sure the duplicated module appears in the history
584 dt_dev_add_history_item(base->dev, base, FALSE, FALSE);
585
586 // first we create the new module
588 dt_iop_module_t *module = dt_dev_module_duplicate(base->dev, base);
590 if(IS_NULL_PTR(module)) return NULL;
591
592 // we set the gui part of it
593 /* initialize gui if iop have one defined */
594 if(!dt_iop_is_hidden(module))
595 {
596 // make sure gui_init and reload defaults is called safely
597 dt_iop_gui_init(module);
598
599 /* add module to right panel */
601 dt_gui_get_global()->scroll_to_header_once = module->gui->expander;
602
603 dt_iop_reload_defaults(module); // some modules like profiled denoise update the gui in reload_defaults
604
605 if(copy_params)
606 {
607 memcpy(module->params, base->params, module->params_size);
608 if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
609 {
611 if(base->blend_params->mask_id > 0)
612 {
613 module->blend_params->mask_id = 0;
614 dt_masks_iop_use_same_as(module, base);
615 }
616 }
617 }
618
619 dt_iop_request_focus(module);
621 if(base != module && !IS_NULL_PTR(base->gui->expander)) _gui_set_single_expanded(base, FALSE);
623
624 if(module->dev->gui_attached)
625 dt_dev_pixelpipe_rebuild_all(module->dev);
626
627 // we save the new instance creation
628 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
629
630 // ...and run it NOW, before returning to the main loop.
631 //
632 // dt_dev_add_history_item() only QUEUES: dev_history_gui.c holds the request behind the
633 // "Pipe recompute timeout" preference (processing/timeout, 200 ms by default) so that a
634 // slider drag does not commit once per step. Creating an instance is not a drag -- there
635 // is nothing to coalesce, and the delay is load-bearing here in a way it is not anywhere
636 // else, because two things read the module's state before the timeout fires:
637 //
638 // - libs/modulegroups.c shows an extra instance only when it is in history
639 // (`in_history || multi_priority == 0`), and its refresh is a g_idle queued by the
640 // dt_iop_request_focus() above -- so it runs a few ms from now, long before 200 ms;
641 // - that module deliberately does NOT listen to history changes (expander reparenting
642 // stole keyboard focus from Bauhaus widgets), so nothing refreshes the panel again
643 // when the commit finally lands.
644 //
645 // The new instance was therefore judged "not in history yet", hidden, and stayed hidden
646 // until an unrelated event queued another refresh -- clicking the base module's header,
647 // which is exactly what users reported doing to make their new copy appear (#1265).
648 // Measured with -d dev: idle at +100 ms saw in_history=0 enabled=0 -> HIDE.
650 }
651
652 /* update ui to new parameters */
653 dt_iop_gui_update(module);
654
655 return module;
656}
657
659
668static gboolean _rename_module_idle(gpointer user_data)
669{
670 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
672
673 return G_SOURCE_REMOVE;
674}
675
676static void _gui_copy_callback(GtkButton *button, gpointer user_data)
677{
678 dt_iop_module_t *module = dt_iop_gui_duplicate(user_data, FALSE);
679 if(IS_NULL_PTR(module)) return;
680
681 g_idle_add(_rename_module_idle, module);
682}
683
684static void _gui_duplicate_callback(GtkButton *button, gpointer user_data)
685{
686 dt_iop_module_t *module = dt_iop_gui_duplicate(user_data, TRUE);
687 if(IS_NULL_PTR(module)) return;
688
689 g_idle_add(_rename_module_idle, module);
690}
691
692static gboolean _rename_module_key_press(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
693{
694 int ended = 0;
695 guint key = dt_keys_mainpad_alternatives(event->keyval);
696
697 if(event->type == GDK_FOCUS_CHANGE || key == GDK_KEY_Return)
698 {
699 if(gtk_entry_get_text_length(GTK_ENTRY(entry)) > 0)
700 {
701 // name is not empty, set new multi_name
702
703 const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry));
704
705 // restore saved 1st character of instance name (without it the same name wouls still produce unnecessary copy + add history item)
706 module->multi_name[0] = module->multi_name[sizeof(module->multi_name) - 1];
707 module->multi_name[sizeof(module->multi_name) - 1] = 0;
708
709 if(g_strcmp0(module->multi_name, name) != 0)
710 {
711 g_strlcpy(module->multi_name, name, sizeof(module->multi_name));
712 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
713 }
714 }
715 else
716 {
717 // clear out multi-name (set 1st char to 0)
718 module->multi_name[0] = 0;
719 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
720 }
721
722 ended = 1;
723 }
724 else if(key == GDK_KEY_Escape)
725 {
726 // restore saved 1st character of instance name
727 module->multi_name[0] = module->multi_name[sizeof(module->multi_name) - 1];
728 module->multi_name[sizeof(module->multi_name) - 1] = 0;
729
730 ended = 1;
731 }
732
733 if(ended)
734 {
735 g_signal_handlers_disconnect_by_func(entry, G_CALLBACK(_rename_module_key_press), module);
736 gtk_widget_destroy(entry);
739 return TRUE;
740 }
741
742 return FALSE; /* event not handled */
743}
744
745static gboolean _rename_module_resize(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
746{
747 int width = 0;
748 GtkBorder padding;
749 GtkBorder border;
750
751 pango_layout_get_pixel_size(gtk_entry_get_layout(GTK_ENTRY(entry)), &width, NULL);
752 // The entry's frame eats into its allocation before the text gets any room, so the request has
753 // to carry the border as well as the padding -- the theme gives every entry a 1px one.
754 GtkStyleContext *context = gtk_widget_get_style_context(entry);
755 const GtkStateFlags state = gtk_widget_get_state_flags(entry);
756 gtk_style_context_get_padding(context, state, &padding);
757 gtk_style_context_get_border(context, state, &border);
758 gtk_widget_set_size_request(entry, width + padding.left + padding.right
759 + border.left + border.right + 1, -1);
760
761 return TRUE;
762}
763
765{
766 // dt_iop_gui_duplicate() returns NULL when the instance could not be created
767 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->header)) return;
768 GtkWidget *focused = gtk_container_get_focus_child(GTK_CONTAINER(module->gui->header));
769 if(focused && GTK_IS_ENTRY(focused)) return;
770
771 GtkWidget *entry = gtk_entry_new();
773
774 gtk_widget_set_name(entry, "iop-panel-label");
775 gtk_entry_set_width_chars(GTK_ENTRY(entry), 0);
776 gtk_entry_set_max_length(GTK_ENTRY(entry), sizeof(module->multi_name) - 1);
777 gtk_entry_set_text(GTK_ENTRY(entry), module->multi_name);
778
779 // remove instance name but save 1st character in case of escape
780 module->multi_name[sizeof(module->multi_name) - 1] = module->multi_name[0];
781 module->multi_name[0] = 0;
783
784 gtk_widget_add_events(entry, GDK_FOCUS_CHANGE_MASK);
785 g_signal_connect(entry, "key-press-event", G_CALLBACK(_rename_module_key_press), module);
786 g_signal_connect(entry, "focus-out-event", G_CALLBACK(_rename_module_key_press), module);
787 g_signal_connect(entry, "style-updated", G_CALLBACK(_rename_module_resize), module);
788 g_signal_connect(entry, "changed", G_CALLBACK(_rename_module_resize), module);
789
790 gtk_box_pack_start(GTK_BOX(module->gui->header), entry, TRUE, TRUE, 0);
791 gtk_widget_show(entry);
792 gtk_widget_grab_focus(entry);
793}
794
795static void _gui_rename_callback(GtkButton *button, dt_iop_module_t *module)
796{
798}
799
800static gboolean _iop_plugin_header_menu_dismiss_idle(gpointer user_data)
801{
802 GtkWidget *expander = GTK_WIDGET(user_data);
803 if(GTK_IS_WIDGET(expander))
804 g_object_set_data(G_OBJECT(expander), DT_IOP_HEADER_MENU_DISMISS_CLICK, NULL);
805
806 g_object_unref(expander);
807 return G_SOURCE_REMOVE;
808}
809
810static void _iop_plugin_header_menu_deactivate(GtkWidget *menu, gpointer user_data)
811{
812 GtkWidget *expander = GTK_WIDGET(user_data);
813 if(!GTK_IS_WIDGET(expander)) return;
814
821 g_object_set_data(G_OBJECT(expander), DT_IOP_HEADER_MENU_OPEN, NULL);
822 g_object_set_data(G_OBJECT(expander), DT_IOP_HEADER_MENU_DISMISS_CLICK, GINT_TO_POINTER(TRUE));
823 g_idle_add(_iop_plugin_header_menu_dismiss_idle, g_object_ref(expander));
824}
825
826static gboolean _gui_multiinstance_callback(GtkButton *button, GdkEventButton *event, gpointer user_data)
827{
828 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
829
830 if(!IS_NULL_PTR(event) && event->button == 3)
831 {
832 if(!(module->flags() & IOP_FLAGS_ONE_INSTANCE)) _gui_copy_callback(button, user_data);
833 return TRUE;
834 }
835 else if(!IS_NULL_PTR(event) && event->button == 2)
836 {
837 return FALSE;
838 }
839
840 GtkMenuShell *menu = GTK_MENU_SHELL(gtk_menu_new());
841 GtkWidget *item;
842
843 item = gtk_menu_item_new_with_label(_("new instance"));
844 // gtk_widget_set_tooltip_text(item, _("add a new instance of this module to the pipe"));
845 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_gui_copy_callback), module);
846 gtk_widget_set_sensitive(item, module->gui->multi_show_new);
847 gtk_menu_shell_append(menu, item);
848
849 item = gtk_menu_item_new_with_label(_("duplicate instance"));
850 // gtk_widget_set_tooltip_text(item, _("add a copy of this instance to the pipe"));
851 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_gui_duplicate_callback), module);
852 gtk_widget_set_sensitive(item, module->gui->multi_show_new);
853 gtk_menu_shell_append(menu, item);
854
855 item = gtk_menu_item_new_with_label(_("delete"));
856 // gtk_widget_set_tooltip_text(item, _("delete this instance"));
857 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_gui_delete_callback), module);
858 gtk_widget_set_sensitive(item, module->gui->multi_show_close);
859 gtk_menu_shell_append(menu, item);
860
861 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
862
863 item = gtk_menu_item_new_with_label(_("rename"));
864 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_gui_rename_callback), module);
865 gtk_menu_shell_append(menu, item);
866
867 if(!IS_NULL_PTR(module->gui->expander))
868 {
869 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN, GINT_TO_POINTER(TRUE));
870 g_signal_connect_data(G_OBJECT(menu), "deactivate",
872 g_object_ref(module->gui->expander), (GClosureNotify)g_object_unref, 0);
873 }
874
875 dt_gui_menu_popup(GTK_MENU(menu), GTK_WIDGET(button), GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST);
876
877 // make sure the button is deactivated now that the menu is opened
878 if(button) dtgtk_button_set_active(DTGTK_BUTTON(button), FALSE);
879 return TRUE;
880}
881
882static void _gui_off_callback(GtkToggleButton *togglebutton, gpointer user_data)
883{
884 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
885
887 {
888 if(gtk_toggle_button_get_active(togglebutton))
889 {
890 module->enabled = 1;
891 dt_sentry_record_module_usage("iop", module->op);
892 dt_telemetry_record_module_usage("iop", module->op);
893 dt_dev_add_history_item(module->dev, module, FALSE, TRUE);
894 }
895 else
896 {
897 module->enabled = 0;
898
899 // if current module is set as the CAT instance, remove that setting
900 if(module->dev->proxy.chroma_adaptation == module)
901 module->dev->proxy.chroma_adaptation = NULL;
902
903 dt_dev_add_history_item(module->dev, module, FALSE, TRUE);
904 }
905 }
906
907 char tooltip[512];
908 gchar *module_label = dt_history_item_get_name(module);
909 snprintf(tooltip, sizeof(tooltip), module->enabled ? _("%s is switched on") : _("%s is switched off"),
910 module_label);
911 dt_free(module_label);
912 gtk_widget_set_tooltip_text(GTK_WIDGET(togglebutton), tooltip);
913 gtk_widget_queue_draw(GTK_WIDGET(togglebutton));
914}
915
917{
918 // module->gui is NULL for hidden modules, and for visible ones between dev->iop
919 // construction and gui_init() during darkroom entry
920 return !dt_iop_is_hidden(module) && !IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->expander)
921 && gtk_widget_is_visible(module->gui->expander);
922}
923
925{
926 GtkWidget *lab = dt_gui_container_nth_child(GTK_CONTAINER(module->gui->header), IOP_MODULE_LABEL);
927 lab = gtk_bin_get_child(GTK_BIN(lab));
928 gtk_widget_set_name(lab, "iop-panel-label");
929
930 char *module_name = dt_history_item_get_label(module);
931 dt_capitalize_label(module_name);
932 gtk_label_set_markup_with_mnemonic(GTK_LABEL(lab), module_name);
933 dt_free(module_name);
934
935 // Module name hasn't changed or no instance name: abort now
936 if(!g_strcmp0(module_name, gtk_label_get_text(GTK_LABEL(lab))) || module->multi_name[0] == '\0')
937 return;
938
939 dt_gui_module_t *mod = (dt_gui_module_t *)module;
941 {
944 }
945
946 gchar *clean_name = delete_underscore(module->name());
947 dt_capitalize_label(clean_name);
948
949 // The module name and the instance name, which the user types, are two levels of the path:
950 // each one goes through dt_accels_build_path() on its own, so that neither can split into more.
951 gchar *module_path = dt_accels_build_path(_("Darkroom/Modules/Instances"), clean_name);
952 const char *instance = (module->multi_name[0] != '\0') ? module->multi_name : "0";
953 mod->instance_accel_path = dt_accels_build_path(module_path, instance);
954
956 dt_gui_get_accels()->darkroom_accels, module_path, instance);
957
958 dt_free(module_path);
959 dt_free(clean_name);
960
961 gtk_label_set_ellipsize(GTK_LABEL(lab), !module->multi_name[0] ? PANGO_ELLIPSIZE_END: PANGO_ELLIPSIZE_MIDDLE);
962 g_object_set(G_OBJECT(lab), "xalign", 0.0, (gchar *)0);
963}
964
966{
967 if (IS_NULL_PTR(module->gui)) return; /* module has no GUI half at all */
968 if (IS_NULL_PTR(module->gui->header)) /* some modules such as overexposed don't actually have a header */
969 return;
970
971 // set panel name to display correct multi-instance
972 _iop_panel_label(module);
975}
976
993
995{
996 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui)) return;
997
998 if(module->gui->off)
999 {
1000 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), module->enabled);
1001 if(module->hide_enable_button)
1002 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->off), FALSE);
1003 else
1004 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->off), TRUE);
1005
1006 dt_gui_remove_class(GTK_WIDGET(module->gui->off), "dt_iop_enable_forced_on");
1007 dt_gui_remove_class(GTK_WIDGET(module->gui->off), "dt_iop_enable_forced_off");
1008 if(module->hide_enable_button)
1009 {
1010 dt_gui_add_class(GTK_WIDGET(module->gui->off),
1011 module->enabled ? "dt_iop_enable_forced_on" : "dt_iop_enable_forced_off");
1012 }
1013
1014 dt_iop_gui_set_enable_button_icon(GTK_WIDGET(module->gui->off), module);
1015 }
1016}
1017
1019{
1020 // The module's interactive half exists from here until dt_iop_gui_cleanup_module();
1021 // its absence IS the headless flag (see dt_iop_module_gui_t in imageop_gui.h).
1022 if(IS_NULL_PTR(module->gui)) module->gui = (dt_iop_module_gui_t *)calloc(1, sizeof(dt_iop_module_gui_t));
1023
1024 // Suppress widget value-changed callbacks for the whole GUI build. Setting a slider's soft
1025 // range (etc.) in gui_init emits "value-changed", which would re-enter the module's
1026 // gui_changed handler before its sibling widgets exist and crash on dt_bauhaus_*(NULL)
1027 // (Sentry #129494618, #129578628). The scope guard releases the freeze automatically on every
1028 // exit path, and the central depth is GUI-thread-only and self-healing, so it cannot drift.
1030
1031 // Add the accelerators
1032 if(!dt_iop_is_hidden(module) && !(module->flags() & IOP_FLAGS_DEPRECATED))
1033 {
1034 gchar *clean_name = delete_underscore(module->name());
1035 dt_capitalize_label(clean_name);
1036
1037 dt_gui_module_t *mod = (dt_gui_module_t *)module;
1038 const gchar *const main_scope = _("Darkroom/Modules");
1039 mod->accel_path = dt_accels_build_path(main_scope, clean_name);
1040
1041 dt_accels_new_darkroom_action(_iop_plugin_focus_accel, module, NULL, main_scope, clean_name, 0, 0,
1042 _("Focuses the module"));
1043
1044 // NOTE: we should enable the accel only if the module is disable-able, but this property is set at runtime
1045 // in reload_defaults(), which depends on the image metadata for each pipeline.
1046 // We have no way of knowing here at init time.
1047 // if(!module->hide_enable_button)
1048 dt_accels_new_darkroom_action(_iop_plugin_enable_accel, module, NULL, mod->accel_path, _("Enable"), 0, 0,
1049 _("Enables the module"));
1050
1051 dt_free(clean_name);
1052 }
1053
1054 // We absolutely need to init the module controls after the module object
1055 if(module->gui_init) module->gui_init(module);
1056 if(_iop_listens_to_picker_data(module))
1057 {
1059 G_CALLBACK(_iop_color_picker_data_ready_callback), module);
1060 }
1061 // the freeze ends here as the scope guard goes out of scope
1062}
1063
1070static void _iop_gui_widget_gone(gpointer user_data, GObject *where_the_object_was)
1071{
1072 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1073 if(IS_NULL_PTR(module)) return;
1074
1075 if(!IS_NULL_PTR(module->gui))
1076 {
1077 if(module->gui->header == (GtkWidget *)where_the_object_was) module->gui->header = NULL;
1078 if(module->gui->expander == (GtkWidget *)where_the_object_was) module->gui->expander = NULL;
1079 }
1080
1081 if(IS_NULL_PTR(dt_gui_get_global())) return;
1082
1083 if(dt_gui_get_global()->scroll_to[0] == (GtkWidget *)where_the_object_was) dt_gui_get_global()->scroll_to[0] = NULL;
1084 if(dt_gui_get_global()->scroll_to[1] == (GtkWidget *)where_the_object_was) dt_gui_get_global()->scroll_to[1] = NULL;
1085 if(dt_gui_get_global()->scroll_to_header_once == (GtkWidget *)where_the_object_was) dt_gui_get_global()->scroll_to_header_once = NULL;
1086}
1087
1089{
1090 if(IS_NULL_PTR(module)) return;
1091 // backend-only modules (hidden, or loaded without gui_init -- see studio-capture.md)
1092 // have nothing to clean up here
1093 if(IS_NULL_PTR(module->gui)) return;
1094 dt_gui_module_t *mod = (dt_gui_module_t *)module;
1095 if(!IS_NULL_PTR(module->dev) && module->dev->gui_module == module) module->dev->gui_module = NULL;
1096
1097 // remove multiple delayed gtk_widget_queue_draw triggers
1098 if(module->gui->widget)
1099 while(g_idle_remove_by_data(module->gui->widget));
1100
1101 // Detach accels. accel_path is only ever set by dt_iop_gui_init() (never for a module that was
1102 // only backend-loaded, e.g. studio_capture's dev->iop -- see studio-capture.md): a NULL path
1103 // here means there is nothing registered to remove, and dt_accels_remove_accel() would
1104 // otherwise g_strrstr() every entry in accels->acceleratables against a NULL needle, tripping
1105 // a GLib-CRITICAL for each one.
1106 if(!dt_iop_is_hidden(module) && !(module->flags() & IOP_FLAGS_DEPRECATED) && !IS_NULL_PTR(mod->accel_path))
1107 {
1109 dt_free(mod->accel_path);
1110 }
1111
1114
1116
1117 // widget_list doesn't own the widget referenced, so don't deep_free
1118 dt_gui_module_t *m = DT_GUI_MODULE(module);
1119 g_list_free(m->widget_list);
1120 m->widget_list = NULL;
1121 g_list_free(m->widget_list_bh);
1122 m->widget_list_bh = NULL;
1123 dt_free(m->name);
1124 m->name = NULL;
1125 dt_free(m->view);
1126 m->view = NULL;
1127
1128 if(_iop_listens_to_picker_data(module))
1129 {
1131 }
1132 // History refresh can delete pipeline-only modules created for ordering/history
1133 // resolution. They have a module GUI cleanup callback but no module GUI data.
1134 if(module->gui_cleanup && !IS_NULL_PTR(dt_iop_gui_data(module)))
1135 module->gui_cleanup(module);
1137
1138 // size-allocate callbacks can still read scroll targets while GTK tears down widgets
1140 {
1141 if(dt_gui_get_global()->scroll_to[0] == module->gui->header || dt_gui_get_global()->scroll_to[0] == module->gui->expander)
1142 dt_gui_get_global()->scroll_to[0] = NULL;
1143 if(dt_gui_get_global()->scroll_to[1] == module->gui->header || dt_gui_get_global()->scroll_to[1] == module->gui->expander)
1144 dt_gui_get_global()->scroll_to[1] = NULL;
1145 if(dt_gui_get_global()->scroll_to_header_once == module->gui->expander)
1147 }
1148
1149 /* Release the transient widget tree explicitly. In normal GUI lifetime, these
1150 * widgets are parented and get destroyed by container teardown. During module
1151 * probe/init paths, they can stay unparented and would otherwise leak. */
1152 if(!IS_NULL_PTR(module->gui->expander) && GTK_IS_WIDGET(module->gui->expander))
1153 {
1154 GtkWidget *expander = module->gui->expander;
1155 g_object_ref_sink(expander);
1156 gtk_widget_destroy(expander);
1157 g_object_unref(expander);
1158 }
1159 else
1160 {
1161 if(!IS_NULL_PTR(module->gui->header) && GTK_IS_WIDGET(module->gui->header))
1162 {
1163 GtkWidget *header = module->gui->header;
1164 g_object_ref_sink(header);
1165 gtk_widget_destroy(header);
1166 g_object_unref(header);
1167 }
1168
1169 if(!IS_NULL_PTR(module->gui->widget) && GTK_IS_WIDGET(module->gui->widget))
1170 {
1171 GtkWidget *widget = module->gui->widget;
1172 g_object_ref_sink(widget);
1173 gtk_widget_destroy(widget);
1174 g_object_unref(widget);
1175 }
1176 }
1177
1178 module->gui->widget = NULL;
1179 module->gui->header = NULL;
1180 module->gui->expander = NULL;
1181 module->gui->off = NULL;
1182
1183 dt_free(module->gui);
1184 module->gui = NULL;
1185}
1186
1188{
1190 if(!dt_iop_is_hidden(module))
1191 {
1192 if(dt_iop_gui_data(module))
1193 {
1195
1196 if(module->params && module->gui_update)
1197 module->gui_update(module);
1198
1201 }
1203 }
1205}
1206
1207static gboolean _gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
1208{
1209 // never use the callback if module is always disabled
1210 const gboolean disabled = !module->default_enabled && module->hide_enable_button;
1211 if(disabled) return FALSE;
1212
1213 //Ctrl is used to apply any auto-presets to the current module
1214 //If Ctrl was not pressed, or no auto-presets were applied, reset the module parameters
1215 // FIXME: can we stop with all the easter-eggs key modifiers doing undocumented stuff all along ?
1216 if(!(event && dt_modifier_is(event->state, DT_PRIMARY_MASK)) || !dt_gui_presets_autoapply_for_module(module))
1217 {
1218 /* Resetting a module's parameters does not change GUI focus, so the
1219 focus-loss cleanup in dt_iop_request_focus() never runs here: an active
1220 eye-dropper (this module's own, or its blending/masking parametric-mask
1221 picker -- both share dev->color_picker) and any live mask-shape editing
1222 state (edit mode, shape buttons, mask/channel display) must be turned
1223 off explicitly, or they keep sampling/drawing on the image against
1224 parameters that no longer exist. */
1227
1228 // if a drawn mask is set, remove it from the list
1229 if(module->blend_params->mask_id > 0)
1230 {
1232 // FIXME: ask the user if he wants to delete the mask, or just unlink them.
1233 if(grp) dt_masks_form_delete(module->dev, module, NULL, grp);
1235 }
1236 /* reset to default params */
1237 dt_iop_reload_defaults(module);
1239
1240 /* reset ui to its defaults */
1241 dt_iop_gui_reset(module);
1242
1243 /* update ui to default params*/
1244 dt_iop_gui_update(module);
1245
1246 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1247 }
1248 return TRUE;
1249}
1250
1251static void _presets_popup_callback(GtkButton *button, dt_iop_module_t *module)
1252{
1253 const gboolean disabled = !module->default_enabled && module->hide_enable_button;
1254 if(disabled) return;
1255
1257
1258 if(!IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->expander)
1259 && !IS_NULL_PTR(dt_gui_get_global()->presets_popup_menu))
1260 {
1261 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN, GINT_TO_POINTER(TRUE));
1262 g_signal_connect_data(G_OBJECT(dt_gui_get_global()->presets_popup_menu), "deactivate",
1264 g_object_ref(module->gui->expander), (GClosureNotify)g_object_unref, 0);
1265 }
1266
1267 dt_gui_menu_popup(dt_gui_get_global()->presets_popup_menu, GTK_WIDGET(button), GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST);
1268}
1269
1271{
1272 dt_develop_t *const dev = dt_dev_get_global();
1273 dt_iop_module_t *out_focus_module = dev->gui_module;
1274
1275 if(dt_gui_widgets_suppressed() || (out_focus_module == module)) return;
1276
1277 dev->gui_module = module;
1278 if(!IS_NULL_PTR(module) && !IS_NULL_PTR(module->gui))
1279 {
1280 const gboolean scroll_new_instance_to_header
1281 = (dt_gui_get_global()->scroll_to_header_once == module->gui->expander
1282 && !IS_NULL_PTR(module->gui->header) && GTK_IS_WIDGET(module->gui->header));
1283 dt_gui_get_global()->scroll_to[1] = scroll_new_instance_to_header ? module->gui->header : module->gui->expander;
1284 }
1285
1286 /* lets lose the focus of previous focus module*/
1287 if(out_focus_module)
1288 {
1289 GtkWidget *out_focus_widget = dt_iop_gui_get_pluginui(out_focus_module);
1290 GtkWidget *scroll_focus = dt_widget_scroll_focus();
1291 if(scroll_focus && out_focus_widget && gtk_widget_is_ancestor(scroll_focus, out_focus_widget))
1292 {
1294 gtk_widget_queue_draw(scroll_focus);
1295 }
1296
1297 if(out_focus_module->gui_focus)
1298 out_focus_module->gui_focus(out_focus_module, FALSE);
1299
1300 /* A module that loses focus (switching to another module, or collapsing the
1301 focused module, which calls dt_iop_request_focus(NULL)) must not leave an
1302 active picker sampling/drawn on the image behind it: the picker's own
1303 "must have GUI focus" invariant only hides its on-screen overlay, it does
1304 not stop it or reset the toggle button, so a stale enabled picker can
1305 resurface unexpectedly (e.g. when the module's GUI is rebuilt). A real
1306 reset (keep = FALSE) is required here, not the "preserve across a soft
1307 refresh" keep = TRUE used by gui_update()/gui_changed() call sites. */
1308 dt_iop_color_picker_reset(out_focus_module, FALSE);
1310
1311 gtk_widget_set_state_flags(dt_iop_gui_get_pluginui(out_focus_module), GTK_STATE_FLAG_NORMAL, TRUE);
1312
1313 /* reset mask view */
1314 dt_masks_reset_form_gui(out_focus_module->dev);
1315
1316 /* do stuff needed in the blending gui */
1317 dt_iop_gui_blending_lose_focus(out_focus_module);
1318
1319 /* redraw the expander */
1320 if(!IS_NULL_PTR(out_focus_module->gui)) gtk_widget_queue_draw(out_focus_module->gui->expander);
1321
1322 /* and finally collection restore hinter messages */
1324
1325 // we also remove the focus css class
1326 GtkWidget *iop_w = gtk_widget_get_parent(dt_iop_gui_get_pluginui(out_focus_module));
1327 dt_gui_remove_class(iop_w, "dt_module_focus");
1328 }
1329
1330 /* set the focus on module */
1331 if(!IS_NULL_PTR(module))
1332 {
1333 // In case we tried giving focus to a module that is not in the visible tab
1334 dt_dev_modulegroups_switch_tab(module->dev, module);
1335
1336 gtk_widget_set_state_flags(dt_iop_gui_get_pluginui(module), GTK_STATE_FLAG_SELECTED, TRUE);
1337
1338 if(module->gui_focus) module->gui_focus(module, TRUE);
1339
1340 /* redraw the expander */
1341 if(!IS_NULL_PTR(module->gui))
1342 {
1343 gtk_widget_queue_draw(module->gui->expander);
1344 gtk_widget_grab_focus(module->gui->expander);
1345 }
1346
1347 /* set the focus on the first child to enable arrow-key navigation and accessibility stuff */
1348 GList *widget_list = ((dt_gui_module_t *)module)->widget_list;
1349 if(widget_list)
1350 {
1351 GList *first_child = g_list_first(widget_list);
1352 if(first_child)
1353 {
1354 GtkWidget *widget = (GtkWidget *)first_child->data;
1355 if(widget) gtk_widget_grab_focus(widget);
1356 }
1357 }
1358
1359 // we also add the focus css class
1360 GtkWidget *iop_w = gtk_widget_get_parent(dt_iop_gui_get_pluginui(dev->gui_module));
1361 dt_gui_add_class(iop_w, "dt_module_focus");
1362 }
1363
1365 dt_control_queue_cursor(GDK_LEFT_PTR);
1367}
1368
1369/*
1370 * NEW EXPANDER
1371 */
1372
1373static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded)
1374{
1375 // reached for every module in dev->iop via the collapse_others fan-out: gui-less
1376 // (hidden / not-yet-inited) modules have nothing to collapse
1377 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return;
1378
1379 /* update expander arrow state */
1381
1382 /* store expanded state of module.
1383 * we do that first, so update_expanded won't think it should be visible
1384 * and undo our changes right away. */
1385 module->gui->expanded = expanded;
1386
1387 /* show / hide plugin widget */
1388 if(expanded)
1389 {
1390 /* set this module to receive focus / draw events*/
1391 dt_iop_request_focus(module);
1392
1393 /* focus the current module */
1394 for(int k = 0; k < DT_UI_CONTAINER_SIZE; k++)
1396
1397 /* redraw center, iop might have post expose */
1399 }
1400 else
1401 {
1402 if(module->dev->gui_module == module)
1403 {
1406 }
1407 }
1408
1409 if(expanded)
1410 dt_gui_add_class(module->gui->expander, "expanded");
1411 else
1412 dt_gui_remove_class(module->gui->expander, "expanded");
1413
1414 char var[1024];
1415 snprintf(var, sizeof(var), "plugins/darkroom/%s/expanded", module->op);
1416 dt_conf_set_bool(var, expanded);
1417}
1418
1420void _iop_dim_all_but(dt_iop_module_t *module, gboolean dim)
1421{
1422 for(GList *iop = g_list_first(dt_dev_get_global()->iop); iop; iop = g_list_next(iop))
1423 {
1424 dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
1425
1426 // Handle invisible modules
1427 if(IS_NULL_PTR(m) || !m->gui || !m->gui->expander) continue;
1428
1429 if(dim && m != module)
1430 dt_gui_add_class(gtk_widget_get_parent(dt_iop_gui_get_pluginui(m)), "module-dimmed");
1431 else
1432 dt_gui_remove_class(gtk_widget_get_parent(dt_iop_gui_get_pluginui(m)), "module-dimmed");
1433 }
1434}
1435
1436void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
1437{
1438 if(IS_NULL_PTR(module) || !module->gui || !module->gui->expander) return;
1439 if(collapse_others)
1440 {
1441 for(GList *iop = g_list_first(module->dev->iop); iop; iop = g_list_next(iop))
1442 {
1443 dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
1444 if(m != module) _gui_set_single_expanded(m, FALSE);
1445 }
1446 }
1447
1448 _gui_set_single_expanded(module, expanded);
1449 _iop_dim_all_but((expanded) ? module : NULL, expanded);
1450 gtk_widget_queue_draw(module->gui->widget);
1451}
1452
1454{
1455 if(IS_NULL_PTR(module->gui->expander)) return;
1456
1457 const gboolean expanded = module->gui->expanded;
1458
1460}
1461
1462static gboolean _iop_plugin_body_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1463{
1464 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1465
1466 /* Reset the scrolling focus. If the click happened on any bauhaus element,
1467 * its internal button_press method will set it for itself */
1469
1470 gboolean handled = FALSE;
1471
1472 if(e->button == 1)
1473 {
1474 dt_iop_request_focus(module);
1475 handled = TRUE;
1476 }
1477 else if(e->button == 3)
1478 {
1479 _presets_popup_callback(NULL, module);
1480 handled = TRUE;
1481 }
1482 return handled;
1483}
1484
1485static gboolean _iop_plugin_header_activate(GtkWidget* self, gboolean group_cycling, gpointer user_data)
1486{
1487 dt_gui_module_t *module = (dt_gui_module_t *)user_data;
1488 if(IS_NULL_PTR(module) || !module->focus) return FALSE;
1489 return module->focus(module, TRUE);
1490}
1491
1492static gboolean _iop_plugin_header_child_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1493{
1494 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1495 if(module && module->gui->expander)
1496 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click", GINT_TO_POINTER(TRUE));
1497
1498 return FALSE;
1499}
1500
1501static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1502 GdkModifierType modifier, gpointer data)
1503{
1504 dt_gui_module_t *module = (dt_gui_module_t *)data;
1505 dt_iop_module_t *iop = (dt_iop_module_t *)data;
1506 if(IS_NULL_PTR(module) || !module->focus) return FALSE;
1507
1508 // Accel search explicitly targets a module, so allow modulegroups to leave
1509 // the Pipeline tab once for this focus request.
1510 if(iop->gui->expander)
1511 g_object_set_data(G_OBJECT(iop->gui->expander), "dt-modulegroups-switch-from-active-once",
1512 GINT_TO_POINTER(TRUE));
1513
1514 return module->focus(module, FALSE);
1515}
1516
1517static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1518 GdkModifierType modifier, gpointer data)
1519{
1520 dt_iop_module_t *module = (dt_iop_module_t *)data;
1521 if(IS_NULL_PTR(module)) return FALSE;
1522
1523 // Direct actions from accel search should prioritize Pipeline when they focus
1524 // the edited module right after applying the change.
1525 if(!IS_NULL_PTR(module->gui->expander))
1526 g_object_set_data(G_OBJECT(module->gui->expander), "dt-modulegroups-prefer-active-once",
1527 GINT_TO_POINTER(TRUE));
1528
1529 // Kind of ugly to go through history to change module GUI state
1530 // FIXME: we should have a GUI callback that enables module and dispatches history instead,
1531 // history should not care about module GUI. This is a wrong, reversed inheritance.
1532 if(!module->hide_enable_button)
1533 {
1534 module->enabled = TRUE;
1535 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1536 }
1537
1538 dt_iop_request_focus(module);
1540 return TRUE;
1541}
1542
1543static gboolean _iop_plugin_header_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1544{
1545 if(e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) return TRUE;
1546
1547 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1548 if(IS_NULL_PTR(module)) return FALSE;
1549
1550 if(!IS_NULL_PTR(module->gui->expander)
1551 && (g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN)
1552 || g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_DISMISS_CLICK)))
1553 {
1554 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE, GINT_TO_POINTER(TRUE));
1555 return TRUE;
1556 }
1557
1558 /* Reset the scrolling focus. If the click happened on any bauhaus element,
1559 * its internal button_press method will set it for itself */
1561
1562 if(e->button == 1)
1563 {
1564 if(module->gui->expander) g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-dragged", NULL);
1565
1566 if(!dt_modifier_is(e->state, DT_PRIMARY_MASK))
1567 {
1568 return FALSE;
1569 }
1570 else
1571 {
1572 if(module->gui->expander)
1573 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE, GINT_TO_POINTER(TRUE));
1575 return TRUE;
1576 }
1577 }
1578 else if(e->button == 3)
1579 {
1580 _presets_popup_callback(NULL, module);
1581
1582 return TRUE;
1583 }
1584 return FALSE;
1585}
1586
1587static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1588{
1589 if(e->button != 1 || e->type != GDK_BUTTON_RELEASE) return FALSE;
1590
1591 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1592 if(IS_NULL_PTR(module) || !module->gui->expander) return FALSE;
1593
1594 if(g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE))
1595 {
1596 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE, NULL);
1597 return TRUE;
1598 }
1599
1600 if(g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN)
1601 || g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_DISMISS_CLICK))
1602 {
1603 return TRUE;
1604 }
1605
1606 if(g_object_get_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click"))
1607 {
1608 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click", NULL);
1609 return FALSE;
1610 }
1611
1612 if(g_object_get_data(G_OBJECT(module->gui->expander), "dt-module-dragged"))
1613 {
1614 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-dragged", NULL);
1615 return TRUE;
1616 }
1617
1618 // make gtk scroll to the module once it updated its allocation size
1619 const gboolean collapse_others = dt_modifier_is(e->state, GDK_SHIFT_MASK) ? FALSE : TRUE;
1620 dt_iop_request_focus(module);
1621 dt_iop_gui_set_expanded(module, !module->gui->expanded, collapse_others);
1622
1623 return TRUE;
1624}
1625
1626static void _display_mask_indicator_callback(GtkToggleButton *bt, dt_iop_module_t *module)
1627{
1628 if(dt_gui_widgets_suppressed()) return;
1629
1630 const gboolean is_active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bt));
1632
1633 module->request_mask_display
1634 &= ~(DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
1635 | DT_DEV_PIXELPIPE_DISPLAY_ANY | DT_DEV_PIXELPIPE_DISPLAY_STICKY);
1636
1637 if(is_active)
1638 module->request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_MASK;
1639
1641
1642 // set the module show mask button too
1643 if(bd->showmask)
1644 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->showmask), is_active);
1645
1647 if(GTK_IS_TOGGLE_BUTTON(bd->filter[0].channel_display))
1648 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->filter[0].channel_display), FALSE);
1649 if(GTK_IS_TOGGLE_BUTTON(bd->filter[1].channel_display))
1650 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->filter[1].channel_display), FALSE);
1652
1653 dt_iop_request_focus(module);
1655}
1656
1657static void _mask_indicator_get_usage(dt_iop_module_t *module, gboolean *top_enabled, gboolean *raster_used,
1658 gboolean *drawn_used, gboolean *parametric_used)
1659{
1660 if(!IS_NULL_PTR(top_enabled)) *top_enabled = FALSE;
1661 if(!IS_NULL_PTR(raster_used)) *raster_used = FALSE;
1662 if(!IS_NULL_PTR(drawn_used)) *drawn_used = FALSE;
1663 if(!IS_NULL_PTR(parametric_used)) *parametric_used = FALSE;
1664
1665 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->blend_params)) return;
1666
1667 const dt_iop_gui_blend_data_t *bd = (const dt_iop_gui_blend_data_t *)module->gui->blend_data;
1668 gboolean top = FALSE;
1669 gboolean raster = FALSE;
1670 gboolean drawn = FALSE;
1671 gboolean parametric = FALSE;
1672 dt_develop_blend_get_mask_usage(module, module->blend_params, &top, &raster, &drawn, &parametric);
1673
1674 // look if the user disabled masks modes
1675
1676 // raster mask must be enabled and a raster mask must be selected in the combo
1677 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->raster_enable)
1678 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->raster_enable)))
1679 raster = FALSE;
1680
1681 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->masks_enable)
1682 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->masks_enable)))
1683 drawn = FALSE;
1684
1685 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->blendif_enable)
1686 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->blendif_enable)))
1687 parametric = FALSE;
1688
1689 if(!IS_NULL_PTR(top_enabled)) *top_enabled = top;
1690 if(!IS_NULL_PTR(raster_used)) *raster_used = raster;
1691 if(!IS_NULL_PTR(drawn_used)) *drawn_used = drawn;
1692 if(!IS_NULL_PTR(parametric_used)) *parametric_used = parametric;
1693}
1694
1695static gboolean _mask_indicator_tooltip(GtkWidget *treeview, gint x, gint y, gboolean kb_mode,
1696 GtkTooltip* tooltip, dt_iop_module_t *module)
1697{
1698 (void)treeview;
1699 (void)x;
1700 (void)y;
1701 (void)kb_mode;
1702
1703 gboolean res = FALSE;
1704 if(module->gui->mask_indicator)
1705 {
1706 gchar *type = _("unknown mask");
1707 gchar *text;
1708 gboolean top_enabled = FALSE, raster_used = FALSE, drawn_used = FALSE, parametric_used = FALSE;
1709 _mask_indicator_get_usage(module, &top_enabled, &raster_used, &drawn_used, &parametric_used);
1710 if(!top_enabled) return FALSE;
1711
1712 if(drawn_used && parametric_used)
1713 type=_("drawn + parametric mask");
1714 else if(drawn_used)
1715 type=_("drawn mask");
1716 else if(parametric_used)
1717 type=_("parametric mask");
1718 else if(raster_used)
1719 type=_("raster mask");
1720 else
1721 return FALSE;
1722 gchar *part1 = g_strdup_printf(_("this module has a '%s'"), type);
1723 gchar *part2 = NULL;
1724 if(raster_used && module->raster_mask.sink.source)
1725 {
1726 gchar *source = dt_history_item_get_name(module->raster_mask.sink.source);
1727 part2 = g_strdup_printf(_("taken from module %s"), source);
1728 dt_free(source);
1729 }
1730
1731 if(part2)
1732 {
1733 gchar *details = g_strdup_printf("%s\n%s", part2, _("click to display (module must be activated first)"));
1734 dt_free(part2);
1735 part2 = details;
1736 }
1737 else
1738 {
1739 part2 = g_strdup(_("click to display (module must be activated first)"));
1740 }
1741
1742 if(part2)
1743 text = g_strconcat(part1, "\n", part2, NULL);
1744 else
1745 text = g_strdup(part1);
1746
1747 gtk_tooltip_set_text(tooltip, text);
1748 res = TRUE;
1749 dt_free(part1);
1750 dt_free(part2);
1751 dt_free(text);
1752 }
1753 return res;
1754}
1755
1757{
1758 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->mask_indicator)) return;
1759
1760 const gboolean support_blending = (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) == IOP_FLAGS_SUPPORTS_BLENDING;
1761
1762 if(!support_blending || !module->blend_params)
1763 {
1764 gtk_widget_set_visible(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1765 gtk_widget_set_has_tooltip(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1766 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1767 return;
1768 }
1769
1770 gboolean top_enabled = FALSE, raster_used = FALSE, drawn_used = FALSE, parametric_used = FALSE;
1771 _mask_indicator_get_usage(module, &top_enabled, &raster_used, &drawn_used, &parametric_used);
1772
1773 const gboolean use_masks = top_enabled && (raster_used || drawn_used || parametric_used);
1774
1775 gtk_widget_set_visible(GTK_WIDGET(module->gui->mask_indicator), use_masks);
1776 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->mask_indicator), module->enabled);
1777 gtk_widget_set_has_tooltip(GTK_WIDGET(module->gui->mask_indicator), use_masks);
1778}
1779
1780gboolean _iop_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
1781 GtkTooltip *tooltip, gpointer user_data)
1782{
1783 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1784
1785 const char **des = module->description(module);
1786
1787 if(IS_NULL_PTR(des)) return FALSE;
1788
1789 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1790 GtkWidget *grid = gtk_grid_new();
1791 gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
1792 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
1793 gtk_widget_set_hexpand(grid, FALSE);
1794
1795 GtkWidget *label = gtk_label_new(des[0] ? des[0] : "");
1796 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1797 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1798 gtk_label_set_max_width_chars(GTK_LABEL(label), 40);
1799 // if there is no more description, do not add a separator
1800 if(des[1]) dt_gui_add_class(label, "dt_section_label");
1801 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1802
1803 gtk_widget_set_size_request(label, DT_PIXEL_APPLY_DPI(300), -1);
1804 gtk_widget_set_size_request(grid, DT_PIXEL_APPLY_DPI(300), -1);
1805 gtk_widget_set_size_request(vbox, DT_PIXEL_APPLY_DPI(300), -1);
1806
1807 const char *icon_purpose = "\342\237\263";
1808 const char *icon_input = "\342\207\245";
1809 const char *icon_process = "\342\237\264";
1810 const char *icon_output = "\342\206\246";
1811
1812 const char *icons[4] = {icon_purpose, icon_input, icon_process, icon_output};
1813 const char *ilabs[4] = {_("Purpose"), _("Input"), _("Process"), _("Output")};
1814
1815 for(int k=1; k<5; k++)
1816 {
1817 if(des[k])
1818 {
1819 label = gtk_label_new(icons[k-1]);
1820 gtk_widget_set_halign(label, GTK_ALIGN_START);
1821 gtk_grid_attach(GTK_GRID(grid), label, 0, k, 1, 1);
1822 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1823
1824 label = gtk_label_new(ilabs[k-1]);
1825 gtk_widget_set_halign(label, GTK_ALIGN_START);
1826 gtk_grid_attach(GTK_GRID(grid), label, 1, k, 1, 1);
1827 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1828
1829 label = gtk_label_new(":");
1830 gtk_widget_set_halign(label, GTK_ALIGN_START);
1831 gtk_grid_attach(GTK_GRID(grid), label, 2, k, 1, 1);
1832 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1833
1834 label = gtk_label_new(des[k]);
1835 gtk_widget_set_halign(label, GTK_ALIGN_START);
1836 gtk_grid_attach(GTK_GRID(grid), label, 3, k, 1, 1);
1837 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1838 }
1839 }
1840
1841 gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 0);
1842 gtk_widget_show_all(vbox);
1843 gtk_tooltip_set_custom(tooltip, vbox);
1844
1845 return TRUE;
1846}
1847
1849{
1850 GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING / 2.);
1851 gtk_widget_set_name(GTK_WIDGET(header), "module-header");
1852
1853 GtkWidget *iopw = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1854 GtkWidget *expander = dtgtk_expander_new(header, iopw);
1855 dt_gui_add_class(expander, "dt_module_frame");
1856 dt_gui_add_class(expander, "dt_iop_module");
1857
1860 GtkWidget *pluginui_frame = dtgtk_expander_get_frame(DTGTK_EXPANDER(expander));
1861
1862 dt_gui_add_class(pluginui_frame, "dt_plugin_ui");
1863
1864 module->gui->header = header;
1865
1866 /* setup the header box */
1867 g_signal_connect(G_OBJECT(header_evb), "button-press-event", G_CALLBACK(_iop_plugin_header_button_press), module);
1868 g_signal_connect(G_OBJECT(header_evb), "button-release-event", G_CALLBACK(_iop_plugin_header_button_release), module);
1869 g_signal_connect(G_OBJECT(header_evb), "mnemonic-activate", G_CALLBACK(_iop_plugin_header_activate), module);
1870 gtk_widget_add_events(header_evb, GDK_POINTER_MOTION_MASK);
1871
1872 /* connect mouse button callbacks for focus and presets */
1873 g_signal_connect(G_OBJECT(body_evb), "button-press-event", G_CALLBACK(_iop_plugin_body_button_press), module);
1874 gtk_widget_add_events(body_evb, GDK_POINTER_MOTION_MASK);
1875
1876 /*
1877 * initialize the header widgets
1878 */
1879 GtkWidget *hw[IOP_MODULE_LAST] = { NULL };
1880
1881 /* init empty place for icon, this is then set in CSS if needed */
1882 char w_name[256] = { 0 };
1883 snprintf(w_name, sizeof(w_name), "iop-panel-icon-%s", module->op);
1884 hw[IOP_MODULE_ICON] = gtk_label_new("");
1885 gtk_widget_set_name(GTK_WIDGET(hw[IOP_MODULE_ICON]), w_name);
1886
1887 /* add module label */
1888 hw[IOP_MODULE_LABEL] = gtk_event_box_new();
1889 GtkWidget *lab = hw[IOP_MODULE_LABEL];
1890 GtkWidget *label = gtk_label_new_with_mnemonic("");
1891 gtk_container_add(GTK_CONTAINER(lab), label);
1892 gtk_label_set_mnemonic_widget(GTK_LABEL(label), header_evb);
1893
1894 if((module->flags() & IOP_FLAGS_DEPRECATED) && module->deprecated_msg())
1895 gtk_widget_set_tooltip_text(lab, module->deprecated_msg());
1896 else
1897 {
1898 gtk_widget_set_name(lab, "iop_description");
1899 g_signal_connect(lab, "query-tooltip", G_CALLBACK(_iop_tooltip_callback), module);
1900 }
1901
1902 /* add mask preview button */
1904
1905 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "toggled",
1906 G_CALLBACK(_display_mask_indicator_callback), module);
1907 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "button-press-event",
1908 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1909 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "query-tooltip",
1910 G_CALLBACK(_mask_indicator_tooltip), module);
1911 module->gui->mask_indicator = hw[IOP_MODULE_MASK];
1912
1913 /* add multi instances menu button */
1915 module->gui->multimenu_button = GTK_WIDGET(hw[IOP_MODULE_INSTANCE]);
1916 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_INSTANCE]),
1917 _("multiple instance actions\nright-click creates new instance"));
1918 g_signal_connect(G_OBJECT(hw[IOP_MODULE_INSTANCE]), "button-press-event",
1919 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1920 g_signal_connect(G_OBJECT(hw[IOP_MODULE_INSTANCE]), "button-press-event", G_CALLBACK(_gui_multiinstance_callback),
1921 module);
1922
1923 dt_gui_add_help_link(expander, dt_get_help_url(module->op));
1924
1925 /* add reset button */
1927 module->gui->reset_button = GTK_WIDGET(hw[IOP_MODULE_RESET]);
1928 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_RESET]), _("reset parameters\nctrl+click to reapply any automatic presets"));
1929 g_signal_connect(G_OBJECT(hw[IOP_MODULE_RESET]), "button-press-event",
1930 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1931 g_signal_connect(G_OBJECT(hw[IOP_MODULE_RESET]), "button-press-event", G_CALLBACK(_gui_reset_callback), module);
1932
1933 /* add preset button if module has implementation */
1935 module->gui->presets_button = GTK_WIDGET(hw[IOP_MODULE_PRESETS]);
1936 if(!(module->flags() & IOP_FLAGS_ONE_INSTANCE))
1937 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_PRESETS]), _("presets\nright-click to apply on new instance"));
1938 g_signal_connect(G_OBJECT(hw[IOP_MODULE_PRESETS]), "button-press-event",
1939 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1940 g_signal_connect(G_OBJECT(hw[IOP_MODULE_PRESETS]), "clicked", G_CALLBACK(_presets_popup_callback), module);
1941
1942 /* add enabled button */
1944
1945 dt_gui_add_class(switch_button, "dt_iop_enable_button");
1946 dt_iop_gui_set_enable_button_icon(switch_button, module);
1947 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(switch_button), module->enabled);
1948 g_signal_connect(G_OBJECT(switch_button), "button-press-event",
1949 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1950 g_signal_connect(G_OBJECT(switch_button), "toggled", G_CALLBACK(_gui_off_callback), module);
1951
1952 module->gui->off = switch_button;
1953 gtk_widget_set_sensitive(GTK_WIDGET(switch_button), !module->hide_enable_button);
1954
1955 /* Wrap the switch in a plain box so the CSS spacing trick that visually tucks it
1956 against the header edge (negative margin, see ansel.css) lives on the wrapper,
1957 not on the button itself. dtgtk_togglebutton's custom draw() reinterprets its own
1958 CSS margin a second time (as a paint-only offset) to size its icon -- if that same
1959 margin also carries a position nudge, the icon gets painted outside the button's
1960 real GTK allocation, which is what drives hover/click hit-testing. Keeping the
1961 button's own margin at 0 keeps its drawing and its hit-region intrinsically in
1962 sync; the wrapper absorbs the nudge at the layout level instead. */
1963 hw[IOP_MODULE_SWITCH] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1964 dt_gui_add_class(hw[IOP_MODULE_SWITCH], "dt_iop_enable_button_box");
1965 gtk_container_add(GTK_CONTAINER(hw[IOP_MODULE_SWITCH]), switch_button);
1966
1967 /* reorder header, for now, iop are always in the right panel */
1968 for(int i = 0; i <= IOP_MODULE_LABEL; i++)
1969 {
1970 if(hw[i])
1971 gtk_box_pack_start(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
1972 }
1973 for(int i = IOP_MODULE_LAST - 1; i > IOP_MODULE_LABEL; i--)
1974 {
1975 if(hw[i])
1976 gtk_box_pack_end(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
1977 }
1978
1979 dt_gui_add_help_link(header, dt_get_help_url("module_header"));
1980 // for the module label, point to module specific help page
1982
1983 gtk_widget_set_halign(hw[IOP_MODULE_LABEL], GTK_ALIGN_START);
1984 gtk_widget_set_halign(hw[IOP_MODULE_INSTANCE], GTK_ALIGN_END);
1985
1986 // show deprecated message if any
1987 if(module->deprecated_msg())
1988 {
1989 GtkWidget *lb = gtk_label_new(module->deprecated_msg());
1990 gtk_label_set_line_wrap(GTK_LABEL(lb), TRUE);
1991 gtk_label_set_xalign(GTK_LABEL(lb), 0.0);
1992 dt_gui_add_class(lb, "dt_warning");
1993 gtk_box_pack_start(GTK_BOX(iopw), lb, TRUE, TRUE, 0);
1994 gtk_widget_show(lb);
1995 }
1996
1997 /* initialize blending state if supported; the detached widget is hosted by the masks lib */
1998 gtk_box_pack_start(GTK_BOX(iopw), module->gui->widget, TRUE, TRUE, 0);
2000 dt_gui_add_class(module->gui->widget, "dt_plugin_ui_main");
2001 dt_gui_add_help_link(module->gui->widget, dt_get_help_url(module->op));
2002 gtk_widget_hide(iopw);
2003
2004 module->gui->expander = expander;
2005 g_object_weak_ref(G_OBJECT(header), _iop_gui_widget_gone, module);
2006 g_object_weak_ref(G_OBJECT(expander), _iop_gui_widget_gone, module);
2007
2008 /* update header */
2010
2011 gtk_widget_set_hexpand(module->gui->widget, FALSE);
2012 gtk_widget_set_vexpand(module->gui->widget, FALSE);
2013
2015}
2016
2018{
2019 // NULL for a module with no GUI half, the way the flat module->expander was NULL before
2020 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return NULL;
2022}
2023
2025{
2026 // return gtkframe (pluginui_frame)
2027 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return NULL;
2029}
2030
2031void dt_iop_gui_changed(dt_iop_module_t *action, GtkWidget *widget, gpointer data)
2032{
2033 if(IS_NULL_PTR(action)) return;
2034 dt_iop_module_t *module = (dt_iop_module_t *)action;
2035
2036 if(module->gui_changed) module->gui_changed(module, widget, data);
2037
2039
2040 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
2041
2042 if(!IS_NULL_PTR(widget) && g_object_get_data(G_OBJECT(widget), "dt-blendop-header-update"))
2044 else
2046}
2047
2048
2050{
2052
2053 for(GList *w = g_list_first(m->widget_list_bh); w; w = g_list_next(w))
2054 {
2055 GtkWidget *widget = (GtkWidget *)w->data;
2056 struct dt_bauhaus_widget_t *bhw = DT_BAUHAUS_WIDGET(widget);
2057 if(IS_NULL_PTR(bhw)) continue;
2058
2059 switch(bhw->type)
2060 {
2061 case DT_BAUHAUS_SLIDER:
2062 switch(bhw->field_type)
2063 {
2065 dt_bauhaus_slider_set(widget, *(float *)bhw->field);
2066 break;
2068 dt_bauhaus_slider_set(widget, *(int *)bhw->field);
2069 break;
2071 dt_bauhaus_slider_set(widget, *(unsigned short *)bhw->field);
2072 break;
2073 default:
2074 fprintf(stderr, "[dt_bauhaus_update_module] unsupported slider data type\n");
2075 }
2076 break;
2078 switch(bhw->field_type)
2079 {
2081 dt_bauhaus_combobox_set_from_value(widget, *(int *)bhw->field);
2082 break;
2084 dt_bauhaus_combobox_set(widget, *(int *)bhw->field);
2085 break;
2087 dt_bauhaus_combobox_set(widget, *(unsigned int *)bhw->field);
2088 break;
2090 dt_bauhaus_combobox_set(widget, *(gboolean *)bhw->field);
2091 break;
2092 default:
2093 fprintf(stderr, "[dt_bauhaus_update_module] unsupported combo data type\n");
2094 }
2095 break;
2096 default:
2097 fprintf(stderr, "[dt_bauhaus_update_module] invalid bauhaus widget type encountered\n");
2098 }
2099 }
2100}
2101
2103{
2105 dt_iop_module_t *module = (dt_iop_module_t *)w->module;
2106 if(IS_NULL_PTR(w->field) || IS_NULL_PTR(module)) return;
2107
2108 switch(w->type)
2109 {
2110 case DT_BAUHAUS_SLIDER:
2111 {
2112 float val = dt_bauhaus_slider_get(widget);
2113 switch(w->field_type)
2114 {
2116 {
2117 float *f = w->field, prevf = *f; *f = val;
2118 if(*f != prevf) dt_iop_gui_changed(module, widget, &prevf);
2119 break;
2120 }
2122 {
2123 int *i = w->field, previ = *i; *i = val;
2124 if(*i != previ) dt_iop_gui_changed(module, widget, &previ);
2125 break;
2126 }
2128 {
2129 unsigned short *s = w->field, prevs = *s; *s = val;
2130 if(*s != prevs) dt_iop_gui_changed(module, widget, &prevs);
2131 break;
2132 }
2133 default:
2134 fprintf(stderr, "[_bauhaus_slider_value_change] unsupported slider data type\n");
2135 }
2136 break;
2137 }
2139 {
2141 switch(w->field_type)
2142 {
2144 {
2145 if(d->active >= 0)
2146 {
2147 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
2148 int *e = w->field, preve = *e; *e = GPOINTER_TO_INT(entry->data);
2149 if(*e != preve) dt_iop_gui_changed(module, widget, &preve);
2150 }
2151 break;
2152 }
2154 {
2155 int *i = w->field, previ = *i; *i = d->active;
2156 if(*i != previ) dt_iop_gui_changed(module, widget, &previ);
2157 break;
2158 }
2160 {
2161 unsigned int *u = w->field, prevu = *u; *u = d->active;
2162 if(*u != prevu) dt_iop_gui_changed(module, widget, &prevu);
2163 break;
2164 }
2166 {
2167 gboolean *b = w->field, prevb = *b; *b = d->active;
2168 if(*b != prevb) dt_iop_gui_changed(module, widget, &prevb);
2169 break;
2170 }
2171 default:
2172 fprintf(stderr, "[_bauhaus_combobox_set] unsupported combo data type\n");
2173 }
2174 break;
2175 }
2176 default:
2177 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);
2178 }
2179}
2181{
2182 // conditional on module->gui and paired with a dt_iop_gui_leave_critical_section() call at
2183 // an unrelated call site, which the thread-safety analyzer can't model -- same reason
2184 // dt_opencl_reserve_device_by_id()/dt_opencl_release_device() use the BAD variants.
2185 if(module->gui) dt_pthread_mutex_BAD_lock(&module->gui->gui_lock);
2186}
2187
2189{
2190 if(module->gui) dt_pthread_mutex_BAD_unlock(&module->gui->gui_lock);
2191}
2192
2194{
2195 return module && module->gui ? module->gui->off : NULL;
2196}
2197
2198gboolean dt_iop_gui_owns_widget(const dt_iop_module_t *module, const GtkWidget *target)
2199{
2200 return module->gui && (module->gui->expander == (const void *)target || module->gui->header == (const void *)target);
2201}
2202
2203// clang-format off
2204// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2205// vim: shiftwidth=2 expandtab tabstop=2 cindent
2206// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2207// clang-format on
2208
2209/* Attached to each reset label; freed with the widget. */
2211{
2212 dt_iop_module_t *module;
2213 int offset; // offset of the parameter inside module->params
2214 int size; // its size in bytes
2216
2217static void _iop_reset_label_reset(GtkWidget *widget, gpointer user_data)
2218{
2220 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->module)) return;
2221
2222 memcpy(((char *)d->module->params) + d->offset, ((char *)d->module->default_params) + d->offset, d->size);
2223 if(d->module->gui_update) d->module->gui_update(d->module);
2225}
2226
2227GtkWidget *dt_iop_gui_reset_label_new(const gchar *label, dt_iop_module_t *module, void *param,
2228 int param_size)
2229{
2230 GtkWidget *w = dtgtk_reset_label_new(label);
2231
2233 d->module = module;
2234 d->offset = param - (void *)module->params;
2235 d->size = param_size;
2236
2237 g_signal_connect_data(G_OBJECT(w), "reset", G_CALLBACK(_iop_reset_label_reset), d,
2238 (GClosureNotify)g_free, 0);
2239 return w;
2240}
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)
Build the accel path of feature under scope.
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, w, 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:1630
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3283
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2122
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:1641
void dt_bauhaus_widget_set_field(GtkWidget *widget, gpointer field, dt_introspection_type_t field_type)
Definition bauhaus.c:1565
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:1849
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
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:1701
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1827
@ 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:301
void dt_iop_gui_init_blending(dt_iop_module_t *module)
Definition blend_gui.c:5170
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4467
void dt_iop_gui_blending_lose_focus(dt_iop_module_t *module)
Definition blend_gui.c:4764
void dt_iop_gui_cleanup_blending(dt_iop_module_t *module)
Definition blend_gui.c:4316
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:970
#define dt_control_queue_cursor(cursor)
Definition control.h:140
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
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
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:1395
void dt_dev_module_remove(dt_develop_t *dev, dt_iop_module_t *module)
Definition develop.c:1509
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1921
gchar * dt_history_item_get_label(const struct dt_iop_module_t *module)
Definition develop.c:1680
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1932
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1712
@ 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:319
void dt_iop_reload_defaults(dt_iop_module_t *module)
Definition imageop.c:792
void dt_iop_commit_blend_params(dt_iop_module_t *module, const dt_develop_blend_params_t *blendop_params)
Definition imageop.c:1234
void dt_iop_gui_reset(dt_iop_module_t *module)
Definition imageop.c:1662
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:736
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1685
void dt_iop_update_multi_priority(dt_iop_module_t *module, int new_priority)
Definition imageop.c:1817
@ 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 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 gboolean _iop_listens_to_picker_data(const 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:56
static void _presets_popup_callback(GtkButton *button, dt_iop_module_t *module)
static gboolean _gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
#define DT_IOP_HEADER_MENU_DISMISS_CLICK
Definition imageop_gui.c:54
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:53
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:75
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
_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)
Definition masks.c:1178
@ 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)
Definition masks.c:1217
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:925
void dt_masks_reset_form_gui(dt_develop_t *dev)
Definition masks_gui.c:4894
void dt_masks_group_update_name(dt_iop_module_t *module)
Definition masks_gui.c:4968
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:701
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:390
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:316
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:324
@ DT_SIGNAL_CONTROL_PICKERDATA_READY
This signal is raised when new color picker data are available in darkroom. no param,...
Definition signal.h:297
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
const float uint32_t state[4]
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
GList * iop
Definition develop.h:269
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...
char * instance_accel_path
GtkWidget * raster_enable
Definition blend_gui.h:98
GtkWidget * blendif_enable
Definition blend_gui.h:99
dt_iop_gui_blendif_filter_t filter[2]
Definition blend_gui.h:110
GtkWidget * masks_enable
Definition blend_gui.h:97
GtkWidget * header
Definition imageop_gui.h:53
GtkWidget * mask_indicator
Definition imageop_gui.h:55
GtkWidget * expander
Definition imageop_gui.h:57
GtkWidget * widget
Definition imageop_gui.h:47
dt_pthread_mutex_t gui_lock
Definition imageop_gui.h:43
int32_t hide_enable_button
Definition imageop.h:270
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
char multi_name[128]
Definition imageop.h:387
struct dt_develop_t * dev
Definition imageop.h:311
gboolean default_enabled
Definition imageop.h:318
int32_t instance
Definition imageop.h:266
gboolean enabled
Definition imageop.h:313
dt_iop_module_so_t * so
Definition imageop.h:376
int request_mask_display
Definition imageop.h:276
struct dt_iop_module_t::@23::@24 source
struct dt_iop_module_t::@23::@25 sink
struct dt_develop_blend_params_t * default_blendop_params
Definition imageop.h:349
struct dt_iop_module_t::@23 raster_mask
dt_dev_operation_t op
Definition imageop.h:259
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:163
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