Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/masks.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013, 2016, 2022 Aldric Renaudin.
4 Copyright (C) 2013-2016 Roman Lebedev.
5 Copyright (C) 2013 Simon Spannagel.
6 Copyright (C) 2013-2018 Tobias Ellinghaus.
7 Copyright (C) 2013, 2015-2016 Ulrich Pegelow.
8 Copyright (C) 2014 parafin.
9 Copyright (C) 2017-2018 Edgardo Hoszowski.
10 Copyright (C) 2018 luzpaz.
11 Copyright (C) 2018 Maurizio Paglia.
12 Copyright (C) 2018 rawfiner.
13 Copyright (C) 2019 Ari.
14 Copyright (C) 2019, 2022-2023, 2025-2026 Aurélien PIERRE.
15 Copyright (C) 2019-2021 Pascal Obry.
16 Copyright (C) 2020, 2022 Chris Elston.
17 Copyright (C) 2020, 2022 Diederik Ter Rahe.
18 Copyright (C) 2020 Hanno Schwalm.
19 Copyright (C) 2020 Hubert Kowalski.
20 Copyright (C) 2020 Marco.
21 Copyright (C) 2021 Philipp Lutz.
22 Copyright (C) 2021 Philippe Weyland.
23 Copyright (C) 2021 Ralf Brown.
24 Copyright (C) 2022 Martin Bařinka.
25 Copyright (C) 2022 Victor Forsiuk.
26 Copyright (C) 2025-2026 Guillaume Stutin.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41#include "develop/masks.h"
42#include "bauhaus/bauhaus.h"
43#include "common/darktable.h"
44#include "common/debug.h"
45#include "common/styles.h"
46#include "control/conf.h"
47#include "control/control.h"
48#include "develop/blend.h"
49#include "develop/develop.h"
50#include "develop/imageop.h"
51#include "dtgtk/button.h"
52#include "gui/draw.h"
53#include "gui/gtk.h"
54#include "gui/styles.h"
55#include "libs/lib.h"
56#include "libs/lib_api.h"
57
58#ifdef GDK_WINDOWING_QUARTZ
59#include "osx/osx.h"
60#endif
61
62
63DT_MODULE(1)
64
65#pragma GCC diagnostic ignored "-Wshadow"
66
69
85
86
87const char *name(struct dt_lib_module_t *self)
88{
89 return _("Masking & Blending");
90}
91
92const char **views(dt_lib_module_t *self)
93{
94 static const char *v[] = {"darkroom", NULL};
95 return v;
96}
97
99{
101}
102
104{
105 return 1;
106}
107
109{
110 return 850;
111}
112
129
130static void _lib_masks_get_values(GtkTreeModel *model, GtkTreeIter *iter,
131 dt_iop_module_t **module, int *groupid, int *formid)
132{
133 // returns module & groupid & formid if requested
134
135 if(module)
136 {
137 GValue gv = { 0, };
138 gtk_tree_model_get_value(model, iter, TREE_MODULE, &gv);
139 *module = NULL;
140 if(G_VALUE_TYPE(&gv) == G_TYPE_POINTER)
141 *module = (dt_iop_module_t *)g_value_get_pointer(&gv);
142 g_value_unset(&gv);
143 }
144
145 if(groupid)
146 {
147 GValue gv = { 0, };
148 gtk_tree_model_get_value(model, iter, TREE_GROUPID, &gv);
149 *groupid = g_value_get_int(&gv);
150 g_value_unset(&gv);
151 }
152
153 if(formid)
154 {
155 GValue gv = { 0,};
156 gtk_tree_model_get_value(model, iter, TREE_FORMID, &gv);
157 *formid = g_value_get_int(&gv);
158 g_value_unset(&gv);
159 }
160}
161
162static gboolean _lib_masks_module_is_current(const dt_iop_module_t *module)
163{
164 return darktable.develop && module && g_list_find(darktable.develop->iop, (gpointer)module);
165}
166
168{
169 if(IS_NULL_PTR(self) || !GTK_IS_WIDGET(self->widget)) return;
170
171 GList *children = gtk_container_get_children(GTK_CONTAINER(self->widget));
172 for(GList *iter = children; iter; iter = g_list_next(iter))
173 gtk_widget_destroy(GTK_WIDGET(iter->data));
174 g_list_free(children);
175 children = NULL;
176}
177
178static gboolean _lib_masks_can_host_blending(const dt_iop_module_t *module)
179{
180 if(!_lib_masks_module_is_current(module) || !module->flags
181 || !(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || !module->blend_data)
182 return FALSE;
183
184 return TRUE;
185}
186
188{
189 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
190 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
191 dt_iop_module_t *hosted_module = lm->hosted_module;
192 lm->hosted_module = NULL;
193
194 if(_lib_masks_can_host_blending(hosted_module))
196 else
198}
199
200static void _lib_masks_show_blending_message(dt_lib_module_t *self, gchar *markup)
201{
202 if(IS_NULL_PTR(self) || !GTK_IS_WIDGET(self->widget) || IS_NULL_PTR(markup)) return;
203
204 GtkWidget *label = gtk_label_new(NULL);
205 gtk_label_set_markup(GTK_LABEL(label), markup);
206 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
207 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
208 gtk_widget_set_margin_top(label, DT_PIXEL_APPLY_DPI(16));
209 gtk_widget_set_margin_bottom(label, DT_PIXEL_APPLY_DPI(16));
210 gtk_widget_set_sensitive(label, FALSE);
211 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
212 // self->widget is the expander body: its own visibility encodes the
213 // expanded/collapsed state persisted in conf, so show only the child.
214 gtk_widget_show_all(label);
215}
216
218{
219 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
220
221 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
222 dt_iop_module_t *module = darktable.develop->gui_module;
223
225 {
228
229 gchar *markup = g_markup_printf_escaped(_("<i>Select a module to edit its blending settings.</i>"));
230
232 g_free(markup);
233
234 return;
235 }
236
238 {
241
242 gchar *module_label = dt_history_item_get_name(module);
243 gchar *markup = g_markup_printf_escaped(_("<i>Blending is not available for the <b>%s</b> module.</i>"), module_label);
244
246 g_free(markup);
247 dt_free(module_label);
248
249 return;
250 }
251
252 const gboolean module_changed = (lm->active_module != module);
253 lm->active_module = module;
254
255 if(module_changed) _lib_masks_release_blending(self);
256
258
259 // dt_iop_gui_init_blending_body() shows the children it packs; don't show
260 // self->widget itself, it is the expander body whose visibility encodes the
261 // expanded/collapsed state persisted in conf.
263 lm->hosted_module = module;
264
266}
267
268static void _tree_add_circle(GtkButton *button, dt_iop_module_t *module)
269{
270 // we create the new form
274}
275
276static void _tree_add_ellipse(GtkButton *button, dt_iop_module_t *module)
277{
278 // we create the new form
282}
283
284static void _tree_add_polygon(GtkButton *button, dt_iop_module_t *module)
285{
286 // we create the new form
290}
291
292static void _tree_add_gradient(GtkButton *button, dt_iop_module_t *module)
293{
294 // we create the new form
298}
299
300static void _tree_add_brush(GtkButton *button, dt_iop_module_t *module)
301{
302 // we create the new form
306}
307
309 dt_masks_type_t type, gpointer user_data)
310{
312}
313
314static void _tree_add_exist(GtkButton *button, dt_masks_form_t *grp)
315{
316 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
317 // we get the new formid
318 const int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "formid"));
319 dt_iop_module_t *module = g_object_get_data(G_OBJECT(button), "module");
320
321 // we add the form in this group
324 if(form && dt_masks_group_add_form(grp, form))
325 {
326 // we save the group
328
329 // and we apply the change
330
331 dt_masks_iop_update(module);
333 }
334}
335
336static void _tree_group(GtkButton *button, dt_lib_module_t *self)
337{
338 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
339 // we create the new group
341 g_snprintf(mask->name, sizeof(mask->name), _("Mask #%d"), g_list_length(darktable.develop->forms));
342
343 // we add all selected forms to this group
344 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
345 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
346
347 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
348 for(GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
349 {
350 GtkTreePath *item = (GtkTreePath *)items_iter->data;
351 GtkTreeIter iter;
352 if(gtk_tree_model_get_iter(model, &iter, item))
353 {
354 int id = -1;
355 _lib_masks_get_values(model, &iter, NULL, NULL, &id);
356
357 if(id > 0)
358 {
360 fpt->formid = id;
361 fpt->parentid = mask->formid;
362 fpt->opacity = 1.0f;
364 mask->points = g_list_append(mask->points, fpt);
365 }
366 }
367 }
368 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
369 items = NULL;
370
371 // we add this group to the general list
372 darktable.develop->forms = g_list_append(darktable.develop->forms, mask);
373
374 // add we save
377 // dt_masks_change_form_gui(grp);
378}
379
380static int _tree_format_form_usage_label(char *str, const size_t str_size,
381 const dt_masks_form_t *form, const dt_iop_module_t *module)
382{
383 if(IS_NULL_PTR(str) || IS_NULL_PTR(form)) return -1;
384
385 str[0] = '\0';
386 g_strlcat(str, form->name, str_size);
387
388 int nbuse = 0;
389 // we search were this form is used
390 for(const GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
391 {
392 dt_iop_module_t *m = (dt_iop_module_t *)modules->data;
393 dt_masks_form_t *grp = dt_masks_get_from_id(m->dev, m->blend_params->mask_id);
394 if(grp && (grp->type & DT_MASKS_GROUP))
395 {
396 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
397 {
399 if(pt->formid == form->formid)
400 {
401 if(m == module) return -1;
402 if(nbuse == 0) g_strlcat(str, " (", str_size);
403 g_strlcat(str, " ", str_size);
404 gchar *module_label = dt_history_item_get_name(m);
405 g_strlcat(str, module_label, str_size);
406 dt_free(module_label);
407 nbuse++;
408 }
409 }
410 }
411 }
412
413 if(nbuse > 0) g_strlcat(str, " )", str_size);
414 return nbuse;
415}
416
417static void _set_iter_name(dt_lib_masks_t *lm, dt_masks_form_t *form, int state, float opacity,
418 GtkTreeModel *model, GtkTreeIter *iter, int index)
419{
420 if(IS_NULL_PTR(form)) return;
421
422 char str[256] = "";
423
424 if(opacity != 1.0f)
425 {
426 g_snprintf(str, sizeof(str), "%s %d%%",
427 form->name, (int)(opacity * 100));
428 }
429 else
430 {
431 g_strlcpy(str, form->name, sizeof(str));
432 }
433
434 GdkPixbuf *icop = NULL;
435 GdkPixbuf *icinv = NULL;
436 if(index != 0)
437 {
439 icop = lm->ic_union;
441 icop = lm->ic_intersection;
443 icop = lm->ic_difference;
445 icop = lm->ic_exclusion;
446 }
447 if(state & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;
448
449 gtk_tree_store_set(GTK_TREE_STORE(model), iter, TREE_TEXT, str, TREE_IC_OP, icop, TREE_IC_OP_VISIBLE,
450 (!IS_NULL_PTR(icop)), TREE_IC_INVERSE, icinv, TREE_IC_INVERSE_VISIBLE, (!IS_NULL_PTR(icinv)), -1);
451}
452
453static void _tree_cleanup(GtkButton *button, dt_lib_module_t *self)
454{
457}
458
460{
461 const int reset = lm->gui_reset;
462 lm->gui_reset = 1;
464 lm->gui_reset = reset;
465}
466
467
468static void _tree_inverse(GtkButton *button, dt_lib_module_t *self)
469{
470 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
471
472 // now we go through all selected nodes
473 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
474 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
475 int change = 0;
476 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
477 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
478 {
479 GtkTreePath *item = (GtkTreePath *)items_iter->data;
480 GtkTreeIter iter;
481 if(gtk_tree_model_get_iter(model, &iter, item))
482 {
483 int grid = -1;
484 int id = -1;
485 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
486
488 if(grp && (grp->type & DT_MASKS_GROUP))
489 {
490 int i = 0;
491 // we search the entry to inverse
492 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
493 {
495 if(pt->formid == id)
496 {
497 const int old_state = pt->state;
499 if(pt->state != old_state)
500 {
502 &iter, i);
503 change = 1;
504 }
505 break;
506 }
507 i++;
508 }
509 }
510 }
511 }
512 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
513 items = NULL;
514
515 if(change)
516 {
518
520 }
521}
522
523static void _tree_intersection(GtkButton *button, dt_lib_module_t *self)
524{
525 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
526
527 // now we go through all selected nodes
528 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
529 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
530 int change = 0;
531 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
532 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
533 {
534 GtkTreePath *item = (GtkTreePath *)items_iter->data;
535 GtkTreeIter iter;
536 if(gtk_tree_model_get_iter(model, &iter, item))
537 {
538 int grid = -1;
539 int id = -1;
540 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
541
543 if(grp && (grp->type & DT_MASKS_GROUP))
544 {
545 int i = 0;
546 // we search the entry to inverse
547 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
548 {
550 if(pt->formid == id)
551 {
552 const int old_state = pt->state;
554 if(pt->state != old_state)
555 {
557 &iter, i);
558 change = 1;
559 }
560 break;
561 }
562 i++;
563 }
564 }
565 }
566 }
567 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
568 items = NULL;
569
570 if(change)
571 {
573
575 }
576}
577
578static void _tree_difference(GtkButton *button, dt_lib_module_t *self)
579{
580 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
581
582 // now we go through all selected nodes
583 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
584 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
585 int change = 0;
586 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
587 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
588 {
589 GtkTreePath *item = (GtkTreePath *)items_iter->data;
590 GtkTreeIter iter;
591 if(gtk_tree_model_get_iter(model, &iter, item))
592 {
593 int grid = -1;
594 int id = -1;
595 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
596
598 if(grp && (grp->type & DT_MASKS_GROUP))
599 {
600 int i = 0;
601 // we search the entry to inverse
602 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
603 {
605 if(pt->formid == id)
606 {
607 const int old_state = pt->state;
609 if(pt->state != old_state)
610 {
612 &iter, i);
613 change = 1;
614 }
615 break;
616 }
617 i++;
618 }
619 }
620 }
621 }
622 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
623 items = NULL;
624
625 if(change)
626 {
628
630 }
631}
632
633static void _tree_exclusion(GtkButton *button, dt_lib_module_t *self)
634{
635 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
636
637 // now we go through all selected nodes
638 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
639 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
640 int change = 0;
641 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
642 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
643 {
644 GtkTreePath *item = (GtkTreePath *)items_iter->data;
645 GtkTreeIter iter;
646 if(gtk_tree_model_get_iter(model, &iter, item))
647 {
648 int grid = -1;
649 int id = -1;
650 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
651
653 if(grp && (grp->type & DT_MASKS_GROUP))
654 {
655 int i = 0;
656 // we search the entry to inverse
657 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
658 {
660 if(pt->formid == id)
661 {
662 const int old_state = pt->state;
664 if(pt->state != old_state)
665 {
667 &iter, i);
668 change = 1;
669 }
670 break;
671 }
672 i++;
673 }
674 }
675 }
676 }
677 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
678 items = NULL;
679
680 if(change)
681 {
683
685 }
686}
687
688static void _tree_union(GtkButton *button, dt_lib_module_t *self)
689{
690 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
691
692 // now we go through all selected nodes
693 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
694 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
695 int change = 0;
696 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
697 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
698 {
699 GtkTreePath *item = (GtkTreePath *)items_iter->data;
700 GtkTreeIter iter;
701 if(gtk_tree_model_get_iter(model, &iter, item))
702 {
703 int grid = -1;
704 int id = -1;
705 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
706
708 if(grp && (grp->type & DT_MASKS_GROUP))
709 {
710 int i = 0;
711 // we search the entry to inverse
712 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
713 {
715 if(pt->formid == id)
716 {
717 const int old_state = pt->state;
719 if(pt->state != old_state)
720 {
722 &iter, i);
723 change = 1;
724 }
725 break;
726 }
727 i++;
728 }
729 }
730 }
731 }
732 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
733 items = NULL;
734
735 if(change)
736 {
738
740 }
741}
742
743static void _tree_moveup(GtkButton *button, dt_lib_module_t *self)
744{
745 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
746
747 // we first discard all visible shapes
749
750 // now we go through all selected nodes
751 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
752 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
753 lm->gui_reset = 1;
754 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
755 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
756 {
757 GtkTreePath *item = (GtkTreePath *)items_iter->data;
758 GtkTreeIter iter;
759 if(gtk_tree_model_get_iter(model, &iter, item))
760 {
761 int grid = -1;
762 int id = -1;
763 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
764
766 group_form = dt_masks_cow_touch(darktable.develop, group_form);
767 dt_masks_form_move(group_form, id, 0);
768 }
769 }
770 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
771 items = NULL;
772
773 lm->gui_reset = 0;
775
776 // Without this, the reorder only mutates the live group's points list: it's never recorded
777 // as its own history step, so the next undo/redo silently discards the new order.
779}
780
781static void _tree_movedown(GtkButton *button, dt_lib_module_t *self)
782{
783 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
784
785 // we first discard all visible shapes
787
788 // now we go through all selected nodes
789 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
790 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
791 lm->gui_reset = 1;
792 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
793 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
794 {
795 GtkTreePath *item = (GtkTreePath *)items_iter->data;
796 GtkTreeIter iter;
797 if(gtk_tree_model_get_iter(model, &iter, item))
798 {
799 int grid = -1;
800 int id = -1;
801 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
802
804 group_form = dt_masks_cow_touch(darktable.develop, group_form);
805 dt_masks_form_move(group_form, id, 1);
806 }
807 }
808 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
809 items = NULL;
810
811 lm->gui_reset = 0;
813
814 // Without this, the reorder only mutates the live group's points list: it's never recorded
815 // as its own history step, so the next undo/redo silently discards the new order.
817}
818
819static void _tree_delete_shape(GtkButton *button, dt_lib_module_t *self)
820{
821 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
822
823 // we first discard all visible shapes
825
826 // now we go through all selected nodes
827 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
828 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
829 dt_iop_module_t *module = NULL;
830 lm->gui_reset = 1;
831 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
832 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
833 {
834 GtkTreePath *item = (GtkTreePath *)items_iter->data;
835 GtkTreeIter iter;
836 if(gtk_tree_model_get_iter(model, &iter, item))
837 {
838 int grid = -1;
839 int id = -1;
840 _lib_masks_get_values(model, &iter, &module, &grid, &id);
841
844 }
845 }
846 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
847 items = NULL;
848
849 lm->gui_reset = 0;
851
852 // Without this, the deletion only mutates the live dev->forms: it's never recorded as its
853 // own history step, so the next history navigation (undo/redo) silently discards it and
854 // reverts to whatever forms snapshot was last actually committed.
856}
857
858static void _tree_duplicate_shape(GtkButton *button, dt_lib_module_t *self)
859{
860 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
861
862 // we get the selected node
863 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
864 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
865 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
866 if(IS_NULL_PTR(items)) return;
867 GtkTreePath *item = (GtkTreePath *)items->data;
868 GtkTreeIter iter;
869 if(gtk_tree_model_get_iter(model, &iter, item))
870 {
871 dt_iop_module_t *module = NULL;
872 int grid = -1;
873 int id = -1;
874 _lib_masks_get_values(model, &iter, &module, &grid, &id);
875
876 const int nid = dt_masks_form_duplicate(darktable.develop, id);
877 if(nid > 0)
878 {
879 // If the source shape was a member of a group (grid != 0, i.e. not itself a top-level
880 // group node), dt_masks_form_duplicate only cloned the shape into dev->forms -- it does
881 // NOT add it to that group. Without this, the duplicate is an orphan: invisible on
882 // canvas and useless to the module, since nothing outside a group ever gets rendered.
883 dt_masks_form_t *grp = (grid > 0) ? dt_masks_get_from_id(darktable.develop, grid) : NULL;
884 if(grp && (grp->type & DT_MASKS_GROUP))
885 {
887
888 dt_masks_form_group_t *orig_entry = NULL;
889 for(GList *pts = grp->points; pts; pts = g_list_next(pts))
890 {
892 if(pt->formid == id) { orig_entry = pt; break; }
893 }
894
896 dt_masks_form_group_t *new_entry = dup_form ? dt_masks_group_add_form(grp, dup_form) : NULL;
897 if(new_entry && orig_entry)
898 {
899 new_entry->state = orig_entry->state;
900 new_entry->opacity = orig_entry->opacity;
901 }
902
903 if(module) dt_masks_iop_update(module);
904 }
905
907
908 // Without this, the new form only exists in the live dev->forms: it's never recorded as
909 // its own history step, so it silently disappears on the next undo/redo.
911
912 // _add_masks_history_item briefly sets lm->gui_reset while committing, and
913 // dt_dev_add_history_item's own list-change signal fires synchronously inside that
914 // window -- _lib_masks_recreate_list's gui_reset guard swallows it. Refresh explicitly,
915 // now that gui_reset is back to its prior value, so the new row actually appears.
917 }
918 }
919 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
920 items = NULL;
921}
922
923static void _tree_cell_edited(GtkCellRendererText *cell, gchar *path_string, gchar *new_text,
924 dt_lib_module_t *self)
925{
926 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
927 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
928 GtkTreeIter iter;
929 if(!gtk_tree_model_get_iter_from_string(model, &iter, path_string)) return;
930
931 int id = -1;
932 _lib_masks_get_values(model, &iter, NULL, NULL, &id);
934 if(IS_NULL_PTR(form)) return;
935
936 // we want to make sure that the new name is not an empty string. else this would convert
937 // in the xmp file into "<rdf:li/>" which produces problems. we use a single whitespace
938 // as the pure minimum text.
939 gchar *text = strlen(new_text) == 0 ? " " : new_text;
940
941 // first, we need to update the mask name
942
943 g_strlcpy(form->name, text, sizeof(form->name));
945}
946
947static void _tree_selection_change(GtkTreeSelection *selection, dt_lib_masks_t *self)
948{
949 if(self->gui_reset) return;
951 if(!IS_NULL_PTR(creation_gui) && creation_gui->creation) return;
952
953 // we reset all "show mask" icon of iops
955
956 // if selection empty, we hide all
957 const int nb = gtk_tree_selection_count_selected_rows(selection);
958 if(nb == 0)
959 {
962 return;
963 }
964
965 // else, we create a new form group with the selection and display it
966 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeview));
968 dt_masks_form_t *selected_form = NULL;
969 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
970 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
971 {
972 GtkTreePath *item = (GtkTreePath *)items_iter->data;
973 GtkTreeIter iter;
974 if(gtk_tree_model_get_iter(model, &iter, item))
975 {
976 int grid = -1;
977 int id = -1;
978 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
979
981 if(!IS_NULL_PTR(form))
982 {
983 if(nb == 1) selected_form = form;
985 fpt->formid = id;
986 fpt->parentid = grid;
988 fpt->opacity = 1.0f;
989 grp->points = g_list_append(grp->points, fpt);
990 // we eventually set the "show masks" icon of iops
991 if(nb == 1 && (form->type & DT_MASKS_GROUP))
992 {
993 dt_iop_module_t *module = NULL;
994 _lib_masks_get_values(model, &iter, &module, NULL, NULL);
995
996 if(module && module->blend_data
997 && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
998 && !(module->flags() & IOP_FLAGS_NO_MASKS))
999 {
1000 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
1001 bd->masks_shown = 1;
1002 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), TRUE);
1003 gtk_widget_queue_draw(bd->masks_edit);
1004 }
1005 }
1006 }
1007 }
1008 }
1009 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1010 items = NULL;
1011
1013 grp2->formid = 0;
1014 dt_masks_group_ungroup(grp2, grp);
1017 if(nb == 1 && !IS_NULL_PTR(selected_form))
1019 else
1021}
1022
1023static GtkWidget *_tree_context_menu(GtkTreeSelection *selection, GtkTreeModel *model,
1024 dt_lib_module_t *self, dt_iop_module_t *module)
1025{
1026 GtkTreeIter iter;
1027 GtkMenuShell *menu = GTK_MENU_SHELL(gtk_menu_new());
1028 GtkWidget *item;
1029
1030 // we get all infos from selection
1031 const int nb = gtk_tree_selection_count_selected_rows(selection);
1032 int from_group = 0;
1033
1034 int grpid = 0;
1035 int depth = 0;
1036
1037 if(nb > 0)
1038 {
1039 GList *selected = gtk_tree_selection_get_selected_rows(selection, NULL);
1040 GtkTreePath *it0 = (GtkTreePath *)selected->data;
1041 depth = gtk_tree_path_get_depth(it0);
1042 if(nb == 1)
1043 {
1044 // before freeing the list of selected rows, we check if the form is a group or not
1045 if(gtk_tree_model_get_iter(model, &iter, it0))
1046 {
1047 _lib_masks_get_values(model, &iter, NULL, NULL, &grpid);
1048 }
1049 }
1050 g_list_free_full(selected, (GDestroyNotify)gtk_tree_path_free);
1051 selected = NULL;
1052 }
1053 if(depth > 1) from_group = 1;
1054
1055 if(nb == 0)
1056 {
1057 GtkWidget *add_menu = gtk_menu_new();
1058 GtkWidget *add_item = gtk_menu_item_new_with_label(_("Add new shape ..."));
1059 gtk_menu_item_set_submenu(GTK_MENU_ITEM(add_item), add_menu);
1060 gtk_menu_shell_append(menu, add_item);
1061
1062 item = gtk_menu_item_new_with_label(_("add circle"));
1063 g_signal_connect(item, "activate", (GCallback)_tree_add_circle, module);
1064 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1065
1066 item = gtk_menu_item_new_with_label(_("add ellipse"));
1067 g_signal_connect(item, "activate", (GCallback)_tree_add_ellipse, module);
1068 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1069
1070 item = gtk_menu_item_new_with_label(_("add path"));
1071 g_signal_connect(item, "activate", (GCallback)_tree_add_polygon, module);
1072 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1073
1074 item = gtk_menu_item_new_with_label(_("add gradient"));
1075 g_signal_connect(item, "activate", (GCallback)_tree_add_gradient, module);
1076 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1077
1078 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1079 }
1080
1081 if(nb == 1)
1082 {
1084 if(grp && (grp->type & DT_MASKS_GROUP))
1085 {
1086 GtkWidget *add_menu = gtk_menu_new();
1087 GtkWidget *add_item = gtk_menu_item_new_with_label(_("Add new shape ..."));
1088 gtk_menu_item_set_submenu(GTK_MENU_ITEM(add_item), add_menu);
1089 gtk_menu_shell_append(menu, add_item);
1090
1091 item = gtk_menu_item_new_with_label(_("Add brush"));
1092 g_signal_connect(item, "activate", (GCallback)_tree_add_brush, module);
1093 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1094
1095 item = gtk_menu_item_new_with_label(_("Add circle"));
1096 g_signal_connect(item, "activate", (GCallback)_tree_add_circle, module);
1097 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1098
1099 item = gtk_menu_item_new_with_label(_("Add ellipse"));
1100 g_signal_connect(item, "activate", (GCallback)_tree_add_ellipse, module);
1101 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1102
1103 item = gtk_menu_item_new_with_label(_("Add polygon"));
1104 g_signal_connect(item, "activate", (GCallback)_tree_add_polygon, module);
1105 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1106
1107 item = gtk_menu_item_new_with_label(_("Add gradient"));
1108 g_signal_connect(item, "activate", (GCallback)_tree_add_gradient, module);
1109 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1110
1111 // existing forms
1112 gboolean has_unused_shapes = FALSE;
1113 GtkWidget *menu0 = gtk_menu_new();
1114 for(GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1115 {
1116 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1117 if((form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE)) || form->formid == grpid)
1118 {
1119 continue;
1120 }
1121 char str[10000] = "";
1122 const int nbuse = _tree_format_form_usage_label(str, sizeof(str), form, module);
1123 if(nbuse == -1) continue;
1124
1125 // we add the menu entry
1126 item = gtk_menu_item_new_with_label(str);
1127 g_object_set_data(G_OBJECT(item), "formid", GUINT_TO_POINTER(form->formid));
1128 g_object_set_data(G_OBJECT(item), "module", module);
1129 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_tree_add_exist), grp);
1130 gtk_menu_shell_append(GTK_MENU_SHELL(menu0), item);
1131 has_unused_shapes = TRUE;
1132 }
1133
1134 if(has_unused_shapes)
1135 {
1136 item = gtk_menu_item_new_with_label(_("Add shape ..."));
1137 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu0);
1138 gtk_menu_shell_append(menu, item);
1139 }
1140 }
1141 }
1142
1143 if(nb > 1 && !from_group)
1144 {
1145 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1146 item = gtk_menu_item_new_with_label(_("Group the forms"));
1147 g_signal_connect(item, "activate", (GCallback)_tree_group, self);
1148 gtk_menu_shell_append(menu, item);
1149 }
1150
1151 if(from_group && depth < 3)
1152 {
1153 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1154 item = gtk_menu_item_new_with_label(_("Invert shape"));
1155 g_signal_connect(item, "activate", (GCallback)_tree_inverse, self);
1156 gtk_menu_shell_append(menu, item);
1157 if(nb == 1)
1158 {
1159 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1160 item = gtk_menu_item_new_with_label(_("Union"));
1161 g_signal_connect(item, "activate", (GCallback)_tree_union, self);
1162 gtk_menu_shell_append(menu, item);
1163 item = gtk_menu_item_new_with_label(_("Intersection"));
1164 g_signal_connect(item, "activate", (GCallback)_tree_intersection, self);
1165 gtk_menu_shell_append(menu, item);
1166 item = gtk_menu_item_new_with_label(_("Difference"));
1167 g_signal_connect(item, "activate", (GCallback)_tree_difference, self);
1168 gtk_menu_shell_append(menu, item);
1169 item = gtk_menu_item_new_with_label(_("Exclusion"));
1170 g_signal_connect(item, "activate", (GCallback)_tree_exclusion, self);
1171 gtk_menu_shell_append(menu, item);
1172 }
1173 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1174 item = gtk_menu_item_new_with_label(_("Move up"));
1175 g_signal_connect(item, "activate", (GCallback)_tree_moveup, self);
1176 gtk_menu_shell_append(menu, item);
1177 item = gtk_menu_item_new_with_label(_("Move down"));
1178 g_signal_connect(item, "activate", (GCallback)_tree_movedown, self);
1179 gtk_menu_shell_append(menu, item);
1180 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1181 }
1182
1184 if(!from_group && !(grp && (grp->type & DT_MASKS_GROUP)) && nb == 1)
1185 {
1186 item = gtk_menu_item_new_with_label(_("Duplicate shape"));
1187 g_signal_connect(item, "activate", (GCallback)_tree_duplicate_shape, self);
1188 gtk_menu_shell_append(menu, item);
1189 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1190 }
1191
1192 if(!from_group && nb > 0)
1193 {
1194 if(!(grp && (grp->type & DT_MASKS_GROUP)))
1195 {
1196 item = gtk_menu_item_new_with_label(_("Delete shape"));
1197 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, self);
1198 gtk_menu_shell_append(menu, item);
1199 }
1200 else
1201 {
1202 item = gtk_menu_item_new_with_label(_("Delete mask"));
1203 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, self);
1204 gtk_menu_shell_append(menu, item);
1205 }
1206 }
1207 else if(nb > 0 && depth < 3)
1208 {
1209 item = gtk_menu_item_new_with_label(_("Remove shape from mask"));
1210 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, self);
1211 gtk_menu_shell_append(menu, item);
1212 }
1213
1214 item = gtk_menu_item_new_with_label(_("Cleanup unused shapes"));
1215 g_signal_connect(item, "activate", (GCallback)_tree_cleanup, self);
1216 gtk_menu_shell_append(menu, item);
1217
1218 return GTK_WIDGET(menu);
1219}
1220
1221static int _tree_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
1222{
1223 // we first need to adjust selection
1224 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1225 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
1226
1227 GtkTreePath *mouse_path = NULL;
1228 GtkTreeIter iter;
1229 dt_iop_module_t *module = NULL;
1230 int on_row = 0;
1231 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint)event->x, (gint)event->y, &mouse_path, NULL,
1232 NULL, NULL))
1233 {
1234 on_row = 1;
1235 // we retrieve the iter and module from path
1236 if(gtk_tree_model_get_iter(model, &iter, mouse_path))
1237 {
1238 _lib_masks_get_values(model, &iter, &module, NULL, NULL);
1239 }
1240 }
1241 /* single click with the right mouse button? */
1242 if(event->type == GDK_BUTTON_PRESS && event->button == 1)
1243 {
1244 // if click on a blank space, then deselect all
1245 if(!on_row)
1246 {
1247 gtk_tree_selection_unselect_all(selection);
1248 }
1249 }
1250 else if(event->type == GDK_BUTTON_PRESS && event->button == 3)
1251 {
1252 // if we are already inside the selection, no change
1253 if(on_row && !gtk_tree_selection_path_is_selected(selection, mouse_path))
1254 {
1255 if(!dt_modifier_is(event->state, GDK_CONTROL_MASK)) gtk_tree_selection_unselect_all(selection);
1256 gtk_tree_selection_select_path(selection, mouse_path);
1257 gtk_tree_path_free(mouse_path);
1258 }
1259
1260 // and we display the context-menu
1261 GtkWidget *menu = _tree_context_menu(selection, model, self, module);
1262
1263 gtk_widget_show_all(menu);
1264
1265 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
1266
1267 return 1;
1268 }
1269
1270 return 0;
1271}
1272
1273static gboolean _tree_restrict_select(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path,
1274 gboolean path_currently_selected, gpointer data)
1275{
1276 dt_lib_masks_t *self = (dt_lib_masks_t *)data;
1277 if(self->gui_reset) return TRUE;
1278
1279 // if the change is SELECT->UNSELECT no pb
1280 if(path_currently_selected) return TRUE;
1281
1282 // if selection is empty, no pb
1283 if(gtk_tree_selection_count_selected_rows(selection) == 0) return TRUE;
1284
1285 // now we unselect all members of selection with not the same parent node
1286 // idem for all those with a different depth
1287 int *indices = gtk_tree_path_get_indices(path);
1288 int depth = gtk_tree_path_get_depth(path);
1289
1290 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
1291 GList *items_iter = items;
1292 while(items_iter)
1293 {
1294 GtkTreePath *item = (GtkTreePath *)items_iter->data;
1295 int dd = gtk_tree_path_get_depth(item);
1296 int *ii = gtk_tree_path_get_indices(item);
1297 int ok = 1;
1298 if(dd != depth)
1299 ok = 0;
1300 else if(dd == 1)
1301 ok = 1;
1302 else if(ii[dd - 2] != indices[dd - 2])
1303 ok = 0;
1304 if(!ok)
1305 {
1306 gtk_tree_selection_unselect_path(selection, item);
1307 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1308 items = NULL;
1309 items_iter = items = gtk_tree_selection_get_selected_rows(selection, NULL);
1310 continue;
1311 }
1312 items_iter = g_list_next(items_iter);
1313 }
1314 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1315 items = NULL;
1316 return TRUE;
1317}
1318
1319static gboolean _tree_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_tip,
1320 GtkTooltip *tooltip, gpointer data)
1321{
1322 GtkTreeIter iter;
1323 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
1324 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
1325 GtkTreePath *path = NULL;
1326 gchar *tmp = NULL;
1327 gboolean show = FALSE;
1328
1329 if(!gtk_tree_view_get_tooltip_context(tree_view, &x, &y, keyboard_tip, &model, &path, &iter)) return FALSE;
1330
1331 gtk_tree_model_get(model, &iter, TREE_IC_USED_VISIBLE, &show, TREE_USED_TEXT, &tmp, -1);
1332 if(show)
1333 {
1334 gtk_tooltip_set_markup(tooltip, tmp);
1335 gtk_tree_view_set_tooltip_row(tree_view, tooltip, path);
1336 }
1337
1338 gtk_tree_path_free(path);
1339 dt_free(tmp);
1340
1341 return show;
1342}
1343
1344static void _is_form_used(int formid, dt_masks_form_t *grp, char *text, size_t text_length, int *nb)
1345{
1346 if(IS_NULL_PTR(grp))
1347 {
1348 for(const GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1349 {
1350 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1351 if(form->type & DT_MASKS_GROUP) _is_form_used(formid, form, text, text_length, nb);
1352 }
1353 }
1354 else if(grp->type & DT_MASKS_GROUP)
1355 {
1356 for(const GList *points = grp->points; points; points = g_list_next(points))
1357 {
1360 if(form)
1361 {
1362 if(point->formid == formid)
1363 {
1364 (*nb)++;
1365 if(*nb > 1) g_strlcat(text, "\n", text_length);
1366 g_strlcat(text, grp->name, text_length);
1367 }
1368 if(form->type & DT_MASKS_GROUP) _is_form_used(formid, form, text, text_length, nb);
1369 }
1370 }
1371 }
1372}
1373
1374static void _lib_masks_list_recurs(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_masks_form_t *form,
1375 int grp_id, dt_iop_module_t *module, int gstate, float opacity,
1376 dt_lib_masks_t *lm, int index)
1377{
1378 if(form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE)) return;
1379 // we create the text entry
1380 char str[256] = "";
1381 g_strlcat(str, form->name, sizeof(str));
1382 // we get the right pixbufs
1383 GdkPixbuf *icop = NULL;
1384 GdkPixbuf *icinv = NULL;
1385 GdkPixbuf *icuse = NULL;
1386 if(gstate & DT_MASKS_STATE_UNION)
1387 icop = lm->ic_union;
1388 else if(gstate & DT_MASKS_STATE_INTERSECTION)
1389 icop = lm->ic_intersection;
1390 else if(gstate & DT_MASKS_STATE_DIFFERENCE)
1391 icop = lm->ic_difference;
1392 else if(gstate & DT_MASKS_STATE_EXCLUSION)
1393 icop = lm->ic_exclusion;
1394 if(gstate & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;
1395 char str2[1000] = "";
1396 int nbuse = 0;
1397 if(grp_id == 0)
1398 {
1399 _is_form_used(form->formid, NULL, str2, sizeof(str2), &nbuse);
1400 if(nbuse > 0) icuse = lm->ic_wired;
1401 }
1402
1403 if(!(form->type & DT_MASKS_GROUP))
1404 {
1405 // we just add it to the tree
1406 GtkTreeIter child;
1407 gtk_tree_store_append(treestore, &child, toplevel);
1408 gtk_tree_store_set(treestore, &child, TREE_TEXT, str, TREE_MODULE, module, TREE_GROUPID, grp_id,
1409 TREE_FORMID, form->formid, TREE_EDITABLE, (grp_id == 0), TREE_IC_OP, icop,
1411 (!IS_NULL_PTR(icinv)), TREE_IC_USED, icuse, TREE_IC_USED_VISIBLE, (nbuse > 0),
1412 TREE_USED_TEXT, str2, -1);
1413 _set_iter_name(lm, form, gstate, opacity, GTK_TREE_MODEL(treestore), &child, index);
1414 }
1415 else
1416 {
1417 // we first check if it's a "module" group or not
1418 if(grp_id == 0 && !module)
1419 {
1420 for(const GList *iops = darktable.develop->iop; iops; iops = g_list_next(iops))
1421 {
1422 dt_iop_module_t *iop = (dt_iop_module_t *)iops->data;
1423 if((iop->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(iop->flags() & IOP_FLAGS_NO_MASKS)
1424 && iop->blend_params->mask_id == form->formid)
1425 {
1426 module = iop;
1427 break;
1428 }
1429 }
1430 }
1431
1432 // we add the group node to the tree
1433 GtkTreeIter child;
1434 gtk_tree_store_append(treestore, &child, toplevel);
1435 gtk_tree_store_set(treestore, &child, TREE_TEXT, str, TREE_MODULE, module, TREE_GROUPID, grp_id,
1436 TREE_FORMID, form->formid, TREE_EDITABLE, (grp_id == 0), TREE_IC_OP, icop,
1438 (!IS_NULL_PTR(icinv)), TREE_IC_USED, icuse, TREE_IC_USED_VISIBLE, (nbuse > 0),
1439 TREE_USED_TEXT, str2, -1);
1440 _set_iter_name(lm, form, gstate, opacity, GTK_TREE_MODEL(treestore), &child, index);
1441
1442 index = 0;
1443 // we add all nodes to the tree
1444 for(const GList *forms = form->points; forms; forms = g_list_next(forms))
1445 {
1446 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
1448 if(f)
1449 _lib_masks_list_recurs(treestore, &child, f, form->formid, module, grpt->state, grpt->opacity, lm, index);
1450 index++;
1451 }
1452 }
1453}
1454
1455gboolean _find_mask_iter_by_values(GtkTreeModel *model, GtkTreeIter *iter,
1456 const dt_iop_module_t *module, const int formid, const int level)
1457{
1458 gboolean found = FALSE;
1459 do
1460 {
1461 int fid = -1;
1462 dt_iop_module_t *mod;
1463 _lib_masks_get_values(model, iter, &mod, NULL, &fid);
1464 found = (fid == formid)
1465 && ((level == 1)
1466 || (IS_NULL_PTR(module) || (mod && (!g_strcmp0(module->op, mod->op)))));
1467 if(found) return found;
1468 GtkTreeIter child, parent = *iter;
1469 if(gtk_tree_model_iter_children(model, &child, &parent))
1470 {
1471 found = _find_mask_iter_by_values(model, &child, module, formid, level + 1);
1472 if(found)
1473 {
1474 *iter = child;
1475 return found;
1476 }
1477 }
1478 } while(gtk_tree_model_iter_next(model, iter));
1479 return found;
1480}
1481
1483{
1484 GList *res = NULL;
1485 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1486
1487 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1488
1489 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1490
1491 GList *items = gtk_tree_selection_get_selected_rows(selection, &model);
1492
1493 for(GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
1494 {
1495 GtkTreePath *item = (GtkTreePath *)items_iter->data;
1496 GtkTreeIter iter;
1497 if(gtk_tree_model_get_iter(model, &iter, item))
1498 {
1499 int fid = -1;
1500 int gid = -1;
1501 dt_iop_module_t *mod;
1502 _lib_masks_get_values(model, &iter, &mod, &gid, &fid);
1503 res = g_list_prepend(res, GINT_TO_POINTER(fid));
1504 res = g_list_prepend(res, GINT_TO_POINTER(gid));
1505 res = g_list_prepend(res, (void *)(mod));
1506 }
1507 }
1508
1509 g_list_foreach(items, (GFunc)gtk_tree_path_free, NULL);
1510 g_list_free(items);
1511 items = NULL;
1512
1513 return res;
1514}
1515
1517{
1518 /* first destroy all buttons in list */
1519 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1520 if(IS_NULL_PTR(lm)) return;
1521 if(lm->gui_reset) return;
1522
1523 const int gui_reset = lm->gui_reset;
1524 lm->gui_reset = 1;
1525 gboolean sync_center_view = FALSE;
1526
1527 // if a treeview is already present, let's get the currently selected items
1528 // as we are going to recreate the tree.
1529 GList *selectids = NULL;
1530
1531 if(lm->treeview)
1532 {
1533 selectids = _lib_masks_get_selected(self);
1534 }
1535
1536 // Rebuilding the shape manager list is also used to refresh shapes created
1537 // during continuous creation. In that case, the active creation button must
1538 // stay active until the user cancels creation explicitly.
1541
1542 GtkTreeStore *treestore;
1543 // we store : text ; *module ; groupid ; formid
1544 treestore = gtk_tree_store_new(TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT,
1545 G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF,
1546 G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, G_TYPE_STRING);
1547
1548 // we first add all groups
1549 for(const GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1550 {
1551 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1552 if(form->type & DT_MASKS_GROUP) _lib_masks_list_recurs(treestore, NULL, form, 0, NULL, 0, 1.0, lm, 0);
1553 }
1554
1555 // and we add all forms
1556 for(const GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1557 {
1558 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1559 if(!(form->type & DT_MASKS_GROUP)) _lib_masks_list_recurs(treestore, NULL, form, 0, NULL, 0, 1.0, lm, 0);
1560 }
1561
1562 gtk_tree_view_set_model(GTK_TREE_VIEW(lm->treeview), GTK_TREE_MODEL(treestore));
1563
1564 // select the images as selected in the previous tree
1565 if(selectids)
1566 {
1567 GList *ids = selectids;
1568 while(ids)
1569 {
1570 GtkTreeModel *model = GTK_TREE_MODEL(treestore);
1571 dt_iop_module_t *mod = (dt_iop_module_t *)ids->data;
1572 ids = g_list_next(ids);
1573 // const int gid = GPOINTER_TO_INT(ids->data); // not needed, skip it
1574 ids = g_list_next(ids);
1575 const int fid = GPOINTER_TO_INT(ids->data);
1576 ids = g_list_next(ids);
1577
1578 GtkTreeIter iter;
1579 gtk_tree_model_get_iter_first(model, &iter);
1580 // get formid in group for the given module
1581 const gboolean found = _find_mask_iter_by_values(model, &iter, mod, fid, 1);
1582
1583 if(found)
1584 {
1585 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1586 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(lm->treeview), path);
1587 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(lm->treeview), path, NULL, TRUE, 0.5, 0.5);
1588 gtk_tree_path_free(path);
1589 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1590 gtk_tree_selection_select_iter(selection, &iter);
1591 }
1592 }
1593 g_list_free(selectids);
1594 selectids = NULL;
1595 }
1596
1597 // After list refresh, keep the tree selection aligned with the current GUI module mask group.
1598 dt_iop_module_t *const current_module = darktable.develop->gui_module;
1599 const int current_group_id
1600 = (!IS_NULL_PTR(current_module) && current_module->blend_params
1601 && (current_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1602 && !(current_module->flags() & IOP_FLAGS_NO_MASKS))
1603 ? current_module->blend_params->mask_id
1604 : 0;
1605
1606 if(current_group_id > 0 && (IS_NULL_PTR(gui) || !gui->creation))
1607 {
1608 GtkTreeModel *model = GTK_TREE_MODEL(treestore);
1609 GtkTreeIter iter;
1610 if(gtk_tree_model_get_iter_first(model, &iter))
1611 {
1612 const gboolean found = _find_mask_iter_by_values(model, &iter, current_module, current_group_id, 1);
1613 if(found)
1614 {
1615 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1616 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1617 gtk_tree_selection_unselect_all(selection);
1618 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(lm->treeview), path);
1619 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(lm->treeview), path, NULL, TRUE, 0.5, 0.5);
1620 gtk_tree_selection_select_iter(selection, &iter);
1621 gtk_tree_path_free(path);
1622 sync_center_view = TRUE;
1623 }
1624 }
1625 }
1626
1627 g_object_unref(treestore);
1628
1629 lm->gui_reset = gui_reset;
1630
1631 if(sync_center_view)
1632 {
1633 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1634 _tree_selection_change(selection, lm);
1635 }
1636}
1637
1638static void _lib_masks_update_item(dt_lib_module_t *self, int formid, int parentid, dt_lib_masks_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
1639{
1640 // we retrieve the forms
1642 if(IS_NULL_PTR(form)) return;
1644
1645 // and the values
1646 int state = 0;
1647 float opacity = 1.0f;
1648
1649 int index = 0;
1650 if(grp && (grp->type & DT_MASKS_GROUP))
1651 {
1652 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
1653 {
1655 if(pt->formid == formid)
1656 {
1657 state = pt->state;
1658 opacity = pt->opacity;
1659 break;
1660 }
1661 index++;
1662 }
1663 }
1664
1665 _set_iter_name(lm, form, state, opacity, model, iter, index);
1666 return;
1667}
1668
1669static gboolean _update_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1670{
1671 if(IS_NULL_PTR(iter)) return 0;
1672
1673 // we retrieve the ids
1674 int grid = -1;
1675 int id = -1;
1676 _lib_masks_get_values(model, iter, NULL, &grid, &id);
1677
1678 // we retrieve the forms
1680 if(IS_NULL_PTR(form)) return 0;
1682
1683 // and the values
1684 int state = 0;
1685 float opacity = 1.0f;
1686
1687 int index = 0;
1688 if(grp && (grp->type & DT_MASKS_GROUP))
1689 {
1690 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
1691 {
1693 if(pt->formid == id)
1694 {
1695 state = pt->state;
1696 opacity = pt->opacity;
1697 break;
1698 }
1699 index++;
1700 }
1701 }
1702
1703 _set_iter_name(data, form, state, opacity, model, iter, index);
1704 return 0;
1705}
1706
1707// Update each item of the list
1709{
1710 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1711 if(IS_NULL_PTR(lm)) return;
1712 if(IS_NULL_PTR(lm->treeview)) return;
1713
1714 // for each node , we refresh the string
1715 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1716 if(!GTK_IS_TREE_MODEL(model)) return;
1717 gtk_tree_model_foreach(model, _update_foreach, lm);
1718}
1719
1720static gboolean _remove_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1721{
1722 if(IS_NULL_PTR(iter)) return 0;
1723 GList **rl = (GList **)data;
1724 const int refid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model), "formid"));
1725 const int refgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model), "groupid"));
1726
1727 int grid = -1;
1728 int id = -1;
1729 _lib_masks_get_values(model, iter, NULL, &grid, &id);
1730
1731 if(grid == refgid && id == refid)
1732 {
1733 GtkTreeRowReference *rowref = gtk_tree_row_reference_new(model, path);
1734 *rl = g_list_append(*rl, rowref);
1735 }
1736 return 0;
1737}
1738
1739static void _lib_masks_remove_item(dt_lib_module_t *self, int formid, int parentid)
1740{
1741 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1742 // for each node , we refresh the string
1743 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1744 GList *rl = NULL;
1745 g_object_set_data(G_OBJECT(model), "formid", GUINT_TO_POINTER(formid));
1746 g_object_set_data(G_OBJECT(model), "groupid", GUINT_TO_POINTER(parentid));
1747 gtk_tree_model_foreach(model, _remove_foreach, &rl);
1748
1749 for(const GList *rlt = rl; rlt; rlt = g_list_next(rlt))
1750 {
1751 GtkTreeRowReference *rowref = (GtkTreeRowReference *)rlt->data;
1752 GtkTreePath *path = gtk_tree_row_reference_get_path(rowref);
1753 gtk_tree_row_reference_free(rowref);
1754 if(path)
1755 {
1756 GtkTreeIter iter;
1757 if(gtk_tree_model_get_iter(model, &iter, path))
1758 {
1759 gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
1760 }
1761 gtk_tree_path_free(path);
1762 }
1763 }
1764 g_list_free(rl);
1765 rl = NULL;
1766}
1767
1768static gboolean _lib_masks_selection_change_r(GtkTreeModel *model, GtkTreeSelection *selection,
1769 GtkTreeIter *iter, struct dt_iop_module_t *module,
1770 const int selectid, int throw_event, const int level)
1771{
1772 gboolean found = FALSE;
1773
1774 GtkTreeIter i = *iter;
1775 do
1776 {
1777 int id = -1;
1778 dt_iop_module_t *mod;
1779 _lib_masks_get_values(model, &i, &mod, NULL, &id);
1780
1781 if((id == selectid)
1782 && ((level == 1)
1783 || (IS_NULL_PTR(module) || (mod && (!g_strcmp0(module->op, mod->op))))))
1784 {
1785 gtk_tree_selection_select_iter(selection, &i);
1786 found = TRUE;
1787 break;
1788 }
1789
1790 // check for children if any
1791 GtkTreeIter child, parent = i;
1792 if(gtk_tree_model_iter_children(model, &child, &parent))
1793 {
1794 found = _lib_masks_selection_change_r(model, selection, &child, module, selectid, throw_event, level + 1);
1795 if(found)
1796 {
1797 break;
1798 }
1799 }
1800 } while(gtk_tree_model_iter_next(model, &i) == TRUE);
1801
1802 return found;
1803}
1804
1805static void _lib_masks_selection_change(dt_lib_module_t *self, struct dt_iop_module_t *module, const int selectid, const int throw_event)
1806{
1807 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1808 if(IS_NULL_PTR(lm->treeview)) return;
1809
1810 // we first unselect all
1811 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1812 lm->gui_reset = 1;
1813 gtk_tree_selection_unselect_all(selection);
1814 lm->gui_reset = 0;
1815
1816 // we go through all nodes
1817 lm->gui_reset = 1 - throw_event;
1818 GtkTreeIter iter;
1819 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1820 if(!GTK_IS_TREE_MODEL(model))
1821 {
1822 lm->gui_reset = 0;
1823 return;
1824 }
1825 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1826
1827 if(valid)
1828 {
1829 gtk_tree_view_expand_all(GTK_TREE_VIEW(lm->treeview));
1830 const gboolean found = _lib_masks_selection_change_r(model, selection, &iter, module, selectid, throw_event, 1);
1831 if(!found) gtk_tree_view_collapse_all(GTK_TREE_VIEW(lm->treeview));
1832 }
1833
1834 lm->gui_reset = 0;
1835}
1836
1837static gboolean _find_child_iter_by_formid(GtkTreeModel *model, GtkTreeIter *parent_iter, int formid, GtkTreeIter *child_iter)
1838{
1839 GtkTreeIter iter;
1840 gboolean found = FALSE;
1841
1842 // Obtenir le premier enfant du parent
1843 if(gtk_tree_model_iter_children(model, &iter, parent_iter))
1844 {
1845 do
1846 {
1847 int current_formid = -1;
1848 gtk_tree_model_get(model, &iter, TREE_FORMID, &current_formid, -1);
1849
1850 if(current_formid == formid)
1851 {
1852 *child_iter = iter;
1853 found = TRUE;
1854 break;
1855 }
1856 } while(gtk_tree_model_iter_next(model, &iter));
1857 }
1858
1859 return found;
1860}
1861
1862static gboolean _find_iter_by_parentid_and_formid(GtkTreeModel *model, int parentid, int formid, GtkTreeIter *iter)
1863{
1864 gboolean found = FALSE;
1865
1866 // Obtenir le premier itérateur du modèle
1867 do
1868 {
1869 int current_parentid = -1;
1870 gtk_tree_model_get(model, iter, TREE_FORMID, &current_parentid, -1);
1871
1872 if(current_parentid == parentid)
1873 {
1874 // Rechercher le formid dans les enfants du parent
1875 found = _find_child_iter_by_formid(model, iter, formid, iter);
1876 if(found)
1877 {
1878 break;
1879 }
1880 }
1881 } while(gtk_tree_model_iter_next(model, iter));
1882
1883 return found;
1884}
1885
1886static void _lib_masks_handler_callback(gpointer instance, const int formid, const int parentid, const dt_masks_event_t event, dt_lib_module_t *self)
1887{
1888 if(IS_NULL_PTR(self)) return;
1889
1890 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1891 if(IS_NULL_PTR(lm)) return;
1892 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1893 if(!GTK_IS_TREE_MODEL(model)) return;
1894 GtkTreeIter iter;
1895 gboolean found_iter = gtk_tree_model_get_iter_first(model, &iter);
1896
1897 if(found_iter && _find_iter_by_parentid_and_formid(model, parentid, formid, &iter))
1898 {
1899 switch(event)
1900 {
1902 {
1903 _lib_masks_update_item(self, formid, parentid, lm, model, &iter);
1904 }
1905 break;
1906
1908 {
1910 }
1911 break;
1912
1914 {
1916 }
1917 break;
1918
1920 {
1922 //_lib_masks_remove_item(self, formid, parentid);
1923 }
1924 break;
1925
1926 case DT_MASKS_EVENT_NONE :
1927 default:
1928 {
1929 dt_print(DT_DEBUG_MASKS, "[_lib_masks_handler_callback] Mask event cannot be found.");
1930 }
1931 break;
1932 }
1933 }
1934
1935 else if(event == DT_MASKS_EVENT_RESET)
1936 {
1938 }
1939
1940 else if(event == DT_MASKS_EVENT_DELETE || event == DT_MASKS_EVENT_REMOVE)
1941 {
1942 // When a shape is deleted from the model, we may no longer find its previous row in the current tree.
1943 // In that case, force a full list refresh so stale rows don't remain visible.
1945 }
1946
1947 else if(event == DT_MASKS_EVENT_ADD)
1948 {
1951 if(IS_NULL_PTR(gui) || !gui->creation)
1953 dt_masks_get_from_id(darktable.develop, parentid ? parentid : formid));
1954 }
1955
1957}
1958
1959static void _lib_masks_popup_button_clicked_cb(GtkWidget *button, gpointer user_data)
1960{
1961 dt_lib_masks_t *d = (dt_lib_masks_t *)user_data;
1962 if(!d->popup_window) return;
1963
1964 if(gtk_widget_get_visible(d->popup_window))
1965 {
1966 gtk_widget_hide(d->popup_window);
1967 }
1968 else
1969 {
1970 gtk_widget_show_all(d->popup_window);
1971 }
1972}
1973
1974/* Idle callback to add the popup button to the module toolbox once the
1975 * module_toolbox proxy has been initialized. Returns FALSE when done so
1976 * it is removed from the idle loop. */
1977static gboolean _lib_masks_add_popup_button_idle(gpointer user_data)
1978{
1979 dt_lib_masks_t *d = (dt_lib_masks_t *)user_data;
1980 if(!d || !d->popup_button) return FALSE;
1981
1983 {
1985 return FALSE; /* stop calling this idle handler */
1986 }
1987 return TRUE; /* try again later */
1988}
1989
1991{
1992 /* initialize ui widgets */
1993 dt_lib_masks_t *d = (dt_lib_masks_t *)g_malloc0(sizeof(dt_lib_masks_t));
1994 self->data = (void *)d;
1995 d->gui_reset = 0;
1996
1997 // initialise all masks pixbuf. This is needed for the "automatic" cell renderer of the treeview
1998 const int bs2 = DT_PIXEL_APPLY_DPI(13);
2005
2006 // 2. Setup the non-modal popup window
2007 d->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2008 gtk_window_set_title(GTK_WINDOW(d->popup_window), _("Mask Manager Panel"));
2009 gtk_window_set_type_hint(GTK_WINDOW(d->popup_window), GDK_WINDOW_TYPE_HINT_UTILITY);
2010
2011 // NON-MODAL & NO FOCUS STEAL: Prevents window manager from stealing active focus when mapped/shown
2012 // because it contains drawing tools that should draw on main window
2013 gtk_window_set_modal(GTK_WINDOW(d->popup_window), FALSE);
2014 gtk_window_set_focus_on_map(GTK_WINDOW(d->popup_window), FALSE);
2015 gtk_window_set_accept_focus(GTK_WINDOW(d->popup_window), FALSE);
2016 gtk_window_set_transient_for(GTK_WINDOW(d->popup_window), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
2017
2018#ifdef GDK_WINDOWING_QUARTZ
2019 dt_osx_disallow_fullscreen(d->popup_window);
2020 gtk_window_set_position(GTK_WINDOW(d->popup_window), GTK_WIN_POS_CENTER_ON_PARENT);
2021#endif
2022
2023 // Intercept the window close action to hide the widget instead of completely destroying it
2024 g_signal_connect(G_OBJECT(d->popup_window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2025
2026 // 3. Create a clean box container inside the popup window to receive original shape elements
2027 GtkWidget *shape_manager_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2028 gtk_container_add(GTK_CONTAINER(d->popup_window), shape_manager_container);
2029
2030 // The main container for module blending params.
2031 // It's populated in blend_gui.c when a mask-able module gets focused.
2032 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2033
2034
2035 // Create and pack the button to control the popup panel.
2036 // NOTE: it's added to the darkroom module toolbox, aka not here.
2037 d->popup_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_drawn, 0, NULL);
2038 gtk_widget_set_tooltip_text(d->popup_button, _("Open mask manager..."));
2039
2040 /* module_toolbox may not be initialized yet when modules are being created.
2041 * Schedule adding the popup button via an idle callback so it runs after
2042 * other modules (including the module_toolbox) have had their gui_init
2043 * called. The callback will remove itself once it succeeds. */
2044 g_idle_add((GSourceFunc)_lib_masks_add_popup_button_idle, d);
2045 g_signal_connect(G_OBJECT(d->popup_button), "clicked", G_CALLBACK(_lib_masks_popup_button_clicked_cb), d);
2046
2047 // From here, everything goes into the mask manager popup,
2048 // so there is no child added to self->widget from here.
2049 GtkWidget *shape_buttons[DEVELOP_MASKS_NB_SHAPES] = { 0 };
2050 const dt_masks_shape_buttons_config_t shape_buttons_config = {
2051 .owner_module = NULL,
2052 .creation_module = NULL,
2053 .buttons = shape_buttons,
2054 .types = NULL,
2055 .action_section = NULL,
2057 .register_flags = DT_MASKS_SHAPE_BUTTONS_NONE,
2058 .local = FALSE,
2059 .user_data = NULL,
2060 .can_start = NULL,
2061 .form_type = NULL,
2063 .exited = NULL,
2064 };
2065 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2066 GtkWidget *shape_buttons_box = dt_masks_shape_buttons_create(&shape_buttons_config);
2067 gtk_box_pack_end(GTK_BOX(hbox), shape_buttons_box, FALSE, FALSE, 0);
2068 d->bt_gradient = shape_buttons[DT_MASKS_SHAPE_INDEX_GRADIENT];
2069 d->bt_path = shape_buttons[DT_MASKS_SHAPE_INDEX_POLYGON];
2070 d->bt_ellipse = shape_buttons[DT_MASKS_SHAPE_INDEX_ELLIPSE];
2071 d->bt_circle = shape_buttons[DT_MASKS_SHAPE_INDEX_CIRCLE];
2072 d->bt_brush = shape_buttons[DT_MASKS_SHAPE_INDEX_BRUSH];
2073
2074 gtk_box_pack_start(GTK_BOX(shape_manager_container), hbox, TRUE, TRUE, 0);
2075
2076 d->treeview = gtk_tree_view_new();
2077 GtkTreeViewColumn *col = gtk_tree_view_column_new();
2078 gtk_tree_view_column_set_title(col, "shapes");
2079 gtk_tree_view_append_column(GTK_TREE_VIEW(d->treeview), col);
2080
2081 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
2082 gtk_tree_view_column_pack_start(col, renderer, FALSE);
2083 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_OP, NULL);
2084 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_OP_VISIBLE);
2085 renderer = gtk_cell_renderer_pixbuf_new();
2086 gtk_tree_view_column_pack_start(col, renderer, FALSE);
2087 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_INVERSE, NULL);
2088 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_INVERSE_VISIBLE);
2089 renderer = gtk_cell_renderer_text_new();
2090 gtk_tree_view_column_pack_start(col, renderer, TRUE);
2091 gtk_tree_view_column_add_attribute(col, renderer, "text", TREE_TEXT);
2092 gtk_tree_view_column_add_attribute(col, renderer, "editable", TREE_EDITABLE);
2093 g_signal_connect(renderer, "edited", (GCallback)_tree_cell_edited, self);
2094 renderer = gtk_cell_renderer_pixbuf_new();
2095 gtk_tree_view_column_pack_end(col, renderer, FALSE);
2096 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_USED, NULL);
2097 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_USED_VISIBLE);
2098
2099 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->treeview));
2100 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
2101 gtk_tree_selection_set_select_function(selection, _tree_restrict_select, d, NULL);
2102 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->treeview), FALSE);
2103 // gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(d->treeview),TREE_USED_TEXT);
2104 g_object_set(d->treeview, "has-tooltip", TRUE, (gchar *)0);
2105 g_signal_connect(d->treeview, "query-tooltip", G_CALLBACK(_tree_query_tooltip), NULL);
2106 g_signal_connect(selection, "changed", G_CALLBACK(_tree_selection_change), d);
2107 g_signal_connect(d->treeview, "button-press-event", (GCallback)_tree_button_pressed, self);
2108
2109 // Auto-grows to its content (the side panel scrolls) up to a user-set, persisted height.
2110 gtk_box_pack_start(GTK_BOX(shape_manager_container),
2111 dt_ui_scroll_wrap(d->treeview, 90, "plugins/darkroom/masks/windowheight",
2113 TRUE, TRUE, 0);
2114
2118
2119 // set proxy functions
2120 darktable.develop->proxy.masks.module = self;
2125
2127}
2128
2130{
2131 if(IS_NULL_PTR(self->data)) return;
2132 if(self && self->data)
2133 {
2134 dt_lib_masks_t *d = (dt_lib_masks_t *)self->data;
2135
2136 // Destroy window allocation to prevent leaks
2137 if(d->popup_window)
2138 {
2139 gtk_widget_destroy(d->popup_window);
2140 d->popup_window = NULL;
2141 }
2142
2143 if(!IS_NULL_PTR(d->ic_inverse)) g_object_unref(d->ic_inverse);
2144 if(!IS_NULL_PTR(d->ic_wired)) g_object_unref(d->ic_wired);
2145 if(!IS_NULL_PTR(d->ic_union)) g_object_unref(d->ic_union);
2146 if(!IS_NULL_PTR(d->ic_intersection)) g_object_unref(d->ic_intersection);
2147 if(!IS_NULL_PTR(d->ic_difference)) g_object_unref(d->ic_difference);
2148 if(!IS_NULL_PTR(d->ic_exclusion)) g_object_unref(d->ic_exclusion);
2149
2150 d->ic_inverse = NULL;
2151 d->ic_wired = NULL;
2152 d->ic_union = NULL;
2153 d->ic_intersection = NULL;
2154 d->ic_difference = NULL;
2155 d->ic_exclusion = NULL;
2157 }
2158
2159 dt_free(self->data);
2160
2163}
2164
2165// clang-format off
2166// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2167// vim: shiftwidth=2 expandtab tabstop=2 cindent
2168// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2169// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void gui_reset(dt_imageio_module_format_t *self)
Definition avif.c:877
#define m
Definition basecurve.c:278
void dt_iop_gui_init_blending_body(GtkWidget *container, dt_iop_module_t *module)
Definition blend_gui.c:4834
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4382
void dt_iop_gui_cleanup_blending_body(dt_iop_module_t *module)
Definition blend_gui.c:4739
void dt_masks_iop_update(dt_iop_module_t *module)
Definition blend_gui.c:3775
const dt_aligned_pixel_t f
int type
char * name
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
void reset(dt_view_t *self)
Definition darkroom.c:1062
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_MASKS
Definition darktable.h:749
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:478
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_dev_pixelpipe_change_zoom_main(dt_develop_t *dev)
void dt_dev_masks_selection_change(dt_develop_t *dev, struct dt_iop_module_t *module, const int selectid, const int throw_event)
Definition develop.c:1255
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1557
static GdkPixbuf * dt_draw_get_pixbuf_from_cairo(DTGTKCairoPaintIconFunc paint, const int width, const int height)
Definition draw.h:947
void dtgtk_cairo_paint_masks_inverse(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_difference(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_union(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_drawn(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_exclusion(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_intersection(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_link_chain(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
const dt_collection_sort_t items[]
Definition filter.c:95
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2945
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:266
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
const char * tooltip
Definition image.h:251
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:205
const char * model
static const float x
const float v
static void _tree_group(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:336
static void _set_iter_name(dt_lib_masks_t *lm, dt_masks_form_t *form, int state, float opacity, GtkTreeModel *model, GtkTreeIter *iter, int index)
Definition libs/masks.c:417
static gboolean _lib_masks_add_popup_button_idle(gpointer user_data)
static void _lib_masks_remove_item(dt_lib_module_t *self, int formid, int parentid)
static void _tree_add_circle(GtkButton *button, dt_iop_module_t *module)
Definition libs/masks.c:268
static void _lib_masks_popup_button_clicked_cb(GtkWidget *button, gpointer user_data)
static void _tree_add_exist(GtkButton *button, dt_masks_form_t *grp)
Definition libs/masks.c:314
static void _tree_union(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:688
static void _lib_masks_update_list(dt_lib_module_t *self)
static void _tree_add_brush(GtkButton *button, dt_iop_module_t *module)
Definition libs/masks.c:300
dt_masks_tree_cols_t
Definition libs/masks.c:114
@ TREE_EDITABLE
Definition libs/masks.c:119
@ TREE_IC_OP_VISIBLE
Definition libs/masks.c:121
@ TREE_IC_INVERSE_VISIBLE
Definition libs/masks.c:123
@ TREE_IC_INVERSE
Definition libs/masks.c:122
@ TREE_IC_USED
Definition libs/masks.c:124
@ TREE_IC_OP
Definition libs/masks.c:120
@ TREE_MODULE
Definition libs/masks.c:116
@ TREE_USED_TEXT
Definition libs/masks.c:126
@ TREE_FORMID
Definition libs/masks.c:118
@ TREE_COUNT
Definition libs/masks.c:127
@ TREE_IC_USED_VISIBLE
Definition libs/masks.c:125
@ TREE_TEXT
Definition libs/masks.c:115
@ TREE_GROUPID
Definition libs/masks.c:117
static void _lib_masks_update_item(dt_lib_module_t *self, int formid, int parentid, dt_lib_masks_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
static void _lib_masks_get_values(GtkTreeModel *model, GtkTreeIter *iter, dt_iop_module_t **module, int *groupid, int *formid)
Definition libs/masks.c:130
static void _tree_inverse(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:468
static GtkWidget * _tree_context_menu(GtkTreeSelection *selection, GtkTreeModel *model, dt_lib_module_t *self, dt_iop_module_t *module)
static gboolean _find_iter_by_parentid_and_formid(GtkTreeModel *model, int parentid, int formid, GtkTreeIter *iter)
static gboolean _find_child_iter_by_formid(GtkTreeModel *model, GtkTreeIter *parent_iter, int formid, GtkTreeIter *child_iter)
static gboolean _remove_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static void _tree_delete_shape(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:819
static int _tree_format_form_usage_label(char *str, const size_t str_size, const dt_masks_form_t *form, const dt_iop_module_t *module)
Definition libs/masks.c:380
static gboolean _tree_restrict_select(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data)
static gboolean _lib_masks_module_is_current(const dt_iop_module_t *module)
Definition libs/masks.c:162
void gui_cleanup(dt_lib_module_t *self)
static void _lib_masks_selection_change(dt_lib_module_t *self, struct dt_iop_module_t *module, const int selectid, const int throw_event)
static void _lib_masks_release_blending(dt_lib_module_t *self)
Definition libs/masks.c:187
static void _is_form_used(int formid, dt_masks_form_t *grp, char *text, size_t text_length, int *nb)
static gboolean _lib_masks_selection_change_r(GtkTreeModel *model, GtkTreeSelection *selection, GtkTreeIter *iter, struct dt_iop_module_t *module, const int selectid, int throw_event, const int level)
static void _tree_selection_change(GtkTreeSelection *selection, dt_lib_masks_t *self)
Definition libs/masks.c:947
static void _add_masks_history_item(dt_lib_masks_t *lm)
Definition libs/masks.c:459
static gboolean _update_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static void _tree_add_gradient(GtkButton *button, dt_iop_module_t *module)
Definition libs/masks.c:292
static void _lib_masks_recreate_list(dt_lib_module_t *self)
static void _tree_add_polygon(GtkButton *button, dt_iop_module_t *module)
Definition libs/masks.c:284
static void _tree_moveup(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:743
static void _tree_movedown(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:781
static void _tree_add_ellipse(GtkButton *button, dt_iop_module_t *module)
Definition libs/masks.c:276
static void _tree_duplicate_shape(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:858
static int _tree_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
uint32_t container(dt_lib_module_t *self)
Definition libs/masks.c:98
static void _tree_difference(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:578
gboolean _find_mask_iter_by_values(GtkTreeModel *model, GtkTreeIter *iter, const dt_iop_module_t *module, const int formid, const int level)
static void _lib_masks_handler_callback(gpointer instance, const int formid, const int parentid, const dt_masks_event_t event, dt_lib_module_t *self)
static void _lib_masks_shape_button_started(GtkWidget *button, dt_iop_module_t *module, dt_masks_type_t type, gpointer user_data)
Definition libs/masks.c:308
static void _tree_cell_edited(GtkCellRendererText *cell, gchar *path_string, gchar *new_text, dt_lib_module_t *self)
Definition libs/masks.c:923
void gui_init(dt_lib_module_t *self)
int position()
Definition libs/masks.c:108
static void _tree_intersection(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:523
const char ** views(dt_lib_module_t *self)
Definition libs/masks.c:92
int expandable(dt_lib_module_t *self)
Definition libs/masks.c:103
static void _lib_masks_clear_blending_box(dt_lib_module_t *self)
Definition libs/masks.c:167
static gboolean _lib_masks_can_host_blending(const dt_iop_module_t *module)
Definition libs/masks.c:178
static void _tree_cleanup(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:453
static gboolean _tree_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, gpointer data)
static void _lib_masks_blending_gui_changed_callback(gpointer instance, dt_lib_module_t *self)
Definition libs/masks.c:217
static void _lib_masks_list_recurs(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_masks_form_t *form, int grp_id, dt_iop_module_t *module, int gstate, float opacity, dt_lib_masks_t *lm, int index)
GList * _lib_masks_get_selected(dt_lib_module_t *self)
static void _tree_exclusion(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:633
static void _lib_masks_show_blending_message(dt_lib_module_t *self, gchar *markup)
Definition libs/masks.c:200
void apply_operation(struct dt_masks_form_group_t *pt, const dt_masks_state_t apply_state)
Apply a mask state operation on a group entry.
dt_masks_form_group_t * dt_masks_group_add_form(dt_masks_form_t *grp, dt_masks_form_t *form)
@ DT_MASKS_SHAPE_INDEX_BRUSH
Definition masks.h:945
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks.h:943
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks.h:944
@ DT_MASKS_SHAPE_INDEX_GRADIENT
Definition masks.h:941
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks.h:942
void dt_masks_change_form_gui(dt_masks_form_t *newform)
@ DT_MASKS_EDIT_FULL
Definition masks.h:203
@ DT_MASKS_STATE_DIFFERENCE
Definition masks.h:174
@ DT_MASKS_STATE_INVERSE
Definition masks.h:171
@ DT_MASKS_STATE_INTERSECTION
Definition masks.h:173
@ DT_MASKS_STATE_EXCLUSION
Definition masks.h:175
@ DT_MASKS_STATE_UNION
Definition masks.h:172
@ DT_MASKS_STATE_USE
Definition masks.h:169
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:87
GtkWidget * dt_masks_shape_buttons_create(const dt_masks_shape_buttons_config_t *config)
Build a synchronized toolbar for creating masks shapes.
Definition masks_gui.c:181
dt_masks_type_t
Definition masks.h:130
@ DT_MASKS_NON_CLONE
Definition masks.h:139
@ DT_MASKS_POLYGON
Definition masks.h:133
@ DT_MASKS_BRUSH
Definition masks.h:138
@ DT_MASKS_ELLIPSE
Definition masks.h:137
@ DT_MASKS_CLONE
Definition masks.h:135
@ DT_MASKS_GRADIENT
Definition masks.h:136
@ DT_MASKS_CIRCLE
Definition masks.h:132
@ DT_MASKS_GROUP
Definition masks.h:134
int dt_masks_center_view_on_form(struct dt_develop_t *dev, const struct dt_masks_form_t *form)
void dt_masks_cleanup_unused(dt_develop_t *dev)
Cleanup unused masks and refresh the current forms snapshot.
dt_masks_event_t
Definition masks.h:157
@ DT_MASKS_EVENT_NONE
Definition masks.h:158
@ DT_MASKS_EVENT_UPDATE
Definition masks.h:161
@ DT_MASKS_EVENT_RESET
Definition masks.h:164
@ DT_MASKS_EVENT_ADD
Definition masks.h:159
@ DT_MASKS_EVENT_DELETE
Definition masks.h:162
@ DT_MASKS_EVENT_REMOVE
Definition masks.h:160
@ DT_MASKS_EVENT_CHANGE
Definition masks.h:163
void dt_masks_group_ungroup(dt_masks_form_t *dest_grp, dt_masks_form_t *grp)
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
@ DT_MASKS_SHAPE_BUTTONS_NONE
Definition masks.h:951
@ DT_MASKS_SHAPE_BUTTONS_ALL
Definition masks.h:963
gboolean dt_masks_creation_mode_enter(dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
void dt_masks_form_move(dt_masks_form_t *grp, int formid, int up)
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
void dt_masks_form_delete(struct dt_iop_module_t *module, dt_masks_form_t *grp, dt_masks_form_t *form)
#define DEVELOP_MASKS_NB_SHAPES
Definition masks.h:937
void dt_masks_reset_show_masks_icons(void)
int dt_masks_form_duplicate(dt_develop_t *dev, int formid)
void dt_masks_set_visible_form(struct dt_develop_t *dev, dt_masks_form_t *form)
dt_masks_form_t * dt_masks_cow_touch(dt_develop_t *dev, dt_masks_form_t *form)
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:312
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:316
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_develop_t * develop
Definition darktable.h:798
struct dt_view_manager_t * view_manager
Definition darktable.h:800
void(* list_update)(struct dt_lib_module_t *self)
Definition develop.h:436
GList * iop
Definition develop.h:285
void(* list_remove)(struct dt_lib_module_t *self, int formid, int parentid)
Definition develop.h:435
struct dt_iop_module_t * gui_module
Definition develop.h:165
struct dt_lib_module_t *void(* list_change)(struct dt_lib_module_t *self)
Definition develop.h:434
GList * history
Definition develop.h:275
struct dt_masks_form_gui_t * form_gui
Definition develop.h:333
struct dt_develop_t::@20::@28 masks
void(* selection_change)(struct dt_lib_module_t *self, struct dt_iop_module_t *module, const int selectid, const int throw_event)
Definition develop.h:438
struct dt_develop_t::@20 proxy
GList * forms
Definition develop.h:327
dt_ui_t * ui
Definition gtk.h:165
GtkWidget * masks_edit
Definition blend.h:371
gpointer blend_data
Definition imageop.h:355
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:353
GModule *dt_dev_operation_t op
Definition imageop.h:286
GtkWidget * popup_window
Definition libs/masks.c:79
GtkWidget * bt_ellipse
Definition libs/masks.c:73
GtkWidget * popup_button
Definition libs/masks.c:80
GdkPixbuf * ic_exclusion
Definition libs/masks.c:82
GtkWidget * hbox
Definition libs/masks.c:72
GdkPixbuf * ic_intersection
Definition libs/masks.c:82
GdkPixbuf * ic_union
Definition libs/masks.c:82
GtkWidget * bt_path
Definition libs/masks.c:73
GdkPixbuf * ic_inverse
Definition libs/masks.c:82
GdkPixbuf * ic_wired
Definition libs/masks.c:82
GdkPixbuf * ic_difference
Definition libs/masks.c:82
GtkWidget * bt_brush
Definition libs/masks.c:73
GtkWidget * bt_circle
Definition libs/masks.c:73
dt_iop_module_t * hosted_module
Definition libs/masks.c:76
GtkWidget * bt_gradient
Definition libs/masks.c:73
GtkWidget * treeview
Definition libs/masks.c:74
dt_iop_module_t * active_module
Definition libs/masks.c:75
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
dt_masks_edit_mode_t edit_mode
Definition masks.h:477
gboolean creation
Definition masks.h:517
dt_masks_type_t type
Definition masks.h:380
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
dt_iop_module_t * owner_module
Definition masks.h:979
struct dt_view_manager_t::@69 proxy
struct dt_view_manager_t::@69::@70 module_toolbox
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
Definition view.c:1357
@ DT_VIEW_DARKROOM
Definition view.h:79
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER