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 850;
106}
107
124
125static void _lib_masks_get_values(GtkTreeModel *model, GtkTreeIter *iter,
126 dt_iop_module_t **module, int *groupid, int *formid)
127{
128 // returns module & groupid & formid if requested
129
130 if(module)
131 {
132 GValue gv = { 0, };
133 gtk_tree_model_get_value(model, iter, TREE_MODULE, &gv);
134 *module = NULL;
135 if(G_VALUE_TYPE(&gv) == G_TYPE_POINTER)
136 *module = (dt_iop_module_t *)g_value_get_pointer(&gv);
137 g_value_unset(&gv);
138 }
139
140 if(groupid)
141 {
142 GValue gv = { 0, };
143 gtk_tree_model_get_value(model, iter, TREE_GROUPID, &gv);
144 *groupid = g_value_get_int(&gv);
145 g_value_unset(&gv);
146 }
147
148 if(formid)
149 {
150 GValue gv = { 0,};
151 gtk_tree_model_get_value(model, iter, TREE_FORMID, &gv);
152 *formid = g_value_get_int(&gv);
153 g_value_unset(&gv);
154 }
155}
156
157static gboolean _lib_masks_module_is_current(const dt_iop_module_t *module)
158{
159 return darktable.develop && module && g_list_find(darktable.develop->iop, (gpointer)module);
160}
161
163{
164 if(IS_NULL_PTR(self) || !GTK_IS_WIDGET(self->widget)) return;
165
166 GList *children = gtk_container_get_children(GTK_CONTAINER(self->widget));
167 for(GList *iter = children; iter; iter = g_list_next(iter))
168 gtk_widget_destroy(GTK_WIDGET(iter->data));
169 g_list_free(children);
170 children = NULL;
171}
172
173static gboolean _lib_masks_can_host_blending(const dt_iop_module_t *module)
174{
175 if(!_lib_masks_module_is_current(module) || !module->flags
176 || !(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || !module->blend_data)
177 return FALSE;
178
179 return TRUE;
180}
181
183{
184 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
185 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
186 dt_iop_module_t *hosted_module = lm->hosted_module;
187 lm->hosted_module = NULL;
188
189 if(_lib_masks_can_host_blending(hosted_module))
191 else
193}
194
195static void _lib_masks_show_blending_message(dt_lib_module_t *self, gchar *markup)
196{
197 if(IS_NULL_PTR(self) || !GTK_IS_WIDGET(self->widget) || IS_NULL_PTR(markup)) return;
198
199 GtkWidget *label = gtk_label_new(NULL);
200 gtk_label_set_markup(GTK_LABEL(label), markup);
201 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
202 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
203 gtk_widget_set_margin_top(label, DT_PIXEL_APPLY_DPI(16));
204 gtk_widget_set_margin_bottom(label, DT_PIXEL_APPLY_DPI(16));
205 gtk_widget_set_sensitive(label, FALSE);
206 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
207 gtk_widget_show_all(self->widget);
208}
209
211{
212 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
213
214 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
215 dt_iop_module_t *module = darktable.develop->gui_module;
216
218 {
221
222 gchar *markup = g_markup_printf_escaped(_("<i>Select a module to edit its blending settings.</i>"));
223
225 g_free(markup);
226
227 return;
228 }
229
231 {
234
235 gchar *module_label = dt_history_item_get_name(module);
236 gchar *markup = g_markup_printf_escaped(_("<i>Blending is not available for the <b>%s</b> module.</i>"), module_label);
237
239 g_free(markup);
240 dt_free(module_label);
241
242 return;
243 }
244
245 const gboolean module_changed = (lm->active_module != module);
246 lm->active_module = module;
247
248 if(module_changed) _lib_masks_release_blending(self);
249
251
253 lm->hosted_module = module;
254 gtk_widget_show(self->widget);
255
257}
258
259static void _tree_add_circle(GtkButton *button, dt_iop_module_t *module)
260{
261 // we create the new form
265}
266
267static void _tree_add_ellipse(GtkButton *button, dt_iop_module_t *module)
268{
269 // we create the new form
273}
274
275static void _tree_add_polygon(GtkButton *button, dt_iop_module_t *module)
276{
277 // we create the new form
281}
282
283static void _tree_add_gradient(GtkButton *button, dt_iop_module_t *module)
284{
285 // we create the new form
289}
290
291static void _tree_add_brush(GtkButton *button, dt_iop_module_t *module)
292{
293 // we create the new form
297}
298
300 dt_masks_type_t type, gpointer user_data)
301{
303}
304
305static void _tree_add_exist(GtkButton *button, dt_masks_form_t *grp)
306{
307 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
308 // we get the new formid
309 const int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "formid"));
310 dt_iop_module_t *module = g_object_get_data(G_OBJECT(button), "module");
311
312 // we add the form in this group
314 if(form && dt_masks_group_add_form(grp, form))
315 {
316 // we save the group
318
319 // and we apply the change
320
321 dt_masks_iop_update(module);
323 }
324}
325
326static void _tree_group(GtkButton *button, dt_lib_module_t *self)
327{
328 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
329 // we create the new group
331 g_snprintf(mask->name, sizeof(mask->name), _("Mask #%d"), g_list_length(darktable.develop->forms));
332
333 // we add all selected forms to this group
334 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
335 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
336
337 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
338 for(GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
339 {
340 GtkTreePath *item = (GtkTreePath *)items_iter->data;
341 GtkTreeIter iter;
342 if(gtk_tree_model_get_iter(model, &iter, item))
343 {
344 int id = -1;
345 _lib_masks_get_values(model, &iter, NULL, NULL, &id);
346
347 if(id > 0)
348 {
350 fpt->formid = id;
351 fpt->parentid = mask->formid;
352 fpt->opacity = 1.0f;
354 mask->points = g_list_append(mask->points, fpt);
355 }
356 }
357 }
358 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
359 items = NULL;
360
361 // we add this group to the general list
362 darktable.develop->forms = g_list_append(darktable.develop->forms, mask);
363
364 // add we save
367 // dt_masks_change_form_gui(grp);
368}
369
370static int _tree_format_form_usage_label(char *str, const size_t str_size,
371 const dt_masks_form_t *form, const dt_iop_module_t *module)
372{
373 if(IS_NULL_PTR(str) || IS_NULL_PTR(form)) return -1;
374
375 str[0] = '\0';
376 g_strlcat(str, form->name, str_size);
377
378 int nbuse = 0;
379 // we search were this form is used
380 for(const GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
381 {
382 dt_iop_module_t *m = (dt_iop_module_t *)modules->data;
383 dt_masks_form_t *grp = dt_masks_get_from_id(m->dev, m->blend_params->mask_id);
384 if(grp && (grp->type & DT_MASKS_GROUP))
385 {
386 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
387 {
389 if(pt->formid == form->formid)
390 {
391 if(m == module) return -1;
392 if(nbuse == 0) g_strlcat(str, " (", str_size);
393 g_strlcat(str, " ", str_size);
394 gchar *module_label = dt_history_item_get_name(m);
395 g_strlcat(str, module_label, str_size);
396 dt_free(module_label);
397 nbuse++;
398 }
399 }
400 }
401 }
402
403 if(nbuse > 0) g_strlcat(str, " )", str_size);
404 return nbuse;
405}
406
407static void _set_iter_name(dt_lib_masks_t *lm, dt_masks_form_t *form, int state, float opacity,
408 GtkTreeModel *model, GtkTreeIter *iter, int index)
409{
410 if(IS_NULL_PTR(form)) return;
411
412 char str[256] = "";
413
414 if(opacity != 1.0f)
415 {
416 g_snprintf(str, sizeof(str), "%s %d%%",
417 form->name, (int)(opacity * 100));
418 }
419 else
420 {
421 g_strlcpy(str, form->name, sizeof(str));
422 }
423
424 GdkPixbuf *icop = NULL;
425 GdkPixbuf *icinv = NULL;
426 if(index != 0)
427 {
429 icop = lm->ic_union;
431 icop = lm->ic_intersection;
433 icop = lm->ic_difference;
435 icop = lm->ic_exclusion;
436 }
437 if(state & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;
438
439 gtk_tree_store_set(GTK_TREE_STORE(model), iter, TREE_TEXT, str, TREE_IC_OP, icop, TREE_IC_OP_VISIBLE,
440 (!IS_NULL_PTR(icop)), TREE_IC_INVERSE, icinv, TREE_IC_INVERSE_VISIBLE, (!IS_NULL_PTR(icinv)), -1);
441}
442
443static void _tree_cleanup(GtkButton *button, dt_lib_module_t *self)
444{
447}
448
450{
451 const int reset = lm->gui_reset;
452 lm->gui_reset = 1;
454 lm->gui_reset = reset;
455}
456
457
458static void _tree_inverse(GtkButton *button, dt_lib_module_t *self)
459{
460 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
461
462 // now we go through all selected nodes
463 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
464 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
465 int change = 0;
466 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
467 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
468 {
469 GtkTreePath *item = (GtkTreePath *)items_iter->data;
470 GtkTreeIter iter;
471 if(gtk_tree_model_get_iter(model, &iter, item))
472 {
473 int grid = -1;
474 int id = -1;
475 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
476
478 if(grp && (grp->type & DT_MASKS_GROUP))
479 {
480 int i = 0;
481 // we search the entry to inverse
482 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
483 {
485 if(pt->formid == id)
486 {
487 const int old_state = pt->state;
489 if(pt->state != old_state)
490 {
492 &iter, i);
493 change = 1;
494 }
495 break;
496 }
497 i++;
498 }
499 }
500 }
501 }
502 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
503 items = NULL;
504
505 if(change)
506 {
508
510 }
511}
512
513static void _tree_intersection(GtkButton *button, dt_lib_module_t *self)
514{
515 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
516
517 // now we go through all selected nodes
518 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
519 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
520 int change = 0;
521 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
522 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
523 {
524 GtkTreePath *item = (GtkTreePath *)items_iter->data;
525 GtkTreeIter iter;
526 if(gtk_tree_model_get_iter(model, &iter, item))
527 {
528 int grid = -1;
529 int id = -1;
530 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
531
533 if(grp && (grp->type & DT_MASKS_GROUP))
534 {
535 int i = 0;
536 // we search the entry to inverse
537 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
538 {
540 if(pt->formid == id)
541 {
542 const int old_state = pt->state;
544 if(pt->state != old_state)
545 {
547 &iter, i);
548 change = 1;
549 }
550 break;
551 }
552 i++;
553 }
554 }
555 }
556 }
557 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
558 items = NULL;
559
560 if(change)
561 {
563
565 }
566}
567
568static void _tree_difference(GtkButton *button, dt_lib_module_t *self)
569{
570 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
571
572 // now we go through all selected nodes
573 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
574 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
575 int change = 0;
576 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
577 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
578 {
579 GtkTreePath *item = (GtkTreePath *)items_iter->data;
580 GtkTreeIter iter;
581 if(gtk_tree_model_get_iter(model, &iter, item))
582 {
583 int grid = -1;
584 int id = -1;
585 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
586
588 if(grp && (grp->type & DT_MASKS_GROUP))
589 {
590 int i = 0;
591 // we search the entry to inverse
592 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
593 {
595 if(pt->formid == id)
596 {
597 const int old_state = pt->state;
599 if(pt->state != old_state)
600 {
602 &iter, i);
603 change = 1;
604 }
605 break;
606 }
607 i++;
608 }
609 }
610 }
611 }
612 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
613 items = NULL;
614
615 if(change)
616 {
618
620 }
621}
622
623static void _tree_exclusion(GtkButton *button, dt_lib_module_t *self)
624{
625 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
626
627 // now we go through all selected nodes
628 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
629 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
630 int change = 0;
631 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
632 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
633 {
634 GtkTreePath *item = (GtkTreePath *)items_iter->data;
635 GtkTreeIter iter;
636 if(gtk_tree_model_get_iter(model, &iter, item))
637 {
638 int grid = -1;
639 int id = -1;
640 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
641
643 if(grp && (grp->type & DT_MASKS_GROUP))
644 {
645 int i = 0;
646 // we search the entry to inverse
647 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
648 {
650 if(pt->formid == id)
651 {
652 const int old_state = pt->state;
654 if(pt->state != old_state)
655 {
657 &iter, i);
658 change = 1;
659 }
660 break;
661 }
662 i++;
663 }
664 }
665 }
666 }
667 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
668 items = NULL;
669
670 if(change)
671 {
673
675 }
676}
677
678static void _tree_union(GtkButton *button, dt_lib_module_t *self)
679{
680 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
681
682 // now we go through all selected nodes
683 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
684 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
685 int change = 0;
686 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
687 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
688 {
689 GtkTreePath *item = (GtkTreePath *)items_iter->data;
690 GtkTreeIter iter;
691 if(gtk_tree_model_get_iter(model, &iter, item))
692 {
693 int grid = -1;
694 int id = -1;
695 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
696
698 if(grp && (grp->type & DT_MASKS_GROUP))
699 {
700 int i = 0;
701 // we search the entry to inverse
702 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
703 {
705 if(pt->formid == id)
706 {
707 const int old_state = pt->state;
709 if(pt->state != old_state)
710 {
712 &iter, i);
713 change = 1;
714 }
715 break;
716 }
717 i++;
718 }
719 }
720 }
721 }
722 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
723 items = NULL;
724
725 if(change)
726 {
728
730 }
731}
732
733static void _tree_moveup(GtkButton *button, dt_lib_module_t *self)
734{
735 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
736
737 // we first discard all visible shapes
739
740 // now we go through all selected nodes
741 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
742 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
743 lm->gui_reset = 1;
744 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
745 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
746 {
747 GtkTreePath *item = (GtkTreePath *)items_iter->data;
748 GtkTreeIter iter;
749 if(gtk_tree_model_get_iter(model, &iter, item))
750 {
751 int grid = -1;
752 int id = -1;
753 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
754
756 }
757 }
758 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
759 items = NULL;
760
761 lm->gui_reset = 0;
763
764}
765
766static void _tree_movedown(GtkButton *button, dt_lib_module_t *self)
767{
768 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
769
770 // we first discard all visible shapes
772
773 // now we go through all selected nodes
774 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
775 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
776 lm->gui_reset = 1;
777 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
778 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
779 {
780 GtkTreePath *item = (GtkTreePath *)items_iter->data;
781 GtkTreeIter iter;
782 if(gtk_tree_model_get_iter(model, &iter, item))
783 {
784 int grid = -1;
785 int id = -1;
786 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
787
789 }
790 }
791 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
792 items = NULL;
793
794 lm->gui_reset = 0;
796
797}
798
799static void _tree_delete_shape(GtkButton *button, dt_lib_module_t *self)
800{
801 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
802
803 // we first discard all visible shapes
805
806 // now we go through all selected nodes
807 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
808 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
809 dt_iop_module_t *module = NULL;
810 lm->gui_reset = 1;
811 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
812 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
813 {
814 GtkTreePath *item = (GtkTreePath *)items_iter->data;
815 GtkTreeIter iter;
816 if(gtk_tree_model_get_iter(model, &iter, item))
817 {
818 int grid = -1;
819 int id = -1;
820 _lib_masks_get_values(model, &iter, &module, &grid, &id);
821
824 }
825 }
826 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
827 items = NULL;
828
829 lm->gui_reset = 0;
831}
832
833static void _tree_duplicate_shape(GtkButton *button, dt_lib_module_t *self)
834{
835 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
836
837 // we get the selected node
838 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
839 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
840 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
841 if(IS_NULL_PTR(items)) return;
842 GtkTreePath *item = (GtkTreePath *)items->data;
843 GtkTreeIter iter;
844 if(gtk_tree_model_get_iter(model, &iter, item))
845 {
846 int id = -1;
847 _lib_masks_get_values(model, &iter, NULL, NULL, &id);
848
849 const int nid = dt_masks_form_duplicate(darktable.develop, id);
850 if(nid > 0)
851 {
853 //_lib_masks_recreate_list(self);
854 }
855 }
856 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
857 items = NULL;
858}
859
860static void _tree_cell_edited(GtkCellRendererText *cell, gchar *path_string, gchar *new_text,
861 dt_lib_module_t *self)
862{
863 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
864 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
865 GtkTreeIter iter;
866 if(!gtk_tree_model_get_iter_from_string(model, &iter, path_string)) return;
867
868 int id = -1;
869 _lib_masks_get_values(model, &iter, NULL, NULL, &id);
871 if(IS_NULL_PTR(form)) return;
872
873 // we want to make sure that the new name is not an empty string. else this would convert
874 // in the xmp file into "<rdf:li/>" which produces problems. we use a single whitespace
875 // as the pure minimum text.
876 gchar *text = strlen(new_text) == 0 ? " " : new_text;
877
878 // first, we need to update the mask name
879
880 g_strlcpy(form->name, text, sizeof(form->name));
882}
883
884static void _tree_selection_change(GtkTreeSelection *selection, dt_lib_masks_t *self)
885{
886 if(self->gui_reset) return;
888 if(!IS_NULL_PTR(creation_gui) && creation_gui->creation) return;
889
890 // we reset all "show mask" icon of iops
892
893 // if selection empty, we hide all
894 const int nb = gtk_tree_selection_count_selected_rows(selection);
895 if(nb == 0)
896 {
899 return;
900 }
901
902 // else, we create a new form group with the selection and display it
903 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeview));
905 dt_masks_form_t *selected_form = NULL;
906 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
907 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
908 {
909 GtkTreePath *item = (GtkTreePath *)items_iter->data;
910 GtkTreeIter iter;
911 if(gtk_tree_model_get_iter(model, &iter, item))
912 {
913 int grid = -1;
914 int id = -1;
915 _lib_masks_get_values(model, &iter, NULL, &grid, &id);
916
918 if(!IS_NULL_PTR(form))
919 {
920 if(nb == 1) selected_form = form;
922 fpt->formid = id;
923 fpt->parentid = grid;
925 fpt->opacity = 1.0f;
926 grp->points = g_list_append(grp->points, fpt);
927 // we eventually set the "show masks" icon of iops
928 if(nb == 1 && (form->type & DT_MASKS_GROUP))
929 {
930 dt_iop_module_t *module = NULL;
931 _lib_masks_get_values(model, &iter, &module, NULL, NULL);
932
933 if(module && module->blend_data
934 && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
935 && !(module->flags() & IOP_FLAGS_NO_MASKS))
936 {
937 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
938 bd->masks_shown = 1;
939 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), TRUE);
940 gtk_widget_queue_draw(bd->masks_edit);
941 }
942 }
943 }
944 }
945 }
946 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
947 items = NULL;
948
950 grp2->formid = 0;
951 dt_masks_group_ungroup(grp2, grp);
954 if(nb == 1 && !IS_NULL_PTR(selected_form))
956 else
958}
959
960static GtkWidget *_tree_context_menu(GtkTreeSelection *selection, GtkTreeModel *model,
961 dt_lib_module_t *self, dt_iop_module_t *module)
962{
963 GtkTreeIter iter;
964 GtkMenuShell *menu = GTK_MENU_SHELL(gtk_menu_new());
965 GtkWidget *item;
966
967 // we get all infos from selection
968 const int nb = gtk_tree_selection_count_selected_rows(selection);
969 int from_group = 0;
970
971 int grpid = 0;
972 int depth = 0;
973
974 if(nb > 0)
975 {
976 GList *selected = gtk_tree_selection_get_selected_rows(selection, NULL);
977 GtkTreePath *it0 = (GtkTreePath *)selected->data;
978 depth = gtk_tree_path_get_depth(it0);
979 if(nb == 1)
980 {
981 // before freeing the list of selected rows, we check if the form is a group or not
982 if(gtk_tree_model_get_iter(model, &iter, it0))
983 {
984 _lib_masks_get_values(model, &iter, NULL, NULL, &grpid);
985 }
986 }
987 g_list_free_full(selected, (GDestroyNotify)gtk_tree_path_free);
988 selected = NULL;
989 }
990 if(depth > 1) from_group = 1;
991
992 if(nb == 0)
993 {
994 GtkWidget *add_menu = gtk_menu_new();
995 GtkWidget *add_item = gtk_menu_item_new_with_label(_("Add new shape ..."));
996 gtk_menu_item_set_submenu(GTK_MENU_ITEM(add_item), add_menu);
997 gtk_menu_shell_append(menu, add_item);
998
999 item = gtk_menu_item_new_with_label(_("add circle"));
1000 g_signal_connect(item, "activate", (GCallback)_tree_add_circle, module);
1001 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1002
1003 item = gtk_menu_item_new_with_label(_("add ellipse"));
1004 g_signal_connect(item, "activate", (GCallback)_tree_add_ellipse, module);
1005 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1006
1007 item = gtk_menu_item_new_with_label(_("add path"));
1008 g_signal_connect(item, "activate", (GCallback)_tree_add_polygon, module);
1009 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1010
1011 item = gtk_menu_item_new_with_label(_("add gradient"));
1012 g_signal_connect(item, "activate", (GCallback)_tree_add_gradient, module);
1013 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1014
1015 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1016 }
1017
1018 if(nb == 1)
1019 {
1021 if(grp && (grp->type & DT_MASKS_GROUP))
1022 {
1023 GtkWidget *add_menu = gtk_menu_new();
1024 GtkWidget *add_item = gtk_menu_item_new_with_label(_("Add new shape ..."));
1025 gtk_menu_item_set_submenu(GTK_MENU_ITEM(add_item), add_menu);
1026 gtk_menu_shell_append(menu, add_item);
1027
1028 item = gtk_menu_item_new_with_label(_("Add brush"));
1029 g_signal_connect(item, "activate", (GCallback)_tree_add_brush, module);
1030 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1031
1032 item = gtk_menu_item_new_with_label(_("Add circle"));
1033 g_signal_connect(item, "activate", (GCallback)_tree_add_circle, module);
1034 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1035
1036 item = gtk_menu_item_new_with_label(_("Add ellipse"));
1037 g_signal_connect(item, "activate", (GCallback)_tree_add_ellipse, module);
1038 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1039
1040 item = gtk_menu_item_new_with_label(_("Add polygon"));
1041 g_signal_connect(item, "activate", (GCallback)_tree_add_polygon, module);
1042 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1043
1044 item = gtk_menu_item_new_with_label(_("Add gradient"));
1045 g_signal_connect(item, "activate", (GCallback)_tree_add_gradient, module);
1046 gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), item);
1047
1048 // existing forms
1049 gboolean has_unused_shapes = FALSE;
1050 GtkWidget *menu0 = gtk_menu_new();
1051 for(GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1052 {
1053 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1054 if((form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE)) || form->formid == grpid)
1055 {
1056 continue;
1057 }
1058 char str[10000] = "";
1059 const int nbuse = _tree_format_form_usage_label(str, sizeof(str), form, module);
1060 if(nbuse == -1) continue;
1061
1062 // we add the menu entry
1063 item = gtk_menu_item_new_with_label(str);
1064 g_object_set_data(G_OBJECT(item), "formid", GUINT_TO_POINTER(form->formid));
1065 g_object_set_data(G_OBJECT(item), "module", module);
1066 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_tree_add_exist), grp);
1067 gtk_menu_shell_append(GTK_MENU_SHELL(menu0), item);
1068 has_unused_shapes = TRUE;
1069 }
1070
1071 if(has_unused_shapes)
1072 {
1073 item = gtk_menu_item_new_with_label(_("Add shape ..."));
1074 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu0);
1075 gtk_menu_shell_append(menu, item);
1076 }
1077 }
1078 }
1079
1080 if(nb > 1 && !from_group)
1081 {
1082 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1083 item = gtk_menu_item_new_with_label(_("Group the forms"));
1084 g_signal_connect(item, "activate", (GCallback)_tree_group, self);
1085 gtk_menu_shell_append(menu, item);
1086 }
1087
1088 if(from_group && depth < 3)
1089 {
1090 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1091 item = gtk_menu_item_new_with_label(_("Invert shape"));
1092 g_signal_connect(item, "activate", (GCallback)_tree_inverse, self);
1093 gtk_menu_shell_append(menu, item);
1094 if(nb == 1)
1095 {
1096 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1097 item = gtk_menu_item_new_with_label(_("Union"));
1098 g_signal_connect(item, "activate", (GCallback)_tree_union, self);
1099 gtk_menu_shell_append(menu, item);
1100 item = gtk_menu_item_new_with_label(_("Intersection"));
1101 g_signal_connect(item, "activate", (GCallback)_tree_intersection, self);
1102 gtk_menu_shell_append(menu, item);
1103 item = gtk_menu_item_new_with_label(_("Difference"));
1104 g_signal_connect(item, "activate", (GCallback)_tree_difference, self);
1105 gtk_menu_shell_append(menu, item);
1106 item = gtk_menu_item_new_with_label(_("Exclusion"));
1107 g_signal_connect(item, "activate", (GCallback)_tree_exclusion, self);
1108 gtk_menu_shell_append(menu, item);
1109 }
1110 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1111 item = gtk_menu_item_new_with_label(_("Move up"));
1112 g_signal_connect(item, "activate", (GCallback)_tree_moveup, self);
1113 gtk_menu_shell_append(menu, item);
1114 item = gtk_menu_item_new_with_label(_("Move down"));
1115 g_signal_connect(item, "activate", (GCallback)_tree_movedown, self);
1116 gtk_menu_shell_append(menu, item);
1117 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1118 }
1119
1121 if(!from_group && !(grp && (grp->type & DT_MASKS_GROUP)) && nb == 1)
1122 {
1123 item = gtk_menu_item_new_with_label(_("Duplicate shape"));
1124 g_signal_connect(item, "activate", (GCallback)_tree_duplicate_shape, self);
1125 gtk_menu_shell_append(menu, item);
1126 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1127 }
1128
1129 if(!from_group && nb > 0)
1130 {
1131 if(!(grp && (grp->type & DT_MASKS_GROUP)))
1132 {
1133 item = gtk_menu_item_new_with_label(_("Delete shape"));
1134 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, self);
1135 gtk_menu_shell_append(menu, item);
1136 }
1137 else
1138 {
1139 item = gtk_menu_item_new_with_label(_("Delete mask"));
1140 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, self);
1141 gtk_menu_shell_append(menu, item);
1142 }
1143 }
1144 else if(nb > 0 && depth < 3)
1145 {
1146 item = gtk_menu_item_new_with_label(_("Remove shape from mask"));
1147 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, self);
1148 gtk_menu_shell_append(menu, item);
1149 }
1150
1151 item = gtk_menu_item_new_with_label(_("Cleanup unused shapes"));
1152 g_signal_connect(item, "activate", (GCallback)_tree_cleanup, self);
1153 gtk_menu_shell_append(menu, item);
1154
1155 return GTK_WIDGET(menu);
1156}
1157
1158static int _tree_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
1159{
1160 // we first need to adjust selection
1161 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1162 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
1163
1164 GtkTreePath *mouse_path = NULL;
1165 GtkTreeIter iter;
1166 dt_iop_module_t *module = NULL;
1167 int on_row = 0;
1168 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint)event->x, (gint)event->y, &mouse_path, NULL,
1169 NULL, NULL))
1170 {
1171 on_row = 1;
1172 // we retrieve the iter and module from path
1173 if(gtk_tree_model_get_iter(model, &iter, mouse_path))
1174 {
1175 _lib_masks_get_values(model, &iter, &module, NULL, NULL);
1176 }
1177 }
1178 /* single click with the right mouse button? */
1179 if(event->type == GDK_BUTTON_PRESS && event->button == 1)
1180 {
1181 // if click on a blank space, then deselect all
1182 if(!on_row)
1183 {
1184 gtk_tree_selection_unselect_all(selection);
1185 }
1186 }
1187 else if(event->type == GDK_BUTTON_PRESS && event->button == 3)
1188 {
1189 // if we are already inside the selection, no change
1190 if(on_row && !gtk_tree_selection_path_is_selected(selection, mouse_path))
1191 {
1192 if(!dt_modifier_is(event->state, GDK_CONTROL_MASK)) gtk_tree_selection_unselect_all(selection);
1193 gtk_tree_selection_select_path(selection, mouse_path);
1194 gtk_tree_path_free(mouse_path);
1195 }
1196
1197 // and we display the context-menu
1198 GtkWidget *menu = _tree_context_menu(selection, model, self, module);
1199
1200 gtk_widget_show_all(menu);
1201
1202 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
1203
1204 return 1;
1205 }
1206
1207 return 0;
1208}
1209
1210static gboolean _tree_restrict_select(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path,
1211 gboolean path_currently_selected, gpointer data)
1212{
1213 dt_lib_masks_t *self = (dt_lib_masks_t *)data;
1214 if(self->gui_reset) return TRUE;
1215
1216 // if the change is SELECT->UNSELECT no pb
1217 if(path_currently_selected) return TRUE;
1218
1219 // if selection is empty, no pb
1220 if(gtk_tree_selection_count_selected_rows(selection) == 0) return TRUE;
1221
1222 // now we unselect all members of selection with not the same parent node
1223 // idem for all those with a different depth
1224 int *indices = gtk_tree_path_get_indices(path);
1225 int depth = gtk_tree_path_get_depth(path);
1226
1227 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
1228 GList *items_iter = items;
1229 while(items_iter)
1230 {
1231 GtkTreePath *item = (GtkTreePath *)items_iter->data;
1232 int dd = gtk_tree_path_get_depth(item);
1233 int *ii = gtk_tree_path_get_indices(item);
1234 int ok = 1;
1235 if(dd != depth)
1236 ok = 0;
1237 else if(dd == 1)
1238 ok = 1;
1239 else if(ii[dd - 2] != indices[dd - 2])
1240 ok = 0;
1241 if(!ok)
1242 {
1243 gtk_tree_selection_unselect_path(selection, item);
1244 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1245 items = NULL;
1246 items_iter = items = gtk_tree_selection_get_selected_rows(selection, NULL);
1247 continue;
1248 }
1249 items_iter = g_list_next(items_iter);
1250 }
1251 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1252 items = NULL;
1253 return TRUE;
1254}
1255
1256static gboolean _tree_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_tip,
1257 GtkTooltip *tooltip, gpointer data)
1258{
1259 GtkTreeIter iter;
1260 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
1261 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
1262 GtkTreePath *path = NULL;
1263 gchar *tmp = NULL;
1264 gboolean show = FALSE;
1265
1266 if(!gtk_tree_view_get_tooltip_context(tree_view, &x, &y, keyboard_tip, &model, &path, &iter)) return FALSE;
1267
1268 gtk_tree_model_get(model, &iter, TREE_IC_USED_VISIBLE, &show, TREE_USED_TEXT, &tmp, -1);
1269 if(show)
1270 {
1271 gtk_tooltip_set_markup(tooltip, tmp);
1272 gtk_tree_view_set_tooltip_row(tree_view, tooltip, path);
1273 }
1274
1275 gtk_tree_path_free(path);
1276 dt_free(tmp);
1277
1278 return show;
1279}
1280
1281static void _is_form_used(int formid, dt_masks_form_t *grp, char *text, size_t text_length, int *nb)
1282{
1283 if(IS_NULL_PTR(grp))
1284 {
1285 for(const GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1286 {
1287 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1288 if(form->type & DT_MASKS_GROUP) _is_form_used(formid, form, text, text_length, nb);
1289 }
1290 }
1291 else if(grp->type & DT_MASKS_GROUP)
1292 {
1293 for(const GList *points = grp->points; points; points = g_list_next(points))
1294 {
1297 if(form)
1298 {
1299 if(point->formid == formid)
1300 {
1301 (*nb)++;
1302 if(*nb > 1) g_strlcat(text, "\n", text_length);
1303 g_strlcat(text, grp->name, text_length);
1304 }
1305 if(form->type & DT_MASKS_GROUP) _is_form_used(formid, form, text, text_length, nb);
1306 }
1307 }
1308 }
1309}
1310
1311static void _lib_masks_list_recurs(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_masks_form_t *form,
1312 int grp_id, dt_iop_module_t *module, int gstate, float opacity,
1313 dt_lib_masks_t *lm, int index)
1314{
1315 if(form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE)) return;
1316 // we create the text entry
1317 char str[256] = "";
1318 g_strlcat(str, form->name, sizeof(str));
1319 // we get the right pixbufs
1320 GdkPixbuf *icop = NULL;
1321 GdkPixbuf *icinv = NULL;
1322 GdkPixbuf *icuse = NULL;
1323 if(gstate & DT_MASKS_STATE_UNION)
1324 icop = lm->ic_union;
1325 else if(gstate & DT_MASKS_STATE_INTERSECTION)
1326 icop = lm->ic_intersection;
1327 else if(gstate & DT_MASKS_STATE_DIFFERENCE)
1328 icop = lm->ic_difference;
1329 else if(gstate & DT_MASKS_STATE_EXCLUSION)
1330 icop = lm->ic_exclusion;
1331 if(gstate & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;
1332 char str2[1000] = "";
1333 int nbuse = 0;
1334 if(grp_id == 0)
1335 {
1336 _is_form_used(form->formid, NULL, str2, sizeof(str2), &nbuse);
1337 if(nbuse > 0) icuse = lm->ic_wired;
1338 }
1339
1340 if(!(form->type & DT_MASKS_GROUP))
1341 {
1342 // we just add it to the tree
1343 GtkTreeIter child;
1344 gtk_tree_store_append(treestore, &child, toplevel);
1345 gtk_tree_store_set(treestore, &child, TREE_TEXT, str, TREE_MODULE, module, TREE_GROUPID, grp_id,
1346 TREE_FORMID, form->formid, TREE_EDITABLE, (grp_id == 0), TREE_IC_OP, icop,
1348 (!IS_NULL_PTR(icinv)), TREE_IC_USED, icuse, TREE_IC_USED_VISIBLE, (nbuse > 0),
1349 TREE_USED_TEXT, str2, -1);
1350 _set_iter_name(lm, form, gstate, opacity, GTK_TREE_MODEL(treestore), &child, index);
1351 }
1352 else
1353 {
1354 // we first check if it's a "module" group or not
1355 if(grp_id == 0 && !module)
1356 {
1357 for(const GList *iops = darktable.develop->iop; iops; iops = g_list_next(iops))
1358 {
1359 dt_iop_module_t *iop = (dt_iop_module_t *)iops->data;
1360 if((iop->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(iop->flags() & IOP_FLAGS_NO_MASKS)
1361 && iop->blend_params->mask_id == form->formid)
1362 {
1363 module = iop;
1364 break;
1365 }
1366 }
1367 }
1368
1369 // we add the group node to the tree
1370 GtkTreeIter child;
1371 gtk_tree_store_append(treestore, &child, toplevel);
1372 gtk_tree_store_set(treestore, &child, TREE_TEXT, str, TREE_MODULE, module, TREE_GROUPID, grp_id,
1373 TREE_FORMID, form->formid, TREE_EDITABLE, (grp_id == 0), TREE_IC_OP, icop,
1375 (!IS_NULL_PTR(icinv)), TREE_IC_USED, icuse, TREE_IC_USED_VISIBLE, (nbuse > 0),
1376 TREE_USED_TEXT, str2, -1);
1377 _set_iter_name(lm, form, gstate, opacity, GTK_TREE_MODEL(treestore), &child, index);
1378
1379 index = 0;
1380 // we add all nodes to the tree
1381 for(const GList *forms = form->points; forms; forms = g_list_next(forms))
1382 {
1383 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
1385 if(f)
1386 _lib_masks_list_recurs(treestore, &child, f, form->formid, module, grpt->state, grpt->opacity, lm, index);
1387 index++;
1388 }
1389 }
1390}
1391
1392gboolean _find_mask_iter_by_values(GtkTreeModel *model, GtkTreeIter *iter,
1393 const dt_iop_module_t *module, const int formid, const int level)
1394{
1395 gboolean found = FALSE;
1396 do
1397 {
1398 int fid = -1;
1399 dt_iop_module_t *mod;
1400 _lib_masks_get_values(model, iter, &mod, NULL, &fid);
1401 found = (fid == formid)
1402 && ((level == 1)
1403 || (IS_NULL_PTR(module) || (mod && (!g_strcmp0(module->op, mod->op)))));
1404 if(found) return found;
1405 GtkTreeIter child, parent = *iter;
1406 if(gtk_tree_model_iter_children(model, &child, &parent))
1407 {
1408 found = _find_mask_iter_by_values(model, &child, module, formid, level + 1);
1409 if(found)
1410 {
1411 *iter = child;
1412 return found;
1413 }
1414 }
1415 } while(gtk_tree_model_iter_next(model, iter));
1416 return found;
1417}
1418
1420{
1421 GList *res = NULL;
1422 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1423
1424 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1425
1426 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1427
1428 GList *items = gtk_tree_selection_get_selected_rows(selection, &model);
1429
1430 for(GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
1431 {
1432 GtkTreePath *item = (GtkTreePath *)items_iter->data;
1433 GtkTreeIter iter;
1434 if(gtk_tree_model_get_iter(model, &iter, item))
1435 {
1436 int fid = -1;
1437 int gid = -1;
1438 dt_iop_module_t *mod;
1439 _lib_masks_get_values(model, &iter, &mod, &gid, &fid);
1440 res = g_list_prepend(res, GINT_TO_POINTER(fid));
1441 res = g_list_prepend(res, GINT_TO_POINTER(gid));
1442 res = g_list_prepend(res, (void *)(mod));
1443 }
1444 }
1445
1446 g_list_foreach(items, (GFunc)gtk_tree_path_free, NULL);
1447 g_list_free(items);
1448 items = NULL;
1449
1450 return res;
1451}
1452
1454{
1455 /* first destroy all buttons in list */
1456 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1457 if(IS_NULL_PTR(lm)) return;
1458 if(lm->gui_reset) return;
1459
1460 const int gui_reset = lm->gui_reset;
1461 lm->gui_reset = 1;
1462 gboolean sync_center_view = FALSE;
1463
1464 // if a treeview is already present, let's get the currently selected items
1465 // as we are going to recreate the tree.
1466 GList *selectids = NULL;
1467
1468 if(lm->treeview)
1469 {
1470 selectids = _lib_masks_get_selected(self);
1471 }
1472
1473 // Rebuilding the shape manager list is also used to refresh shapes created
1474 // during continuous creation. In that case, the active creation button must
1475 // stay active until the user cancels creation explicitly.
1478
1479 GtkTreeStore *treestore;
1480 // we store : text ; *module ; groupid ; formid
1481 treestore = gtk_tree_store_new(TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT,
1482 G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF,
1483 G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, G_TYPE_STRING);
1484
1485 // we first add all groups
1486 for(const GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1487 {
1488 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1489 if(form->type & DT_MASKS_GROUP) _lib_masks_list_recurs(treestore, NULL, form, 0, NULL, 0, 1.0, lm, 0);
1490 }
1491
1492 // and we add all forms
1493 for(const GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1494 {
1495 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1496 if(!(form->type & DT_MASKS_GROUP)) _lib_masks_list_recurs(treestore, NULL, form, 0, NULL, 0, 1.0, lm, 0);
1497 }
1498
1499 gtk_tree_view_set_model(GTK_TREE_VIEW(lm->treeview), GTK_TREE_MODEL(treestore));
1500
1501 // select the images as selected in the previous tree
1502 if(selectids)
1503 {
1504 GList *ids = selectids;
1505 while(ids)
1506 {
1507 GtkTreeModel *model = GTK_TREE_MODEL(treestore);
1508 dt_iop_module_t *mod = (dt_iop_module_t *)ids->data;
1509 ids = g_list_next(ids);
1510 // const int gid = GPOINTER_TO_INT(ids->data); // not needed, skip it
1511 ids = g_list_next(ids);
1512 const int fid = GPOINTER_TO_INT(ids->data);
1513 ids = g_list_next(ids);
1514
1515 GtkTreeIter iter;
1516 gtk_tree_model_get_iter_first(model, &iter);
1517 // get formid in group for the given module
1518 const gboolean found = _find_mask_iter_by_values(model, &iter, mod, fid, 1);
1519
1520 if(found)
1521 {
1522 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1523 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(lm->treeview), path);
1524 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(lm->treeview), path, NULL, TRUE, 0.5, 0.5);
1525 gtk_tree_path_free(path);
1526 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1527 gtk_tree_selection_select_iter(selection, &iter);
1528 }
1529 }
1530 g_list_free(selectids);
1531 selectids = NULL;
1532 }
1533
1534 // After list refresh, keep the tree selection aligned with the current GUI module mask group.
1535 dt_iop_module_t *const current_module = darktable.develop->gui_module;
1536 const int current_group_id
1537 = (!IS_NULL_PTR(current_module) && current_module->blend_params
1538 && (current_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1539 && !(current_module->flags() & IOP_FLAGS_NO_MASKS))
1540 ? current_module->blend_params->mask_id
1541 : 0;
1542
1543 if(current_group_id > 0 && (IS_NULL_PTR(gui) || !gui->creation))
1544 {
1545 GtkTreeModel *model = GTK_TREE_MODEL(treestore);
1546 GtkTreeIter iter;
1547 if(gtk_tree_model_get_iter_first(model, &iter))
1548 {
1549 const gboolean found = _find_mask_iter_by_values(model, &iter, current_module, current_group_id, 1);
1550 if(found)
1551 {
1552 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1553 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1554 gtk_tree_selection_unselect_all(selection);
1555 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(lm->treeview), path);
1556 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(lm->treeview), path, NULL, TRUE, 0.5, 0.5);
1557 gtk_tree_selection_select_iter(selection, &iter);
1558 gtk_tree_path_free(path);
1559 sync_center_view = TRUE;
1560 }
1561 }
1562 }
1563
1564 g_object_unref(treestore);
1565
1566 lm->gui_reset = gui_reset;
1567
1568 if(sync_center_view)
1569 {
1570 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1571 _tree_selection_change(selection, lm);
1572 }
1573}
1574
1575static void _lib_masks_update_item(dt_lib_module_t *self, int formid, int parentid, dt_lib_masks_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
1576{
1577 // we retrieve the forms
1579 if(IS_NULL_PTR(form)) return;
1581
1582 // and the values
1583 int state = 0;
1584 float opacity = 1.0f;
1585
1586 int index = 0;
1587 if(grp && (grp->type & DT_MASKS_GROUP))
1588 {
1589 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
1590 {
1592 if(pt->formid == formid)
1593 {
1594 state = pt->state;
1595 opacity = pt->opacity;
1596 break;
1597 }
1598 index++;
1599 }
1600 }
1601
1602 _set_iter_name(lm, form, state, opacity, model, iter, index);
1603 return;
1604}
1605
1606static gboolean _update_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1607{
1608 if(IS_NULL_PTR(iter)) return 0;
1609
1610 // we retrieve the ids
1611 int grid = -1;
1612 int id = -1;
1613 _lib_masks_get_values(model, iter, NULL, &grid, &id);
1614
1615 // we retrieve the forms
1617 if(IS_NULL_PTR(form)) return 0;
1619
1620 // and the values
1621 int state = 0;
1622 float opacity = 1.0f;
1623
1624 int index = 0;
1625 if(grp && (grp->type & DT_MASKS_GROUP))
1626 {
1627 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
1628 {
1630 if(pt->formid == id)
1631 {
1632 state = pt->state;
1633 opacity = pt->opacity;
1634 break;
1635 }
1636 index++;
1637 }
1638 }
1639
1640 _set_iter_name(data, form, state, opacity, model, iter, index);
1641 return 0;
1642}
1643
1644// Update each item of the list
1646{
1647 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1648 if(IS_NULL_PTR(lm)) return;
1649 if(IS_NULL_PTR(lm->treeview)) return;
1650
1651 // for each node , we refresh the string
1652 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1653 if(!GTK_IS_TREE_MODEL(model)) return;
1654 gtk_tree_model_foreach(model, _update_foreach, lm);
1655}
1656
1657static gboolean _remove_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1658{
1659 if(IS_NULL_PTR(iter)) return 0;
1660 GList **rl = (GList **)data;
1661 const int refid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model), "formid"));
1662 const int refgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model), "groupid"));
1663
1664 int grid = -1;
1665 int id = -1;
1666 _lib_masks_get_values(model, iter, NULL, &grid, &id);
1667
1668 if(grid == refgid && id == refid)
1669 {
1670 GtkTreeRowReference *rowref = gtk_tree_row_reference_new(model, path);
1671 *rl = g_list_append(*rl, rowref);
1672 }
1673 return 0;
1674}
1675
1676static void _lib_masks_remove_item(dt_lib_module_t *self, int formid, int parentid)
1677{
1678 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1679 // for each node , we refresh the string
1680 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1681 GList *rl = NULL;
1682 g_object_set_data(G_OBJECT(model), "formid", GUINT_TO_POINTER(formid));
1683 g_object_set_data(G_OBJECT(model), "groupid", GUINT_TO_POINTER(parentid));
1684 gtk_tree_model_foreach(model, _remove_foreach, &rl);
1685
1686 for(const GList *rlt = rl; rlt; rlt = g_list_next(rlt))
1687 {
1688 GtkTreeRowReference *rowref = (GtkTreeRowReference *)rlt->data;
1689 GtkTreePath *path = gtk_tree_row_reference_get_path(rowref);
1690 gtk_tree_row_reference_free(rowref);
1691 if(path)
1692 {
1693 GtkTreeIter iter;
1694 if(gtk_tree_model_get_iter(model, &iter, path))
1695 {
1696 gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
1697 }
1698 gtk_tree_path_free(path);
1699 }
1700 }
1701 g_list_free(rl);
1702 rl = NULL;
1703}
1704
1705static gboolean _lib_masks_selection_change_r(GtkTreeModel *model, GtkTreeSelection *selection,
1706 GtkTreeIter *iter, struct dt_iop_module_t *module,
1707 const int selectid, int throw_event, const int level)
1708{
1709 gboolean found = FALSE;
1710
1711 GtkTreeIter i = *iter;
1712 do
1713 {
1714 int id = -1;
1715 dt_iop_module_t *mod;
1716 _lib_masks_get_values(model, &i, &mod, NULL, &id);
1717
1718 if((id == selectid)
1719 && ((level == 1)
1720 || (IS_NULL_PTR(module) || (mod && (!g_strcmp0(module->op, mod->op))))))
1721 {
1722 gtk_tree_selection_select_iter(selection, &i);
1723 found = TRUE;
1724 break;
1725 }
1726
1727 // check for children if any
1728 GtkTreeIter child, parent = i;
1729 if(gtk_tree_model_iter_children(model, &child, &parent))
1730 {
1731 found = _lib_masks_selection_change_r(model, selection, &child, module, selectid, throw_event, level + 1);
1732 if(found)
1733 {
1734 break;
1735 }
1736 }
1737 } while(gtk_tree_model_iter_next(model, &i) == TRUE);
1738
1739 return found;
1740}
1741
1742static void _lib_masks_selection_change(dt_lib_module_t *self, struct dt_iop_module_t *module, const int selectid, const int throw_event)
1743{
1744 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1745 if(IS_NULL_PTR(lm->treeview)) return;
1746
1747 // we first unselect all
1748 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
1749 lm->gui_reset = 1;
1750 gtk_tree_selection_unselect_all(selection);
1751 lm->gui_reset = 0;
1752
1753 // we go through all nodes
1754 lm->gui_reset = 1 - throw_event;
1755 GtkTreeIter iter;
1756 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1757 if(!GTK_IS_TREE_MODEL(model))
1758 {
1759 lm->gui_reset = 0;
1760 return;
1761 }
1762 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1763
1764 if(valid)
1765 {
1766 gtk_tree_view_expand_all(GTK_TREE_VIEW(lm->treeview));
1767 const gboolean found = _lib_masks_selection_change_r(model, selection, &iter, module, selectid, throw_event, 1);
1768 if(!found) gtk_tree_view_collapse_all(GTK_TREE_VIEW(lm->treeview));
1769 }
1770
1771 lm->gui_reset = 0;
1772}
1773
1774static gboolean _find_child_iter_by_formid(GtkTreeModel *model, GtkTreeIter *parent_iter, int formid, GtkTreeIter *child_iter)
1775{
1776 GtkTreeIter iter;
1777 gboolean found = FALSE;
1778
1779 // Obtenir le premier enfant du parent
1780 if(gtk_tree_model_iter_children(model, &iter, parent_iter))
1781 {
1782 do
1783 {
1784 int current_formid = -1;
1785 gtk_tree_model_get(model, &iter, TREE_FORMID, &current_formid, -1);
1786
1787 if(current_formid == formid)
1788 {
1789 *child_iter = iter;
1790 found = TRUE;
1791 break;
1792 }
1793 } while(gtk_tree_model_iter_next(model, &iter));
1794 }
1795
1796 return found;
1797}
1798
1799static gboolean _find_iter_by_parentid_and_formid(GtkTreeModel *model, int parentid, int formid, GtkTreeIter *iter)
1800{
1801 gboolean found = FALSE;
1802
1803 // Obtenir le premier itérateur du modèle
1804 do
1805 {
1806 int current_parentid = -1;
1807 gtk_tree_model_get(model, iter, TREE_FORMID, &current_parentid, -1);
1808
1809 if(current_parentid == parentid)
1810 {
1811 // Rechercher le formid dans les enfants du parent
1812 found = _find_child_iter_by_formid(model, iter, formid, iter);
1813 if(found)
1814 {
1815 break;
1816 }
1817 }
1818 } while(gtk_tree_model_iter_next(model, iter));
1819
1820 return found;
1821}
1822
1823static void _lib_masks_handler_callback(gpointer instance, const int formid, const int parentid, const dt_masks_event_t event, dt_lib_module_t *self)
1824{
1825 if(IS_NULL_PTR(self)) return;
1826
1827 dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
1828 if(IS_NULL_PTR(lm)) return;
1829 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
1830 if(!GTK_IS_TREE_MODEL(model)) return;
1831 GtkTreeIter iter;
1832 gboolean found_iter = gtk_tree_model_get_iter_first(model, &iter);
1833
1834 if(found_iter && _find_iter_by_parentid_and_formid(model, parentid, formid, &iter))
1835 {
1836 switch(event)
1837 {
1839 {
1840 _lib_masks_update_item(self, formid, parentid, lm, model, &iter);
1841 }
1842 break;
1843
1845 {
1847 }
1848 break;
1849
1851 {
1853 }
1854 break;
1855
1857 {
1859 //_lib_masks_remove_item(self, formid, parentid);
1860 }
1861 break;
1862
1863 case DT_MASKS_EVENT_NONE :
1864 default:
1865 {
1866 dt_print(DT_DEBUG_MASKS, "[_lib_masks_handler_callback] Mask event cannot be found.");
1867 }
1868 break;
1869 }
1870 }
1871
1872 else if(event == DT_MASKS_EVENT_RESET)
1873 {
1875 }
1876
1877 else if(event == DT_MASKS_EVENT_DELETE || event == DT_MASKS_EVENT_REMOVE)
1878 {
1879 // When a shape is deleted from the model, we may no longer find its previous row in the current tree.
1880 // In that case, force a full list refresh so stale rows don't remain visible.
1882 }
1883
1884 else if(event == DT_MASKS_EVENT_ADD)
1885 {
1888 if(IS_NULL_PTR(gui) || !gui->creation)
1890 dt_masks_get_from_id(darktable.develop, parentid ? parentid : formid));
1891 }
1892
1894}
1895
1896static void _lib_masks_popup_button_clicked_cb(GtkWidget *button, gpointer user_data)
1897{
1898 dt_lib_masks_t *d = (dt_lib_masks_t *)user_data;
1899 if(!d->popup_window) return;
1900
1901 if(gtk_widget_get_visible(d->popup_window))
1902 {
1903 gtk_widget_hide(d->popup_window);
1904 }
1905 else
1906 {
1907 gtk_widget_show_all(d->popup_window);
1908 }
1909}
1910
1911/* Idle callback to add the popup button to the module toolbox once the
1912 * module_toolbox proxy has been initialized. Returns FALSE when done so
1913 * it is removed from the idle loop. */
1914static gboolean _lib_masks_add_popup_button_idle(gpointer user_data)
1915{
1916 dt_lib_masks_t *d = (dt_lib_masks_t *)user_data;
1917 if(!d || !d->popup_button) return FALSE;
1918
1920 {
1922 return FALSE; /* stop calling this idle handler */
1923 }
1924 return TRUE; /* try again later */
1925}
1926
1928{
1929 /* initialize ui widgets */
1930 dt_lib_masks_t *d = (dt_lib_masks_t *)g_malloc0(sizeof(dt_lib_masks_t));
1931 self->data = (void *)d;
1932 d->gui_reset = 0;
1933
1934 // initialise all masks pixbuf. This is needed for the "automatic" cell renderer of the treeview
1935 const int bs2 = DT_PIXEL_APPLY_DPI(13);
1942
1943 // 2. Setup the non-modal popup window
1944 d->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1945 gtk_window_set_title(GTK_WINDOW(d->popup_window), _("Mask Manager Panel"));
1946 gtk_window_set_type_hint(GTK_WINDOW(d->popup_window), GDK_WINDOW_TYPE_HINT_UTILITY);
1947
1948 // NON-MODAL & NO FOCUS STEAL: Prevents window manager from stealing active focus when mapped/shown
1949 // because it contains drawing tools that should draw on main window
1950 gtk_window_set_modal(GTK_WINDOW(d->popup_window), FALSE);
1951 gtk_window_set_focus_on_map(GTK_WINDOW(d->popup_window), FALSE);
1952 gtk_window_set_accept_focus(GTK_WINDOW(d->popup_window), FALSE);
1953 gtk_window_set_transient_for(GTK_WINDOW(d->popup_window), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
1954
1955#ifdef GDK_WINDOWING_QUARTZ
1956 dt_osx_disallow_fullscreen(d->popup_window);
1957 gtk_window_set_position(GTK_WINDOW(d->popup_window), GTK_WIN_POS_CENTER_ON_PARENT);
1958#endif
1959
1960 // Intercept the window close action to hide the widget instead of completely destroying it
1961 g_signal_connect(G_OBJECT(d->popup_window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1962
1963 // 3. Create a clean box container inside the popup window to receive original shape elements
1964 GtkWidget *shape_manager_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1965 gtk_container_add(GTK_CONTAINER(d->popup_window), shape_manager_container);
1966
1967 // The main container for module blending params.
1968 // It's populated in blend_gui.c when a mask-able module gets focused.
1969 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1970
1971
1972 // Create and pack the button to control the popup panel.
1973 // NOTE: it's added to the darkroom module toolbox, aka not here.
1974 d->popup_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_drawn, 0, NULL);
1975 gtk_widget_set_tooltip_text(d->popup_button, _("Open mask manager..."));
1976
1977 /* module_toolbox may not be initialized yet when modules are being created.
1978 * Schedule adding the popup button via an idle callback so it runs after
1979 * other modules (including the module_toolbox) have had their gui_init
1980 * called. The callback will remove itself once it succeeds. */
1981 g_idle_add((GSourceFunc)_lib_masks_add_popup_button_idle, d);
1982 g_signal_connect(G_OBJECT(d->popup_button), "clicked", G_CALLBACK(_lib_masks_popup_button_clicked_cb), d);
1983
1984 // From here, everything goes into the mask manager popup,
1985 // so there is no child added to self->widget from here.
1986 GtkWidget *shape_buttons[DEVELOP_MASKS_NB_SHAPES] = { 0 };
1987 const dt_masks_shape_buttons_config_t shape_buttons_config = {
1988 .owner_module = NULL,
1989 .creation_module = NULL,
1990 .buttons = shape_buttons,
1991 .types = NULL,
1992 .action_section = NULL,
1994 .register_flags = DT_MASKS_SHAPE_BUTTONS_NONE,
1995 .local = FALSE,
1996 .user_data = NULL,
1997 .can_start = NULL,
1998 .form_type = NULL,
2000 .exited = NULL,
2001 };
2002 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2003 GtkWidget *shape_buttons_box = dt_masks_shape_buttons_create(&shape_buttons_config);
2004 gtk_box_pack_end(GTK_BOX(hbox), shape_buttons_box, FALSE, FALSE, 0);
2005 d->bt_gradient = shape_buttons[DT_MASKS_SHAPE_INDEX_GRADIENT];
2006 d->bt_path = shape_buttons[DT_MASKS_SHAPE_INDEX_POLYGON];
2007 d->bt_ellipse = shape_buttons[DT_MASKS_SHAPE_INDEX_ELLIPSE];
2008 d->bt_circle = shape_buttons[DT_MASKS_SHAPE_INDEX_CIRCLE];
2009 d->bt_brush = shape_buttons[DT_MASKS_SHAPE_INDEX_BRUSH];
2010
2011 gtk_box_pack_start(GTK_BOX(shape_manager_container), hbox, TRUE, TRUE, 0);
2012
2013 d->treeview = gtk_tree_view_new();
2014 GtkTreeViewColumn *col = gtk_tree_view_column_new();
2015 gtk_tree_view_column_set_title(col, "shapes");
2016 gtk_tree_view_append_column(GTK_TREE_VIEW(d->treeview), col);
2017
2018 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
2019 gtk_tree_view_column_pack_start(col, renderer, FALSE);
2020 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_OP, NULL);
2021 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_OP_VISIBLE);
2022 renderer = gtk_cell_renderer_pixbuf_new();
2023 gtk_tree_view_column_pack_start(col, renderer, FALSE);
2024 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_INVERSE, NULL);
2025 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_INVERSE_VISIBLE);
2026 renderer = gtk_cell_renderer_text_new();
2027 gtk_tree_view_column_pack_start(col, renderer, TRUE);
2028 gtk_tree_view_column_add_attribute(col, renderer, "text", TREE_TEXT);
2029 gtk_tree_view_column_add_attribute(col, renderer, "editable", TREE_EDITABLE);
2030 g_signal_connect(renderer, "edited", (GCallback)_tree_cell_edited, self);
2031 renderer = gtk_cell_renderer_pixbuf_new();
2032 gtk_tree_view_column_pack_end(col, renderer, FALSE);
2033 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_USED, NULL);
2034 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_USED_VISIBLE);
2035
2036 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->treeview));
2037 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
2038 gtk_tree_selection_set_select_function(selection, _tree_restrict_select, d, NULL);
2039 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->treeview), FALSE);
2040 // gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(d->treeview),TREE_USED_TEXT);
2041 g_object_set(d->treeview, "has-tooltip", TRUE, (gchar *)0);
2042 g_signal_connect(d->treeview, "query-tooltip", G_CALLBACK(_tree_query_tooltip), NULL);
2043 g_signal_connect(selection, "changed", G_CALLBACK(_tree_selection_change), d);
2044 g_signal_connect(d->treeview, "button-press-event", (GCallback)_tree_button_pressed, self);
2045
2046 // Auto-grows to its content (the side panel scrolls) up to a user-set, persisted height.
2047 gtk_box_pack_start(GTK_BOX(shape_manager_container),
2048 dt_ui_scroll_wrap(d->treeview, 90, "plugins/darkroom/masks/windowheight",
2050 TRUE, TRUE, 0);
2051
2055
2056 // set proxy functions
2057 darktable.develop->proxy.masks.module = self;
2062
2064}
2065
2067{
2068 if(IS_NULL_PTR(self->data)) return;
2069 if(self && self->data)
2070 {
2071 dt_lib_masks_t *d = (dt_lib_masks_t *)self->data;
2072
2073 // Destroy window allocation to prevent leaks
2074 if(d->popup_window)
2075 {
2076 gtk_widget_destroy(d->popup_window);
2077 d->popup_window = NULL;
2078 }
2079
2080 if(!IS_NULL_PTR(d->ic_inverse)) g_object_unref(d->ic_inverse);
2081 if(!IS_NULL_PTR(d->ic_wired)) g_object_unref(d->ic_wired);
2082 if(!IS_NULL_PTR(d->ic_union)) g_object_unref(d->ic_union);
2083 if(!IS_NULL_PTR(d->ic_intersection)) g_object_unref(d->ic_intersection);
2084 if(!IS_NULL_PTR(d->ic_difference)) g_object_unref(d->ic_difference);
2085 if(!IS_NULL_PTR(d->ic_exclusion)) g_object_unref(d->ic_exclusion);
2086
2087 d->ic_inverse = NULL;
2088 d->ic_wired = NULL;
2089 d->ic_union = NULL;
2090 d->ic_intersection = NULL;
2091 d->ic_difference = NULL;
2092 d->ic_exclusion = NULL;
2094 }
2095
2096 dt_free(self->data);
2097
2100}
2101
2102// clang-format off
2103// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2104// vim: shiftwidth=2 expandtab tabstop=2 cindent
2105// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2106// 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:4787
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4336
void dt_iop_gui_cleanup_blending_body(dt_iop_module_t *module)
Definition blend_gui.c:4693
void dt_masks_iop_update(dt_iop_module_t *module)
Definition blend_gui.c:3743
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:861
void reset(dt_view_t *self)
Definition darkroom.c:1266
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_MASKS
Definition darktable.h:727
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#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:281
#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:1202
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1493
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:2713
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#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:167
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:175
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:326
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:407
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:259
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:305
static void _tree_union(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:678
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:291
dt_masks_tree_cols_t
Definition libs/masks.c:109
@ TREE_EDITABLE
Definition libs/masks.c:114
@ TREE_IC_OP_VISIBLE
Definition libs/masks.c:116
@ TREE_IC_INVERSE_VISIBLE
Definition libs/masks.c:118
@ TREE_IC_INVERSE
Definition libs/masks.c:117
@ TREE_IC_USED
Definition libs/masks.c:119
@ TREE_IC_OP
Definition libs/masks.c:115
@ TREE_MODULE
Definition libs/masks.c:111
@ TREE_USED_TEXT
Definition libs/masks.c:121
@ TREE_FORMID
Definition libs/masks.c:113
@ TREE_COUNT
Definition libs/masks.c:122
@ TREE_IC_USED_VISIBLE
Definition libs/masks.c:120
@ TREE_TEXT
Definition libs/masks.c:110
@ TREE_GROUPID
Definition libs/masks.c:112
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:125
static void _tree_inverse(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:458
static GtkWidget * _tree_context_menu(GtkTreeSelection *selection, GtkTreeModel *model, dt_lib_module_t *self, dt_iop_module_t *module)
Definition libs/masks.c:960
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:799
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:370
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:157
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:182
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:884
static void _add_masks_history_item(dt_lib_masks_t *lm)
Definition libs/masks.c:449
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:283
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:275
static void _tree_moveup(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:733
static void _tree_movedown(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:766
static void _tree_add_ellipse(GtkButton *button, dt_iop_module_t *module)
Definition libs/masks.c:267
static void _tree_duplicate_shape(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:833
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:568
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:299
static void _tree_cell_edited(GtkCellRendererText *cell, gchar *path_string, gchar *new_text, dt_lib_module_t *self)
Definition libs/masks.c:860
void gui_init(dt_lib_module_t *self)
int position()
Definition libs/masks.c:103
static void _tree_intersection(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:513
const char ** views(dt_lib_module_t *self)
Definition libs/masks.c:92
static void _lib_masks_clear_blending_box(dt_lib_module_t *self)
Definition libs/masks.c:162
static gboolean _lib_masks_can_host_blending(const dt_iop_module_t *module)
Definition libs/masks.c:173
static void _tree_cleanup(GtkButton *button, dt_lib_module_t *self)
Definition libs/masks.c:443
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:210
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:623
static void _lib_masks_show_blending_message(dt_lib_module_t *self, gchar *markup)
Definition libs/masks.c:195
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:935
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks.h:933
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks.h:934
@ DT_MASKS_SHAPE_INDEX_GRADIENT
Definition masks.h:931
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks.h:932
void dt_masks_change_form_gui(dt_masks_form_t *newform)
@ DT_MASKS_EDIT_FULL
Definition masks.h:202
@ DT_MASKS_STATE_DIFFERENCE
Definition masks.h:173
@ DT_MASKS_STATE_INVERSE
Definition masks.h:170
@ DT_MASKS_STATE_INTERSECTION
Definition masks.h:172
@ DT_MASKS_STATE_EXCLUSION
Definition masks.h:174
@ DT_MASKS_STATE_UNION
Definition masks.h:171
@ DT_MASKS_STATE_USE
Definition masks.h:168
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:86
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:180
dt_masks_type_t
Definition masks.h:129
@ DT_MASKS_NON_CLONE
Definition masks.h:138
@ DT_MASKS_POLYGON
Definition masks.h:132
@ DT_MASKS_BRUSH
Definition masks.h:137
@ DT_MASKS_ELLIPSE
Definition masks.h:136
@ DT_MASKS_CLONE
Definition masks.h:134
@ DT_MASKS_GRADIENT
Definition masks.h:135
@ DT_MASKS_CIRCLE
Definition masks.h:131
@ DT_MASKS_GROUP
Definition masks.h:133
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:156
@ DT_MASKS_EVENT_NONE
Definition masks.h:157
@ DT_MASKS_EVENT_UPDATE
Definition masks.h:160
@ DT_MASKS_EVENT_RESET
Definition masks.h:163
@ DT_MASKS_EVENT_ADD
Definition masks.h:158
@ DT_MASKS_EVENT_DELETE
Definition masks.h:161
@ DT_MASKS_EVENT_REMOVE
Definition masks.h:159
@ DT_MASKS_EVENT_CHANGE
Definition masks.h:162
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:941
@ DT_MASKS_SHAPE_BUTTONS_ALL
Definition masks.h:953
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:927
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)
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:303
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:307
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_view_manager_t * view_manager
Definition darktable.h:772
void(* list_update)(struct dt_lib_module_t *self)
Definition develop.h:430
GList * iop
Definition develop.h:279
void(* list_remove)(struct dt_lib_module_t *self, int formid, int parentid)
Definition develop.h:429
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:428
GList * history
Definition develop.h:275
struct dt_masks_form_gui_t * form_gui
Definition develop.h:327
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:432
struct dt_develop_t::@20 proxy
GList * forms
Definition develop.h:321
dt_ui_t * ui
Definition gtk.h:164
GtkWidget * masks_edit
Definition blend.h:371
gpointer blend_data
Definition imageop.h:318
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:316
GModule *dt_dev_operation_t op
Definition imageop.h:256
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:463
gboolean creation
Definition masks.h:503
dt_masks_type_t type
Definition masks.h:378
char name[128]
Definition masks.h:396
GList * points
Definition masks.h:377
dt_iop_module_t * owner_module
Definition masks.h:969
struct dt_view_manager_t::@67 proxy
struct dt_view_manager_t::@67::@68 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:1326
@ DT_VIEW_DARKROOM
Definition view.h:78
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER