Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
imageop_gui.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2022 Diederik Ter Rahe.
4 Copyright (C) 2020-2022 Pascal Obry.
5 Copyright (C) 2022 Aldric Renaudin.
6 Copyright (C) 2022 Martin Bařinka.
7 Copyright (C) 2022 Miloš Komarčević.
8 Copyright (C) 2025 Aurélien PIERRE.
9
10 darktable is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 darktable is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with darktable. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#include "widgets/container.h"
25#include "common/telemetry.h"
26#include "common/sentry.h"
27#include "widgets/gdkkeys.h"
28#include "widgets/popup.h"
29#include "gui/application.h"
31#include "common/collection.h"
32#include "common/hash.h"
33#include "common/module.h"
36#include "control/control.h"
37#include "control/signal.h"
40#include "develop/blend.h"
41#include "develop/blend_gui.h"
43#include "develop/masks_gui.h"
45#include "gui/presets.h"
46#include "widgets/expander.h"
47#include "widgets/label.h"
50#include "history/notify.h"
51#include "develop/imageop_gui.h"
52#define DT_IOP_HEADER_MENU_OPEN "dt-module-header-menu-open"
53#define DT_IOP_HEADER_MENU_DISMISS_CLICK "dt-module-header-menu-dismiss-click"
54
55#define DT_IOP_HEADER_IGNORE_RELEASE "dt-module-header-ignore-release"
56static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded);
57static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data);
58static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
59 GdkModifierType modifier, gpointer data);
60static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
61 GdkModifierType modifier, gpointer data);
62#include "develop/dev_history.h"
63#include "develop/develop.h"
64#include "widgets/resetlabel.h"
65#include "common/conf.h"
66#include "develop/imageop.h"
67#include "widgets/bauhaus.h"
68#include "widgets/button.h"
69#include "system/macros.h"
70#include "system/mem_alloc.h"
71#include "common/utility.h"
72
73
74#ifdef GDK_WINDOWING_QUARTZ
75#endif
76
77#include <assert.h>
78#include <math.h>
79#include <stdlib.h>
80#include <string.h>
81#include <time.h>
83
84typedef struct dt_module_param_t
85{
86 dt_iop_module_t *module;
87 void *param;
89
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;
940 if(mod->instance_name)
941 {
942 char *instance_path = dt_accels_build_path(_("Darkroom/Modules/Instances"), mod->instance_name);
944 dt_free(instance_path);
946 }
947
948 gchar *clean_name = delete_underscore(module->name());
949 dt_capitalize_label(clean_name);
950
951 mod->instance_name
952 = g_strdup_printf("%s/%s", clean_name, (module->multi_name[0] != '\0') ? module->multi_name : "0");
953
955 dt_gui_get_accels()->darkroom_accels, _("Darkroom/Modules/Instances"),
956 mod->instance_name);
957
958 dt_free(clean_name);
959
960 gtk_label_set_ellipsize(GTK_LABEL(lab), !module->multi_name[0] ? PANGO_ELLIPSIZE_END: PANGO_ELLIPSIZE_MIDDLE);
961 g_object_set(G_OBJECT(lab), "xalign", 0.0, (gchar *)0);
962}
963
965{
966 if (IS_NULL_PTR(module->gui)) return; /* module has no GUI half at all */
967 if (IS_NULL_PTR(module->gui->header)) /* some modules such as overexposed don't actually have a header */
968 return;
969
970 // set panel name to display correct multi-instance
971 _iop_panel_label(module);
974}
975
992
994{
995 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui)) return;
996
997 if(module->gui->off)
998 {
999 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), module->enabled);
1000 if(module->hide_enable_button)
1001 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->off), FALSE);
1002 else
1003 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->off), TRUE);
1004
1005 dt_gui_remove_class(GTK_WIDGET(module->gui->off), "dt_iop_enable_forced_on");
1006 dt_gui_remove_class(GTK_WIDGET(module->gui->off), "dt_iop_enable_forced_off");
1007 if(module->hide_enable_button)
1008 {
1009 dt_gui_add_class(GTK_WIDGET(module->gui->off),
1010 module->enabled ? "dt_iop_enable_forced_on" : "dt_iop_enable_forced_off");
1011 }
1012
1013 dt_iop_gui_set_enable_button_icon(GTK_WIDGET(module->gui->off), module);
1014 }
1015}
1016
1018{
1019 // The module's interactive half exists from here until dt_iop_gui_cleanup_module();
1020 // its absence IS the headless flag (see dt_iop_module_gui_t in imageop_gui.h).
1021 if(IS_NULL_PTR(module->gui)) module->gui = (dt_iop_module_gui_t *)calloc(1, sizeof(dt_iop_module_gui_t));
1022
1023 // Suppress widget value-changed callbacks for the whole GUI build. Setting a slider's soft
1024 // range (etc.) in gui_init emits "value-changed", which would re-enter the module's
1025 // gui_changed handler before its sibling widgets exist and crash on dt_bauhaus_*(NULL)
1026 // (Sentry #129494618, #129578628). The scope guard releases the freeze automatically on every
1027 // exit path, and the central depth is GUI-thread-only and self-healing, so it cannot drift.
1029
1030 // Add the accelerators
1031 if(!dt_iop_is_hidden(module) && !(module->flags() & IOP_FLAGS_DEPRECATED))
1032 {
1033 gchar *clean_name = delete_underscore(module->name());
1034 dt_capitalize_label(clean_name);
1035
1036 // slash is not allowed in module names because that makes accel pathes fail
1037 assert(g_strrstr(clean_name, "/") == NULL);
1038
1039 dt_gui_module_t *mod = (dt_gui_module_t *)module;
1040 const gchar *const main_scope = _("Darkroom/Modules");
1041 mod->accel_path = dt_accels_build_path(main_scope, clean_name);
1042
1043 dt_accels_new_darkroom_action(_iop_plugin_focus_accel, module, NULL, main_scope, clean_name, 0, 0,
1044 _("Focuses the module"));
1045
1046 // NOTE: we should enable the accel only if the module is disable-able, but this property is set at runtime
1047 // in reload_defaults(), which depends on the image metadata for each pipeline.
1048 // We have no way of knowing here at init time.
1049 // if(!module->hide_enable_button)
1050 dt_accels_new_darkroom_action(_iop_plugin_enable_accel, module, NULL, mod->accel_path, _("Enable"), 0, 0,
1051 _("Enables the module"));
1052
1053 dt_free(clean_name);
1054 }
1055
1056 // We absolutely need to init the module controls after the module object
1057 if(module->gui_init) module->gui_init(module);
1058 if(_iop_listens_to_picker_data(module))
1059 {
1061 G_CALLBACK(_iop_color_picker_data_ready_callback), module);
1062 }
1063 // the freeze ends here as the scope guard goes out of scope
1064}
1065
1072static void _iop_gui_widget_gone(gpointer user_data, GObject *where_the_object_was)
1073{
1074 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1075 if(IS_NULL_PTR(module)) return;
1076
1077 if(!IS_NULL_PTR(module->gui))
1078 {
1079 if(module->gui->header == (GtkWidget *)where_the_object_was) module->gui->header = NULL;
1080 if(module->gui->expander == (GtkWidget *)where_the_object_was) module->gui->expander = NULL;
1081 }
1082
1083 if(IS_NULL_PTR(dt_gui_get_global())) return;
1084
1085 if(dt_gui_get_global()->scroll_to[0] == (GtkWidget *)where_the_object_was) dt_gui_get_global()->scroll_to[0] = NULL;
1086 if(dt_gui_get_global()->scroll_to[1] == (GtkWidget *)where_the_object_was) dt_gui_get_global()->scroll_to[1] = NULL;
1087 if(dt_gui_get_global()->scroll_to_header_once == (GtkWidget *)where_the_object_was) dt_gui_get_global()->scroll_to_header_once = NULL;
1088}
1089
1091{
1092 if(IS_NULL_PTR(module)) return;
1093 // backend-only modules (hidden, or loaded without gui_init -- see studio-capture.md)
1094 // have nothing to clean up here
1095 if(IS_NULL_PTR(module->gui)) return;
1096 dt_gui_module_t *mod = (dt_gui_module_t *)module;
1097 if(!IS_NULL_PTR(module->dev) && module->dev->gui_module == module) module->dev->gui_module = NULL;
1098
1099 // remove multiple delayed gtk_widget_queue_draw triggers
1100 if(module->gui->widget)
1101 while(g_idle_remove_by_data(module->gui->widget));
1102
1103 // Detach accels. accel_path is only ever set by dt_iop_gui_init() (never for a module that was
1104 // only backend-loaded, e.g. studio_capture's dev->iop -- see studio-capture.md): a NULL path
1105 // here means there is nothing registered to remove, and dt_accels_remove_accel() would
1106 // otherwise g_strrstr() every entry in accels->acceleratables against a NULL needle, tripping
1107 // a GLib-CRITICAL for each one.
1108 if(!dt_iop_is_hidden(module) && !(module->flags() & IOP_FLAGS_DEPRECATED) && !IS_NULL_PTR(mod->accel_path))
1109 {
1111 dt_free(mod->accel_path);
1112 }
1113
1114 if(mod->instance_name)
1115 {
1116 char *instance_path = dt_accels_build_path(_("Darkroom/Modules/Instances"), mod->instance_name);
1118 dt_free(instance_path);
1119 }
1120
1121 dt_free(mod->instance_name);
1122
1123 // widget_list doesn't own the widget referenced, so don't deep_free
1124 dt_gui_module_t *m = DT_GUI_MODULE(module);
1125 g_list_free(m->widget_list);
1126 m->widget_list = NULL;
1127 g_list_free(m->widget_list_bh);
1128 m->widget_list_bh = NULL;
1129 dt_free(m->name);
1130 m->name = NULL;
1131 dt_free(m->view);
1132 m->view = NULL;
1133
1134 if(_iop_listens_to_picker_data(module))
1135 {
1137 }
1138 // History refresh can delete pipeline-only modules created for ordering/history
1139 // resolution. They have a module GUI cleanup callback but no module GUI data.
1140 if(module->gui_cleanup && !IS_NULL_PTR(dt_iop_gui_data(module)))
1141 module->gui_cleanup(module);
1143
1144 // size-allocate callbacks can still read scroll targets while GTK tears down widgets
1146 {
1147 if(dt_gui_get_global()->scroll_to[0] == module->gui->header || dt_gui_get_global()->scroll_to[0] == module->gui->expander)
1148 dt_gui_get_global()->scroll_to[0] = NULL;
1149 if(dt_gui_get_global()->scroll_to[1] == module->gui->header || dt_gui_get_global()->scroll_to[1] == module->gui->expander)
1150 dt_gui_get_global()->scroll_to[1] = NULL;
1151 if(dt_gui_get_global()->scroll_to_header_once == module->gui->expander)
1153 }
1154
1155 /* Release the transient widget tree explicitly. In normal GUI lifetime, these
1156 * widgets are parented and get destroyed by container teardown. During module
1157 * probe/init paths, they can stay unparented and would otherwise leak. */
1158 if(!IS_NULL_PTR(module->gui->expander) && GTK_IS_WIDGET(module->gui->expander))
1159 {
1160 GtkWidget *expander = module->gui->expander;
1161 g_object_ref_sink(expander);
1162 gtk_widget_destroy(expander);
1163 g_object_unref(expander);
1164 }
1165 else
1166 {
1167 if(!IS_NULL_PTR(module->gui->header) && GTK_IS_WIDGET(module->gui->header))
1168 {
1169 GtkWidget *header = module->gui->header;
1170 g_object_ref_sink(header);
1171 gtk_widget_destroy(header);
1172 g_object_unref(header);
1173 }
1174
1175 if(!IS_NULL_PTR(module->gui->widget) && GTK_IS_WIDGET(module->gui->widget))
1176 {
1177 GtkWidget *widget = module->gui->widget;
1178 g_object_ref_sink(widget);
1179 gtk_widget_destroy(widget);
1180 g_object_unref(widget);
1181 }
1182 }
1183
1184 module->gui->widget = NULL;
1185 module->gui->header = NULL;
1186 module->gui->expander = NULL;
1187 module->gui->off = NULL;
1188
1189 dt_free(module->gui);
1190 module->gui = NULL;
1191}
1192
1194{
1196 if(!dt_iop_is_hidden(module))
1197 {
1198 if(dt_iop_gui_data(module))
1199 {
1201
1202 if(module->params && module->gui_update)
1203 module->gui_update(module);
1204
1207 }
1209 }
1211}
1212
1213static void _gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
1214{
1215 // never use the callback if module is always disabled
1216 const gboolean disabled = !module->default_enabled && module->hide_enable_button;
1217 if(disabled) return;
1218
1219 //Ctrl is used to apply any auto-presets to the current module
1220 //If Ctrl was not pressed, or no auto-presets were applied, reset the module parameters
1221 // FIXME: can we stop with all the easter-eggs key modifiers doing undocumented stuff all along ?
1222 if(!(event && dt_modifier_is(event->state, DT_PRIMARY_MASK)) || !dt_gui_presets_autoapply_for_module(module))
1223 {
1224 /* Resetting a module's parameters does not change GUI focus, so the
1225 focus-loss cleanup in dt_iop_request_focus() never runs here: an active
1226 eye-dropper (this module's own, or its blending/masking parametric-mask
1227 picker -- both share dev->color_picker) and any live mask-shape editing
1228 state (edit mode, shape buttons, mask/channel display) must be turned
1229 off explicitly, or they keep sampling/drawing on the image against
1230 parameters that no longer exist. */
1233
1234 // if a drawn mask is set, remove it from the list
1235 if(module->blend_params->mask_id > 0)
1236 {
1238 // FIXME: ask the user if he wants to delete the mask, or just unlink them.
1239 if(grp) dt_masks_form_delete(module->dev, module, NULL, grp);
1241 }
1242 /* reset to default params */
1243 dt_iop_reload_defaults(module);
1245
1246 /* reset ui to its defaults */
1247 dt_iop_gui_reset(module);
1248
1249 /* update ui to default params*/
1250 dt_iop_gui_update(module);
1251
1252 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1253 }
1254}
1255
1256static void _presets_popup_callback(GtkButton *button, dt_iop_module_t *module)
1257{
1258 const gboolean disabled = !module->default_enabled && module->hide_enable_button;
1259 if(disabled) return;
1260
1262
1263 if(!IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->expander)
1264 && !IS_NULL_PTR(dt_gui_get_global()->presets_popup_menu))
1265 {
1266 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN, GINT_TO_POINTER(TRUE));
1267 g_signal_connect_data(G_OBJECT(dt_gui_get_global()->presets_popup_menu), "deactivate",
1269 g_object_ref(module->gui->expander), (GClosureNotify)g_object_unref, 0);
1270 }
1271
1272 dt_gui_menu_popup(dt_gui_get_global()->presets_popup_menu, GTK_WIDGET(button), GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST);
1273}
1274
1276{
1277 dt_develop_t *const dev = dt_dev_get_global();
1278 dt_iop_module_t *out_focus_module = dev->gui_module;
1279
1280 if(dt_gui_widgets_suppressed() || (out_focus_module == module)) return;
1281
1282 dev->gui_module = module;
1283 if(!IS_NULL_PTR(module) && !IS_NULL_PTR(module->gui))
1284 {
1285 const gboolean scroll_new_instance_to_header
1286 = (dt_gui_get_global()->scroll_to_header_once == module->gui->expander
1287 && !IS_NULL_PTR(module->gui->header) && GTK_IS_WIDGET(module->gui->header));
1288 dt_gui_get_global()->scroll_to[1] = scroll_new_instance_to_header ? module->gui->header : module->gui->expander;
1289 }
1290
1291 /* lets lose the focus of previous focus module*/
1292 if(out_focus_module)
1293 {
1294 GtkWidget *out_focus_widget = dt_iop_gui_get_pluginui(out_focus_module);
1295 GtkWidget *scroll_focus = dt_widget_scroll_focus();
1296 if(scroll_focus && out_focus_widget && gtk_widget_is_ancestor(scroll_focus, out_focus_widget))
1297 {
1299 gtk_widget_queue_draw(scroll_focus);
1300 }
1301
1302 if(out_focus_module->gui_focus)
1303 out_focus_module->gui_focus(out_focus_module, FALSE);
1304
1305 /* A module that loses focus (switching to another module, or collapsing the
1306 focused module, which calls dt_iop_request_focus(NULL)) must not leave an
1307 active picker sampling/drawn on the image behind it: the picker's own
1308 "must have GUI focus" invariant only hides its on-screen overlay, it does
1309 not stop it or reset the toggle button, so a stale enabled picker can
1310 resurface unexpectedly (e.g. when the module's GUI is rebuilt). A real
1311 reset (keep = FALSE) is required here, not the "preserve across a soft
1312 refresh" keep = TRUE used by gui_update()/gui_changed() call sites. */
1313 dt_iop_color_picker_reset(out_focus_module, FALSE);
1315
1316 gtk_widget_set_state_flags(dt_iop_gui_get_pluginui(out_focus_module), GTK_STATE_FLAG_NORMAL, TRUE);
1317
1318 /* reset mask view */
1319 dt_masks_reset_form_gui(out_focus_module->dev);
1320
1321 /* do stuff needed in the blending gui */
1322 dt_iop_gui_blending_lose_focus(out_focus_module);
1323
1324 /* redraw the expander */
1325 if(!IS_NULL_PTR(out_focus_module->gui)) gtk_widget_queue_draw(out_focus_module->gui->expander);
1326
1327 /* and finally collection restore hinter messages */
1329
1330 // we also remove the focus css class
1331 GtkWidget *iop_w = gtk_widget_get_parent(dt_iop_gui_get_pluginui(out_focus_module));
1332 dt_gui_remove_class(iop_w, "dt_module_focus");
1333 }
1334
1335 /* set the focus on module */
1336 if(!IS_NULL_PTR(module))
1337 {
1338 // In case we tried giving focus to a module that is not in the visible tab
1339 dt_dev_modulegroups_switch_tab(module->dev, module);
1340
1341 gtk_widget_set_state_flags(dt_iop_gui_get_pluginui(module), GTK_STATE_FLAG_SELECTED, TRUE);
1342
1343 if(module->gui_focus) module->gui_focus(module, TRUE);
1344
1345 /* redraw the expander */
1346 if(!IS_NULL_PTR(module->gui))
1347 {
1348 gtk_widget_queue_draw(module->gui->expander);
1349 gtk_widget_grab_focus(module->gui->expander);
1350 }
1351
1352 /* set the focus on the first child to enable arrow-key navigation and accessibility stuff */
1353 GList *widget_list = ((dt_gui_module_t *)module)->widget_list;
1354 if(widget_list)
1355 {
1356 GList *first_child = g_list_first(widget_list);
1357 if(first_child)
1358 {
1359 GtkWidget *widget = (GtkWidget *)first_child->data;
1360 if(widget) gtk_widget_grab_focus(widget);
1361 }
1362 }
1363
1364 // we also add the focus css class
1365 GtkWidget *iop_w = gtk_widget_get_parent(dt_iop_gui_get_pluginui(dev->gui_module));
1366 dt_gui_add_class(iop_w, "dt_module_focus");
1367 }
1368
1370 dt_control_queue_cursor(GDK_LEFT_PTR);
1372}
1373
1374/*
1375 * NEW EXPANDER
1376 */
1377
1378static void _gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded)
1379{
1380 // reached for every module in dev->iop via the collapse_others fan-out: gui-less
1381 // (hidden / not-yet-inited) modules have nothing to collapse
1382 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return;
1383
1384 /* update expander arrow state */
1386
1387 /* store expanded state of module.
1388 * we do that first, so update_expanded won't think it should be visible
1389 * and undo our changes right away. */
1390 module->gui->expanded = expanded;
1391
1392 /* show / hide plugin widget */
1393 if(expanded)
1394 {
1395 /* set this module to receive focus / draw events*/
1396 dt_iop_request_focus(module);
1397
1398 /* focus the current module */
1399 for(int k = 0; k < DT_UI_CONTAINER_SIZE; k++)
1401
1402 /* redraw center, iop might have post expose */
1404 }
1405 else
1406 {
1407 if(module->dev->gui_module == module)
1408 {
1411 }
1412 }
1413
1414 if(expanded)
1415 dt_gui_add_class(module->gui->expander, "expanded");
1416 else
1417 dt_gui_remove_class(module->gui->expander, "expanded");
1418
1419 char var[1024];
1420 snprintf(var, sizeof(var), "plugins/darkroom/%s/expanded", module->op);
1421 dt_conf_set_bool(var, expanded);
1422}
1423
1425void _iop_dim_all_but(dt_iop_module_t *module, gboolean dim)
1426{
1427 for(GList *iop = g_list_first(dt_dev_get_global()->iop); iop; iop = g_list_next(iop))
1428 {
1429 dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
1430
1431 // Handle invisible modules
1432 if(IS_NULL_PTR(m) || !m->gui || !m->gui->expander) continue;
1433
1434 if(dim && m != module)
1435 dt_gui_add_class(gtk_widget_get_parent(dt_iop_gui_get_pluginui(m)), "module-dimmed");
1436 else
1437 dt_gui_remove_class(gtk_widget_get_parent(dt_iop_gui_get_pluginui(m)), "module-dimmed");
1438 }
1439}
1440
1441void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
1442{
1443 if(IS_NULL_PTR(module) || !module->gui || !module->gui->expander) return;
1444 if(collapse_others)
1445 {
1446 for(GList *iop = g_list_first(module->dev->iop); iop; iop = g_list_next(iop))
1447 {
1448 dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
1449 if(m != module) _gui_set_single_expanded(m, FALSE);
1450 }
1451 }
1452
1453 _gui_set_single_expanded(module, expanded);
1454 _iop_dim_all_but((expanded) ? module : NULL, expanded);
1455 gtk_widget_queue_draw(module->gui->widget);
1456}
1457
1459{
1460 if(IS_NULL_PTR(module->gui->expander)) return;
1461
1462 const gboolean expanded = module->gui->expanded;
1463
1465}
1466
1467static gboolean _iop_plugin_body_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1468{
1469 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1470
1471 /* Reset the scrolling focus. If the click happened on any bauhaus element,
1472 * its internal button_press method will set it for itself */
1474
1475 gboolean handled = FALSE;
1476
1477 if(e->button == 1)
1478 {
1479 dt_iop_request_focus(module);
1480 handled = TRUE;
1481 }
1482 else if(e->button == 3)
1483 {
1484 _presets_popup_callback(NULL, module);
1485 handled = TRUE;
1486 }
1487 return handled;
1488}
1489
1490static gboolean _iop_plugin_header_activate(GtkWidget* self, gboolean group_cycling, gpointer user_data)
1491{
1492 dt_gui_module_t *module = (dt_gui_module_t *)user_data;
1493 if(IS_NULL_PTR(module) || !module->focus) return FALSE;
1494 return module->focus(module, TRUE);
1495}
1496
1497static gboolean _iop_plugin_header_child_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1498{
1499 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1500 if(module && module->gui->expander)
1501 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click", GINT_TO_POINTER(TRUE));
1502
1503 return FALSE;
1504}
1505
1506static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1507 GdkModifierType modifier, gpointer data)
1508{
1509 dt_gui_module_t *module = (dt_gui_module_t *)data;
1510 dt_iop_module_t *iop = (dt_iop_module_t *)data;
1511 if(IS_NULL_PTR(module) || !module->focus) return FALSE;
1512
1513 // Accel search explicitly targets a module, so allow modulegroups to leave
1514 // the Pipeline tab once for this focus request.
1515 if(iop->gui->expander)
1516 g_object_set_data(G_OBJECT(iop->gui->expander), "dt-modulegroups-switch-from-active-once",
1517 GINT_TO_POINTER(TRUE));
1518
1519 return module->focus(module, FALSE);
1520}
1521
1522static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
1523 GdkModifierType modifier, gpointer data)
1524{
1525 dt_iop_module_t *module = (dt_iop_module_t *)data;
1526 if(IS_NULL_PTR(module)) return FALSE;
1527
1528 // Direct actions from accel search should prioritize Pipeline when they focus
1529 // the edited module right after applying the change.
1530 if(!IS_NULL_PTR(module->gui->expander))
1531 g_object_set_data(G_OBJECT(module->gui->expander), "dt-modulegroups-prefer-active-once",
1532 GINT_TO_POINTER(TRUE));
1533
1534 // Kind of ugly to go through history to change module GUI state
1535 // FIXME: we should have a GUI callback that enables module and dispatches history instead,
1536 // history should not care about module GUI. This is a wrong, reversed inheritance.
1537 if(!module->hide_enable_button)
1538 {
1539 module->enabled = TRUE;
1540 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1541 }
1542
1543 dt_iop_request_focus(module);
1545 return TRUE;
1546}
1547
1548static gboolean _iop_plugin_header_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1549{
1550 if(e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) return TRUE;
1551
1552 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1553 if(IS_NULL_PTR(module)) return FALSE;
1554
1555 if(!IS_NULL_PTR(module->gui->expander)
1556 && (g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN)
1557 || g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_DISMISS_CLICK)))
1558 {
1559 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE, GINT_TO_POINTER(TRUE));
1560 return TRUE;
1561 }
1562
1563 /* Reset the scrolling focus. If the click happened on any bauhaus element,
1564 * its internal button_press method will set it for itself */
1566
1567 if(e->button == 1)
1568 {
1569 if(module->gui->expander) g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-dragged", NULL);
1570
1571 if(!dt_modifier_is(e->state, DT_PRIMARY_MASK))
1572 {
1573 return FALSE;
1574 }
1575 else
1576 {
1577 if(module->gui->expander)
1578 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE, GINT_TO_POINTER(TRUE));
1580 return TRUE;
1581 }
1582 }
1583 else if(e->button == 3)
1584 {
1585 _presets_popup_callback(NULL, module);
1586
1587 return TRUE;
1588 }
1589 return FALSE;
1590}
1591
1592static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data)
1593{
1594 if(e->button != 1 || e->type != GDK_BUTTON_RELEASE) return FALSE;
1595
1596 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1597 if(IS_NULL_PTR(module) || !module->gui->expander) return FALSE;
1598
1599 if(g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE))
1600 {
1601 g_object_set_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_IGNORE_RELEASE, NULL);
1602 return TRUE;
1603 }
1604
1605 if(g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_OPEN)
1606 || g_object_get_data(G_OBJECT(module->gui->expander), DT_IOP_HEADER_MENU_DISMISS_CLICK))
1607 {
1608 return TRUE;
1609 }
1610
1611 if(g_object_get_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click"))
1612 {
1613 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-header-child-click", NULL);
1614 return FALSE;
1615 }
1616
1617 if(g_object_get_data(G_OBJECT(module->gui->expander), "dt-module-dragged"))
1618 {
1619 g_object_set_data(G_OBJECT(module->gui->expander), "dt-module-dragged", NULL);
1620 return TRUE;
1621 }
1622
1623 // make gtk scroll to the module once it updated its allocation size
1624 const gboolean collapse_others = dt_modifier_is(e->state, GDK_SHIFT_MASK) ? FALSE : TRUE;
1625 dt_iop_request_focus(module);
1626 dt_iop_gui_set_expanded(module, !module->gui->expanded, collapse_others);
1627
1628 return TRUE;
1629}
1630
1631static void _display_mask_indicator_callback(GtkToggleButton *bt, dt_iop_module_t *module)
1632{
1633 if(dt_gui_widgets_suppressed()) return;
1634
1635 const gboolean is_active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bt));
1637
1638 module->request_mask_display
1639 &= ~(DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
1640 | DT_DEV_PIXELPIPE_DISPLAY_ANY | DT_DEV_PIXELPIPE_DISPLAY_STICKY);
1641
1642 if(is_active)
1643 module->request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_MASK;
1644
1646
1647 // set the module show mask button too
1648 if(bd->showmask)
1649 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->showmask), is_active);
1650
1652 if(GTK_IS_TOGGLE_BUTTON(bd->filter[0].channel_display))
1653 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->filter[0].channel_display), FALSE);
1654 if(GTK_IS_TOGGLE_BUTTON(bd->filter[1].channel_display))
1655 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->filter[1].channel_display), FALSE);
1657
1658 dt_iop_request_focus(module);
1660}
1661
1662static void _mask_indicator_get_usage(dt_iop_module_t *module, gboolean *top_enabled, gboolean *raster_used,
1663 gboolean *drawn_used, gboolean *parametric_used)
1664{
1665 if(!IS_NULL_PTR(top_enabled)) *top_enabled = FALSE;
1666 if(!IS_NULL_PTR(raster_used)) *raster_used = FALSE;
1667 if(!IS_NULL_PTR(drawn_used)) *drawn_used = FALSE;
1668 if(!IS_NULL_PTR(parametric_used)) *parametric_used = FALSE;
1669
1670 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->blend_params)) return;
1671
1672 const dt_iop_gui_blend_data_t *bd = (const dt_iop_gui_blend_data_t *)module->gui->blend_data;
1673 gboolean top = FALSE;
1674 gboolean raster = FALSE;
1675 gboolean drawn = FALSE;
1676 gboolean parametric = FALSE;
1677 dt_develop_blend_get_mask_usage(module, module->blend_params, &top, &raster, &drawn, &parametric);
1678
1679 // look if the user disabled masks modes
1680
1681 // raster mask must be enabled and a raster mask must be selected in the combo
1682 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->raster_enable)
1683 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->raster_enable)))
1684 raster = FALSE;
1685
1686 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->masks_enable)
1687 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->masks_enable)))
1688 drawn = FALSE;
1689
1690 if(!IS_NULL_PTR(bd) && GTK_IS_TOGGLE_BUTTON(bd->blendif_enable)
1691 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->blendif_enable)))
1692 parametric = FALSE;
1693
1694 if(!IS_NULL_PTR(top_enabled)) *top_enabled = top;
1695 if(!IS_NULL_PTR(raster_used)) *raster_used = raster;
1696 if(!IS_NULL_PTR(drawn_used)) *drawn_used = drawn;
1697 if(!IS_NULL_PTR(parametric_used)) *parametric_used = parametric;
1698}
1699
1700static gboolean _mask_indicator_tooltip(GtkWidget *treeview, gint x, gint y, gboolean kb_mode,
1701 GtkTooltip* tooltip, dt_iop_module_t *module)
1702{
1703 (void)treeview;
1704 (void)x;
1705 (void)y;
1706 (void)kb_mode;
1707
1708 gboolean res = FALSE;
1709 if(module->gui->mask_indicator)
1710 {
1711 gchar *type = _("unknown mask");
1712 gchar *text;
1713 gboolean top_enabled = FALSE, raster_used = FALSE, drawn_used = FALSE, parametric_used = FALSE;
1714 _mask_indicator_get_usage(module, &top_enabled, &raster_used, &drawn_used, &parametric_used);
1715 if(!top_enabled) return FALSE;
1716
1717 if(drawn_used && parametric_used)
1718 type=_("drawn + parametric mask");
1719 else if(drawn_used)
1720 type=_("drawn mask");
1721 else if(parametric_used)
1722 type=_("parametric mask");
1723 else if(raster_used)
1724 type=_("raster mask");
1725 else
1726 return FALSE;
1727 gchar *part1 = g_strdup_printf(_("this module has a '%s'"), type);
1728 gchar *part2 = NULL;
1729 if(raster_used && module->raster_mask.sink.source)
1730 {
1731 gchar *source = dt_history_item_get_name(module->raster_mask.sink.source);
1732 part2 = g_strdup_printf(_("taken from module %s"), source);
1733 dt_free(source);
1734 }
1735
1736 if(part2)
1737 {
1738 gchar *details = g_strdup_printf("%s\n%s", part2, _("click to display (module must be activated first)"));
1739 dt_free(part2);
1740 part2 = details;
1741 }
1742 else
1743 {
1744 part2 = g_strdup(_("click to display (module must be activated first)"));
1745 }
1746
1747 if(part2)
1748 text = g_strconcat(part1, "\n", part2, NULL);
1749 else
1750 text = g_strdup(part1);
1751
1752 gtk_tooltip_set_text(tooltip, text);
1753 res = TRUE;
1754 dt_free(part1);
1755 dt_free(part2);
1756 dt_free(text);
1757 }
1758 return res;
1759}
1760
1762{
1763 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->mask_indicator)) return;
1764
1765 const gboolean support_blending = (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) == IOP_FLAGS_SUPPORTS_BLENDING;
1766
1767 if(!support_blending || !module->blend_params)
1768 {
1769 gtk_widget_set_visible(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1770 gtk_widget_set_has_tooltip(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1771 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->mask_indicator), FALSE);
1772 return;
1773 }
1774
1775 gboolean top_enabled = FALSE, raster_used = FALSE, drawn_used = FALSE, parametric_used = FALSE;
1776 _mask_indicator_get_usage(module, &top_enabled, &raster_used, &drawn_used, &parametric_used);
1777
1778 const gboolean use_masks = top_enabled && (raster_used || drawn_used || parametric_used);
1779
1780 gtk_widget_set_visible(GTK_WIDGET(module->gui->mask_indicator), use_masks);
1781 gtk_widget_set_sensitive(GTK_WIDGET(module->gui->mask_indicator), module->enabled);
1782 gtk_widget_set_has_tooltip(GTK_WIDGET(module->gui->mask_indicator), use_masks);
1783}
1784
1785gboolean _iop_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
1786 GtkTooltip *tooltip, gpointer user_data)
1787{
1788 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1789
1790 const char **des = module->description(module);
1791
1792 if(IS_NULL_PTR(des)) return FALSE;
1793
1794 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1795 GtkWidget *grid = gtk_grid_new();
1796 gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
1797 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
1798 gtk_widget_set_hexpand(grid, FALSE);
1799
1800 GtkWidget *label = gtk_label_new(des[0] ? des[0] : "");
1801 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1802 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1803 gtk_label_set_max_width_chars(GTK_LABEL(label), 40);
1804 // if there is no more description, do not add a separator
1805 if(des[1]) dt_gui_add_class(label, "dt_section_label");
1806 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1807
1808 gtk_widget_set_size_request(label, DT_PIXEL_APPLY_DPI(300), -1);
1809 gtk_widget_set_size_request(grid, DT_PIXEL_APPLY_DPI(300), -1);
1810 gtk_widget_set_size_request(vbox, DT_PIXEL_APPLY_DPI(300), -1);
1811
1812 const char *icon_purpose = "\342\237\263";
1813 const char *icon_input = "\342\207\245";
1814 const char *icon_process = "\342\237\264";
1815 const char *icon_output = "\342\206\246";
1816
1817 const char *icons[4] = {icon_purpose, icon_input, icon_process, icon_output};
1818 const char *ilabs[4] = {_("Purpose"), _("Input"), _("Process"), _("Output")};
1819
1820 for(int k=1; k<5; k++)
1821 {
1822 if(des[k])
1823 {
1824 label = gtk_label_new(icons[k-1]);
1825 gtk_widget_set_halign(label, GTK_ALIGN_START);
1826 gtk_grid_attach(GTK_GRID(grid), label, 0, k, 1, 1);
1827 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1828
1829 label = gtk_label_new(ilabs[k-1]);
1830 gtk_widget_set_halign(label, GTK_ALIGN_START);
1831 gtk_grid_attach(GTK_GRID(grid), label, 1, k, 1, 1);
1832 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1833
1834 label = gtk_label_new(":");
1835 gtk_widget_set_halign(label, GTK_ALIGN_START);
1836 gtk_grid_attach(GTK_GRID(grid), label, 2, k, 1, 1);
1837 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1838
1839 label = gtk_label_new(des[k]);
1840 gtk_widget_set_halign(label, GTK_ALIGN_START);
1841 gtk_grid_attach(GTK_GRID(grid), label, 3, k, 1, 1);
1842 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1843 }
1844 }
1845
1846 gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 0);
1847 gtk_widget_show_all(vbox);
1848 gtk_tooltip_set_custom(tooltip, vbox);
1849
1850 return TRUE;
1851}
1852
1854{
1855 GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING / 2.);
1856 gtk_widget_set_name(GTK_WIDGET(header), "module-header");
1857
1858 GtkWidget *iopw = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1859 GtkWidget *expander = dtgtk_expander_new(header, iopw);
1860 dt_gui_add_class(expander, "dt_module_frame");
1861 dt_gui_add_class(expander, "dt_iop_module");
1862
1865 GtkWidget *pluginui_frame = dtgtk_expander_get_frame(DTGTK_EXPANDER(expander));
1866
1867 dt_gui_add_class(pluginui_frame, "dt_plugin_ui");
1868
1869 module->gui->header = header;
1870
1871 /* setup the header box */
1872 g_signal_connect(G_OBJECT(header_evb), "button-press-event", G_CALLBACK(_iop_plugin_header_button_press), module);
1873 g_signal_connect(G_OBJECT(header_evb), "button-release-event", G_CALLBACK(_iop_plugin_header_button_release), module);
1874 g_signal_connect(G_OBJECT(header_evb), "mnemonic-activate", G_CALLBACK(_iop_plugin_header_activate), module);
1875 gtk_widget_add_events(header_evb, GDK_POINTER_MOTION_MASK);
1876
1877 /* connect mouse button callbacks for focus and presets */
1878 g_signal_connect(G_OBJECT(body_evb), "button-press-event", G_CALLBACK(_iop_plugin_body_button_press), module);
1879 gtk_widget_add_events(body_evb, GDK_POINTER_MOTION_MASK);
1880
1881 /*
1882 * initialize the header widgets
1883 */
1884 GtkWidget *hw[IOP_MODULE_LAST] = { NULL };
1885
1886 /* init empty place for icon, this is then set in CSS if needed */
1887 char w_name[256] = { 0 };
1888 snprintf(w_name, sizeof(w_name), "iop-panel-icon-%s", module->op);
1889 hw[IOP_MODULE_ICON] = gtk_label_new("");
1890 gtk_widget_set_name(GTK_WIDGET(hw[IOP_MODULE_ICON]), w_name);
1891
1892 /* add module label */
1893 hw[IOP_MODULE_LABEL] = gtk_event_box_new();
1894 GtkWidget *lab = hw[IOP_MODULE_LABEL];
1895 GtkWidget *label = gtk_label_new_with_mnemonic("");
1896 gtk_container_add(GTK_CONTAINER(lab), label);
1897 gtk_label_set_mnemonic_widget(GTK_LABEL(label), header_evb);
1898
1899 if((module->flags() & IOP_FLAGS_DEPRECATED) && module->deprecated_msg())
1900 gtk_widget_set_tooltip_text(lab, module->deprecated_msg());
1901 else
1902 {
1903 gtk_widget_set_name(lab, "iop_description");
1904 g_signal_connect(lab, "query-tooltip", G_CALLBACK(_iop_tooltip_callback), module);
1905 }
1906
1907 /* add mask preview button */
1909
1910 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "toggled",
1911 G_CALLBACK(_display_mask_indicator_callback), module);
1912 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "button-press-event",
1913 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1914 g_signal_connect(G_OBJECT(hw[IOP_MODULE_MASK]), "query-tooltip",
1915 G_CALLBACK(_mask_indicator_tooltip), module);
1916 module->gui->mask_indicator = hw[IOP_MODULE_MASK];
1917
1918 /* add multi instances menu button */
1920 module->gui->multimenu_button = GTK_WIDGET(hw[IOP_MODULE_INSTANCE]);
1921 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_INSTANCE]),
1922 _("multiple instance actions\nright-click creates new instance"));
1923 g_signal_connect(G_OBJECT(hw[IOP_MODULE_INSTANCE]), "button-press-event",
1924 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1925 g_signal_connect(G_OBJECT(hw[IOP_MODULE_INSTANCE]), "button-press-event", G_CALLBACK(_gui_multiinstance_callback),
1926 module);
1927
1928 dt_gui_add_help_link(expander, dt_get_help_url(module->op));
1929
1930 /* add reset button */
1932 module->gui->reset_button = GTK_WIDGET(hw[IOP_MODULE_RESET]);
1933 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_RESET]), _("reset parameters\nctrl+click to reapply any automatic presets"));
1934 g_signal_connect(G_OBJECT(hw[IOP_MODULE_RESET]), "button-press-event",
1935 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1936 g_signal_connect(G_OBJECT(hw[IOP_MODULE_RESET]), "button-press-event", G_CALLBACK(_gui_reset_callback), module);
1937
1938 /* add preset button if module has implementation */
1940 module->gui->presets_button = GTK_WIDGET(hw[IOP_MODULE_PRESETS]);
1941 if(!(module->flags() & IOP_FLAGS_ONE_INSTANCE))
1942 gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_PRESETS]), _("presets\nright-click to apply on new instance"));
1943 g_signal_connect(G_OBJECT(hw[IOP_MODULE_PRESETS]), "button-press-event",
1944 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1945 g_signal_connect(G_OBJECT(hw[IOP_MODULE_PRESETS]), "clicked", G_CALLBACK(_presets_popup_callback), module);
1946
1947 /* add enabled button */
1949
1950 dt_gui_add_class(switch_button, "dt_iop_enable_button");
1951 dt_iop_gui_set_enable_button_icon(switch_button, module);
1952 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(switch_button), module->enabled);
1953 g_signal_connect(G_OBJECT(switch_button), "button-press-event",
1954 G_CALLBACK(_iop_plugin_header_child_button_press), module);
1955 g_signal_connect(G_OBJECT(switch_button), "toggled", G_CALLBACK(_gui_off_callback), module);
1956
1957 module->gui->off = switch_button;
1958 gtk_widget_set_sensitive(GTK_WIDGET(switch_button), !module->hide_enable_button);
1959
1960 /* Wrap the switch in a plain box so the CSS spacing trick that visually tucks it
1961 against the header edge (negative margin, see ansel.css) lives on the wrapper,
1962 not on the button itself. dtgtk_togglebutton's custom draw() reinterprets its own
1963 CSS margin a second time (as a paint-only offset) to size its icon -- if that same
1964 margin also carries a position nudge, the icon gets painted outside the button's
1965 real GTK allocation, which is what drives hover/click hit-testing. Keeping the
1966 button's own margin at 0 keeps its drawing and its hit-region intrinsically in
1967 sync; the wrapper absorbs the nudge at the layout level instead. */
1968 hw[IOP_MODULE_SWITCH] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1969 dt_gui_add_class(hw[IOP_MODULE_SWITCH], "dt_iop_enable_button_box");
1970 gtk_container_add(GTK_CONTAINER(hw[IOP_MODULE_SWITCH]), switch_button);
1971
1972 /* reorder header, for now, iop are always in the right panel */
1973 for(int i = 0; i <= IOP_MODULE_LABEL; i++)
1974 {
1975 if(hw[i])
1976 gtk_box_pack_start(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
1977 }
1978 for(int i = IOP_MODULE_LAST - 1; i > IOP_MODULE_LABEL; i--)
1979 {
1980 if(hw[i])
1981 gtk_box_pack_end(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
1982 }
1983
1984 dt_gui_add_help_link(header, dt_get_help_url("module_header"));
1985 // for the module label, point to module specific help page
1987
1988 gtk_widget_set_halign(hw[IOP_MODULE_LABEL], GTK_ALIGN_START);
1989 gtk_widget_set_halign(hw[IOP_MODULE_INSTANCE], GTK_ALIGN_END);
1990
1991 // show deprecated message if any
1992 if(module->deprecated_msg())
1993 {
1994 GtkWidget *lb = gtk_label_new(module->deprecated_msg());
1995 gtk_label_set_line_wrap(GTK_LABEL(lb), TRUE);
1996 gtk_label_set_xalign(GTK_LABEL(lb), 0.0);
1997 dt_gui_add_class(lb, "dt_warning");
1998 gtk_box_pack_start(GTK_BOX(iopw), lb, TRUE, TRUE, 0);
1999 gtk_widget_show(lb);
2000 }
2001
2002 /* initialize blending state if supported; the detached widget is hosted by the masks lib */
2003 gtk_box_pack_start(GTK_BOX(iopw), module->gui->widget, TRUE, TRUE, 0);
2005 dt_gui_add_class(module->gui->widget, "dt_plugin_ui_main");
2006 dt_gui_add_help_link(module->gui->widget, dt_get_help_url(module->op));
2007 gtk_widget_hide(iopw);
2008
2009 module->gui->expander = expander;
2010 g_object_weak_ref(G_OBJECT(header), _iop_gui_widget_gone, module);
2011 g_object_weak_ref(G_OBJECT(expander), _iop_gui_widget_gone, module);
2012
2013 /* update header */
2015
2016 gtk_widget_set_hexpand(module->gui->widget, FALSE);
2017 gtk_widget_set_vexpand(module->gui->widget, FALSE);
2018
2020}
2021
2023{
2024 // NULL for a module with no GUI half, the way the flat module->expander was NULL before
2025 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return NULL;
2027}
2028
2030{
2031 // return gtkframe (pluginui_frame)
2032 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return NULL;
2034}
2035
2036void dt_iop_gui_changed(dt_iop_module_t *action, GtkWidget *widget, gpointer data)
2037{
2038 if(IS_NULL_PTR(action)) return;
2039 dt_iop_module_t *module = (dt_iop_module_t *)action;
2040
2041 if(module->gui_changed) module->gui_changed(module, widget, data);
2042
2044
2045 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
2046
2047 if(!IS_NULL_PTR(widget) && g_object_get_data(G_OBJECT(widget), "dt-blendop-header-update"))
2049 else
2051}
2052
2053
2055{
2057
2058 for(GList *w = g_list_first(m->widget_list_bh); w; w = g_list_next(w))
2059 {
2060 GtkWidget *widget = (GtkWidget *)w->data;
2061 struct dt_bauhaus_widget_t *bhw = DT_BAUHAUS_WIDGET(widget);
2062 if(IS_NULL_PTR(bhw)) continue;
2063
2064 switch(bhw->type)
2065 {
2066 case DT_BAUHAUS_SLIDER:
2067 switch(bhw->field_type)
2068 {
2070 dt_bauhaus_slider_set(widget, *(float *)bhw->field);
2071 break;
2073 dt_bauhaus_slider_set(widget, *(int *)bhw->field);
2074 break;
2076 dt_bauhaus_slider_set(widget, *(unsigned short *)bhw->field);
2077 break;
2078 default:
2079 fprintf(stderr, "[dt_bauhaus_update_module] unsupported slider data type\n");
2080 }
2081 break;
2083 switch(bhw->field_type)
2084 {
2086 dt_bauhaus_combobox_set_from_value(widget, *(int *)bhw->field);
2087 break;
2089 dt_bauhaus_combobox_set(widget, *(int *)bhw->field);
2090 break;
2092 dt_bauhaus_combobox_set(widget, *(unsigned int *)bhw->field);
2093 break;
2095 dt_bauhaus_combobox_set(widget, *(gboolean *)bhw->field);
2096 break;
2097 default:
2098 fprintf(stderr, "[dt_bauhaus_update_module] unsupported combo data type\n");
2099 }
2100 break;
2101 default:
2102 fprintf(stderr, "[dt_bauhaus_update_module] invalid bauhaus widget type encountered\n");
2103 }
2104 }
2105}
2106
2108{
2110 dt_iop_module_t *module = (dt_iop_module_t *)w->module;
2111 if(IS_NULL_PTR(w->field) || IS_NULL_PTR(module)) return;
2112
2113 switch(w->type)
2114 {
2115 case DT_BAUHAUS_SLIDER:
2116 {
2117 float val = dt_bauhaus_slider_get(widget);
2118 switch(w->field_type)
2119 {
2121 {
2122 float *f = w->field, prevf = *f; *f = val;
2123 if(*f != prevf) dt_iop_gui_changed(module, widget, &prevf);
2124 break;
2125 }
2127 {
2128 int *i = w->field, previ = *i; *i = val;
2129 if(*i != previ) dt_iop_gui_changed(module, widget, &previ);
2130 break;
2131 }
2133 {
2134 unsigned short *s = w->field, prevs = *s; *s = val;
2135 if(*s != prevs) dt_iop_gui_changed(module, widget, &prevs);
2136 break;
2137 }
2138 default:
2139 fprintf(stderr, "[_bauhaus_slider_value_change] unsupported slider data type\n");
2140 }
2141 break;
2142 }
2144 {
2146 switch(w->field_type)
2147 {
2149 {
2150 if(d->active >= 0)
2151 {
2152 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
2153 int *e = w->field, preve = *e; *e = GPOINTER_TO_INT(entry->data);
2154 if(*e != preve) dt_iop_gui_changed(module, widget, &preve);
2155 }
2156 break;
2157 }
2159 {
2160 int *i = w->field, previ = *i; *i = d->active;
2161 if(*i != previ) dt_iop_gui_changed(module, widget, &previ);
2162 break;
2163 }
2165 {
2166 unsigned int *u = w->field, prevu = *u; *u = d->active;
2167 if(*u != prevu) dt_iop_gui_changed(module, widget, &prevu);
2168 break;
2169 }
2171 {
2172 gboolean *b = w->field, prevb = *b; *b = d->active;
2173 if(*b != prevb) dt_iop_gui_changed(module, widget, &prevb);
2174 break;
2175 }
2176 default:
2177 fprintf(stderr, "[_bauhaus_combobox_set] unsupported combo data type\n");
2178 }
2179 break;
2180 }
2181 default:
2182 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);
2183 }
2184}
2186{
2187 // conditional on module->gui and paired with a dt_iop_gui_leave_critical_section() call at
2188 // an unrelated call site, which the thread-safety analyzer can't model -- same reason
2189 // dt_opencl_reserve_device_by_id()/dt_opencl_release_device() use the BAD variants.
2190 if(module->gui) dt_pthread_mutex_BAD_lock(&module->gui->gui_lock);
2191}
2192
2194{
2195 if(module->gui) dt_pthread_mutex_BAD_unlock(&module->gui->gui_lock);
2196}
2197
2199{
2200 return module && module->gui ? module->gui->off : NULL;
2201}
2202
2203gboolean dt_iop_gui_owns_widget(const dt_iop_module_t *module, const GtkWidget *target)
2204{
2205 return module->gui && (module->gui->expander == (const void *)target || module->gui->header == (const void *)target);
2206}
2207
2208// clang-format off
2209// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2210// vim: shiftwidth=2 expandtab tabstop=2 cindent
2211// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2212// clang-format on
2213
2214/* Attached to each reset label; freed with the widget. */
2216{
2217 dt_iop_module_t *module;
2218 int offset; // offset of the parameter inside module->params
2219 int size; // its size in bytes
2221
2222static void _iop_reset_label_reset(GtkWidget *widget, gpointer user_data)
2223{
2225 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->module)) return;
2226
2227 memcpy(((char *)d->module->params) + d->offset, ((char *)d->module->default_params) + d->offset, d->size);
2228 if(d->module->gui_update) d->module->gui_update(d->module);
2230}
2231
2232GtkWidget *dt_iop_gui_reset_label_new(const gchar *label, dt_iop_module_t *module, void *param,
2233 int param_size)
2234{
2235 GtkWidget *w = dtgtk_reset_label_new(label);
2236
2238 d->module = module;
2239 d->offset = param - (void *)module->params;
2240 d->size = param_size;
2241
2242 g_signal_connect_data(G_OBJECT(w), "reset", G_CALLBACK(_iop_reset_label_reset), d,
2243 (GClosureNotify)g_free, 0);
2244 return w;
2245}
void dt_accels_remove_shortcut(dt_accels_t *accels, const char *path)
Remove the shortcut object identified by path and all its accels.
void dt_accels_new_virtual_instance_shortcut(dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name)
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
void dt_accels_remove_accel(dt_accels_t *accels, const char *path, gpointer data)
Recursively remove all accels for all shortcuts containing path. This is unneeded for accels attached...
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnect accels while a text or search entry has the focus, and reconnect them when it loses it....
Handle default and user-set shortcuts (accelerators)
#define dt_accels_new_darkroom_action(a, b, 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:1627
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2119
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:1638
void dt_bauhaus_widget_set_field(GtkWidget *widget, gpointer field, dt_introspection_type_t field_type)
Definition bauhaus.c:1562
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:1846
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
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:1698
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
@ 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:5169
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4466
void dt_iop_gui_blending_lose_focus(dt_iop_module_t *module)
Definition blend_gui.c:4763
void dt_iop_gui_cleanup_blending(dt_iop_module_t *module)
Definition blend_gui.c:4315
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:924
#define dt_control_queue_cursor(cursor)
Definition control.h:140
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
void dt_dev_write_history(dt_develop_t *dev, gboolean async)
Thread-safe wrapper around dt_dev_write_history_ext() for dev->image_storage.id.
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
void dt_dev_history_flush_pending_commits(struct dt_develop_t *dev)
Run every history commit still queued for dev, now.
#define dt_dev_pixelpipe_rebuild_all(dev)
#define dt_dev_pixelpipe_update_history_main(dev)
void dt_dev_modulegroups_switch_tab(dt_develop_t *dev, dt_iop_module_t *module)
Definition develop.c:1328
void dt_dev_module_remove(dt_develop_t *dev, dt_iop_module_t *module)
Definition develop.c:1442
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1854
gchar * dt_history_item_get_label(const struct dt_iop_module_t *module)
Definition develop.c:1613
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1865
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
static int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:367
static int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:377
const int res
Definition dtpthread.h:351
GtkWidget * dtgtk_expander_get_header_event_box(GtkDarktableExpander *expander)
Definition expander.c:49
GtkWidget * dtgtk_expander_get_body_event_box(GtkDarktableExpander *expander)
Definition expander.c:63
GtkWidget * dtgtk_expander_get_frame(GtkDarktableExpander *expander)
Definition expander.c:35
GtkWidget * dtgtk_expander_get_body(GtkDarktableExpander *expander)
Definition expander.c:56
void dtgtk_expander_set_expanded(GtkDarktableExpander *expander, gboolean expanded)
Definition expander.c:70
GtkWidget * dtgtk_expander_new(GtkWidget *header, GtkWidget *body)
Definition expander.c:101
#define DTGTK_EXPANDER(obj)
Definition expander.h:31
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
gboolean dt_gui_presets_autoapply_for_module(dt_iop_module_t *module)
void dt_gui_presets_popup_menu_show_for_module(dt_iop_module_t *module)
#define DT_GUI_MODULE(x)
Everything the history, styles and presets code says to the outside world: messages for the user,...
const char * tooltip
Definition image.h:310
void dt_iop_reload_defaults(dt_iop_module_t *module)
Definition imageop.c: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:1646
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:1669
void dt_iop_update_multi_priority(dt_iop_module_t *module, int new_priority)
Definition imageop.c:1801
@ IOP_MODULE_ICON
Definition imageop.h:95
@ IOP_MODULE_PRESETS
Definition imageop.h:100
@ IOP_MODULE_RESET
Definition imageop.h:99
@ IOP_MODULE_LABEL
Definition imageop.h:96
@ IOP_MODULE_MASK
Definition imageop.h:97
@ IOP_MODULE_LAST
Definition imageop.h:101
@ IOP_MODULE_SWITCH
Definition imageop.h:94
@ IOP_MODULE_INSTANCE
Definition imageop.h:98
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
static gboolean _iop_plugin_enable_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
GtkWidget * dt_iop_togglebutton_new_no_register(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
static void _gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
static gboolean _gui_multiinstance_callback(GtkButton *button, GdkEventButton *event, gpointer user_data)
void dt_iop_gui_cleanup_module(dt_iop_module_t *module)
static gboolean _iop_plugin_header_button_release(GtkWidget *w, GdkEventButton *e, gpointer user_data)
GtkWidget * dt_iop_gui_get_pluginui(dt_iop_module_t *module)
static void _iop_plugin_header_menu_deactivate(GtkWidget *menu, gpointer user_data)
void dt_iop_add_remove_mask_indicator(dt_iop_module_t *module)
GtkWidget * dt_iop_button_new(dt_iop_module_t *self, const gchar *label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, gint paintflags, GtkWidget *box)
void dt_iop_gui_set_expander(dt_iop_module_t *module)
dt_iop_module_t * dt_iop_gui_get_next_visible_module(dt_iop_module_t *module)
static gboolean _rename_module_key_press(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
static gboolean _iop_plugin_header_activate(GtkWidget *self, gboolean group_cycling, gpointer user_data)
static void _iop_panel_label(dt_iop_module_t *module)
void dt_iop_gui_init(dt_iop_module_t *module)
static gboolean _iop_plugin_focus_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
void dt_iop_gui_update(dt_iop_module_t *module)
void dt_bauhaus_update_module(dt_iop_module_t *self)
GtkWidget * dt_iop_gui_reset_label_new(const gchar *label, dt_iop_module_t *module, void *param, int param_size)
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
static void _display_mask_indicator_callback(GtkToggleButton *bt, dt_iop_module_t *module)
static 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:55
static void _presets_popup_callback(GtkButton *button, dt_iop_module_t *module)
#define DT_IOP_HEADER_MENU_DISMISS_CLICK
Definition imageop_gui.c:53
static void _iop_gui_widget_gone(gpointer user_data, GObject *where_the_object_was)
Clear GUI pointers that still reference one iop widget being finalized.
static gboolean _rename_module_resize(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
static void _gui_delete_callback(GtkButton *button, dt_iop_module_t *module)
static gboolean _rename_module_idle(gpointer user_data)
static gboolean _iop_plugin_header_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
gboolean dt_mask_scroll_increases(int up)
gboolean _iop_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void dt_iop_gui_rename_module(dt_iop_module_t *module)
dt_iop_module_t * dt_iop_gui_duplicate(dt_iop_module_t *base, gboolean copy_params)
Create another instance of base, insert it into dev->iop and give it a GUI.
gboolean dt_iop_gui_module_is_visible(dt_iop_module_t *module)
GtkWidget * dt_iop_gui_get_off(dt_iop_module_t *module)
static void _gui_duplicate_callback(GtkButton *button, gpointer user_data)
static void _gui_off_callback(GtkToggleButton *togglebutton, gpointer user_data)
static void _gui_rename_callback(GtkButton *button, dt_iop_module_t *module)
#define DT_IOP_HEADER_MENU_OPEN
Definition imageop_gui.c:52
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
@ DT_INTROSPECTION_TYPE_BOOL
@ DT_INTROSPECTION_TYPE_ENUM
@ DT_INTROSPECTION_TYPE_FLOAT
@ DT_INTROSPECTION_TYPE_UINT
@ DT_INTROSPECTION_TYPE_USHORT
@ DT_INTROSPECTION_TYPE_INT
gchar * delete_underscore(const char *s)
Definition label.c:75
_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:1157
@ 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:1196
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
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:692
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:315
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:323
@ DT_SIGNAL_CONTROL_PICKERDATA_READY
This signal is raised when new color picker data are available in darkroom. no param,...
Definition signal.h:296
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
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...
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:140
GtkWidget * dt_widget_scroll_focus(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define dt_gui_widget_freeze()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
void dt_capitalize_label(gchar *text)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_module_switch_on(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_presets(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_multiinstance(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_module_switch(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_reset(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dt_ui_container_add_widget(dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w)
@ DT_UI_CONTAINER_SIZE
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER