Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/shape_manager.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/imageop_gui.h"
42#include "develop/masks.h"
43#include "develop/masks_group.h" // dt_masks_group_set_member_operation(), dt_masks_group_get_member()
44#include "develop/masks_gui.h"
45#include "develop/masks/masks_history.h" // dt_masks_form_unref()
46#include "common/logging.h"
47#include "system/macros.h"
49#include "control/redraw.h"
50#include "develop/blend.h"
51#include "develop/blend_gui.h"
52#include "develop/develop.h"
53#include "develop/imageop.h"
54#include "widgets/draw.h"
56#include "gui/application.h"
57#include "libs/lib.h"
58#include "libs/lib_api.h"
59#include "views/view.h"
60#include "widgets/scroll_wrap.h"
61#include "widgets/widget_settings.h" // dt_widget_root_window(), dt_widget_store_int()
62#include "common/conf.h" // dt_conf_get_int(), dt_conf_key_exists()
63
64#include "widgets/label.h" // dt_gui_symbolic_icon_pixbuf()
65#include "widgets/dialog.h" // dt_gui_refocus_parent()
67#include "control/signal.h"
68#include "control/user_message.h" // dt_control_log()
69
70#ifdef GDK_WINDOWING_WAYLAND
71#include <gdk/gdkwayland.h> // conditional-ok: GDK_IS_WAYLAND_DISPLAY() is used only inside the same #ifdef
72#endif
73#ifdef GDK_WINDOWING_QUARTZ
74#include "osx/osx.h"
75#endif
76
77
78DT_MODULE(1)
79
82static GList *_modules_owning_group(const dt_masks_form_t *group);
83static void _shape_manager_broadcast(dt_lib_module_t *self, const int formid, const int parentid,
84 const dt_masks_event_t event);
85
86/* The panel splits the forms by the one question that separates them: is this group some
87 * module's drawn mask, or nobody's yet? The left list is the inventory -- every shape and every
88 * group, module masks included, so any of them can be picked up and reused -- and the right one
89 * is the assignment: only the groups modules actually render, members underneath. A module mask
90 * therefore appears in both, once in the inventory as an ordinary reusable group and once in the
91 * assignment under the module rendering it, the same way a shape a module uses appears in both
92 * its own row and as a member -- the arrangement a module's own Drawn tab already uses. */
93typedef enum dt_shape_list_t
94{
95 DT_SHAPE_LIST_SHAPES = 0, // every shape and every group, module masks included
96 DT_SHAPE_LIST_MODULES, // only the groups that are some module's drawn mask
99
100/* One of the two lists. Every tree handler is handed this rather than the module, because the
101 * first thing each of them needs to know is which of the two trees the user acted on. */
103{
105
106 /* The rightmost column, the one carrying the per-row trash / minus icon. Kept because a click
107 * and a tooltip are both answered by comparing against the column the pointer is over. */
108 GtkTreeViewColumn *action_col;
109
110 /* The "add to the selected module group" column, on the inventory list only -- NULL on the
111 * module list. Kept for the same reason as action_col: a click and a tooltip are both answered
112 * by comparing against the column the pointer is over. */
113 GtkTreeViewColumn *add_col;
114
115 /* The "pick the modules that should render this" column, inventory only. */
116 GtkTreeViewColumn *assign_col;
117
118 /* The name column and the renderer inside it, kept so a row can be opened straight into
119 * editing -- gtk_tree_view_set_cursor_on_cell() needs both. */
120 GtkTreeViewColumn *name_col;
121 GtkCellRenderer *name_renderer;
122
124 dt_lib_module_t *self; // the module both lists belong to
125
126 /* The conf key dt_ui_scroll_wrap() persists this list's dragged height under. Kept so the
127 * popup can raise the ceiling on it at show time, independently of whatever gui_init built the
128 * wrapper with -- see _shape_manager_relax_height_caps(). */
129 const char *height_key;
131
132typedef struct dt_shape_manager_t
133{
135
136 /* Replacement for shape_manager_expander */
139
140 GdkPixbuf *ic_used;
141 /* The "+" in its two states. A cell renderer has no colour of its own and its insensitive
142 * rendering is far too faint to read as disabled (measured: at most 49 of 255 on a channel),
143 * so availability is shown by swapping the icon for a differently tinted one. */
144 GdkPixbuf *ic_add;
145 GdkPixbuf *ic_add_off;
146 GdkPixbuf *ic_assign;
147 GdkPixbuf *ic_inverse;
148 GdkPixbuf *ic_union;
149 GdkPixbuf *ic_intersection;
150 GdkPixbuf *ic_difference;
151 GdkPixbuf *ic_exclusion;
153
154 /* Set for the life of one _tree_start_name_editing() call, read by _tree_name_editing_started():
155 * the editor opens with the cursor after the text instead of the text selected. A name the user
156 * is meant to complete -- "Group " -- wants the caret, a name they are meant to replace wants
157 * the selection. */
159
160 /* The name edit waiting for the tree to be laid out, see _tree_edit_group_name(). A formid of 0
161 * is none pending; only one can be, since one action creates one group. */
166
168 int formid, gboolean cursor_at_end);
169
170
171const char *name(struct dt_lib_module_t *self __attribute__((unused)))
172{
173 return _("Shape Manager");
174}
175
176/* Never shown in a panel: everything this module builds lives in its own window, opened by the
177 * button it pushes into the darkroom toolbox. Same arrangement as libs/export.c, which is also a
178 * module whose interface is a window rather than a panel section. */
179const char **views(dt_lib_module_t *self __attribute__((unused)))
180{
181 static const char *v[] = {"special", NULL};
182 return v;
183}
184
185uint32_t container(dt_lib_module_t *self __attribute__((unused)))
186{
188}
189
191{
203 /* Which of the two action icons the row shows -- a top-level row deletes, a row under a group
204 * detaches. Exactly one is TRUE on a form row, and neither on the separator. */
207 /* What the row has to say about itself beyond its name -- currently only that the same shape
208 * is reached twice within one module's mask. Empty on every row that has nothing to add. */
210 /* The name struck through: this application of the shape is a repeat of one the same mask makes
211 * earlier, so it is the note's own claim said on the name. FALSE on every other row. */
216
217static void _shape_manager_get_values(GtkTreeModel *model, GtkTreeIter *iter,
218 dt_iop_module_t **module, int *groupid, int *formid)
219{
220 // returns module & groupid & formid if requested
221
222 if(module)
223 {
224 GValue gv = { 0, };
225 gtk_tree_model_get_value(model, iter, TREE_MODULE, &gv);
226 *module = NULL;
227 if(G_VALUE_TYPE(&gv) == G_TYPE_POINTER)
228 *module = (dt_iop_module_t *)g_value_get_pointer(&gv);
229 g_value_unset(&gv);
230 }
231
232 if(groupid)
233 {
234 GValue gv = { 0, };
235 gtk_tree_model_get_value(model, iter, TREE_GROUPID, &gv);
236 *groupid = g_value_get_int(&gv);
237 g_value_unset(&gv);
238 }
239
240 if(formid)
241 {
242 GValue gv = { 0,};
243 gtk_tree_model_get_value(model, iter, TREE_FORMID, &gv);
244 *formid = g_value_get_int(&gv);
245 g_value_unset(&gv);
246 }
247}
248
249/* The shape to create rides on the menu item, the way "masks-operation" already does below --
250 * one handler for the five entries, which is also what lets them be built from a loop. Arming the
251 * tool is dt_masks_creation_mode_enter()'s business alone, toolbars included: it tells every shape
252 * toolbar to press the matching button, so the entry and the button agree without either knowing
253 * about the other. */
254static void _tree_add_shape(GtkWidget *menu_item, dt_iop_module_t *module)
255{
257 = (dt_masks_type_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "masks-shape-type"));
258
262}
263
265{
266 GtkWidget *item = dt_masks_shape_menu_item_new(menu, type, G_CALLBACK(_tree_add_shape), module);
267 if(!IS_NULL_PTR(item)) g_object_set_data(G_OBJECT(item), "masks-shape-type", GINT_TO_POINTER(type));
268}
269
271 dt_masks_type_t type __attribute__((unused)), gpointer user_data __attribute__((unused)))
272{
274}
275
276/* Refresh the masks header of every module whose drawn mask IS @p grp, after its members changed:
277 * those are the panels whose "N shapes used" moved. Not the module a tree row stores: a nested
278 * group's row carries the module of the mask it sits in, whose own count does not change, and a
279 * group no module renders yet carries none at all. Every module's member list is rebuilt by the
280 * DT_SIGNAL_MASK_CHANGED the caller raises. */
282{
283 GList *owners = _modules_owning_group(grp);
284 for(const GList *m = owners; m; m = g_list_next(m))
286 g_list_free(owners);
287}
288
289static void _tree_add_exist(GtkButton *button, dt_masks_form_t *grp)
290{
291 dt_develop_t *const dev = dt_dev_get_global();
292 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
293 // we get the new formid
294 const int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "formid"));
295
296 // we add the form in this group
297 dt_masks_form_t *form = dt_masks_get_from_id(dev, id);
298 grp = dt_masks_cow_touch(dev, grp);
299 if(form && dt_masks_group_add_form(dev, grp, form))
300 {
301 // we save the group
303
305 dt_dev_masks_selection_change(dev, NULL, grp->formid, TRUE);
306
307 /* Raised rather than broadcast: unlike the handlers above, this one does not rebuild the tree
308 * itself, so our own handler is left to do it as well as blend_gui's. */
311 }
312}
313
314/* The form ids the selection of @p list names, each once, in selection order. The same form can
315 * sit on several rows -- a shape at top level and under each group holding it -- and an action on
316 * the selection must act on it once.
317 *
318 * Returns a GList of GINT_TO_POINTER(formid) the caller frees with g_list_free(). */
320{
321 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
322 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
323
324 GList *ids = NULL;
325 GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
326 for(const GList *row = rows; row; row = g_list_next(row))
327 {
328 GtkTreeIter iter;
329 if(!gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)row->data)) continue;
330
331 int id = -1;
332 _shape_manager_get_values(model, &iter, NULL, NULL, &id);
333 if(id > 0 && IS_NULL_PTR(g_list_find(ids, GINT_TO_POINTER(id)))) ids = g_list_prepend(ids, GINT_TO_POINTER(id));
334 }
335 g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
336
337 return g_list_reverse(ids);
338}
339
340/* "New group from selection": a new group holding every selected form, one or several. */
341static void _tree_group(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
342{
343 dt_lib_module_t *self = list->self;
344
345 // Nothing to put in it, no group: an empty one would only be a row left to delete.
346 GList *ids = _selected_form_ids(list);
347 if(IS_NULL_PTR(ids)) return;
348
349 // we create the new group
350 // create_ext registers the group in dev->allforms and dt_masks_append_form() below takes
351 // dev->forms's own reference, so both lists have a claim and teardown balances. Neither
352 // touches dev->forms outside masks_mutex, which a hand-rolled g_list_append does -- and the
353 // pipeline thread reads that list under the same lock.
355
356 /* A name to complete rather than one to keep: the row opens straight into editing with the caret
357 * after it, for the user to say what the group holds. It carries its rank among the groups all
358 * the same, so an edit the user cancels still leaves the rows told apart -- the group is not in
359 * dev->forms yet, so the count is of the ones before it. Not translated, for the reason
360 * dt_masks_group_name_for_module() is not either -- the string goes into the form, the database
361 * and the XMP, so it may not depend on the language the group happened to be created in. */
362 g_snprintf(mask->name, sizeof(mask->name), "Group %u",
363 dt_masks_group_list(dt_dev_get_global(), NULL, 0) + 1);
364
365 // Its id, to find its row again once the tree is rebuilt -- asked of the masks module rather
366 // than read off the form, which the boundary gate counts.
367 dt_masks_form_info_t info = { 0 };
368 const int new_id = dt_masks_form_get_info(mask, &info) ? info.formid : -1;
369
370 // we add all selected forms to this group
371 for(const GList *id = ids; id; id = g_list_next(id))
372 {
373 dt_masks_form_t *member = dt_masks_get_from_id(dt_dev_get_global(), GPOINTER_TO_INT(id->data));
374 if(IS_NULL_PTR(member)) continue;
375
376 dt_masks_group_add_form_with_state(dt_dev_get_global(), mask, member, mask->formid,
378 }
379 g_list_free(ids);
380
381 // we add this group to the general list
383
384 // add we save
388
389 // The group renders for no module yet, so its row is the inventory's.
390 if(new_id > 0) _tree_edit_group_name(self, DT_SHAPE_LIST_SHAPES, new_id, TRUE);
391}
392
393static int _tree_format_form_usage_label(char *str, const size_t str_size,
394 const dt_masks_form_t *form, const dt_iop_module_t *module)
395{
396 if(IS_NULL_PTR(str) || IS_NULL_PTR(form)) return -1;
397
398 str[0] = '\0';
399 g_strlcat(str, form->name, str_size);
400
401 int nbuse = 0;
402 // we search were this form is used
403 for(const GList *modules = dt_dev_get_global()->iop; modules; modules = g_list_next(modules))
404 {
405 dt_iop_module_t *m = (dt_iop_module_t *)modules->data;
406 const dt_masks_form_t *grp = dt_masks_get_from_id(m->dev, m->blend_params->mask_id);
407 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) continue;
408
409 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
410 {
411 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
412 // The caller's own module is not worth naming to it: it is the one asking.
413 if(pt->formid != form->formid || m == module) continue;
414
415 if(nbuse == 0) g_strlcat(str, " (", str_size);
416 g_strlcat(str, " ", str_size);
417 gchar *module_label = dt_history_item_get_name(m);
418 g_strlcat(str, module_label, str_size);
419 dt_free(module_label);
420 nbuse++;
421 }
422 }
423
424 if(nbuse > 0) g_strlcat(str, " )", str_size);
425 return nbuse;
426}
427
428static void _set_iter_name(dt_shape_manager_t *lm, dt_masks_form_t *form, int state, float opacity,
429 GtkTreeModel *model, GtkTreeIter *iter, int index)
430{
431 if(IS_NULL_PTR(form)) return;
432
433 char str[256] = "";
434
435 if(opacity != 1.0f)
436 {
437 g_snprintf(str, sizeof(str), "%s %d%%",
438 form->name, (int)(opacity * 100));
439 }
440 else
441 {
442 g_strlcpy(str, form->name, sizeof(str));
443 }
444
445 GdkPixbuf *icop = NULL;
446 GdkPixbuf *icinv = NULL;
447 if(index != 0)
448 {
450 icop = lm->ic_union;
452 icop = lm->ic_intersection;
454 icop = lm->ic_difference;
456 icop = lm->ic_exclusion;
457 }
458 if(state & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;
459
460 gtk_tree_store_set(GTK_TREE_STORE(model), iter, TREE_TEXT, str, TREE_IC_OP, icop, TREE_IC_OP_VISIBLE,
461 (!IS_NULL_PTR(icop)), TREE_IC_INVERSE, icinv, TREE_IC_INVERSE_VISIBLE, (!IS_NULL_PTR(icinv)), -1);
462}
463
464static void _tree_delete_unused(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
465{
466 dt_lib_module_t *self = list->self;
468
469 /* The undo record has to be opened HERE, before the sweep. dt_dev_add_history_item() below
470 * opens one of its own, but by then every hist->forms has been rewritten in place and the
471 * "before" state it captures is the swept one. dt_dev_history_undo_start_record()'s depth
472 * counter makes that inner pair a no-op, so the recorded before/after spans the whole
473 * operation.
474 *
475 * What makes the restore work is that dt_history_duplicate() copies each item's forms LIST
476 * (g_list_copy plus one reference per form) rather than aliasing it: the snapshot owns its
477 * own cells, the sweep's g_list_remove() on the live items cannot reach them, and every
478 * swept shape stays alive as long as the record holds it. _pop_undo() rewrites the database
479 * from the restored history, so undoing puts the masks_history rows back too. */
481
485
486 // The sweep only rewrote the in-memory snapshots. main.history and main.masks_history are
487 // rewritten wholesale from dev->history by the write a commit triggers, so without one the
488 // deleted shapes stay in the database and come back on the next read -- and, like every other
489 // forms mutation here, the deletion is never recorded as its own history step.
491
493}
494
495/* Tells the rest of the GUI that the shapes changed.
496 *
497 * The Drawn tab of a module's blending panel keeps its own two lists, and blend_gui.c refreshes
498 * them from DT_SIGNAL_MASK_CHANGED -- the same signal it raises when the user edits shapes from
499 * there, which is how this manager already hears about those. Only this direction was missing:
500 * the manager rebuilt its own tree and told nobody, so a shape grouped, renamed or deleted here
501 * stayed as it was in the panel until something else happened to refresh it.
502 *
503 * gui_reset is held over the raise because the caller rebuilds our own tree itself: it makes our
504 * own handler's rebuild a no-op instead of doing the same work twice. blend_gui's handler is a
505 * different callback with its own data and is unaffected.
506 *
507 * The ids only matter for DT_MASKS_EVENT_UPDATE, the one event both handlers answer by
508 * refreshing a single row; anything else refreshes the whole list on either side. */
509static void _shape_manager_broadcast(dt_lib_module_t *self, const int formid, const int parentid,
510 const dt_masks_event_t event)
511{
512 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
514
515 const int reset = lm->gui_reset;
516 lm->gui_reset = 1;
518 lm->gui_reset = reset;
519}
520
522{
523 const int reset = lm->gui_reset;
524 lm->gui_reset = 1;
526 lm->gui_reset = reset;
527}
528
529
530/* One handler for all five operations. They differed by a single constant and were otherwise
531 * identical to the line, which is how five copies of a copy-on-write mistake got written; the
532 * operation now rides on the menu item, the way develop/blend_gui.c already carries "blend-state". */
534{
535 const dt_masks_state_t operation
536 = (dt_masks_state_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "masks-operation"));
537 if(operation == DT_MASKS_STATE_NONE) return;
538
539 dt_lib_module_t *self = list->self;
541
542 // now we go through all selected nodes
543 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
544 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
545 int change = 0;
546 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
547 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
548 {
549 GtkTreePath *item = (GtkTreePath *)items_iter->data;
550 GtkTreeIter iter;
551 if(gtk_tree_model_get_iter(model, &iter, item))
552 {
553 int grid = -1;
554 int id = -1;
555 _shape_manager_get_values(model, &iter, NULL, &grid, &id);
556
557 /* The module owns the copy-on-write: it touches the group before resolving the row, which
558 * this loop did not do -- it mutated a refcounted membership block that a history snapshot
559 * could still be observing. UNCHANGED (the row already had this operator) deliberately does
560 * not count as a change, so a no-op click writes no undo step. */
561 dt_masks_member_t member;
562 if(dt_masks_group_set_member_operation(dt_dev_get_global(), grid, id, operation, &member)
563 == DT_MASKS_OK)
564 {
566 model, &iter, (int)member.index);
567 change = 1;
568 }
569 }
570 }
571 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
572 items = NULL;
573
574 if(change)
575 {
577
578 /* Raised rather than broadcast: unlike the handlers above, this one does not rebuild the tree
579 * itself, so our own handler is left to do it as well as blend_gui's. */
582
584 }
585}
586
587static void _tree_moveup(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
588{
589 dt_lib_module_t *self = list->self;
591
592 // we first discard all visible shapes
594
595 // now we go through all selected nodes
596 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
597 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
598 lm->gui_reset = 1;
599 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
600 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
601 {
602 GtkTreePath *item = (GtkTreePath *)items_iter->data;
603 GtkTreeIter iter;
604 if(gtk_tree_model_get_iter(model, &iter, item))
605 {
606 int grid = -1;
607 int id = -1;
608 _shape_manager_get_values(model, &iter, NULL, &grid, &id);
609
611 group_form = dt_masks_cow_touch(dt_dev_get_global(), group_form);
612 dt_masks_form_move(group_form, id, 0);
613 }
614 }
615 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
616 items = NULL;
617
618 lm->gui_reset = 0;
621
622 // Without this, the reorder only mutates the live group's points list: it's never recorded
623 // as its own history step, so the next undo/redo silently discards the new order.
625}
626
627static void _tree_movedown(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
628{
629 dt_lib_module_t *self = list->self;
631
632 // we first discard all visible shapes
634
635 // now we go through all selected nodes
636 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
637 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
638 lm->gui_reset = 1;
639 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
640 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
641 {
642 GtkTreePath *item = (GtkTreePath *)items_iter->data;
643 GtkTreeIter iter;
644 if(gtk_tree_model_get_iter(model, &iter, item))
645 {
646 int grid = -1;
647 int id = -1;
648 _shape_manager_get_values(model, &iter, NULL, &grid, &id);
649
651 group_form = dt_masks_cow_touch(dt_dev_get_global(), group_form);
652 dt_masks_form_move(group_form, id, 1);
653 }
654 }
655 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
656 items = NULL;
657
658 lm->gui_reset = 0;
661
662 // Without this, the reorder only mutates the live group's points list: it's never recorded
663 // as its own history step, so the next undo/redo silently discards the new order.
665}
666
667static void _tree_delete_shape(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
668{
669 dt_lib_module_t *self = list->self;
671
672 // we first discard all visible shapes
674
675 // now we go through all selected nodes
676 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
677 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
678 dt_iop_module_t *module = NULL;
679 lm->gui_reset = 1;
680 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
681 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
682 {
683 GtkTreePath *item = (GtkTreePath *)items_iter->data;
684 GtkTreeIter iter;
685 if(gtk_tree_model_get_iter(model, &iter, item))
686 {
687 int grid = -1;
688 int id = -1;
689 _shape_manager_get_values(model, &iter, &module, &grid, &id);
690
693 }
694 }
695 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
696 items = NULL;
697
698 lm->gui_reset = 0;
701
702 // Without this, the deletion only mutates the live dev->forms: it's never recorded as its
703 // own history step, so the next history navigation (undo/redo) silently discards it and
704 // reverts to whatever forms snapshot was last actually committed.
706}
707
708/* The group the module list currently points at, which is what the inventory's "+" adds to.
709 *
710 * A row that is itself a group answers for itself; any other row hands the question up to its
711 * parent, so clicking "+" after selecting a shape inside a module's mask adds to that mask
712 * rather than doing nothing. Returns 0 when nothing usable is selected, which is also what
713 * greys the button out. */
714/* The top-level ancestor of a row -- itself, if it has none. The module list holds one row per
715 * module and that row's whole subtree, so nothing below the top level is its own destination:
716 * selecting a sub-group or a shape nested inside a module's mask still means "this mask". */
717static int _tree_root_formid(GtkTreeModel *model, GtkTreeIter iter)
718{
719 GtkTreeIter parent;
720 while(gtk_tree_model_iter_parent(model, &parent, &iter))
721 iter = parent;
722
723 int formid = 0;
724 gtk_tree_model_get(model, &iter, TREE_FORMID, &formid, -1);
725 return formid;
726}
727
729{
731 if(IS_NULL_PTR(list->treeview)) return 0;
732
733 GtkTreeModel *model = NULL;
734 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
735 GList *rows = gtk_tree_selection_get_selected_rows(selection, &model);
736 if(IS_NULL_PTR(rows)) return 0;
737
738 int group_id = 0;
739 GtkTreeIter iter;
740 if(gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)rows->data))
741 group_id = _tree_root_formid(model, iter);
742
743 g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
744 return group_id;
745}
746
747/* Whether the form @p fid can join the group @p group_id. Three ways it cannot: the group already
748 * reaches this form, directly or through one of its own sub-groups, so adding it again would write
749 * an undo step for a no-op; the form is a group that already contains the target, and adding it
750 * would close a cycle in the membership graph -- every walk over it, the tree build first of all,
751 * would then stop terminating; or the form is a group every one of whose shapes the target already
752 * renders, adding nothing new.
753 *
754 * A row's "+", the "Attach to the group" menu and its action all ask this one function, so an offer
755 * that looks available always is. */
756static gboolean _form_can_join_group(dt_develop_t *dev, const int group_id, const int fid)
757{
758 if(group_id <= 0 || fid <= 0) return FALSE;
759
760 const dt_masks_form_t *grp = dt_masks_get_from_id(dev, group_id);
761 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return FALSE;
762
763 /* Subsumes "it is the same group" (trivial at depth 0) and "it is already a direct member": a
764 * mask that already reaches this form ANYWHERE in its own subtree gains nothing from reaching
765 * it again at the top. */
766 if(dt_masks_group_contains(dev, group_id, fid) == DT_MASKS_OK) return FALSE;
767 if(dt_masks_group_contains(dev, fid, group_id) == DT_MASKS_OK) return FALSE;
768
769 /* A group every one of whose shapes the target already renders would add nothing: nesting it
770 * duplicates coverage the mask already has. */
771 const dt_masks_form_t *form = dt_masks_get_from_id(dev, fid);
772 if(!IS_NULL_PTR(form) && (form->type & DT_MASKS_GROUP))
773 {
774 gboolean has_shapes = FALSE;
775 if(dt_masks_group_covers_shapes(dev, fid, group_id, &has_shapes) == DT_MASKS_OK && has_shapes)
776 return FALSE;
777 }
778
779 return TRUE;
780}
781
782/* Whether this row's "+" can do anything: not when nothing is selected in the module list, so
783 * there is nowhere to add to, and otherwise whatever _form_can_join_group() answers for the target
784 * mask -- always resolved to its top level, never to whatever sub-group or shape happens to be
785 * selected inside it.
786 *
787 * The icon and the click both ask this one function, so a button that looks available always is. */
788static gboolean _row_can_be_added(const dt_shape_manager_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
789{
790 int fid = -1;
791 _shape_manager_get_values(model, iter, NULL, NULL, &fid);
793}
794
795/* "Attach to the group": every selected form that can join the group named on the menu item joins
796 * it, the others are left alone. Asked again form by form rather than once when the menu was
797 * built: a group joining first may bring later selected shapes along with it. */
798static void _tree_add_to_group(GtkMenuItem *item, dt_shape_manager_list_t *list)
799{
800 dt_lib_module_t *self = list->self;
801 dt_develop_t *const dev = dt_dev_get_global();
802
803 const int group_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "group-formid"));
804 dt_masks_form_t *grp = dt_masks_get_from_id(dev, group_id);
805 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
806
807 GList *ids = _selected_form_ids(list);
808 gboolean added = FALSE;
809 for(const GList *id = ids; id; id = g_list_next(id))
810 {
811 const int fid = GPOINTER_TO_INT(id->data);
812 dt_masks_form_t *form = dt_masks_get_from_id(dev, fid);
813 if(IS_NULL_PTR(form) || !_form_can_join_group(dev, group_id, fid)) continue;
814
815 // Touched before the first change only: a group nothing joins must not be cloned for nothing.
816 if(!added) grp = dt_masks_cow_touch(dev, grp);
817 if(!IS_NULL_PTR(dt_masks_group_add_form(dev, grp, form))) added = TRUE;
818 }
819 g_list_free(ids);
820 if(!added) return;
821
826}
827
828/* Availability is per row and depends on the OTHER list's selection, so it is recomputed at draw
829 * time rather than stored: nothing can go stale, and there is no model column to keep in step.
830 *
831 * The renderer's sensitivity carries the meaning but not the look -- measured on this exact
832 * renderer, offscreen, an insensitive icon-name pixbuf cell differs from a sensitive one by at
833 * most 49 of 255 on a channel, a faint dim rather than a grey-out -- so the icon is swapped for
834 * a differently tinted one, the same answer the "used by" icon got.
835 *
836 * A cell data func replaces the column's attribute mapping rather than adding to it, so this
837 * sets "visible" too instead of leaving it to gtk_tree_view_column_add_attribute(). */
838static void _add_cell_data_func(GtkTreeViewColumn *col __attribute__((unused)), GtkCellRenderer *renderer,
839 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
840{
841 const dt_shape_manager_t *lm = (const dt_shape_manager_t *)data;
842
843 gboolean on_row = FALSE;
844 gtk_tree_model_get(model, iter, TREE_IC_DELETE_VISIBLE, &on_row, -1);
845 g_object_set(renderer, "visible", on_row, NULL);
846 if(!on_row) return;
847
848 const gboolean available = _row_can_be_added(lm, model, iter);
849 gtk_cell_renderer_set_sensitive(renderer, available);
850
851 GdkPixbuf *icon = available ? lm->ic_add : lm->ic_add_off;
852 if(!IS_NULL_PTR(icon)) g_object_set(renderer, "pixbuf", icon, NULL);
853}
854
855/* The module list's selection decides every inventory row's "+", so a change there redraws the
856 * inventory and the data func above answers again. */
858{
860 if(IS_NULL_PTR(list->add_col) || IS_NULL_PTR(list->treeview)) return;
861 gtk_widget_queue_draw(list->treeview);
862}
863
864/* The panel never takes the keyboard focus on its own (see gui_init: its drawing tools act on
865 * the main window), so while a name is being edited every keystroke would still go to the main
866 * window, where dt_accels_dispatch() and the view read it as a shortcut -- Escape leaves the
867 * darkroom for the lighttable, a letter fires whatever it is bound to -- and the editor would get
868 * none of it. For the life of the editor the panel accepts the focus and asks for it: the entry
869 * then receives every key and consumes Escape itself (it cancels the edit and does nothing else),
870 * while both main-window handlers stand down, since they act only when that window is active. */
871static void _tree_name_editing_done(GtkCellEditable *editable __attribute__((unused)), dt_shape_manager_t *lm)
872{
873 if(IS_NULL_PTR(lm->popup_window)) return;
874
875 const gboolean had_focus = gtk_window_is_active(GTK_WINDOW(lm->popup_window));
876 gtk_window_set_accept_focus(GTK_WINDOW(lm->popup_window), FALSE);
877 // Ended by a key (Enter, Escape), the panel still holds the focus and hands it back. Ended by a
878 // click in another window, the focus is already where that click put it.
879 if(had_focus) dt_gui_refocus_parent(GTK_WINDOW(dt_gui_main_window()));
880}
881
882/* Places the caret after the text, once. The editor is a GtkEntry, and an entry selects all its
883 * text when it takes the focus -- which the tree makes it do right after "editing-started", so
884 * anything the started handler does to the selection is undone a moment later (measured
885 * offscreen: set_position and select_region alike come back as the full selection, while the same
886 * call after "grab-focus" holds). It disconnects itself, so a later focus grab within the same
887 * edit -- the user clicking back into the entry -- leaves their caret where they put it. */
888static void _tree_name_caret_to_end(GtkWidget *editable, gpointer user_data __attribute__((unused)))
889{
890 g_signal_handlers_disconnect_by_func(editable, G_CALLBACK(_tree_name_caret_to_end), NULL);
891 if(GTK_IS_EDITABLE(editable)) gtk_editable_set_position(GTK_EDITABLE(editable), -1);
892}
893
894static void _tree_name_editing_started(GtkCellRenderer *renderer __attribute__((unused)),
895 GtkCellEditable *editable, const gchar *path __attribute__((unused)),
897{
899 if(IS_NULL_PTR(lm->popup_window)) return;
900
902 g_signal_connect_after(editable, "grab-focus", G_CALLBACK(_tree_name_caret_to_end), NULL);
903
904 // "editing-done" is emitted however the edit ends -- Enter, Escape, or the entry losing the
905 // focus -- and the connection goes with the entry, which the tree destroys right after.
906 g_signal_connect(editable, "editing-done", G_CALLBACK(_tree_name_editing_done), lm);
907 gtk_window_set_accept_focus(GTK_WINDOW(lm->popup_window), TRUE);
908 gtk_window_present(GTK_WINDOW(lm->popup_window));
909}
910
911/* Opens a row's name for editing, when the row may be renamed at all (TREE_EDITABLE).
912 *
913 * The renderer's "editable" property is deliberately not bound to TREE_EDITABLE. Whenever a cell
914 * is editable, GtkTreeView opens the editor on a single click on a row that is already selected --
915 * and that same click is how a drag, a Ctrl+click or a right-click on a selected row starts, so
916 * each of them would open the editor by accident. The property therefore stays FALSE and is set
917 * TRUE only while this function opens the editor. Once open, the editor does not read it again:
918 * "edited" is still emitted when the name is validated. */
919static void _tree_start_name_editing(const dt_shape_manager_list_t *list, GtkTreeModel *model,
920 GtkTreeIter *iter, GtkTreePath *path, const gboolean cursor_at_end)
921{
923 return;
924
925 gboolean editable = FALSE;
926 gtk_tree_model_get(model, iter, TREE_EDITABLE, &editable, -1);
927 if(!editable) return;
928
930 lm->name_edit_cursor_at_end = cursor_at_end;
931
932 g_object_set(list->name_renderer, "editable", TRUE, NULL);
933 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(list->treeview), path, list->name_col,
934 list->name_renderer, TRUE);
935 g_object_set(list->name_renderer, "editable", FALSE, NULL);
936
938}
939
940static gboolean _tree_edit_group_name_idle(gpointer user_data)
941{
942 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
944
945 const int formid = lm->name_edit_formid;
946 const gboolean cursor_at_end = lm->name_edit_at_end;
947 const dt_shape_manager_list_t *list = &lm->lists[lm->name_edit_which];
948 lm->name_edit_formid = 0;
949
950 if(formid <= 0 || IS_NULL_PTR(list->treeview)) return G_SOURCE_REMOVE;
951
952 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
953 if(!GTK_IS_TREE_MODEL(model)) return G_SOURCE_REMOVE;
954
955 GtkTreeIter iter;
956 if(!gtk_tree_model_get_iter_first(model, &iter)) return G_SOURCE_REMOVE;
957
958 do
959 {
960 int fid = -1;
961 _shape_manager_get_values(model, &iter, NULL, NULL, &fid);
962 if(fid != formid) continue;
963
964 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
965 if(!IS_NULL_PTR(path))
966 {
967 _tree_start_name_editing(list, model, &iter, path, cursor_at_end);
968 gtk_tree_path_free(path);
969 }
970 return G_SOURCE_REMOVE;
971 } while(gtk_tree_model_iter_next(model, &iter));
972
973 return G_SOURCE_REMOVE;
974}
975
976/* Opens one list's top-level row for that group straight into name editing, one loop turn from
977 * now. Only a top-level row is editable (TREE_EDITABLE), which is what both lists hold a group as.
978 *
979 * The wait is not politeness. A rebuilt tree holds the row in its model immediately but has not
980 * been laid out yet, so the editor GtkTreeView opens on it is placed against an allocation that
981 * does not exist and is never mapped -- measured in the running application: the entry is alive
982 * and carries the right text, with mapped == 0, no focus and no grab held by anyone, which reads
983 * as "the editor did not open". G_PRIORITY_DEFAULT_IDLE runs below GTK's resize and redraw, so by
984 * then the row has its place. The row is found by id rather than by an iter kept across the wait,
985 * so a further rebuild in between costs the edit and nothing else. */
987 const int formid, const gboolean cursor_at_end)
988{
990 lm->name_edit_which = which;
991 lm->name_edit_formid = formid;
992 lm->name_edit_at_end = cursor_at_end;
993 g_idle_add(_tree_edit_group_name_idle, self);
994}
995
996/* ---------------------------------------------------------------------------------------------
997 * The module chooser: which modules should render a shape.
998 *
999 * Same set of modules as the "Pipeline" tab of the module groups panel, in the same order --
1000 * dev->iop walked backwards, so the list reads bottom-up the way the pipeline is applied --
1001 * narrowed to the ones a drawn mask means anything to. Disabled modules are in: attaching a mask
1002 * to a module that is off is legitimate, it applies the day the module is switched on.
1003 * ------------------------------------------------------------------------------------------- */
1004
1006{
1008 MODCHOOSER_WAS_CHECKED, // as the dialog opened, so validation can act on the difference
1009 MODCHOOSER_SENSITIVE, // FALSE for a row ticking could not act on -- the module's own mask
1011 MODCHOOSER_NOTE, // why an insensitive row is insensitive; empty otherwise
1015
1016static void _modchooser_toggled(GtkCellRendererToggle *cell __attribute__((unused)),
1017 const gchar *path_string, GtkListStore *store)
1018{
1019 GtkTreeIter iter;
1020 if(!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path_string)) return;
1021
1022 gboolean checked = FALSE;
1023 gboolean sensitive = TRUE;
1024 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, MODCHOOSER_CHECKED, &checked,
1025 MODCHOOSER_SENSITIVE, &sensitive, -1);
1026
1027 // A row ticking could not change stays where it is, whichever way it was reached.
1028 if(!sensitive) return;
1029
1030 gtk_list_store_set(store, &iter, MODCHOOSER_CHECKED, !checked, -1);
1031}
1032
1033/* A left click anywhere on the row toggles it, not just on the 12 pixels of the checkbox. */
1034static gboolean _modchooser_button_pressed(GtkWidget *treeview, const GdkEventButton *event,
1035 GtkListStore *store)
1036{
1037 if(event->type != GDK_BUTTON_PRESS || event->button != GDK_BUTTON_PRIMARY) return FALSE;
1038
1039 GtkTreePath *path = NULL;
1040 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint)event->x, (gint)event->y, &path,
1041 NULL, NULL, NULL))
1042 return FALSE;
1043
1044 gchar *path_string = gtk_tree_path_to_string(path);
1045 gtk_tree_path_free(path);
1046 _modchooser_toggled(NULL, path_string, store);
1047 dt_free(path_string);
1048
1049 return TRUE;
1050}
1051
1052/* Clicking past this window dismisses it, applying nothing -- the same as Cancel, which is what
1053 * dismissing a window by clicking past it means everywhere else.
1054 *
1055 * Tested against the press itself rather than against the keyboard focus. Focus is too coarse a
1056 * signal for this: it also moves when the window manager hands the pointer over a frame edge, so
1057 * a focus-out handler closed the dialog on a click on its OWN border.
1058 *
1059 * The window is modal, so GTK routes every button press in the application to it; a press landing
1060 * on one of its own GdkWindows is inside it and is left to the widget under the pointer. */
1061static gboolean _modchooser_button_press(GtkWidget *dialog, const GdkEventButton *event,
1062 gpointer user_data __attribute__((unused)))
1063{
1064 if(event->type != GDK_BUTTON_PRESS) return FALSE;
1065
1066 const GdkWindow *const toplevel = gtk_widget_get_window(dialog);
1067 for(GdkWindow *w = event->window; !IS_NULL_PTR(w); w = gdk_window_get_parent(w))
1068 if(w == toplevel) return FALSE;
1069
1070 gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
1071 return TRUE;
1072}
1073
1074/* Reads the validated dialog back: the rows whose box the user actually moved, in the order they
1075 * were listed. An untouched module goes in neither list -- ticking a box and unticking it again
1076 * is not a change, and reporting it as one would rewrite a mask the user left alone. */
1077static void _modchooser_collect(GtkListStore *store, GList **to_attach, GList **to_detach)
1078{
1079 GtkTreeIter iter;
1080 for(gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); valid;
1081 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter))
1082 {
1083 gboolean checked = FALSE;
1084 gboolean was_checked = FALSE;
1085 dt_iop_module_t *module = NULL;
1086 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, MODCHOOSER_CHECKED, &checked,
1087 MODCHOOSER_WAS_CHECKED, &was_checked, MODCHOOSER_MODULE, &module, -1);
1088
1089 if(IS_NULL_PTR(module) || checked == was_checked) continue;
1090
1091 GList **target = checked ? to_attach : to_detach;
1092 *target = g_list_prepend(*target, module);
1093 }
1094
1095 *to_attach = g_list_reverse(*to_attach);
1096 *to_detach = g_list_reverse(*to_detach);
1097}
1098
1099/* Runs the attachment manager modally and answers with what the user changed: the modules to
1100 * attach the shape to, and the ones to detach it from. Both lists are in the order the modules
1101 * were listed, borrow their modules, and are freed by the caller with g_list_free().
1102 *
1103 * A box is ticked when the shape is already a DIRECT member of that module's mask. Direct, not
1104 * at any depth: the box is the attachment this dialog manages, and unticking it has to be able
1105 * to undo exactly what ticking it did. A shape reaching a module through a nested group is that
1106 * group's business, not this dialog's.
1107 *
1108 * Whether an unticked box may be ticked is _form_can_join_group(), the one rule a row's "+" and
1109 * the "Attach to the group" menu already ask -- so the dialog cannot offer an attachment the rest
1110 * of the window refuses. A module reaching the shape only through a nested group therefore comes
1111 * up unticked AND insensitive, carrying the group's name in MODCHOOSER_NOTE, rather than unticked
1112 * and free: ticking it added the shape a second time to a mask that already applied it, which is
1113 * a duplicate no other path in this window can create. Ticking it is not the answer either --
1114 * unticking would then have to reach into a nested group this module does not own and may share,
1115 * and no untick may change a group the user has not opened. The one case ticked without asking is
1116 * a module with no mask yet: there is no group to ask about, and ticking is what creates it.
1117 *
1118 * @return whether the user validated; both lists are empty when nothing changed. */
1119static gboolean _modchooser_run(const dt_masks_form_t *form, GList **to_attach, GList **to_detach)
1120{
1121 *to_attach = NULL;
1122 *to_detach = NULL;
1123
1124 // One by-value description of the form: every field below is taken from it, not from a raw
1125 // reach into the struct.
1126 dt_masks_form_info_t info = { 0 };
1127 if(!dt_masks_form_get_info(form, &info)) return FALSE;
1128
1129 dt_develop_t *const dev = dt_dev_get_global();
1130 GtkListStore *store = gtk_list_store_new(MODCHOOSER_COUNT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
1131 G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
1132
1133 // What the dialog's title, intro and tooltip call the thing being managed.
1134 const char *noun = info.is_group ? _("group") : _("shape");
1135
1136 gboolean any = FALSE;
1137 for(const GList *iops = g_list_last(dt_dev_get_global()->iop); iops; iops = g_list_previous(iops))
1138 {
1139 dt_iop_module_t *module = (dt_iop_module_t *)iops->data;
1140 if(!dt_iop_module_is_in_pipeline(module) || !dt_iop_module_supports_drawn_mask(module)) continue;
1141
1142 const int own_id = module->blend_params->mask_id;
1143
1144 /* A module cannot render its own mask as a member of itself -- ticking this would ask
1145 * dt_masks_group_add_form() to nest a group inside itself, which dt_masks_group_contains()
1146 * already refuses trivially at depth 0, so _form_can_join_group() below would answer the same.
1147 * Kept as its own case for the note it can then give, which says more than the general one. */
1148 const gboolean is_self_mask = (own_id == info.formid);
1149
1150 // What ticking manages, and the only state unticking can undo.
1151 const gboolean attached
1152 = !is_self_mask && (dt_masks_group_get_member(dev, own_id, info.formid, NULL) == DT_MASKS_OK);
1153
1154 /* No mask yet is the case ticking CREATES, and there is no group to ask about: the same test
1155 * validation makes before calling _module_create_own_mask(). _form_can_join_group() answers
1156 * FALSE for such a module, so it has to be settled before the call rather than by it. */
1157 const dt_masks_form_t *const own = dt_masks_get_from_id(dev, own_id);
1158 const gboolean has_mask = !IS_NULL_PTR(own) && (own->type & DT_MASKS_GROUP);
1159
1160 gchar *note = NULL;
1161 gboolean sensitive = TRUE;
1162
1163 if(is_self_mask)
1164 {
1165 sensitive = FALSE;
1166 note = g_strdup(_("this is the module's own mask"));
1167 }
1168 else if(!attached && has_mask && !_form_can_join_group(dev, own_id, info.formid))
1169 {
1170 sensitive = FALSE;
1171
1172 /* Which of the rule's three refusals this is, in the words the tree already uses for the
1173 * first of them. The mask reaching the form through a nested group is the common one and
1174 * the only one that used to slip through as a tickable box. */
1175 char holder_name[DT_MASKS_FORM_NAME_LEN] = "";
1176 if(dt_masks_group_first_use(dev, own_id, info.formid, NULL, NULL, holder_name,
1177 sizeof(holder_name)) == DT_MASKS_OK)
1178 note = g_strdup_printf(_("Already in '%s'"), holder_name);
1179 else if(dt_masks_group_contains(dev, info.formid, own_id) == DT_MASKS_OK)
1180 note = g_strdup(_("this group holds the module's own mask"));
1181 else
1182 note = g_strdup(_("the module already applies every shape of this group"));
1183 }
1184
1185 gchar *label = dt_history_item_get_name(module);
1186 GtkTreeIter iter;
1187 gtk_list_store_append(store, &iter);
1188 gtk_list_store_set(store, &iter, MODCHOOSER_CHECKED, attached, MODCHOOSER_WAS_CHECKED, attached,
1189 MODCHOOSER_SENSITIVE, sensitive, MODCHOOSER_NAME, label,
1190 MODCHOOSER_NOTE, IS_NULL_PTR(note) ? "" : note,
1191 MODCHOOSER_MODULE, module, -1);
1192 dt_free(label);
1193 dt_free(note);
1194 any = TRUE;
1195 }
1196
1197 if(!any)
1198 {
1199 g_object_unref(store);
1200 dt_control_log(_("No module in the pipeline can carry a drawn mask."));
1201 return FALSE;
1202 }
1203
1204 /* Parented to the main window rather than to the shape manager's own panel: that panel is a
1205 * UTILITY window that declines focus, which is not something to hang a modal dialog off. */
1206 GtkWindow *parent = GTK_WINDOW(dt_gui_main_window());
1207 gchar *title = g_strdup_printf(_("Modules using this %s"), noun);
1208 GtkWidget *dialog = gtk_dialog_new_with_buttons(title, parent,
1209 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1210 _("Cancel"), GTK_RESPONSE_CANCEL,
1211 _("Apply"), GTK_RESPONSE_ACCEPT, NULL);
1212 dt_free(title); // gtk_window_set_title() (called internally) copies it
1213 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1214
1215 /* Above everything, because the window it is about -- the shape manager's own panel -- is a
1216 * toplevel of its own and would otherwise be free to cover it. */
1217 gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
1218
1219 gtk_widget_add_events(dialog, GDK_BUTTON_PRESS_MASK);
1220 g_signal_connect(dialog, "button-press-event", G_CALLBACK(_modchooser_button_press), NULL);
1221
1222 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1223 gtk_container_set_border_width(GTK_CONTAINER(content), DT_GUI_BOX_SPACING);
1224 gtk_box_set_spacing(GTK_BOX(content), DT_GUI_BOX_SPACING);
1225
1226 gchar *intro = g_strdup_printf(_("Tick the modules that should use the %s '%s', untick the ones "
1227 "that should stop."), noun, info.name);
1228 GtkWidget *label = dt_ui_label_new(intro);
1229 dt_free(intro);
1230 gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0);
1231
1232 GtkWidget *treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1233 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
1234 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)), GTK_SELECTION_NONE);
1235
1236 GtkTreeViewColumn *col = gtk_tree_view_column_new();
1237 gtk_tree_view_column_set_expand(col, TRUE);
1238
1239 GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
1240 g_object_set(renderer, "activatable", TRUE, NULL);
1241 g_signal_connect(renderer, "toggled", G_CALLBACK(_modchooser_toggled), store);
1242 gtk_tree_view_column_pack_start(col, renderer, FALSE);
1243 gtk_tree_view_column_add_attribute(col, renderer, "active", MODCHOOSER_CHECKED);
1244 gtk_tree_view_column_add_attribute(col, renderer, "sensitive", MODCHOOSER_SENSITIVE);
1245
1246 renderer = gtk_cell_renderer_text_new();
1247 gtk_tree_view_column_pack_start(col, renderer, TRUE);
1248 gtk_tree_view_column_add_attribute(col, renderer, "text", MODCHOOSER_NAME);
1249 gtk_tree_view_column_add_attribute(col, renderer, "sensitive", MODCHOOSER_SENSITIVE);
1250
1251 /* The reason a row is dead, said on the row itself: italic and against the right edge, so it
1252 * reads as an annotation rather than part of the module's name. Empty on every live row, which
1253 * costs it no space. */
1254 renderer = gtk_cell_renderer_text_new();
1255 g_object_set(renderer, "style", PANGO_STYLE_ITALIC, "xalign", 1.0f, NULL);
1256 gtk_cell_renderer_set_sensitive(renderer, FALSE);
1257 gtk_tree_view_column_pack_end(col, renderer, FALSE);
1258 gtk_tree_view_column_add_attribute(col, renderer, "text", MODCHOOSER_NOTE);
1259
1260 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
1261
1262 g_signal_connect(treeview, "button-press-event", G_CALLBACK(_modchooser_button_pressed), store);
1263
1264 gtk_box_pack_start(GTK_BOX(content),
1265 dt_ui_scroll_wrap(treeview, 200, "plugins/darkroom/masks/modulechooserheight",
1267 TRUE, TRUE, 0);
1268
1269 gtk_widget_show_all(dialog);
1270 const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
1271
1272 if(response == GTK_RESPONSE_ACCEPT) _modchooser_collect(store, to_attach, to_detach);
1273
1274 gtk_widget_destroy(dialog);
1275 g_object_unref(store);
1276 dt_gui_refocus_parent(parent);
1277
1278 return response == GTK_RESPONSE_ACCEPT;
1279}
1280
1281/* Gives a module a drawn mask of its own -- an empty group named after it, with drawn blending
1282 * switched on -- and answers it, writing its id to own_id. Answers NULL if the form could not be
1283 * made; the abandoned one is registered in dev->allforms and released with the image. */
1285{
1287 if(IS_NULL_PTR(own)) return NULL;
1288
1289 dt_masks_form_info_t own_info = { 0 };
1290 if(!dt_masks_form_get_info(own, &own_info)) return NULL;
1291 *own_id = own_info.formid;
1292
1293 dt_masks_append_form(dev, own);
1294
1295 // Named by id, so the naming convention and the copy-on-write both stay inside the masks
1296 // module. The touch it takes may replace the group with a clone, which is why the live one is
1297 // resolved again afterwards rather than the pointer created above being returned.
1298 dt_masks_group_set_name_from_module(dev, *own_id, module);
1299 own = dt_masks_get_from_id(dev, *own_id);
1300 if(IS_NULL_PTR(own)) return NULL;
1301
1302 // A module's blend_params are its own history entry; the forms get one of their own later.
1303 if(dt_iop_gui_blend_set_drawn_mask_group(module, *own_id))
1304 dt_dev_add_history_item(dev, module, TRUE, TRUE);
1305
1306 return own;
1307}
1308
1309/* Puts the row's form to work in the modules the user picks.
1310 *
1311 * Every module renders its OWN mask group -- created here, named "Group <module>", if it has none
1312 * yet -- and the row's form is nested as a member of each. What is shared between the modules is
1313 * that form, not the mask holding it: one shape or shape group, referenced by as many module
1314 * masks as tick it.
1315 *
1316 * That is what keeps each module independent. A module's own mask carries its own combine
1317 * operators, opacities and order, so attaching the same shape group to a second module cannot
1318 * disturb the first -- and unticking one removes the form from THAT module's mask alone. The
1319 * alternative, pointing several modules at one mask group, would make every one of those
1320 * settings, and every detach, common to all of them.
1321 */
1323 GtkTreeIter *iter)
1324{
1325 dt_lib_module_t *self = list->self;
1326 dt_develop_t *const dev = dt_dev_get_global();
1327
1328 int fid = -1;
1329 _shape_manager_get_values(model, iter, NULL, NULL, &fid);
1330 dt_masks_form_t *form = dt_masks_get_from_id(dev, fid);
1331 if(IS_NULL_PTR(form)) return;
1332
1333 GList *to_attach = NULL;
1334 GList *to_detach = NULL;
1335 if(!_modchooser_run(form, &to_attach, &to_detach)) return;
1336
1337 gboolean changed = FALSE;
1338
1339 /* Detaching first, so a module the user unticked and one they ticked cannot fight over the
1340 * same mask within one validation. */
1341 for(const GList *m = to_detach; m; m = g_list_next(m))
1342 {
1343 dt_iop_module_t *module = (dt_iop_module_t *)m->data;
1344 dt_masks_form_t *own = dt_masks_get_from_id(dev, module->blend_params->mask_id);
1345 if(IS_NULL_PTR(own) || !(own->type & DT_MASKS_GROUP)) continue;
1346
1347 // Empties the mask and deletes it if nothing is left, which dt_masks_form_delete() handles.
1348 dt_masks_form_delete(dev, module, own, form);
1350 changed = TRUE;
1351 }
1352
1353 for(const GList *m = to_attach; m; m = g_list_next(m))
1354 {
1355 dt_iop_module_t *module = (dt_iop_module_t *)m->data;
1356 int own_id = module->blend_params->mask_id;
1357 dt_masks_form_t *own = dt_masks_get_from_id(dev, own_id);
1358
1359 if(IS_NULL_PTR(own) || !(own->type & DT_MASKS_GROUP))
1360 {
1361 own = _module_create_own_mask(dev, module, &own_id);
1362 if(IS_NULL_PTR(own)) break;
1363
1364 changed = TRUE;
1365 }
1366
1367 /* The same rule the box was offered under, asked again against the state as it is NOW: a
1368 * dialog left open while the window is used elsewhere must not carry a stale answer past it. */
1369 if(!_form_can_join_group(dev, own_id, fid)) continue;
1370
1371 own = dt_masks_cow_touch(dev, own);
1372 if(IS_NULL_PTR(dt_masks_group_add_form(dev, own, form))) continue;
1373
1375 changed = TRUE;
1376 }
1377
1378 g_list_free(to_attach);
1379 g_list_free(to_detach);
1380 if(!changed) return;
1381
1382 dt_dev_add_history_item(dev, NULL, FALSE, TRUE);
1385}
1386
1387/* The inventory's "+": add this row's form to the group the module list points at. */
1388static void _tree_row_add_to_group(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter)
1389{
1390 dt_lib_module_t *self = list->self;
1391 const dt_shape_manager_t *const lm = (const dt_shape_manager_t *)self->data;
1392 dt_develop_t *const dev = dt_dev_get_global();
1393
1394 // The same question the icon was drawn from, so a greyed "+" cannot act.
1395 if(!_row_can_be_added(lm, model, iter)) return;
1396
1397 int fid = -1;
1398 _shape_manager_get_values(model, iter, NULL, NULL, &fid);
1399
1400 dt_masks_form_t *form = dt_masks_get_from_id(dev, fid);
1402 if(IS_NULL_PTR(form) || IS_NULL_PTR(grp)) return;
1403
1404 grp = dt_masks_cow_touch(dev, grp);
1405 if(IS_NULL_PTR(dt_masks_group_add_form(dev, grp, form))) return;
1406
1407 dt_dev_add_history_item(dev, NULL, FALSE, TRUE);
1410}
1411
1412/* The per-row action icon at the right end of every form row, the same two the shape lists of
1413 * the Drawn tab offer (develop/blend_gui.c): a top-level row carries a trash and is deleted from
1414 * every mask and from the list of shapes, a row under a group carries a minus and is only
1415 * detached from that group, staying available for reuse. Which one a row shows is the model's
1416 * business (TREE_IC_DELETE_VISIBLE / TREE_IC_UNLINK_VISIBLE); which one a click means is decided
1417 * here, from the same group id, so the icon and the action cannot disagree.
1418 *
1419 * dt_masks_form_delete() reads that distinction off its group argument: a group to detach from,
1420 * or NULL to destroy. A top-level row has group id 0, which no form answers to. */
1421static void _tree_row_action(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter)
1422{
1423 dt_lib_module_t *self = list->self;
1425 dt_develop_t *const dev = dt_dev_get_global();
1426
1427 dt_iop_module_t *module = NULL;
1428 int grid = -1;
1429 int id = -1;
1430 _shape_manager_get_values(model, iter, &module, &grid, &id);
1431
1432 dt_masks_form_t *form = dt_masks_get_from_id(dev, id);
1433 if(IS_NULL_PTR(form)) return;
1434
1435 // Only the permanent delete destroys anything, so only it asks.
1436 if(grid == 0 && !dt_masks_gui_confirm_permanent_delete(form->name)) return;
1437
1438 // we first discard all visible shapes
1439 dt_masks_change_form_gui(dev, NULL);
1440
1441 lm->gui_reset = 1;
1442 dt_masks_form_delete(dev, module, dt_masks_get_from_id(dev, grid), form);
1443 lm->gui_reset = 0;
1444
1447
1448 // Without this, the change only mutates the live dev->forms: it's never recorded as its own
1449 // history step, so the next history navigation (undo/redo) silently discards it and reverts
1450 // to whatever forms snapshot was last actually committed.
1451 dt_dev_add_history_item(dev, NULL, FALSE, TRUE);
1452}
1453
1454static void _tree_duplicate_shape(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
1455{
1456 dt_lib_module_t *self = list->self;
1458
1459 // we get the selected node
1460 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
1461 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
1462 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
1463 if(IS_NULL_PTR(items)) return;
1464 GtkTreePath *item = (GtkTreePath *)items->data;
1465 GtkTreeIter iter;
1466 if(gtk_tree_model_get_iter(model, &iter, item))
1467 {
1468 dt_iop_module_t *module = NULL;
1469 int grid = -1;
1470 int id = -1;
1471 _shape_manager_get_values(model, &iter, &module, &grid, &id);
1472
1473 // dt_masks_form_duplicate_in_group also attaches the duplicate to the source shape's
1474 // group (grid), inheriting its state/opacity -- without that, the duplicate would be an
1475 // orphan: invisible on canvas and useless to the module, since nothing outside a group
1476 // ever gets rendered.
1477 const int nid = dt_masks_form_duplicate_in_group(dt_dev_get_global(), grid, id);
1478 if(nid > 0)
1479 {
1480 if(module) dt_iop_gui_blend_masks_update(module);
1481
1483
1484 // Without this, the new form only exists in the live dev->forms: it's never recorded as
1485 // its own history step, so it silently disappears on the next undo/redo.
1487
1488 // _add_masks_history_item briefly sets lm->gui_reset while committing, and
1489 // dt_dev_add_history_item's own list-change signal fires synchronously inside that
1490 // window -- _shape_manager_recreate_list's gui_reset guard swallows it. Refresh explicitly,
1491 // now that gui_reset is back to its prior value, so the new row actually appears.
1494 }
1495 }
1496 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1497 items = NULL;
1498}
1499
1500/* The "edited" signal hands both strings as gchar *, but this only reads them -- and the
1501 * connection goes through a GCallback cast, so nothing checks the signature against GTK's. */
1502static void _tree_cell_edited(GtkCellRendererText *cell __attribute__((unused)), const gchar *path_string,
1503 const gchar *new_text, dt_shape_manager_list_t *list)
1504{
1505 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
1506 GtkTreeIter iter;
1507 if(!gtk_tree_model_get_iter_from_string(model, &iter, path_string)) return;
1508
1509 int id = -1;
1510 _shape_manager_get_values(model, &iter, NULL, NULL, &id);
1512 if(IS_NULL_PTR(form)) return;
1513
1514 // we want to make sure that the new name is not an empty string. else this would convert
1515 // in the xmp file into "<rdf:li/>" which produces problems. we use a single whitespace
1516 // as the pure minimum text.
1517 const gchar *text = strlen(new_text) == 0 ? " " : new_text;
1518
1519 // first, we need to update the mask name
1520
1521 g_strlcpy(form->name, text, sizeof(form->name));
1523
1524 /* Raised rather than broadcast: unlike the handlers above, this one does not rebuild the tree
1525 * itself, so our own handler is left to do it as well as blend_gui's. */
1528}
1529
1530/* A group's own module, when the tree row names one that can show masks. Presses its "show and
1531 * edit" toggle, so selecting a module's mask group in the manager lights the module's own
1532 * button too. */
1533static void _show_masks_on_owning_module(GtkTreeModel *model, GtkTreeIter *iter)
1534{
1535 dt_iop_module_t *module = NULL;
1536 _shape_manager_get_values(model, iter, &module, NULL, NULL);
1537
1538 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)
1540 return;
1541
1542 /* Blend data exists as soon as the module has a GUI, but its masks widgets only once the
1543 * masks body has been built, which masks_inited says -- dt_iop_gui_blend_masks_update() asks
1544 * the same question. A module whose mask the manager lists need never have been expanded. */
1545 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->gui->blend_data;
1546 if(!bd->masks_inited) return;
1547
1548 bd->masks_shown = 1;
1549 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), TRUE);
1550 gtk_widget_queue_draw(bd->masks_edit);
1551}
1552
1553static void _tree_selection_change(GtkTreeSelection *selection, dt_shape_manager_list_t *list)
1554{
1555 const dt_shape_manager_t *lm = (const dt_shape_manager_t *)list->self->data;
1556 dt_develop_t *const dev = dt_dev_get_global();
1557
1558 /* Ahead of the gui_reset gate: what the module list points at decides whether the inventory's
1559 * "+" is available, and that stays true while the panel is driving itself -- a rebuild
1560 * reselects rows with gui_reset raised, and the button must follow. */
1562
1563 if(lm->gui_reset) return;
1564 dt_masks_form_gui_t *creation_gui = dev->form_gui;
1565 if(!IS_NULL_PTR(creation_gui) && creation_gui->creation) return;
1566
1567 // we reset all "show mask" icon of iops
1569
1570 // if selection empty, we hide all
1571 const int nb = gtk_tree_selection_count_selected_rows(selection);
1572 if(nb == 0)
1573 {
1574 dt_masks_change_form_gui(dev, NULL);
1576 return;
1577 }
1578
1579 // else, we create a new form group with the selection and display it
1580 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
1582 dt_masks_form_t *selected_form = NULL;
1583 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
1584 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
1585 {
1586 GtkTreePath *item = (GtkTreePath *)items_iter->data;
1587 GtkTreeIter iter;
1588 if(!gtk_tree_model_get_iter(model, &iter, item)) continue;
1589
1590 int grid = -1;
1591 int id = -1;
1592 _shape_manager_get_values(model, &iter, NULL, &grid, &id);
1593
1594 dt_masks_form_t *form = dt_masks_get_from_id(dev, id);
1595 if(IS_NULL_PTR(form)) continue;
1596
1597 if(nb == 1) selected_form = form;
1598 dt_masks_group_add_form_with_state(dev, grp, form, grid, DT_MASKS_STATE_USE, 1.0f);
1599
1600 // we eventually set the "show masks" icon of iops
1601 if(nb == 1 && (form->type & DT_MASKS_GROUP)) _show_masks_on_owning_module(model, &iter);
1602 }
1603 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
1604 items = NULL;
1605
1607 grp_dest->formid = 0;
1608 dt_masks_group_ungroup(dev, grp_dest, grp);
1609 // grp was a scratch group built to flatten the selection into grp_dest, which only reads it.
1610 // It never joined dev->forms or dev->allforms, so this is the only reference there is --
1611 // unlike grp_dest, whose reference passes to form_visible below.
1613 dt_masks_change_form_gui(dev, grp_dest);
1614 /* dt_masks_change_form_gui() is NULL-safe on dev->form_gui throughout -- it is only ever
1615 * allocated by dt_masks_gui_init(), on entering darkroom, and freed back to NULL on leaving it
1616 * (views/darkroom.c, views/studio_capture.c's own dev teardown). This panel's window is a
1617 * standalone toplevel that can outlive that: switching away from darkroom while it stays open,
1618 * then selecting a row here, reached this point with dev->form_gui NULL and no guard (SIGSEGV,
1619 * observed live). edit_mode has nothing to record it into then, and there is no "current form"
1620 * for the pipeline to preview either -- the whole point of a view with no darkroom -- so this
1621 * simply has nothing to do. */
1622 if(IS_NULL_PTR(dev->form_gui)) return;
1624 if(nb == 1 && !IS_NULL_PTR(selected_form))
1625 dt_masks_center_view_on_form(dev, selected_form);
1626 else
1628}
1629
1630/* A separator, unless the menu is empty or already ends with one. Every section of the context
1631 * menu is optional and opens with a separator, so two sections with nothing between them would
1632 * otherwise draw two in a row. */
1633static void _menu_append_separator(GtkMenuShell *menu)
1634{
1635 GList *children = gtk_container_get_children(GTK_CONTAINER(menu));
1636 const GList *last = g_list_last(children);
1637 const gboolean needed = !IS_NULL_PTR(last) && !GTK_IS_SEPARATOR_MENU_ITEM(last->data);
1638 g_list_free(children);
1639
1640 if(needed) gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1641}
1642
1643/* The five shapes a group can gain, as their own submenu. Offered on an empty selection and on a
1644 * selected group alike, which is why it is not written out twice. */
1645static void _menu_append_new_shape_submenu(GtkMenuShell *menu, dt_iop_module_t *module)
1646{
1647 GtkWidget *add_menu = gtk_menu_new();
1648 GtkWidget *add_item = gtk_menu_item_new_with_label(_("Add new shape ..."));
1649 gtk_menu_item_set_submenu(GTK_MENU_ITEM(add_item), add_menu);
1650 gtk_menu_shell_append(menu, add_item);
1651
1652 _tree_add_shape_menu_item(add_menu, DT_MASKS_BRUSH, module);
1653 _tree_add_shape_menu_item(add_menu, DT_MASKS_CIRCLE, module);
1657}
1658
1659/* The shapes already drawn on this image, each labelled with the other modules already using it.
1660 * One grp cannot take -- it already holds it, directly or through a sub-group, or it is a group
1661 * that would close a cycle -- is listed greyed rather than left out, so the user sees it is there;
1662 * the same rule, _form_can_join_group(), as "Attach to the group". */
1663static void _menu_append_existing_shapes(GtkMenuShell *menu, dt_masks_form_t *grp, const int grpid,
1664 dt_iop_module_t *module)
1665{
1666 gboolean any = FALSE;
1667 GtkWidget *shapes_menu = gtk_menu_new();
1668
1669 for(const GList *forms = dt_dev_get_global()->forms; forms; forms = g_list_next(forms))
1670 {
1671 const dt_masks_form_t *form = (const dt_masks_form_t *)forms->data;
1673 if(!dt_masks_form_get_info(form, &info) || (info.type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
1674 || info.formid == grpid)
1675 continue;
1676
1677 char str[10000] = "";
1678 if(_tree_format_form_usage_label(str, sizeof(str), form, module) < 0) continue;
1679
1680 GtkWidget *item = gtk_menu_item_new_with_label(str);
1681 g_object_set_data(G_OBJECT(item), "formid", GUINT_TO_POINTER(info.formid));
1682 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_tree_add_exist), grp);
1683 gtk_widget_set_sensitive(item, _form_can_join_group(dt_dev_get_global(), grpid, info.formid));
1684 gtk_menu_shell_append(GTK_MENU_SHELL(shapes_menu), item);
1685 any = TRUE;
1686 }
1687
1688 if(!any)
1689 {
1690 gtk_widget_destroy(shapes_menu);
1691 return;
1692 }
1693
1694 GtkWidget *item = gtk_menu_item_new_with_label(_("Attach shape ..."));
1695 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), shapes_menu);
1696 gtk_menu_shell_append(menu, item);
1697}
1698
1699/* "Attach to the group": every group of the image, as its own submenu. The whole selection joins the
1700 * group picked, less whatever cannot -- _tree_add_to_group() asks _form_can_join_group() again,
1701 * form by form. A group none of the selection can join (it already holds it, or taking it would
1702 * close a cycle) is listed greyed rather than left out, so the user sees where the shapes already
1703 * are instead of wondering where a group went. Left out are the selected groups themselves -- a
1704 * group joining itself is not a choice at all -- and the retouch and spot groups, which belong to
1705 * their module, as in "Attach shape ...". The entry itself is greyed only when there is no group to
1706 * list. */
1707static void _menu_append_join_group(GtkMenuShell *menu, dt_shape_manager_list_t *list)
1708{
1709 dt_develop_t *const dev = dt_dev_get_global();
1710
1711 // Counted, then copied: a list that grew in between is only cut short, never overrun.
1712 const guint capacity = dt_masks_group_list(dev, NULL, 0);
1713 dt_masks_form_info_t *groups = g_new0(dt_masks_form_info_t, MAX(capacity, 1));
1714 const guint count = MIN(capacity, dt_masks_group_list(dev, groups, capacity));
1715
1716 GList *ids = _selected_form_ids(list);
1717 GtkWidget *groups_menu = gtk_menu_new();
1718 gboolean any = FALSE;
1719 for(guint g = 0; g < count; g++)
1720 {
1721 if(groups[g].is_retouch || !IS_NULL_PTR(g_list_find(ids, GINT_TO_POINTER(groups[g].formid)))) continue;
1722
1723 gboolean joinable = FALSE;
1724 for(const GList *id = ids; id && !joinable; id = g_list_next(id))
1725 joinable = _form_can_join_group(dev, groups[g].formid, GPOINTER_TO_INT(id->data));
1726
1727 GtkWidget *item = gtk_menu_item_new_with_label(groups[g].name);
1728 g_object_set_data(G_OBJECT(item), "group-formid", GINT_TO_POINTER(groups[g].formid));
1729 g_signal_connect(item, "activate", G_CALLBACK(_tree_add_to_group), list);
1730 gtk_widget_set_sensitive(item, joinable);
1731 gtk_menu_shell_append(GTK_MENU_SHELL(groups_menu), item);
1732 any = TRUE;
1733 }
1734 g_list_free(ids);
1735 dt_free(groups);
1736
1737 GtkWidget *item = gtk_menu_item_new_with_label(_("Attach to the group"));
1738 gtk_menu_shell_append(menu, item);
1739 if(any)
1740 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), groups_menu);
1741 else
1742 {
1743 gtk_widget_destroy(groups_menu);
1744 gtk_widget_set_sensitive(item, FALSE);
1745 }
1746}
1747
1748/* One entry per combine mode, plus the reordering pair. The same Invert/Union/Intersection/
1749 * Difference/Exclusion grouping the darkroom canvas and the blend module offer; all five differ
1750 * by a constant, so they ride on the menu item under "masks-operation". */
1751static void _menu_append_operations(GtkMenuShell *menu, dt_shape_manager_list_t *list, const int nb)
1752{
1753 static const struct
1754 {
1755 const char *label;
1757 } combine[] = {
1758 { N_("Union"), DT_MASKS_STATE_UNION },
1759 { N_("Intersection"), DT_MASKS_STATE_INTERSECTION },
1760 { N_("Difference"), DT_MASKS_STATE_DIFFERENCE },
1761 { N_("Exclusion"), DT_MASKS_STATE_EXCLUSION },
1762 };
1763
1765
1766 GtkWidget *item = gtk_menu_item_new_with_label(_("Operation"));
1767 GtkWidget *op_submenu = gtk_menu_new();
1768 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), op_submenu);
1769 gtk_menu_shell_append(menu, item);
1770
1771 item = gtk_menu_item_new_with_label(_("Invert shape"));
1772 g_object_set_data(G_OBJECT(item), "masks-operation", GINT_TO_POINTER(DT_MASKS_STATE_INVERSE));
1773 g_signal_connect(item, "activate", (GCallback)_tree_apply_operation, list);
1774 gtk_menu_shell_append(GTK_MENU_SHELL(op_submenu), item);
1775
1776 // Combining is a question about one shape against its group; several at once has no answer.
1777 if(nb == 1)
1778 {
1779 gtk_menu_shell_append(GTK_MENU_SHELL(op_submenu), gtk_separator_menu_item_new());
1780 for(size_t i = 0; i < sizeof(combine) / sizeof(combine[0]); i++)
1781 {
1782 item = gtk_menu_item_new_with_label(_(combine[i].label));
1783 g_object_set_data(G_OBJECT(item), "masks-operation", GINT_TO_POINTER(combine[i].state));
1784 g_signal_connect(item, "activate", (GCallback)_tree_apply_operation, list);
1785 gtk_menu_shell_append(GTK_MENU_SHELL(op_submenu), item);
1786 }
1787 }
1788
1789 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1790 item = gtk_menu_item_new_with_label(_("Move up"));
1791 g_signal_connect(item, "activate", (GCallback)_tree_moveup, list);
1792 gtk_menu_shell_append(menu, item);
1793 item = gtk_menu_item_new_with_label(_("Move down"));
1794 g_signal_connect(item, "activate", (GCallback)_tree_movedown, list);
1795 gtk_menu_shell_append(menu, item);
1796 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1797}
1798
1799static GtkWidget *_tree_context_menu(GtkTreeSelection *selection, GtkTreeModel *model,
1801{
1802 GtkTreeIter iter;
1803 GtkMenuShell *menu = GTK_MENU_SHELL(gtk_menu_new());
1804 GtkWidget *item;
1805
1806 // we get all infos from selection
1807 const int nb = gtk_tree_selection_count_selected_rows(selection);
1808 int from_group = 0;
1809
1810 int grpid = 0;
1811 int parentid = 0;
1812 int depth = 0;
1813
1814 if(nb > 0)
1815 {
1816 GList *selected = gtk_tree_selection_get_selected_rows(selection, NULL);
1817 GtkTreePath *it0 = (GtkTreePath *)selected->data;
1818 depth = gtk_tree_path_get_depth(it0);
1819 // A single selected row is the only case whose group the menu below needs to know about;
1820 // read it before the list of paths is freed.
1821 if(nb == 1 && gtk_tree_model_get_iter(model, &iter, it0))
1822 _shape_manager_get_values(model, &iter, NULL, &parentid, &grpid);
1823 g_list_free_full(selected, (GDestroyNotify)gtk_tree_path_free);
1824 selected = NULL;
1825 }
1826 if(depth > 1) from_group = 1;
1827
1828 // The form the single selected row names, when there is one: several sections below ask
1829 // whether it is a group, and one lookup answers them all.
1831 const gboolean grp_is_group = !IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP);
1832
1833 if(nb == 0)
1834 {
1835 _menu_append_new_shape_submenu(menu, module);
1836 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1837 }
1838
1839 if(nb == 1 && grp_is_group)
1840 {
1841 _menu_append_new_shape_submenu(menu, module);
1842 _menu_append_existing_shapes(menu, grp, grpid, module);
1843 }
1844
1845 // Same shape-parameter sliders (size/fading/rotation/opacity) as the darkroom canvas's and
1846 // the blend module's own shape context menus. Available for any single selected shape, not
1847 // just one nested under a group in the tree: _shape_manager_list_recurs also lists every shape
1848 // at top level regardless of group membership (TREE_GROUPID == 0 there), so when the tree
1849 // doesn't hand us the parent directly, look up whichever group actually references it.
1850 if(nb == 1 && !IS_NULL_PTR(grp) && !grp_is_group)
1851 {
1852 const int holding_group = from_group ? parentid
1854
1855 if(holding_group != 0)
1856 {
1857 dt_masks_gui_populate_interaction_sliders(GTK_WIDGET(menu), dt_dev_get_global(), grp, holding_group,
1858 dt_dev_get_global()->form_gui, module);
1859 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1860 }
1861 }
1862
1863 if(from_group && depth < 3) _menu_append_operations(menu, list, nb);
1864
1865 // Below the shape's own parameters and its combine operations: both act on the whole
1866 // selection, one row or several, wherever the rows sit.
1867 if(nb > 0)
1868 {
1870 item = gtk_menu_item_new_with_label(_("New group from selection"));
1871 g_signal_connect(item, "activate", (GCallback)_tree_group, list);
1872 gtk_menu_shell_append(menu, item);
1873 _menu_append_join_group(menu, list);
1875 }
1876
1877 if(!from_group && !grp_is_group && nb == 1)
1878 {
1879 item = gtk_menu_item_new_with_label(_("Duplicate shape"));
1880 g_signal_connect(item, "activate", (GCallback)_tree_duplicate_shape, list);
1881 gtk_menu_shell_append(menu, item);
1882 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
1883 }
1884
1885 if(!from_group && nb > 0)
1886 {
1887 // One entry, named for what the row holds -- the whole group when it is one.
1888 item = gtk_menu_item_new_with_label(grp_is_group ? _("Delete group") : _("Delete shape"));
1889 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, list);
1890 gtk_menu_shell_append(menu, item);
1891 }
1892 else if(nb > 0 && depth < 3)
1893 {
1894 item = gtk_menu_item_new_with_label(_("Remove shape from mask"));
1895 g_signal_connect(item, "activate", (GCallback)_tree_delete_shape, list);
1896 gtk_menu_shell_append(menu, item);
1897 }
1898
1899 item = gtk_menu_item_new_with_label(_("Delete unused shapes"));
1900 g_signal_connect(item, "activate", (GCallback)_tree_delete_unused, list);
1901 gtk_menu_shell_append(menu, item);
1902
1903 return GTK_WIDGET(menu);
1904}
1905
1906/* The selection a left click asks for. Ctrl toggles the row, so a second one takes it back out
1907 * rather than being a no-op; Shift extends from the cursor, which is the anchor GTK would have
1908 * used, and rows the range picks up that _tree_restrict_select refuses -- a different parent, a
1909 * different depth -- are dropped by it as usual. A click on blank space clears the selection.
1910 *
1911 * Returns whether the gesture was answered here, which is what the caller reports as handled:
1912 * an unmodified click on a row is left to GtkTreeView, the one case it does act on. */
1913static int _tree_apply_click_selection(GtkWidget *treeview, GtkTreeSelection *selection,
1914 const GdkEventButton *event, GtkTreePath *mouse_path)
1915{
1916 if(IS_NULL_PTR(mouse_path))
1917 {
1918 gtk_tree_selection_unselect_all(selection);
1919 return 0;
1920 }
1921
1922 if(dt_modifier_is(event->state, DT_PRIMARY_MASK))
1923 {
1924 if(gtk_tree_selection_path_is_selected(selection, mouse_path))
1925 gtk_tree_selection_unselect_path(selection, mouse_path);
1926 else
1927 gtk_tree_selection_select_path(selection, mouse_path);
1928 return 1;
1929 }
1930
1931 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
1932 {
1933 GtkTreePath *anchor = NULL;
1934 gtk_tree_view_get_cursor(GTK_TREE_VIEW(treeview), &anchor, NULL);
1935 if(anchor)
1936 {
1937 gtk_tree_selection_select_range(selection, anchor, mouse_path);
1938 gtk_tree_path_free(anchor);
1939 }
1940 else
1941 gtk_tree_selection_select_path(selection, mouse_path);
1942 return 1;
1943 }
1944
1945 return 0;
1946}
1947
1948/* Ctrl+click toggles a row, Shift+click extends from the cursor. GtkTreeView's own handler does
1949 * neither on this tree: measured, the intent masks it derives are the expected ones (modify 0x4,
1950 * extend 0x1), the event carries them, the selection is GTK_SELECTION_MULTIPLE and the select
1951 * function allows the row -- yet no selection change follows a modified click, while a plain one
1952 * works. Why it stays inert was not found; driving the two gestures here is not a workaround for
1953 * that so much as the place this widget already adjusts its own selection. */
1954static int _tree_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_shape_manager_list_t *list)
1955{
1956 // we first need to adjust selection
1957 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1958 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
1959
1960 GtkTreePath *mouse_path = NULL;
1961 GtkTreeViewColumn *mouse_col = NULL;
1962 GtkTreeIter iter;
1963 gboolean on_row = FALSE;
1964 dt_iop_module_t *module = NULL;
1965 int handled = 0;
1966 // mouse_path is non-NULL exactly when the pointer is over a row, so it answers "on a row?" too.
1967 // The module is only wanted for the context menu below; a row that resolves to no iter simply
1968 // leaves it NULL, which is what _tree_context_menu() already expects.
1969 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint)event->x, (gint)event->y, &mouse_path,
1970 &mouse_col, NULL, NULL)
1971 && gtk_tree_model_get_iter(model, &iter, mouse_path))
1972 {
1973 on_row = TRUE;
1974 _shape_manager_get_values(model, &iter, &module, NULL, NULL);
1975 }
1976 /* single click with the right mouse button? */
1977 if(event->type == GDK_BUTTON_PRESS && event->button == 1)
1978 {
1979 // The action icons act on the row under the pointer alone, whatever is selected in this
1980 // list: they are buttons the row carries, not a command applied to the selection.
1981 if(on_row && mouse_col == list->action_col)
1982 {
1983 gtk_tree_path_free(mouse_path);
1984 _tree_row_action(list, model, &iter);
1985 return 1;
1986 }
1987
1988 /* The "+" is the one exception: it reads the OTHER list's selection, which is what it adds
1989 * to. Greyed out when there is none, and then a click on it does nothing rather than
1990 * falling through to selecting the row -- the row under a disabled button is not what the
1991 * user was aiming at. */
1992 if(on_row && !IS_NULL_PTR(list->add_col) && mouse_col == list->add_col)
1993 {
1994 gtk_tree_path_free(mouse_path);
1995 _tree_row_add_to_group(list, model, &iter);
1996 return 1;
1997 }
1998
1999 if(on_row && !IS_NULL_PTR(list->assign_col) && mouse_col == list->assign_col)
2000 {
2001 gtk_tree_path_free(mouse_path);
2002 _tree_row_assign_to_modules(list, model, &iter);
2003 return 1;
2004 }
2005
2006 handled = _tree_apply_click_selection(treeview, selection, event, mouse_path);
2007 }
2008 else if(event->type == GDK_BUTTON_PRESS && event->button == 3)
2009 {
2010 // if we are already inside the selection, no change
2011 if(!IS_NULL_PTR(mouse_path) && !gtk_tree_selection_path_is_selected(selection, mouse_path))
2012 {
2013 if(!dt_modifier_is(event->state, DT_PRIMARY_MASK)) gtk_tree_selection_unselect_all(selection);
2014 gtk_tree_selection_select_path(selection, mouse_path);
2015 }
2016
2017 // and we display the context-menu
2018 GtkWidget *menu = _tree_context_menu(selection, model, list, module);
2019
2020 gtk_widget_show_all(menu);
2021
2022 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2023
2024 handled = 1;
2025 }
2026
2027 // One exit for the path: it was leaked on every button-1 press before, and freed on only one
2028 // of the two button-3 branches.
2029 if(mouse_path) gtk_tree_path_free(mouse_path);
2030 return handled;
2031}
2032
2033/* A double-click on a name renames it. GtkTreeView reports it as an activation of that row and
2034 * column, which nothing else on these trees answers. The icon columns never get here from the
2035 * mouse: _tree_button_pressed() consumes both presses of a double-click on them. */
2036static void _tree_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column,
2038{
2039 if(column != list->name_col) return;
2040
2041 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
2042 GtkTreeIter iter;
2043 if(!gtk_tree_model_get_iter(model, &iter, path)) return;
2044
2045 // Renaming something already named: the text is there to be replaced, so it opens selected.
2046 _tree_start_name_editing(list, model, &iter, path, FALSE);
2047}
2048
2049static gboolean _tree_restrict_select(GtkTreeSelection *selection, GtkTreeModel *model __attribute__((unused)), GtkTreePath *path,
2050 gboolean path_currently_selected, gpointer data)
2051{
2052 dt_shape_manager_t *self = (dt_shape_manager_t *)data;
2053 if(self->gui_reset) return TRUE;
2054
2055 // if the change is SELECT->UNSELECT no pb
2056 if(path_currently_selected) return TRUE;
2057
2058 // if selection is empty, no pb
2059 if(gtk_tree_selection_count_selected_rows(selection) == 0) return TRUE;
2060
2061 /* A row joins the selection only among peers: the same depth, and for a child row the same
2062 * parent. Whatever is already selected and does not qualify is dropped.
2063 *
2064 * The rows to drop are gathered before any is dropped. Unselecting re-enters this function --
2065 * with path_currently_selected TRUE, so those calls return at the top -- and the previous form
2066 * answered that by re-reading the selection and restarting the walk after every single
2067 * removal, which is quadratic for no gain: the list gtk_tree_selection_get_selected_rows()
2068 * returns is our own copy, and unselecting does not touch it. */
2069 const int *indices = gtk_tree_path_get_indices(path);
2070 const int depth = gtk_tree_path_get_depth(path);
2071
2072 GList *items = gtk_tree_selection_get_selected_rows(selection, NULL);
2073 GList *doomed = NULL;
2074 for(const GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
2075 {
2076 GtkTreePath *item = (GtkTreePath *)items_iter->data;
2077 const int dd = gtk_tree_path_get_depth(item);
2078 const int *ii = gtk_tree_path_get_indices(item);
2079 const gboolean peer = (dd == depth) && (dd == 1 || ii[dd - 2] == indices[dd - 2]);
2080 if(!peer) doomed = g_list_prepend(doomed, item);
2081 }
2082
2083 for(const GList *doomed_iter = doomed; doomed_iter; doomed_iter = g_list_next(doomed_iter))
2084 gtk_tree_selection_unselect_path(selection, (GtkTreePath *)doomed_iter->data);
2085
2086 // doomed borrows its paths from items, which owns and frees them
2087 g_list_free(doomed);
2088 g_list_free_full(items, (GDestroyNotify)gtk_tree_path_free);
2089
2090 return TRUE;
2091}
2092
2093/* What the action column's icon does depends on the row's depth: a top-level row owns its shape
2094 * and deleting it is permanent, a nested one only holds a membership. */
2095static const char *_tooltip_action_text(const int grid)
2096{
2097 return (grid == 0) ? _("Permanently delete this shape. It is detached from every mask "
2098 "and removed from the list of available shapes.")
2099 : _("Detach this shape from the mask. The shape is kept and stays "
2100 "available for reuse.");
2101}
2102
2103/* The chooser manages attachments for whatever the row holds, so the text names it. */
2104static gchar *_tooltip_assign_text(GtkTreeModel *model, GtkTreeIter *iter)
2105{
2106 int fid = -1;
2107 _shape_manager_get_values(model, iter, NULL, NULL, &fid);
2108
2109 dt_masks_form_info_t row_info = { 0 };
2111
2112 return g_strdup_printf(_("Manage which modules use this %s. A mask is created for the "
2113 "ticked ones that have none, and shared between them."),
2114 row_info.is_group ? _("group") : _("shape"));
2115}
2116
2117/* The "+" says what it will do, so it has to name what is selected right now -- and when it is
2118 * dead, which of the reasons it is dead for. The refusals are read off the same questions
2119 * _row_can_be_added() asks, so the wording can never disagree with the icon. */
2120static gchar *_tooltip_add_text(const dt_shape_manager_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
2121{
2122 const int group_id = _selected_group_in_module_list(lm);
2123 const dt_masks_form_t *grp = dt_masks_get_from_id(dt_dev_get_global(), group_id);
2124
2125 if(IS_NULL_PTR(grp))
2126 return g_strdup(_("Select a mask in the module groups list to attach this shape to it."));
2127
2128 if(_row_can_be_added(lm, model, iter))
2129 return g_strdup_printf(_("Attach this to the mask '%s'."), grp->name);
2130
2131 int fid = -1;
2132 _shape_manager_get_values(model, iter, NULL, NULL, &fid);
2133 dt_develop_t *const dev = dt_dev_get_global();
2134
2135 if(dt_masks_group_contains(dev, group_id, fid) == DT_MASKS_OK)
2136 return g_strdup_printf(_("This is already part of the mask '%s'."), grp->name);
2137
2138 if(dt_masks_group_contains(dev, fid, group_id) == DT_MASKS_OK)
2139 return g_strdup_printf(_("This group cannot be added to the mask '%s': it already contains it."),
2140 grp->name);
2141
2142 // The remaining refusal: every shape it holds is already in the target.
2143 return g_strdup_printf(_("The mask '%s' already uses every shape of this group."), grp->name);
2144}
2145
2146/* The tooltip of the row's buttons, answered from the column the pointer is over.
2147 *
2148 * The pointer's column has to be asked here rather than left to gtk_tree_view_get_tooltip_context(),
2149 * which reports the row but not the column, and rewrites x/y on the way.
2150 *
2151 * @return whether the pointer was over a button column, i.e. whether the tooltip was set. */
2152static gboolean _tree_button_tooltip(const dt_shape_manager_list_t *list, GtkTreeView *tree_view,
2153 const gint x, const gint y, GtkTooltip *tooltip)
2154{
2155 gint bx = 0, by = 0;
2156 gtk_tree_view_convert_widget_to_bin_window_coords(tree_view, x, y, &bx, &by);
2157
2158 GtkTreePath *path = NULL;
2159 GtkTreeViewColumn *column = NULL;
2160 if(!gtk_tree_view_get_path_at_pos(tree_view, bx, by, &path, &column, NULL, NULL)) return FALSE;
2161
2162 const gboolean on_action = (column == list->action_col);
2163 const gboolean on_add = !IS_NULL_PTR(list->add_col) && (column == list->add_col);
2164 const gboolean on_assign = !IS_NULL_PTR(list->assign_col) && (column == list->assign_col);
2165
2166 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
2167 GtkTreeIter iter;
2168 const gboolean got = (on_action || on_add || on_assign) && gtk_tree_model_get_iter(model, &iter, path);
2169 gtk_tree_path_free(path);
2170 if(!got) return FALSE;
2171
2172 if(on_action)
2173 {
2174 int grid = -1;
2175 _shape_manager_get_values(model, &iter, NULL, &grid, NULL);
2176 gtk_tooltip_set_text(tooltip, _tooltip_action_text(grid));
2177 return TRUE;
2178 }
2179
2180 gchar *text = on_assign ? _tooltip_assign_text(model, &iter)
2181 : _tooltip_add_text((const dt_shape_manager_t *)list->self->data, model, &iter);
2182 gtk_tooltip_set_text(tooltip, text);
2183 dt_free(text);
2184
2185 return TRUE;
2186}
2187
2188static gboolean _tree_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_tip,
2189 GtkTooltip *tooltip, gpointer data)
2190{
2191 GtkTreeIter iter;
2192 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
2193 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
2194 GtkTreePath *path = NULL;
2195 gchar *tmp = NULL;
2196 gboolean show = FALSE;
2197 const dt_shape_manager_list_t *list = (const dt_shape_manager_list_t *)data;
2198
2199 // Keyboard tooltips carry no position, so they can only be about the row.
2200 if(!keyboard_tip && !IS_NULL_PTR(list) && _tree_button_tooltip(list, tree_view, x, y, tooltip))
2201 return TRUE;
2202
2203 if(!gtk_tree_view_get_tooltip_context(tree_view, &x, &y, keyboard_tip, &model, &path, &iter)) return FALSE;
2204
2205 gtk_tree_model_get(model, &iter, TREE_IC_USED_VISIBLE, &show, TREE_USED_TEXT, &tmp, -1);
2206 if(show)
2207 {
2208 gtk_tooltip_set_markup(tooltip, tmp);
2209 gtk_tree_view_set_tooltip_row(tree_view, tooltip, path);
2210 }
2211
2212 gtk_tree_path_free(path);
2213 dt_free(tmp);
2214
2215 return show;
2216}
2217
2218/* Appends the name of every group inside grp that lists formid, one per line, and counts them.
2219 * Recurses into member groups, so a shape held by a nested group names that group too. */
2220static void _groups_naming_form(const int formid, const dt_masks_form_t *grp, char *text,
2221 const size_t text_length, int *nb)
2222{
2223 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
2224
2225 for(const GList *points = grp->points; points; points = g_list_next(points))
2226 {
2227 const dt_masks_form_group_t *point = (const dt_masks_form_group_t *)points->data;
2229 if(IS_NULL_PTR(form)) continue;
2230
2231 if(point->formid == formid)
2232 {
2233 (*nb)++;
2234 if(*nb > 1) g_strlcat(text, "\n", text_length);
2235 g_strlcat(text, grp->name, text_length);
2236 }
2237
2238 if(form->type & DT_MASKS_GROUP) _groups_naming_form(formid, form, text, text_length, nb);
2239 }
2240}
2241
2242/* Same, over every group in the image. The entry and the walk used to be one function switching
2243 * on a NULL group, which is why it took one. */
2244static void _is_form_used(const int formid, char *text, const size_t text_length, int *nb)
2245{
2246 for(const GList *forms = dt_dev_get_global()->forms; forms; forms = g_list_next(forms))
2247 {
2248 const dt_masks_form_t *form = (const dt_masks_form_t *)forms->data;
2249 if(form->type & DT_MASKS_GROUP) _groups_naming_form(formid, form, text, text_length, nb);
2250 }
2251}
2252
2253/* What one row of the tree says about the form it shows. The recursion used to pass these as
2254 * nine separate arguments; only treestore and lm are the same for every row. */
2255typedef struct _tree_row_t
2256{
2258 int grp_id; // 0 for a row listed at top level, the parent group's id otherwise
2259 dt_iop_module_t *module; // the module owning the group this row sits under, when there is one
2260 int gstate; // the combine/invert bits this form carries inside its parent
2261 float opacity;
2262 int index; // rank inside the parent, which _set_iter_name() shows
2263 int root_id; /* the module mask this row sits somewhere inside, 0 outside the
2264 * module list -- the scope the note below is searched in */
2265 gboolean flat; /* TRUE to append this row without descending into its own members --
2266 * a module mask shown in the inventory: a single, non-expandable row
2267 * there, its full subtree still shown, expandable, in the module list */
2269
2270/* Every module whose drawn mask is this group, in pipeline order.
2271 *
2272 * The link between a group and a module is blend_params->mask_id, which lives in each module's
2273 * own params blob, so nothing stops several modules from naming the same group -- and that is
2274 * what a group shared between modules is. There is no stored back-reference to keep in step:
2275 * the answer is derived here, from the one place the truth is, on a walk over ~80 modules.
2276 *
2277 * Returns a GList of dt_iop_module_t* the caller frees with g_list_free(), borrowing the
2278 * modules themselves; NULL when no module uses the group. */
2279static GList *_modules_owning_group(const dt_masks_form_t *group)
2280{
2281 if(IS_NULL_PTR(group)) return NULL;
2282
2283 GList *owners = NULL;
2284 for(const GList *iops = dt_dev_get_global()->iop; iops; iops = g_list_next(iops))
2285 {
2286 dt_iop_module_t *iop = (dt_iop_module_t *)iops->data;
2288 owners = g_list_prepend(owners, iop);
2289 }
2290
2291 return g_list_reverse(owners);
2292}
2293
2294/* The first module using this group, for the callers that need one module to speak for the row --
2295 * the tree stores a single module per row. A group listed at top level with no module yet is the
2296 * only case worth asking about: a nested one inherits its parent's. */
2298{
2299 GList *owners = _modules_owning_group(group);
2300 dt_iop_module_t *first = IS_NULL_PTR(owners) ? NULL : (dt_iop_module_t *)owners->data;
2301 g_list_free(owners);
2302 return first;
2303}
2304
2305/* Appends the row and returns its iter, which a group needs to hang its members from. Shapes and
2306 * groups are described identically here; only what happens afterwards differs. */
2307static void _tree_append_row(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_shape_manager_t *lm,
2308 const _tree_row_t *row, GtkTreeIter *child)
2309{
2310 GdkPixbuf *icop = NULL;
2311 if(row->gstate & DT_MASKS_STATE_UNION)
2312 icop = lm->ic_union;
2313 else if(row->gstate & DT_MASKS_STATE_INTERSECTION)
2314 icop = lm->ic_intersection;
2315 else if(row->gstate & DT_MASKS_STATE_DIFFERENCE)
2316 icop = lm->ic_difference;
2317 else if(row->gstate & DT_MASKS_STATE_EXCLUSION)
2318 icop = lm->ic_exclusion;
2319
2320 GdkPixbuf *icinv = (row->gstate & DT_MASKS_STATE_INVERSE) ? lm->ic_inverse : NULL;
2321
2322 /* One by-value description of the form, which every field below is taken from. The name it
2323 * carries is copied rather than borrowed, so nothing here holds a pointer into a refcounted
2324 * form across a call that could clone it. */
2325 dt_masks_form_info_t info = { 0 };
2326 if(!dt_masks_form_get_info(row->form, &info)) return;
2327
2328 // Only a top-level row asks who else uses the shape: a row under a group already says so.
2329 char used_by[1000] = "";
2330 int nbuse = 0;
2331 if(row->grp_id == 0) _is_form_used(info.formid, used_by, sizeof(used_by), &nbuse);
2332
2333 /* Only inside a module's mask, and only for a shape sitting under a group: a top-level row is
2334 * not reached through anything, and a group's own duplication is a different question.
2335 *
2336 * Only the instances AFTER the first are marked. Both used to be, symmetrically, which said
2337 * that the shape appears twice but not which application the other is measured against. */
2338 gchar *note = NULL;
2339 if(row->root_id > 0 && row->grp_id > 0 && !info.is_group)
2340 {
2341 int holder_id = 0;
2342 guint holder_index = 0;
2343 char holder_name[DT_MASKS_FORM_NAME_LEN] = "";
2344 if(dt_masks_group_first_use(dt_dev_get_global(), row->root_id, info.formid, &holder_id,
2345 &holder_index, holder_name, sizeof(holder_name)) == DT_MASKS_OK
2346 && (holder_id != row->grp_id || (int)holder_index != row->index))
2347 // Same wording the Drawn tab's shape list already uses for the same kind of remark.
2348 note = g_strdup_printf(_("Already in '%s'"), holder_name);
2349 }
2350
2351 gtk_tree_store_append(treestore, child, toplevel);
2352 gtk_tree_store_set(treestore, child, TREE_TEXT, info.name, TREE_MODULE, row->module,
2353 TREE_GROUPID, row->grp_id, TREE_FORMID, info.formid,
2354 TREE_EDITABLE, (row->grp_id == 0), TREE_IC_OP, icop,
2357 TREE_IC_USED_VISIBLE, (nbuse > 0), TREE_USED_TEXT, used_by,
2358 TREE_IC_DELETE_VISIBLE, (row->grp_id == 0),
2359 TREE_IC_UNLINK_VISIBLE, (row->grp_id != 0),
2360 TREE_NOTE, IS_NULL_PTR(note) ? "" : note,
2361 TREE_STRIKE, !IS_NULL_PTR(note), -1);
2362 dt_free(note);
2363 _set_iter_name(lm, row->form, row->gstate, row->opacity, GTK_TREE_MODEL(treestore), child, row->index);
2364}
2365
2366static void _shape_manager_list_recurs(GtkTreeStore *treestore, GtkTreeIter *toplevel,
2368{
2369 // Clone sources belong to retouch's own UI, not to this tree.
2370 if(row->form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE)) return;
2371
2372 _tree_row_t self = *row;
2373 if((self.form->type & DT_MASKS_GROUP) && self.grp_id == 0 && IS_NULL_PTR(self.module))
2374 self.module = _module_owning_group(self.form);
2375
2376 GtkTreeIter child;
2377 _tree_append_row(treestore, toplevel, lm, &self, &child);
2378
2379 // A flat row is a single line by request: no expander, no children appended under it.
2380 if(self.flat) return;
2381
2382 if(!(self.form->type & DT_MASKS_GROUP)) return;
2383
2384 int index = 0;
2385 for(const GList *forms = self.form->points; forms; forms = g_list_next(forms))
2386 {
2387 const dt_masks_form_group_t *grpt = (const dt_masks_form_group_t *)forms->data;
2389 if(!IS_NULL_PTR(member))
2390 {
2391 const _tree_row_t member_row = { .form = member, .grp_id = self.form->formid,
2392 .module = self.module, .gstate = grpt->state,
2393 .opacity = grpt->opacity, .index = index,
2394 .root_id = self.root_id };
2395 _shape_manager_list_recurs(treestore, &child, lm, &member_row);
2396 }
2397 index++;
2398 }
2399}
2400
2401gboolean _find_mask_iter_by_values(GtkTreeModel *model, GtkTreeIter *iter,
2402 const dt_iop_module_t *module, const int formid, const int level)
2403{
2404 gboolean found = FALSE;
2405 do
2406 {
2407 int fid = -1;
2408 dt_iop_module_t *mod;
2409 _shape_manager_get_values(model, iter, &mod, NULL, &fid);
2410 found = (fid == formid)
2411 && ((level == 1)
2412 || (IS_NULL_PTR(module) || (mod && (!g_strcmp0(module->op, mod->op)))));
2413 if(found) return found;
2414 GtkTreeIter child;
2415 GtkTreeIter parent = *iter;
2416 if(gtk_tree_model_iter_children(model, &child, &parent))
2417 {
2418 found = _find_mask_iter_by_values(model, &child, module, formid, level + 1);
2419 if(found)
2420 {
2421 *iter = child;
2422 return found;
2423 }
2424 }
2425 } while(gtk_tree_model_iter_next(model, iter));
2426 return found;
2427}
2428
2430{
2431 GList *res = NULL;
2432
2433 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
2434
2435 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
2436
2437 GList *items = gtk_tree_selection_get_selected_rows(selection, &model);
2438
2439 for(GList *items_iter = items; items_iter; items_iter = g_list_next(items_iter))
2440 {
2441 GtkTreePath *item = (GtkTreePath *)items_iter->data;
2442 GtkTreeIter iter;
2443 if(gtk_tree_model_get_iter(model, &iter, item))
2444 {
2445 int fid = -1;
2446 int gid = -1;
2447 dt_iop_module_t *mod;
2448 _shape_manager_get_values(model, &iter, &mod, &gid, &fid);
2449 res = g_list_prepend(res, GINT_TO_POINTER(fid));
2450 res = g_list_prepend(res, GINT_TO_POINTER(gid));
2451 res = g_list_prepend(res, (void *)(mod));
2452 }
2453 }
2454
2455 g_list_foreach(items, (GFunc)gtk_tree_path_free, NULL);
2456 g_list_free(items);
2457 items = NULL;
2458
2459 return res;
2460}
2461
2462/* Expands to the row, scrolls it into view and selects it. */
2463static void _tree_reveal_row(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter,
2464 const gboolean exclusive)
2465{
2466 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
2467 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
2468
2469 if(exclusive) gtk_tree_selection_unselect_all(selection);
2470 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(list->treeview), path);
2471 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(list->treeview), path, NULL, TRUE, 0.5, 0.5);
2472 gtk_tree_selection_select_iter(selection, iter);
2473
2474 gtk_tree_path_free(path);
2475}
2476
2477/* Whether this group is some module's drawn mask, which is what the module list holds. */
2478static gboolean _group_is_module_mask(const dt_masks_form_t *form)
2479{
2480 if(IS_NULL_PTR(form) || !(form->type & DT_MASKS_GROUP)) return FALSE;
2481
2482 GList *owners = _modules_owning_group(form);
2483 const gboolean assigned = !IS_NULL_PTR(owners);
2484 g_list_free(owners);
2485
2486 return assigned;
2487}
2488
2489/* Appends one group's row (and, recursively, everything under it, unless @p flat) and answers
2490 * whether it did -- FALSE only when a module-mask row's own id could not be read, which leaves
2491 * the row unlisted rather than mislabelled. root_id is a property of the row's SCOPE (see
2492 * _tree_row_t), not of the group itself: it is only ever set for a module mask, so the "applied
2493 * twice in this mask" note has something to search.
2494 *
2495 * @param flat the inventory's own copy of a module mask: a single row, no expander, no members
2496 * appended under it -- that subtree is the module list's to show, not shown twice. */
2497static gboolean _tree_store_append_group_row(GtkTreeStore *treestore, dt_shape_manager_t *lm,
2498 dt_masks_form_t *form, const gboolean is_module_mask,
2499 const gboolean flat)
2500{
2501 int root_id = 0;
2502 if(is_module_mask)
2503 {
2504 dt_masks_form_info_t info = { 0 };
2505 if(!dt_masks_form_get_info(form, &info)) return FALSE;
2506 root_id = info.formid;
2507 }
2508
2509 const _tree_row_t row = { .form = form, .opacity = 1.0f, .root_id = root_id, .flat = flat };
2510 _shape_manager_list_recurs(treestore, NULL, lm, &row);
2511 return TRUE;
2512}
2513
2514/* Returns whether it added anything, which is what tells the caller a separator is worth having. */
2515static gboolean _tree_store_add_forms(GtkTreeStore *treestore, dt_shape_manager_t *lm,
2516 const dt_shape_list_t which, const gboolean groups)
2517{
2518 gboolean any = FALSE;
2519
2520 /* Module masks are walked in pipeline order rather than in creation order, in both lists --
2521 * the inventory holds them too (see _group_is_module_mask()'s own comment).
2522 * Reverse iop_order, the "bottom of the stack first" convention _modchooser_run() and the
2523 * module groups panel's Pipeline tab already use for the same kind of module list, so a
2524 * module's mask lands at the same relative position here as its own row does everywhere else
2525 * in the darkroom.
2526 *
2527 * Scoped exactly like _modules_owning_group() -- every module in dev->iop, not just the ones
2528 * dt_iop_module_is_in_pipeline() currently shows -- so a mask belonging to a hidden or
2529 * not-yet-reached instance is still found here rather than silently dropped from both this
2530 * loop and the "unclaimed groups" one below, which skips it on the assumption it was already
2531 * handled. */
2532 if(groups)
2533 {
2534 dt_develop_t *const dev = dt_dev_get_global();
2535 for(const GList *iops = g_list_last(dev->iop); iops; iops = g_list_previous(iops))
2536 {
2537 dt_iop_module_t *module = (dt_iop_module_t *)iops->data;
2538 if(!dt_iop_module_supports_drawn_mask(module)) continue;
2539
2540 dt_masks_form_t *mask = dt_masks_get_from_id(dev, module->blend_params->mask_id);
2541 if(IS_NULL_PTR(mask) || !(mask->type & DT_MASKS_GROUP)) continue;
2542
2543 // Expandable in the module list (its own home), a single flat row in the inventory.
2544 if(_tree_store_append_group_row(treestore, lm, mask, TRUE, which == DT_SHAPE_LIST_SHAPES))
2545 any = TRUE;
2546 }
2547
2548 // The module list holds nothing else.
2549 if(which == DT_SHAPE_LIST_MODULES) return any;
2550 }
2551
2552 for(const GList *forms = dt_dev_get_global()->forms; forms; forms = g_list_next(forms))
2553 {
2554 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
2555 if(!!(form->type & DT_MASKS_GROUP) != groups) continue;
2556
2557 // Already appended above, in pipeline order: this pass is unclaimed groups (and shapes) only.
2558 if(groups && _group_is_module_mask(form)) continue;
2559
2560 if(_tree_store_append_group_row(treestore, lm, form, FALSE, FALSE)) any = TRUE;
2561 }
2562
2563 return any;
2564}
2565
2566/* The one row GTK draws as a rule rather than as content. It carries no form, so every walk that
2567 * looks a row up by id passes over it, and GtkTreeView skips it for selection on its own. */
2568static gboolean _tree_row_is_separator(GtkTreeModel *model, GtkTreeIter *iter,
2569 gpointer data __attribute__((unused)))
2570{
2571 gboolean is_separator = FALSE;
2572 gtk_tree_model_get(model, iter, TREE_IS_SEPARATOR, &is_separator, -1);
2573 return is_separator;
2574}
2575
2576/* An empty store with the columns both lists read (see dt_masks_tree_cols_t). */
2577static GtkTreeStore *_tree_store_new(void)
2578{
2579 // we store : text ; *module ; groupid ; formid
2580 return gtk_tree_store_new(TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT, G_TYPE_BOOLEAN,
2581 GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
2582 G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING,
2583 G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
2584}
2585
2586/* The inventory list shows the unclaimed groups first, then every shape, with a rule between
2587 * them. The module list has only groups, so it gets neither the second pass nor the rule. */
2588static GtkTreeStore *_tree_store_build(dt_shape_manager_t *lm, const dt_shape_list_t which)
2589{
2590 GtkTreeStore *treestore = _tree_store_new();
2591 const gboolean had_groups = _tree_store_add_forms(treestore, lm, which, TRUE);
2592 if(which == DT_SHAPE_LIST_MODULES) return treestore;
2593
2594
2595 /* A rule between the module groups and the loose shapes, added between the two passes and kept
2596 * only when both sides of it exist -- one opening or closing the list would be a line against
2597 * nothing.
2598 *
2599 * Its ids are -1, not 0: _shape_manager_selection_change_r() is asked for id 0 whenever no mask
2600 * is current, and a row carrying 0 would answer. Nothing ever looks for -1. */
2601 GtkTreeIter separator;
2602 if(had_groups)
2603 {
2604 gtk_tree_store_append(treestore, &separator, NULL);
2605 gtk_tree_store_set(treestore, &separator, TREE_IS_SEPARATOR, TRUE, TREE_FORMID, -1,
2606 TREE_GROUPID, -1, TREE_EDITABLE, FALSE, -1);
2607 }
2608
2609 const gboolean had_shapes = _tree_store_add_forms(treestore, lm, which, FALSE);
2610 if(had_groups && !had_shapes) gtk_tree_store_remove(treestore, &separator);
2611
2612 return treestore;
2613}
2614
2615/* Puts back what was selected before the store was replaced. selectids holds three entries per
2616 * row -- module, group id, form id -- as _shape_manager_get_selected() built it. */
2617static void _tree_restore_selection(dt_shape_manager_list_t *list, GtkTreeModel *model, const GList *selectids)
2618{
2619 const GList *ids = selectids;
2620 while(ids)
2621 {
2622 dt_iop_module_t *mod = (dt_iop_module_t *)ids->data;
2623 ids = g_list_next(ids);
2624 // the group id sits between the module and the form id, and this walk has no use for it
2625 ids = g_list_next(ids);
2626 const int fid = GPOINTER_TO_INT(ids->data);
2627 ids = g_list_next(ids);
2628
2629 GtkTreeIter iter;
2630 // An empty store leaves iter untouched, and _find_mask_iter_by_values() then walks a
2631 // stack-garbage iterator: gtk_tree_store_get_value() and gtk_tree_store_iter_next() assert
2632 // on it, and the walk has no reason to terminate. Nothing later can make the store
2633 // non-empty, so stop rather than skip.
2634 if(!gtk_tree_model_get_iter_first(model, &iter)) return;
2635
2636 if(_find_mask_iter_by_values(model, &iter, mod, fid, 1)) _tree_reveal_row(list, model, &iter, FALSE);
2637 }
2638}
2639
2640/* Points the tree at the focused module's mask group, and says whether it moved the selection --
2641 * the caller replays the selection handler when it did, so the canvas follows. */
2642static gboolean _tree_select_module_group(dt_shape_manager_list_t *list, GtkTreeModel *model,
2643 const dt_masks_form_gui_t *gui)
2644{
2645 dt_iop_module_t *const module = dt_dev_get_global()->gui_module;
2646 const int group_id = dt_iop_module_supports_drawn_mask(module) ? module->blend_params->mask_id : 0;
2647
2648 if(group_id <= 0) return FALSE;
2649 // Mid-creation the tree follows the shape being drawn, not the module.
2650 if(!IS_NULL_PTR(gui) && gui->creation) return FALSE;
2651
2652 GtkTreeIter iter;
2653 if(!gtk_tree_model_get_iter_first(model, &iter)) return FALSE;
2654 if(!_find_mask_iter_by_values(model, &iter, module, group_id, 1)) return FALSE;
2655
2656 _tree_reveal_row(list, model, &iter, TRUE);
2657 return TRUE;
2658}
2659
2660/* Whether the panel has anything to show: only the darkroom has a develop to read shapes from.
2661 * The window is a toplevel of its own and outlives the view -- the user can leave the darkroom
2662 * with it open -- while what its rows point at does not: the darkroom's leave() frees dev->iop,
2663 * and a row stores its module by address (TREE_MODULE). */
2664static gboolean _shape_manager_is_active(void)
2665{
2667 const dt_view_t *cv = IS_NULL_PTR(vm) ? NULL : dt_view_manager_get_current_view(vm);
2668 return !IS_NULL_PTR(cv) && cv->view((dt_view_t *)cv) == DT_VIEW_DARKROOM;
2669}
2670
2672{
2674 if(IS_NULL_PTR(lm) || lm->gui_reset) return;
2675
2676 // Everything below drives the trees itself, so the handlers they would wake must stay quiet.
2677 const int gui_reset = lm->gui_reset;
2678 lm->gui_reset = 1;
2679
2680 /* Outside the darkroom both lists are emptied and the whole window greyed out: no row would
2681 * still be valid, and no button has a develop to act on. Decided here, where every rebuild
2682 * passes, so no caller -- a mask signal, the develop proxy, a view switch -- can refill it. */
2683 const gboolean active = _shape_manager_is_active();
2684 GtkWidget *content = IS_NULL_PTR(lm->popup_window) ? NULL : gtk_bin_get_child(GTK_BIN(lm->popup_window));
2685 if(!IS_NULL_PTR(content)) gtk_widget_set_sensitive(content, active);
2686
2687 if(!active)
2688 {
2689 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
2690 {
2691 dt_shape_manager_list_t *list = &lm->lists[i];
2692 if(IS_NULL_PTR(list->treeview)) continue;
2693
2694 // An empty store rather than none: every handler reads the model without a NULL check.
2695 GtkTreeStore *empty = _tree_store_new();
2696 gtk_tree_view_set_model(GTK_TREE_VIEW(list->treeview), GTK_TREE_MODEL(empty));
2697 g_object_unref(empty);
2698 }
2699 lm->gui_reset = gui_reset;
2700 return;
2701 }
2702
2703 // Rebuilding the list also refreshes shapes created during continuous creation. In that case
2704 // the active creation button must stay active until the user cancels creation explicitly.
2707
2708 /* Both stores are rebuilt from scratch: a form crosses from one list to the other the moment
2709 * a module claims or releases its group, so neither can be refreshed on its own. */
2710 dt_shape_manager_list_t *follow = NULL;
2711 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
2712 {
2713 dt_shape_manager_list_t *list = &lm->lists[i];
2714 if(IS_NULL_PTR(list->treeview)) continue;
2715
2716 // The store is about to be replaced, so what is selected has to be read before it goes.
2717 GList *selectids = _shape_manager_get_selected(list);
2718
2719 GtkTreeStore *treestore = _tree_store_build(lm, list->which);
2720 GtkTreeModel *model = GTK_TREE_MODEL(treestore);
2721 gtk_tree_view_set_model(GTK_TREE_VIEW(list->treeview), model);
2722
2723 if(selectids)
2724 {
2725 _tree_restore_selection(list, model, selectids);
2726 g_list_free(selectids);
2727 }
2728
2729 // Only the module list can follow the focused module's own mask group; the inventory does
2730 // not hold it.
2732 follow = list;
2733
2734 g_object_unref(treestore);
2735 }
2736
2737 lm->gui_reset = gui_reset;
2738
2739 // Both models were replaced, so whatever the module list held may or may not have come back.
2741
2742 if(!IS_NULL_PTR(follow))
2743 _tree_selection_change(gtk_tree_view_get_selection(GTK_TREE_VIEW(follow->treeview)), follow);
2744}
2745
2746static void _shape_manager_update_item(dt_lib_module_t *self __attribute__((unused)), int formid, int parentid, dt_shape_manager_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
2747{
2748 // we retrieve the forms
2750 if(IS_NULL_PTR(form)) return;
2752
2753 // and the values
2754 int state = 0;
2755 float opacity = 1.0f;
2756
2757 int index = 0;
2758 if(grp && (grp->type & DT_MASKS_GROUP))
2759 {
2760 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
2761 {
2763 if(pt->formid == formid)
2764 {
2765 state = pt->state;
2766 opacity = pt->opacity;
2767 break;
2768 }
2769 index++;
2770 }
2771 }
2772
2773 _set_iter_name(lm, form, state, opacity, model, iter, index);
2774 return;
2775}
2776
2777static gboolean _update_foreach(GtkTreeModel *model, GtkTreePath *path __attribute__((unused)), GtkTreeIter *iter, gpointer data)
2778{
2779 if(IS_NULL_PTR(iter)) return 0;
2780
2781 // we retrieve the ids
2782 int grid = -1;
2783 int id = -1;
2784 _shape_manager_get_values(model, iter, NULL, &grid, &id);
2785
2786 // we retrieve the forms
2788 if(IS_NULL_PTR(form)) return 0;
2790
2791 // and the values
2792 int state = 0;
2793 float opacity = 1.0f;
2794
2795 int index = 0;
2796 if(grp && (grp->type & DT_MASKS_GROUP))
2797 {
2798 for(const GList *pts = grp->points; pts; pts = g_list_next(pts))
2799 {
2801 if(pt->formid == id)
2802 {
2803 state = pt->state;
2804 opacity = pt->opacity;
2805 break;
2806 }
2807 index++;
2808 }
2809 }
2810
2811 _set_iter_name(data, form, state, opacity, model, iter, index);
2812 return 0;
2813}
2814
2815// Update each item of both lists. The same form can have a row in each, so neither is skipped.
2817{
2819 if(IS_NULL_PTR(lm)) return;
2820
2821 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
2822 {
2823 const dt_shape_manager_list_t *list = &lm->lists[i];
2824 if(IS_NULL_PTR(list->treeview)) continue;
2825
2826 // for each node , we refresh the string
2827 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
2828 if(!GTK_IS_TREE_MODEL(model)) continue;
2829 gtk_tree_model_foreach(model, _update_foreach, lm);
2830 }
2831}
2832
2833static gboolean _remove_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
2834{
2835 if(IS_NULL_PTR(iter)) return 0;
2836 GList **rl = (GList **)data;
2837 const int refid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model), "formid"));
2838 const int refgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model), "groupid"));
2839
2840 int grid = -1;
2841 int id = -1;
2842 _shape_manager_get_values(model, iter, NULL, &grid, &id);
2843
2844 if(grid == refgid && id == refid)
2845 {
2846 GtkTreeRowReference *rowref = gtk_tree_row_reference_new(model, path);
2847 *rl = g_list_append(*rl, rowref);
2848 }
2849 return 0;
2850}
2851
2852/* Drops the (formid, parentid) row from one list's store. */
2853static void _shape_manager_remove_item_from(const dt_shape_manager_list_t *list, int formid, int parentid)
2854{
2855 if(IS_NULL_PTR(list->treeview)) return;
2856 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
2857 if(!GTK_IS_TREE_MODEL(model)) return;
2858 GList *rl = NULL;
2859 g_object_set_data(G_OBJECT(model), "formid", GUINT_TO_POINTER(formid));
2860 g_object_set_data(G_OBJECT(model), "groupid", GUINT_TO_POINTER(parentid));
2861 gtk_tree_model_foreach(model, _remove_foreach, &rl);
2862
2863 for(const GList *rlt = rl; rlt; rlt = g_list_next(rlt))
2864 {
2865 GtkTreeRowReference *rowref = (GtkTreeRowReference *)rlt->data;
2866 GtkTreePath *path = gtk_tree_row_reference_get_path(rowref);
2867 gtk_tree_row_reference_free(rowref);
2868 if(path)
2869 {
2870 GtkTreeIter iter;
2871 if(gtk_tree_model_get_iter(model, &iter, path))
2872 {
2873 gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
2874 }
2875 gtk_tree_path_free(path);
2876 }
2877 }
2878 g_list_free(rl);
2879 rl = NULL;
2880}
2881
2882static void _shape_manager_remove_item(dt_lib_module_t *self, int formid, int parentid)
2883{
2885 if(IS_NULL_PTR(lm)) return;
2886
2887 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
2888 _shape_manager_remove_item_from(&lm->lists[i], formid, parentid);
2889}
2890
2891static gboolean _shape_manager_selection_change_r(GtkTreeModel *model, GtkTreeSelection *selection,
2892 GtkTreeIter *iter, struct dt_iop_module_t *module,
2893 const int selectid, int throw_event, const int level)
2894{
2895 gboolean found = FALSE;
2896
2897 // The walk stops at the first match, whether this level made it or a child did, so the loop
2898 // carries that in its own condition rather than breaking out of it twice.
2899 GtkTreeIter i = *iter;
2900 do
2901 {
2902 int id = -1;
2903 dt_iop_module_t *mod;
2904 _shape_manager_get_values(model, &i, &mod, NULL, &id);
2905
2906 if((id == selectid)
2907 && ((level == 1)
2908 || (IS_NULL_PTR(module) || (mod && (!g_strcmp0(module->op, mod->op))))))
2909 {
2910 gtk_tree_selection_select_iter(selection, &i);
2911 found = TRUE;
2912 continue;
2913 }
2914
2915 // check for children if any
2916 GtkTreeIter child;
2917 GtkTreeIter parent = i;
2918 if(gtk_tree_model_iter_children(model, &child, &parent))
2919 found = _shape_manager_selection_change_r(model, selection, &child, module, selectid, throw_event, level + 1);
2920 } while(!found && gtk_tree_model_iter_next(model, &i) == TRUE);
2921
2922 return found;
2923}
2924
2925/* Points one list at the form, and says whether it holds it at all. */
2927 struct dt_iop_module_t *module, const int selectid,
2928 const int throw_event)
2929{
2930 if(IS_NULL_PTR(list->treeview)) return FALSE;
2931
2932 // we first unselect all
2933 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
2934 lm->gui_reset = 1;
2935 gtk_tree_selection_unselect_all(selection);
2936 lm->gui_reset = 0;
2937
2938 // we go through all nodes
2939 lm->gui_reset = 1 - throw_event;
2940 GtkTreeIter iter;
2941 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
2942 if(!GTK_IS_TREE_MODEL(model))
2943 {
2944 lm->gui_reset = 0;
2945 return FALSE;
2946 }
2947
2948 /* The recursive search below walks the MODEL (gtk_tree_model_iter_children()), which holds
2949 * every row regardless of the view's own expand/collapse display state -- it needs nothing
2950 * expanded to find its target. Expanding the WHOLE tree first, as this used to, was only ever
2951 * about making the match visible afterward, and it did that by exploding every OTHER group
2952 * open too, module masks and unclaimed groups alike, for a search that had nothing to do with
2953 * them. Revealing just the path to the row actually found -- the same targeted
2954 * expand-to-path/scroll _tree_reveal_row() already uses elsewhere -- gets the same visibility
2955 * without the side effect. */
2956 gboolean found = FALSE;
2957 if(gtk_tree_model_get_iter_first(model, &iter))
2958 found = _shape_manager_selection_change_r(model, selection, &iter, module, selectid, throw_event, 1);
2959
2960 if(found)
2961 {
2962 GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
2963 if(!IS_NULL_PTR(rows))
2964 {
2965 GtkTreePath *path = (GtkTreePath *)rows->data;
2966 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(list->treeview), path);
2967 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(list->treeview), path, NULL, TRUE, 0.5, 0.5);
2968 }
2969 g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
2970 }
2971
2972 lm->gui_reset = 0;
2973 return found;
2974}
2975
2976/* A form can have a row in either list, or in both -- a shape is listed in the inventory and
2977 * again under whichever module group holds it -- so both are pointed at it. */
2978static void _shape_manager_selection_change(dt_lib_module_t *self, struct dt_iop_module_t *module, const int selectid, const int throw_event)
2979{
2981 if(IS_NULL_PTR(lm)) return;
2982
2983 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
2984 _shape_manager_selection_change_in(lm, &lm->lists[i], module, selectid, throw_event);
2985}
2986
2987static gboolean _find_child_iter_by_formid(GtkTreeModel *model, GtkTreeIter *parent_iter, int formid, GtkTreeIter *child_iter)
2988{
2989 GtkTreeIter iter;
2990 gboolean found = FALSE;
2991
2992 // Obtenir le premier enfant du parent
2993 if(gtk_tree_model_iter_children(model, &iter, parent_iter))
2994 {
2995 do
2996 {
2997 int current_formid = -1;
2998 gtk_tree_model_get(model, &iter, TREE_FORMID, &current_formid, -1);
2999
3000 if(current_formid == formid)
3001 {
3002 *child_iter = iter;
3003 found = TRUE;
3004 break;
3005 }
3006 } while(gtk_tree_model_iter_next(model, &iter));
3007 }
3008
3009 return found;
3010}
3011
3012static gboolean _find_iter_by_parentid_and_formid(GtkTreeModel *model, int parentid, int formid, GtkTreeIter *iter)
3013{
3014 gboolean found = FALSE;
3015
3016 // Obtenir le premier itérateur du modèle
3017 do
3018 {
3019 int current_parentid = -1;
3020 gtk_tree_model_get(model, iter, TREE_FORMID, &current_parentid, -1);
3021
3022 if(current_parentid == parentid)
3023 {
3024 // Rechercher le formid dans les enfants du parent
3025 found = _find_child_iter_by_formid(model, iter, formid, iter);
3026 if(found)
3027 {
3028 break;
3029 }
3030 }
3031 } while(gtk_tree_model_iter_next(model, iter));
3032
3033 return found;
3034}
3035
3036/* Answers whether the event names a row this panel is currently showing, refreshing every row
3037 * that does when the event is an UPDATE.
3038 *
3039 * A single-row event can name a row in either list -- or in both, when a module group holds a
3040 * shape the inventory also lists -- so both are asked. */
3042 const int formid, const int parentid,
3043 const dt_masks_event_t event)
3044{
3045 gboolean found = FALSE;
3046 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
3047 {
3048 const dt_shape_manager_list_t *list = &lm->lists[i];
3049 if(IS_NULL_PTR(list->treeview)) continue;
3050
3051 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview));
3052 if(!GTK_IS_TREE_MODEL(model)) continue;
3053
3054 GtkTreeIter iter;
3055 if(!gtk_tree_model_get_iter_first(model, &iter)) continue;
3056 if(!_find_iter_by_parentid_and_formid(model, parentid, formid, &iter)) continue;
3057
3058 found = TRUE;
3059 if(event == DT_MASKS_EVENT_UPDATE)
3060 _shape_manager_update_item(self, formid, parentid, lm, model, &iter);
3061 }
3062
3063 return found;
3064}
3065
3066static void _shape_manager_handler_callback(gpointer instance __attribute__((unused)), const int formid, const int parentid, const dt_masks_event_t event, dt_lib_module_t *self)
3067{
3068 if(IS_NULL_PTR(self)) return;
3069
3071 if(IS_NULL_PTR(lm)) return;
3072
3073 const gboolean found = _shape_manager_refresh_row(self, lm, formid, parentid, event);
3074
3075 if(found)
3076 {
3077 switch(event)
3078 {
3080 // already done, once per list holding the row
3081 break;
3082
3086 {
3088 }
3089 break;
3090
3091 case DT_MASKS_EVENT_NONE :
3092 default:
3093 {
3094 dt_print(DT_DEBUG_MASKS, "[_shape_manager_handler_callback] Mask event cannot be found.");
3095 }
3096 break;
3097 }
3098 }
3099
3100 else if(event == DT_MASKS_EVENT_RESET)
3101 {
3103 }
3104
3105 /* A change or a deletion whose row cannot be found still rebuilds the whole list. A deleted
3106 * shape may no longer have its row; and a CHANGE raised with the ids (0, 0) names no row at
3107 * all -- it is how "the structure changed" is spelled by the handlers here that raise rather
3108 * than broadcast (adding an existing shape to a group, renaming, changing a combine
3109 * operation), which count on this handler to rebuild the tree. The handlers that rebuild it
3110 * themselves broadcast under gui_reset, which makes the rebuild a no-op for them. */
3111 else if(event == DT_MASKS_EVENT_CHANGE || event == DT_MASKS_EVENT_DELETE || event == DT_MASKS_EVENT_REMOVE)
3112 {
3114 }
3115
3116 else if(event == DT_MASKS_EVENT_ADD)
3117 {
3120 if(IS_NULL_PTR(gui) || !gui->creation)
3122 dt_masks_get_from_id(dt_dev_get_global(), parentid ? parentid : formid));
3123 }
3124
3126}
3127
3128/* Empties or refills the lists on a view switch (see _shape_manager_is_active()). A "special"
3129 * module is never handed view_enter()/view_leave(), hence the signal; it is raised once the new
3130 * view has entered, so the darkroom's modules are loaded by the time the rows are built. */
3131static void _shape_manager_view_changed(gpointer instance __attribute__((unused)),
3132 dt_view_t *old_view __attribute__((unused)),
3133 dt_view_t *new_view __attribute__((unused)), dt_lib_module_t *self)
3134{
3136}
3137
3138/* Both lists order module masks by the pipe order (reverse iop_order), which is read at each
3139 * rebuild, so a reorder has to trigger one. Every reorder path raises this signal: drag and drop,
3140 * an order preset or reset (dt_iop_gui_commit_iop_order_change()), history navigation. */
3141static void _shape_manager_modules_moved(gpointer instance __attribute__((unused)), dt_lib_module_t *self)
3142{
3144}
3145
3146/* Geometry the user gives the shape manager by hand. The height is not ours: the shape list
3147 * carries its own persisted height (dt_ui_scroll_wrap below) and the window follows it. */
3148#define DT_MASKS_PANEL_CONF_WIDTH "plugins/darkroom/masks/windowwidth"
3149#define DT_MASKS_PANEL_CONF_X "plugins/darkroom/masks/window_x"
3150#define DT_MASKS_PANEL_CONF_Y "plugins/darkroom/masks/window_y"
3151
3155{
3156#ifdef GDK_WINDOWING_WAYLAND
3157 return !GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(window));
3158#else
3159 return TRUE;
3160#endif
3161}
3162
3166{
3167 if(!GTK_IS_WINDOW(d->popup_window) || !gtk_widget_get_visible(d->popup_window)) return;
3168
3169 gint width = 0;
3170 gint height = 0;
3171 gtk_window_get_size(GTK_WINDOW(d->popup_window), &width, &height);
3173
3174 if(!_shape_manager_popup_position_is_usable(d->popup_window)) return;
3175
3176 gint x = 0;
3177 gint y = 0;
3178 gtk_window_get_position(GTK_WINDOW(d->popup_window), &x, &y);
3181}
3182
3187{
3188 if(!GTK_IS_WINDOW(d->popup_window)) return;
3189
3190 gint width = 0;
3191 gint height = 0;
3192 gtk_window_get_size(GTK_WINDOW(d->popup_window), &width, &height);
3193
3195 {
3196 const int stored_width = dt_conf_get_int(DT_MASKS_PANEL_CONF_WIDTH);
3197 if(stored_width > 0)
3198 {
3199 width = stored_width;
3200 gtk_window_resize(GTK_WINDOW(d->popup_window), width, MAX(height, 1));
3201 }
3202 }
3203
3204 if(!_shape_manager_popup_position_is_usable(d->popup_window)) return;
3206
3209
3210 // A position saved on a monitor that is no longer attached would strand the panel off screen,
3211 // so it is clamped into the work area of whichever monitor it now lands on.
3212 int clamped_x = x;
3213 int clamped_y = y;
3214 GdkDisplay *display = gtk_widget_get_display(d->popup_window);
3215 if(!IS_NULL_PTR(display))
3216 {
3217 GdkMonitor *monitor = gdk_display_get_monitor_at_point(display, x + width / 2, y + height / 2);
3218 if(IS_NULL_PTR(monitor)) monitor = gdk_display_get_primary_monitor(display);
3219 if(IS_NULL_PTR(monitor) && gdk_display_get_n_monitors(display) > 0)
3220 monitor = gdk_display_get_monitor(display, 0);
3221
3222 if(!IS_NULL_PTR(monitor))
3223 {
3224 GdkRectangle workarea = { 0 };
3225 gdk_monitor_get_workarea(monitor, &workarea);
3226 clamped_x = CLAMP(x, workarea.x, workarea.x + MAX(0, workarea.width - width));
3227 clamped_y = CLAMP(y, workarea.y, workarea.y + MAX(0, workarea.height - height));
3228 }
3229 }
3230
3231 gtk_window_move(GTK_WINDOW(d->popup_window), clamped_x, clamped_y);
3232}
3233
3241{
3243 return win ? gtk_widget_get_allocated_height(win) : DT_PIXEL_APPLY_DPI(1000);
3244}
3245
3254{
3255 const gint ceiling = _shape_manager_height_ceiling();
3256 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
3257 {
3258 const dt_shape_manager_list_t *list = &d->lists[i];
3259 if(!IS_NULL_PTR(list->height_key)) dt_widget_store_int(list->height_key, ceiling);
3260 }
3261}
3262
3263static void _shape_manager_popup_button_toggled_cb(GtkWidget *button, gpointer user_data)
3264{
3265 dt_shape_manager_t *d = (dt_shape_manager_t *)user_data;
3266 if(IS_NULL_PTR(d->popup_window)) return;
3267
3268 const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
3269 if(active == gtk_widget_get_visible(d->popup_window)) return;
3270
3271 if(active)
3272 {
3273 // before mapping: a move applied to a mapped window makes it jump in view
3275
3276 // Also before mapping: dt_ui_scroll_wrap's sizing rule reads this the moment each treeview
3277 // realizes, which show_all() triggers below.
3279
3280 gtk_widget_show_all(d->popup_window);
3281 }
3282 else
3283 {
3285 gtk_widget_hide(d->popup_window);
3286 }
3287}
3288
3293 GdkEvent *event __attribute__((unused)), gpointer user_data)
3294{
3295 dt_shape_manager_t *d = (dt_shape_manager_t *)user_data;
3296 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->popup_button), FALSE);
3297 return TRUE;
3298}
3299
3300/* Idle callback to add the popup button to the module toolbox once the
3301 * module_toolbox proxy has been initialized. Returns FALSE when done so
3302 * it is removed from the idle loop. */
3303/* Published through dev->proxy.masks so the darkroom can draw the mask overlays while the user
3304 * is looking at the manager -- what dt_lib_gui_get_expanded() answered while this was a panel
3305 * section, and what no expander can answer now that it is a window. */
3307{
3308 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return FALSE;
3310 return !IS_NULL_PTR(d->popup_window) && gtk_widget_get_visible(d->popup_window);
3311}
3312
3313static gboolean _shape_manager_add_popup_button_idle(gpointer user_data)
3314{
3315 dt_shape_manager_t *d = (dt_shape_manager_t *)user_data;
3316 if(!d || !d->popup_button) return FALSE;
3317
3319 {
3321 return FALSE; /* stop calling this idle handler */
3322 }
3323 return TRUE; /* try again later */
3324}
3325
3327{
3328 /* initialize ui widgets */
3330 self->data = (void *)d;
3331 d->gui_reset = 0;
3332
3333 // initialise all masks pixbuf. This is needed for the "automatic" cell renderer of the treeview
3334 const int bs2 = DT_PIXEL_APPLY_DPI(13);
3340
3341 // 2. Setup the non-modal popup window
3342 d->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3343 gtk_window_set_title(GTK_WINDOW(d->popup_window), _("Shape Manager"));
3344 gtk_window_set_type_hint(GTK_WINDOW(d->popup_window), GDK_WINDOW_TYPE_HINT_UTILITY);
3345
3346 // NON-MODAL & NO FOCUS STEAL: Prevents window manager from stealing active focus when mapped/shown
3347 // because it contains drawing tools that should draw on main window
3348 gtk_window_set_modal(GTK_WINDOW(d->popup_window), FALSE);
3349 gtk_window_set_focus_on_map(GTK_WINDOW(d->popup_window), FALSE);
3350 gtk_window_set_accept_focus(GTK_WINDOW(d->popup_window), FALSE);
3351 gtk_window_set_transient_for(GTK_WINDOW(d->popup_window), GTK_WINDOW(dt_gui_main_window()));
3352
3353 // Being transient for the main window does not decide where the window manager puts this
3354 // one: with no position asked for, it lands at the root origin, i.e. on the leftmost
3355 // monitor rather than on the one the application sits on. GTK honours the hint on the
3356 // first mapping only, so a panel the user has dragged elsewhere keeps its place.
3357 gtk_window_set_position(GTK_WINDOW(d->popup_window), GTK_WIN_POS_CENTER_ON_PARENT);
3358
3359 /* No width request of its own: the window's own minimum is whatever the two lists below need
3360 * -- their own content (names are never ellipsized, so a long one raises that floor) or their
3361 * min-content-width default when there is none -- added together, and it grows further with
3362 * whatever the user drags the paned or the frame to. Heights come from each list's
3363 * dt_ui_scroll_wrap() rule. */
3364
3365#ifdef GDK_WINDOWING_QUARTZ
3366 dt_osx_disallow_fullscreen(d->popup_window);
3367#endif
3368
3369 // Intercept the window close action to hide the widget instead of completely destroying it
3370 g_signal_connect(G_OBJECT(d->popup_window), "delete-event", G_CALLBACK(_shape_manager_popup_delete_cb), d);
3371
3372 // 3. Create a clean box container inside the popup window to receive original shape elements
3373 GtkWidget *shape_manager_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3374 // The window is its own frame, so nothing would otherwise separate the content from its edges.
3375 // DT_GUI_BOX_SPACING is the 0.625em the theme gives a module body (.dt_plugin_ui_main), so the
3376 // panel breathes the same way a side-panel module does, at any font size.
3377 gtk_container_set_border_width(GTK_CONTAINER(shape_manager_container), DT_GUI_BOX_SPACING);
3378 gtk_container_add(GTK_CONTAINER(d->popup_window), shape_manager_container);
3379
3380 // No panel body: this module's interface is the window built below, so self->widget stays
3381 // NULL and the module is never packed -- dt_lib_is_visible_in_view() already excludes a
3382 // "special" view from every panel.
3383
3384 // Create and pack the button to control the popup panel.
3385 // NOTE: it's added to the darkroom module toolbox, aka not here.
3386 d->popup_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_drawn, 0, NULL);
3387 gtk_widget_set_tooltip_text(d->popup_button, _("Open shape manager..."));
3388
3389 /* module_toolbox may not be initialized yet when modules are being created.
3390 * Schedule adding the popup button via an idle callback so it runs after
3391 * other modules (including the module_toolbox) have had their gui_init
3392 * called. The callback will remove itself once it succeeds. */
3393 g_idle_add((GSourceFunc)_shape_manager_add_popup_button_idle, d);
3394 g_signal_connect(G_OBJECT(d->popup_button), "toggled", G_CALLBACK(_shape_manager_popup_button_toggled_cb), d);
3395
3396 // From here, everything goes into the shape manager popup,
3397 // so there is no child added to self->widget from here.
3398 const dt_masks_shape_buttons_config_t shape_buttons_config = {
3400 .owner_module = NULL,
3401 .creation_module = NULL,
3402 .buttons = NULL, // nothing here reads the individual buttons back
3403 .types = NULL,
3404 .action_section = NULL,
3406 .register_flags = DT_MASKS_SHAPE_BUTTONS_NONE,
3407 .local = FALSE,
3408 .user_data = NULL,
3409 .can_start = NULL,
3410 .form_type = NULL,
3412 .exited = NULL,
3413 };
3414 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3415 GtkWidget *shape_buttons_box = dt_masks_shape_buttons_create(&shape_buttons_config);
3416 gtk_box_pack_start(GTK_BOX(hbox), shape_buttons_box, FALSE, FALSE, 0);
3417 // The button row keeps its natural height and stays at the top: expanding it would split the
3418 // surplus with the shape list below and stretch the buttons vertically.
3419 gtk_box_pack_start(GTK_BOX(shape_manager_container), hbox, FALSE, FALSE, 0);
3420
3421 /* The "used by" icon, tinted once for both trees. It is a remark about the shape rather than
3422 * something to click, so it is drawn in the theme's own disabled grey -- a flat grey, next to
3423 * the action icon it sits beside, rather than the washed-out foreground a GtkCellRenderer's
3424 * insensitive state produces. A cell renderer has no colour of its own, hence a pre-tinted
3425 * pixbuf; the model still only carries whether a row shows one. */
3426 GdkRGBA used_color;
3427 if(!gtk_style_context_lookup_color(gtk_widget_get_style_context(shape_manager_container),
3428 "disabled_fg_color", &used_color))
3429 used_color = (GdkRGBA){ 0.62, 0.62, 0.62, 1.0 };
3430
3431 d->ic_used = dt_gui_symbolic_icon_pixbuf("mail-attachment-symbolic", GTK_ICON_SIZE_MENU, &used_color, NULL);
3432
3433 // The "+" in its available and unavailable tints: the plain foreground against the same
3434 // disabled grey the "used by" icon uses, which is the contrast that reads as a dead button.
3435 GdkRGBA fg_color;
3436 if(!gtk_style_context_lookup_color(gtk_widget_get_style_context(shape_manager_container),
3437 "fg_color", &fg_color))
3438 fg_color = (GdkRGBA){ 1.0, 1.0, 1.0, 1.0 };
3439
3440 d->ic_add = dt_gui_symbolic_icon_pixbuf("list-add-symbolic", GTK_ICON_SIZE_MENU, &fg_color, NULL);
3441 d->ic_add_off = dt_gui_symbolic_icon_pixbuf("list-add-symbolic", GTK_ICON_SIZE_MENU, &used_color, NULL);
3442 d->ic_assign = dt_gui_symbolic_icon_pixbuf("view-list-symbolic", GTK_ICON_SIZE_MENU, &fg_color, NULL);
3443
3444 /* The two lists sit side by side in a paned, so the split is the user's and persists. Each
3445 * half is built identically -- the only thing that differs between them is which forms their
3446 * store holds, which _tree_store_build() decides from list->which. */
3447 GtkWidget *lists_paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
3448 // Named so the theme can draw the divider between the two lists: a paned's handle is invisible
3449 // by default, and these are two separate inventories rather than two views of one thing.
3450 gtk_widget_set_name(lists_paned, "shape-manager-lists");
3451
3452 static const struct
3453 {
3454 const char *title;
3455 const char *tooltip;
3456 const char *height_key;
3457 } list_defs[DT_SHAPE_LIST_COUNT] = {
3458 [DT_SHAPE_LIST_SHAPES] = { N_("All shapes"),
3459 N_("Every shape and group drawn on this image, including the masks "
3460 "modules already use -- pick any of them up to reuse."),
3461 "plugins/darkroom/masks/windowheight" },
3462 [DT_SHAPE_LIST_MODULES] = { N_("Module groups"),
3463 N_("The masks modules actually render, and the shapes each one is made of."),
3464 "plugins/darkroom/masks/moduleslistheight" },
3465 };
3466
3467 for(int i = 0; i < DT_SHAPE_LIST_COUNT; i++)
3468 {
3469 dt_shape_manager_list_t *list = &d->lists[i];
3470 list->which = (dt_shape_list_t)i;
3471 list->self = self;
3472 list->height_key = list_defs[i].height_key;
3473 list->treeview = gtk_tree_view_new();
3474
3475 GtkTreeViewColumn *col = gtk_tree_view_column_new();
3476 gtk_tree_view_append_column(GTK_TREE_VIEW(list->treeview), col);
3477
3478 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
3479 gtk_tree_view_column_pack_start(col, renderer, FALSE);
3480 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_OP, NULL);
3481 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_OP_VISIBLE);
3482
3483 renderer = gtk_cell_renderer_pixbuf_new();
3484 gtk_tree_view_column_pack_start(col, renderer, FALSE);
3485 gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", TREE_IC_INVERSE, NULL);
3486 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_INVERSE_VISIBLE);
3487
3488 /* No ellipsize: a name that does not fit is not something to shorten, it is something the
3489 * column has to make room for. Measured offscreen -- with no ellipsize set, a GtkTreeView
3490 * reports its true, full-content preferred width, and that width propagates all the way up
3491 * through a GTK_POLICY_NEVER scrolled window to the window itself; gtk_window_resize() (used
3492 * to restore a persisted width below) cannot force the window narrower than that reported
3493 * minimum, GTK clamps it back up. So the name is simply never compressed, in either list, by
3494 * a narrow paned split or a narrow restored window -- both floors hold at the content's own
3495 * minimum instead. */
3496 renderer = gtk_cell_renderer_text_new();
3497 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3498 gtk_tree_view_column_add_attribute(col, renderer, "text", TREE_TEXT);
3499 /* A repeat of a shape the mask already applies is struck through, so the row says on its own
3500 * name what the note spells out to its right. Struck, never greyed or left out: the repeat is
3501 * not necessarily a no-op -- union and intersection are idempotent, difference and exclusion
3502 * are not -- so the list may mark it and may not claim it does nothing. */
3503 gtk_tree_view_column_add_attribute(col, renderer, "strikethrough", TREE_STRIKE);
3504 // No "editable" attribute: see _tree_start_name_editing(), the only place that opens the editor.
3505 g_signal_connect(renderer, "edited", (GCallback)_tree_cell_edited, list);
3506 g_signal_connect(renderer, "editing-started", (GCallback)_tree_name_editing_started, list);
3507 // Kept so a double-clicked row, or a freshly created group's, can be opened into editing.
3508 list->name_col = col;
3509 list->name_renderer = renderer;
3510
3511 /* Measured offscreen: of two renderers packed at a column's end, the FIRST one packed lands
3512 * at the true right edge, and each renderer packed after it sits closer to the main content
3513 * instead -- so the icon has to be packed before the note text, not after, to end up to the
3514 * note's right. */
3515 renderer = gtk_cell_renderer_pixbuf_new();
3516 // A theme with no symbolic variant of that icon leaves the pixbuf NULL: name the icon instead
3517 // and let GTK draw it, untinted, rather than show nothing.
3518 if(IS_NULL_PTR(d->ic_used))
3519 g_object_set(renderer, "icon-name", "mail-attachment-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3520 else
3521 g_object_set(renderer, "pixbuf", d->ic_used, NULL);
3522 gtk_tree_view_column_pack_end(col, renderer, FALSE);
3523 gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_USED_VISIBLE);
3524
3525 /* What the row has to say about itself, in italics, sitting to the LEFT of the "used by" icon
3526 * above (packed after it, per the same measurement) so it reads as an annotation on the name
3527 * rather than as part of it. Empty on every row that has nothing to add, which costs those no
3528 * space. */
3529 renderer = gtk_cell_renderer_text_new();
3530 g_object_set(renderer, "style", PANGO_STYLE_ITALIC, "xalign", 1.0f, NULL);
3531 gtk_cell_renderer_set_sensitive(renderer, FALSE);
3532 gtk_tree_view_column_pack_end(col, renderer, FALSE);
3533 gtk_tree_view_column_add_attribute(col, renderer, "text", TREE_NOTE);
3534
3535 /* The per-row action icon, to the right of everything the name column carries -- the "used
3536 * by" icon included, since that one is packed at that column's end. Both renderers live in
3537 * this one column and exactly one of them is visible on any row, so the icon lands in the
3538 * same place whichever action the row offers. The name column expands to take up the slack,
3539 * which is what keeps the action flush right. Clicks are answered in _tree_button_pressed()
3540 * by comparing the column, the way develop/blend_gui.c does for the same two icons. */
3541 gtk_tree_view_column_set_expand(col, TRUE);
3542
3543 /* The inventory's "+", between the "used by" icon and the trash. Shown on the same rows the
3544 * trash is -- the top-level ones -- and greyed out until the module list points at a mask. */
3545 if(list->which == DT_SHAPE_LIST_SHAPES)
3546 {
3547 list->add_col = gtk_tree_view_column_new();
3548 gtk_tree_view_column_set_sizing(list->add_col, GTK_TREE_VIEW_COLUMN_FIXED);
3549 gtk_tree_view_column_set_fixed_width(list->add_col, DT_PIXEL_APPLY_DPI(24));
3550
3551 renderer = gtk_cell_renderer_pixbuf_new();
3552 // Same fallback as the "used by" icon: a theme with no symbolic variant gets the plain
3553 // named icon, untinted, rather than nothing. The data func swaps the tinted pixbufs in
3554 // when they exist, and sets visibility, so no attribute is bound on this column.
3555 if(IS_NULL_PTR(d->ic_add_off))
3556 g_object_set(renderer, "icon-name", "list-add-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3557 gtk_cell_renderer_set_sensitive(renderer, FALSE);
3558 gtk_tree_view_column_pack_start(list->add_col, renderer, FALSE);
3559 gtk_tree_view_column_set_cell_data_func(list->add_col, renderer, _add_cell_data_func, d, NULL);
3560
3561 gtk_tree_view_append_column(GTK_TREE_VIEW(list->treeview), list->add_col);
3562
3563 /* And next to it, the button that picks the modules instead of reusing a selected mask.
3564 * Always available: it needs nothing selected anywhere. */
3565 list->assign_col = gtk_tree_view_column_new();
3566 gtk_tree_view_column_set_sizing(list->assign_col, GTK_TREE_VIEW_COLUMN_FIXED);
3567 gtk_tree_view_column_set_fixed_width(list->assign_col, DT_PIXEL_APPLY_DPI(24));
3568
3569 renderer = gtk_cell_renderer_pixbuf_new();
3570 if(IS_NULL_PTR(d->ic_assign))
3571 g_object_set(renderer, "icon-name", "view-list-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3572 else
3573 g_object_set(renderer, "pixbuf", d->ic_assign, NULL);
3574 gtk_tree_view_column_pack_start(list->assign_col, renderer, FALSE);
3575 gtk_tree_view_column_add_attribute(list->assign_col, renderer, "visible", TREE_IC_DELETE_VISIBLE);
3576
3577 gtk_tree_view_append_column(GTK_TREE_VIEW(list->treeview), list->assign_col);
3578 }
3579
3580 list->action_col = gtk_tree_view_column_new();
3581 gtk_tree_view_column_set_sizing(list->action_col, GTK_TREE_VIEW_COLUMN_FIXED);
3582 gtk_tree_view_column_set_fixed_width(list->action_col, DT_PIXEL_APPLY_DPI(24));
3583
3584 renderer = gtk_cell_renderer_pixbuf_new();
3585 g_object_set(renderer, "icon-name", "list-remove-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3586 gtk_tree_view_column_pack_start(list->action_col, renderer, FALSE);
3587 gtk_tree_view_column_add_attribute(list->action_col, renderer, "visible", TREE_IC_UNLINK_VISIBLE);
3588
3589 renderer = gtk_cell_renderer_pixbuf_new();
3590 g_object_set(renderer, "icon-name", "user-trash-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3591 gtk_tree_view_column_pack_start(list->action_col, renderer, FALSE);
3592 gtk_tree_view_column_add_attribute(list->action_col, renderer, "visible", TREE_IC_DELETE_VISIBLE);
3593
3594 gtk_tree_view_append_column(GTK_TREE_VIEW(list->treeview), list->action_col);
3595
3596 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->treeview));
3597 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
3598 gtk_tree_selection_set_select_function(selection, _tree_restrict_select, d, NULL);
3599 // Only the inventory list carries a rule; the module list holds groups alone.
3600 if(list->which == DT_SHAPE_LIST_SHAPES)
3601 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(list->treeview), _tree_row_is_separator, NULL, NULL);
3602 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list->treeview), FALSE);
3603 // A query-tooltip handler rather than a tooltip column: only the rows that carry a "used by"
3604 // text show one, which a column would not let us decide per row.
3605 g_object_set(list->treeview, "has-tooltip", TRUE, (gchar *)0);
3606 g_signal_connect(list->treeview, "query-tooltip", G_CALLBACK(_tree_query_tooltip), list);
3607 g_signal_connect(selection, "changed", G_CALLBACK(_tree_selection_change), list);
3608 g_signal_connect(list->treeview, "button-press-event", (GCallback)_tree_button_pressed, list);
3609 g_signal_connect(list->treeview, "row-activated", (GCallback)_tree_row_activated, list);
3610
3611 /* Each half is a titled section of its own: with two trees side by side and no column
3612 * headers, the heading is what says which is which, and the rule the theme draws under a
3613 * section label is what closes each list off from the other. */
3614 GtkWidget *half = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3615 gtk_widget_set_name(half, "shape-manager-list");
3616 GtkWidget *title = dt_ui_section_label_new(_(list_defs[i].title));
3617 gtk_widget_set_tooltip_text(title, _(list_defs[i].tooltip));
3618 gtk_box_pack_start(GTK_BOX(half), title, FALSE, FALSE, 0);
3619
3620 // Auto-grows to its content (the window scrolls) up to a user-set, persisted height, plus one
3621 // blank row so a list that fits shows that nothing is left below it.
3622 GtkWidget *wrapper = dt_ui_scroll_wrap(list->treeview, 90, list_defs[i].height_key,
3625
3626 /* A default floor for an EMPTY list, where the treeview's own content-derived minimum is
3627 * near zero: without this, an empty paned half could be dragged down to nothing. It never
3628 * shrinks a list below its actual content, though -- gtk_scrolled_window_set_min_content_width()
3629 * only raises the reported minimum when it is the larger of the two; a longer name simply
3630 * wins on its own, per the name renderer's own comment above. */
3632 if(GTK_IS_SCROLLED_WINDOW(scrolled))
3633 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scrolled), DT_PIXEL_APPLY_DPI(190));
3634
3635 gtk_box_pack_start(GTK_BOX(half), wrapper, TRUE, TRUE, 0);
3636
3637 /* Measured offscreen: with both sides resize=TRUE, GtkPaned splits any width the window
3638 * gains between the two -- the divider drifts to keep an even split, rather than staying
3639 * where it was left. The inventory (pack1) is pinned instead (resize=FALSE) so growing the
3640 * window hands all of the new width to the module list (pack2, resize=TRUE) and the divider
3641 * itself does not move; a manual drag still repositions it normally either way. */
3642 if(i == 0)
3643 gtk_paned_pack1(GTK_PANED(lists_paned), half, FALSE, FALSE);
3644 else
3645 gtk_paned_pack2(GTK_PANED(lists_paned), half, TRUE, FALSE);
3646 }
3647
3648 gtk_box_pack_start(GTK_BOX(shape_manager_container), lists_paned, TRUE, TRUE, 0);
3649
3652 G_CALLBACK(_shape_manager_view_changed), self);
3654 G_CALLBACK(_shape_manager_modules_moved), self);
3655
3656 // Modules are loaded before any view is entered: start out empty and greyed out.
3658
3659 // set proxy functions
3660 dt_dev_get_global()->proxy.masks.module = self;
3666}
3667
3669{
3670 if(IS_NULL_PTR(self->data)) return;
3671 if(self && self->data)
3672 {
3674
3675 // Destroy window allocation to prevent leaks
3676 if(d->popup_window)
3677 {
3678 // leaving with the panel open still counts as where the user left it
3680 gtk_widget_destroy(d->popup_window);
3681 d->popup_window = NULL;
3682 }
3683
3684 if(!IS_NULL_PTR(d->ic_used)) g_object_unref(d->ic_used);
3685 if(!IS_NULL_PTR(d->ic_add)) g_object_unref(d->ic_add);
3686 if(!IS_NULL_PTR(d->ic_add_off)) g_object_unref(d->ic_add_off);
3687 if(!IS_NULL_PTR(d->ic_assign)) g_object_unref(d->ic_assign);
3688 if(!IS_NULL_PTR(d->ic_inverse)) g_object_unref(d->ic_inverse);
3689 if(!IS_NULL_PTR(d->ic_union)) g_object_unref(d->ic_union);
3690 if(!IS_NULL_PTR(d->ic_intersection)) g_object_unref(d->ic_intersection);
3691 if(!IS_NULL_PTR(d->ic_difference)) g_object_unref(d->ic_difference);
3692 if(!IS_NULL_PTR(d->ic_exclusion)) g_object_unref(d->ic_exclusion);
3693
3694 d->ic_used = NULL;
3695 d->ic_add = NULL;
3696 d->ic_add_off = NULL;
3697 d->ic_assign = NULL;
3698 d->ic_inverse = NULL;
3699 d->ic_union = NULL;
3700 d->ic_intersection = NULL;
3701 d->ic_difference = NULL;
3702 d->ic_exclusion = NULL;
3703 }
3704
3705 dt_free(self->data);
3706
3710}
3711
3712// clang-format off
3713// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3714// vim: shiftwidth=2 expandtab tabstop=2 cindent
3715// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3716// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
GtkWidget * dt_gui_main_window(void)
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Definition ashift.c:4949
#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:883
#define m
Definition basecurve.c:283
void dt_iop_gui_blend_masks_update(dt_iop_module_t *module)
Definition blend_gui.c:3700
gboolean dt_iop_gui_blend_set_drawn_mask_group(dt_iop_module_t *module, const int group_id)
Definition blend_gui.c:1745
The blending panel's GUI state and API, cut out of develop/blend.h.
static const float x
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const int row
int dt_conf_key_exists(const char *key)
Does key have a value?
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void reset(dt_view_t *self)
Definition darkroom.c:1222
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
#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:1355
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1854
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1865
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
void dt_gui_refocus_parent(GtkWindow *parent)
Definition dialog.c:64
GtkWidget * window
GtkTreeStore * store
its model, owned by the view
GHashTable * selected
set of checked row labels, mirrored to conf on every change
static GdkPixbuf * dt_draw_get_pixbuf_from_cairo(DTGTKCairoPaintIconFunc paint, const int width, const int height)
Definition draw.h:1116
const int res
Definition dtpthread.h:351
const dt_collection_sort_t items[]
Definition filter.c:102
const char * tooltip
Definition image.h:310
gboolean dt_iop_module_supports_drawn_mask(dt_iop_module_t *iop)
Definition imageop.c:785
gboolean dt_iop_module_is_in_pipeline(dt_iop_module_t *iop)
Definition imageop.c:778
int groups()
const char * model
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
GdkPixbuf * dt_gui_symbolic_icon_pixbuf(const char *icon_name, GtkIconSize size, const GdkRGBA *color, GtkStyleContext *context)
Load a symbolic icon as a pre-tinted pixbuf, at the pixel size size names.
Definition label.c:29
static gboolean _modchooser_button_press(GtkWidget *dialog, const GdkEventButton *event, gpointer user_data __attribute__((unused)))
static gboolean _tree_button_tooltip(const dt_shape_manager_list_t *list, GtkTreeView *tree_view, const gint x, const gint y, GtkTooltip *tooltip)
static void _tree_row_assign_to_modules(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter)
static void _tree_add_exist(GtkButton *button, dt_masks_form_t *grp)
static void _tree_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, dt_shape_manager_list_t *list)
dt_shape_list_t
@ DT_SHAPE_LIST_MODULES
@ DT_SHAPE_LIST_SHAPES
@ DT_SHAPE_LIST_COUNT
static void _is_form_used(const int formid, char *text, const size_t text_length, int *nb)
static void _tree_start_name_editing(const dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path, const gboolean cursor_at_end)
dt_modchooser_col_t
@ MODCHOOSER_SENSITIVE
@ MODCHOOSER_MODULE
@ MODCHOOSER_WAS_CHECKED
@ MODCHOOSER_NAME
@ MODCHOOSER_COUNT
@ MODCHOOSER_CHECKED
@ MODCHOOSER_NOTE
dt_masks_tree_cols_t
@ TREE_EDITABLE
@ TREE_IC_OP_VISIBLE
@ TREE_IC_INVERSE_VISIBLE
@ TREE_IC_INVERSE
@ TREE_IS_SEPARATOR
@ TREE_IC_OP
@ TREE_MODULE
@ TREE_USED_TEXT
@ TREE_STRIKE
@ TREE_IC_DELETE_VISIBLE
@ TREE_FORMID
@ TREE_COUNT
@ TREE_IC_UNLINK_VISIBLE
@ TREE_IC_USED_VISIBLE
@ TREE_NOTE
@ TREE_TEXT
@ TREE_GROUPID
static void _shape_manager_broadcast(dt_lib_module_t *self, const int formid, const int parentid, const dt_masks_event_t event)
static void _tree_add_shape_menu_item(GtkWidget *menu, const dt_masks_type_t type, dt_iop_module_t *module)
static int _selected_group_in_module_list(const dt_shape_manager_t *lm)
static gboolean _tree_restrict_select(GtkTreeSelection *selection, GtkTreeModel *model __attribute__((unused)), GtkTreePath *path, gboolean path_currently_selected, gpointer data)
static void _shape_manager_selection_change(dt_lib_module_t *self, struct dt_iop_module_t *module, const int selectid, const int throw_event)
static void _menu_append_new_shape_submenu(GtkMenuShell *menu, dt_iop_module_t *module)
static void _tree_moveup(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
static void _groups_naming_form(const int formid, const dt_masks_form_t *grp, char *text, const size_t text_length, int *nb)
static gchar * _tooltip_add_text(const dt_shape_manager_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
static void _shape_manager_modules_moved(gpointer instance __attribute__((unused)), dt_lib_module_t *self)
static void _menu_append_separator(GtkMenuShell *menu)
static gboolean _find_iter_by_parentid_and_formid(GtkTreeModel *model, int parentid, int formid, GtkTreeIter *iter)
static int _tree_apply_click_selection(GtkWidget *treeview, GtkTreeSelection *selection, const GdkEventButton *event, GtkTreePath *mouse_path)
static void _shape_manager_popup_restore_geometry(dt_shape_manager_t *d)
Put the panel back where it was left, before it is mapped. With nothing stored – first run,...
static gboolean _row_can_be_added(const dt_shape_manager_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
static void _set_iter_name(dt_shape_manager_t *lm, dt_masks_form_t *form, int state, float opacity, GtkTreeModel *model, GtkTreeIter *iter, int index)
static void _tree_row_action(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter)
static gboolean _find_child_iter_by_formid(GtkTreeModel *model, GtkTreeIter *parent_iter, int formid, GtkTreeIter *child_iter)
static void _menu_append_join_group(GtkMenuShell *menu, dt_shape_manager_list_t *list)
static gboolean _remove_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static void _modchooser_toggled(GtkCellRendererToggle *cell __attribute__((unused)), const gchar *path_string, GtkListStore *store)
static void _shape_manager_popup_button_toggled_cb(GtkWidget *button, gpointer user_data)
#define DT_MASKS_PANEL_CONF_WIDTH
static void _add_cell_data_func(GtkTreeViewColumn *col __attribute__((unused)), GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
static gboolean _group_is_module_mask(const dt_masks_form_t *form)
static void _shape_manager_relax_height_caps(const dt_shape_manager_t *d)
Forget how far the user last dragged each list, so the auto-sizing pass that follows measures true,...
static GtkWidget * _tree_context_menu(GtkTreeSelection *selection, GtkTreeModel *model, dt_shape_manager_list_t *list, dt_iop_module_t *module)
static void _shape_manager_popup_save_geometry(dt_shape_manager_t *d)
Remember where the user put the panel and how wide they made it. Called on every path that takes the ...
static void _shape_manager_update_list(dt_lib_module_t *self)
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)
void gui_cleanup(dt_lib_module_t *self)
static void _tree_restore_selection(dt_shape_manager_list_t *list, GtkTreeModel *model, const GList *selectids)
static gboolean _tree_row_is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data __attribute__((unused)))
static void _shape_manager_list_recurs(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_shape_manager_t *lm, const _tree_row_t *row)
static gboolean _shape_manager_popup_position_is_usable(GtkWidget *window)
Is this window on a backend where absolute coordinates mean anything? Wayland gives a client neither ...
static gint _shape_manager_height_ceiling(void)
The toolbox button is the panel's only state: showing and hiding both go through its active flag,...
static int _tree_root_formid(GtkTreeModel *model, GtkTreeIter iter)
static gboolean _shape_manager_is_active(void)
const char ** views(dt_lib_module_t *self __attribute__((unused)))
static const char * _tooltip_action_text(const int grid)
static int _tree_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_shape_manager_list_t *list)
static void _add_masks_history_item(dt_shape_manager_t *lm)
static void _shape_manager_sync_add_sensitivity(const dt_shape_manager_t *lm)
static gboolean _shape_manager_refresh_row(dt_lib_module_t *self, dt_shape_manager_t *lm, const int formid, const int parentid, const dt_masks_event_t event)
static void _shape_manager_view_changed(gpointer instance __attribute__((unused)), dt_view_t *old_view __attribute__((unused)), dt_view_t *new_view __attribute__((unused)), dt_lib_module_t *self)
#define DT_MASKS_PANEL_CONF_X
static GtkTreeStore * _tree_store_build(dt_shape_manager_t *lm, const dt_shape_list_t which)
static void _tree_delete_shape(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
static void _modchooser_collect(GtkListStore *store, GList **to_attach, GList **to_detach)
static void _tree_row_add_to_group(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter)
static void _menu_append_existing_shapes(GtkMenuShell *menu, dt_masks_form_t *grp, const int grpid, dt_iop_module_t *module)
static void _tree_edit_group_name(dt_lib_module_t *self, dt_shape_list_t which, int formid, gboolean cursor_at_end)
static gboolean _shape_manager_add_popup_button_idle(gpointer user_data)
static gboolean _modchooser_run(const dt_masks_form_t *form, GList **to_attach, GList **to_detach)
static void _shape_manager_handler_callback(gpointer instance __attribute__((unused)), const int formid, const int parentid, const dt_masks_event_t event, dt_lib_module_t *self)
static gboolean _tree_edit_group_name_idle(gpointer user_data)
static gboolean _tree_store_add_forms(GtkTreeStore *treestore, dt_shape_manager_t *lm, const dt_shape_list_t which, const gboolean groups)
uint32_t container(dt_lib_module_t *self __attribute__((unused)))
gboolean _find_mask_iter_by_values(GtkTreeModel *model, GtkTreeIter *iter, const dt_iop_module_t *module, const int formid, const int level)
GList * _shape_manager_get_selected(dt_shape_manager_list_t *list)
static void _menu_append_operations(GtkMenuShell *menu, dt_shape_manager_list_t *list, const int nb)
static dt_iop_module_t * _module_owning_group(const dt_masks_form_t *group)
static void _tree_duplicate_shape(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
static void _tree_group(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
static void _tree_apply_operation(GtkWidget *menu_item, dt_shape_manager_list_t *list)
static gboolean _tree_store_append_group_row(GtkTreeStore *treestore, dt_shape_manager_t *lm, dt_masks_form_t *form, const gboolean is_module_mask, const gboolean flat)
static void _shape_manager_recreate_list(dt_lib_module_t *self)
static void _tree_add_shape(GtkWidget *menu_item, dt_iop_module_t *module)
static GtkTreeStore * _tree_store_new(void)
#define DT_MASKS_PANEL_CONF_Y
static void _shape_manager_remove_item_from(const dt_shape_manager_list_t *list, int formid, int parentid)
static void _show_masks_on_owning_module(GtkTreeModel *model, GtkTreeIter *iter)
static void _shape_manager_get_values(GtkTreeModel *model, GtkTreeIter *iter, dt_iop_module_t **module, int *groupid, int *formid)
void gui_init(dt_lib_module_t *self)
static gboolean _shape_manager_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_reveal_row(dt_shape_manager_list_t *list, GtkTreeModel *model, GtkTreeIter *iter, const gboolean exclusive)
static dt_masks_form_t * _module_create_own_mask(dt_develop_t *dev, dt_iop_module_t *module, int *own_id)
static void _refresh_owning_modules(const dt_masks_form_t *grp)
static void _tree_name_caret_to_end(GtkWidget *editable, gpointer user_data __attribute__((unused)))
static void _tree_name_editing_done(GtkCellEditable *editable __attribute__((unused)), dt_shape_manager_t *lm)
static void _tree_movedown(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
static void _tree_cell_edited(GtkCellRendererText *cell __attribute__((unused)), const gchar *path_string, const gchar *new_text, dt_shape_manager_list_t *list)
static void _tree_delete_unused(GtkButton *button __attribute__((unused)), dt_shape_manager_list_t *list)
static gchar * _tooltip_assign_text(GtkTreeModel *model, GtkTreeIter *iter)
static gboolean _shape_manager_selection_change_in(dt_shape_manager_t *lm, const dt_shape_manager_list_t *list, struct dt_iop_module_t *module, const int selectid, const int throw_event)
static gboolean _shape_manager_popup_delete_cb(GtkWidget *window __attribute__((unused)), GdkEvent *event __attribute__((unused)), gpointer user_data)
Closing from the window manager hides the panel, same as the toolbox button, so its widgets and state...
static gboolean _modchooser_button_pressed(GtkWidget *treeview, const GdkEventButton *event, GtkListStore *store)
static gboolean _tree_select_module_group(dt_shape_manager_list_t *list, GtkTreeModel *model, const dt_masks_form_gui_t *gui)
static void _tree_add_to_group(GtkMenuItem *item, dt_shape_manager_list_t *list)
static gboolean _shape_manager_is_window_visible(dt_lib_module_t *self)
static GList * _modules_owning_group(const dt_masks_form_t *group)
static gboolean _tree_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, gpointer data)
static void _tree_selection_change(GtkTreeSelection *selection, dt_shape_manager_list_t *list)
static void _tree_append_row(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_shape_manager_t *lm, const _tree_row_t *row, GtkTreeIter *child)
static GList * _selected_form_ids(dt_shape_manager_list_t *list)
static void _tree_name_editing_started(GtkCellRenderer *renderer __attribute__((unused)), GtkCellEditable *editable, const gchar *path __attribute__((unused)), dt_shape_manager_list_t *list)
static void _shape_manager_remove_item(dt_lib_module_t *self, int formid, int parentid)
static gboolean _form_can_join_group(dt_develop_t *dev, const int group_id, const int fid)
static void _shape_manager_update_item(dt_lib_module_t *self __attribute__((unused)), int formid, int parentid, dt_shape_manager_t *lm, GtkTreeModel *model, GtkTreeIter *iter)
static void _shape_manager_shape_button_started(GtkWidget *button __attribute__((unused)), dt_iop_module_t *module __attribute__((unused)), dt_masks_type_t type __attribute__((unused)), gpointer user_data __attribute__((unused)))
static gboolean _update_foreach(GtkTreeModel *model, GtkTreePath *path __attribute__((unused)), GtkTreeIter *iter, gpointer data)
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_MASKS
Definition logging.h:62
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
dt_masks_result_t dt_masks_group_get_member(dt_develop_t *dev, const int group_id, const int formid, dt_masks_member_t *out)
Read one group member by identity.
Definition masks.c:1909
dt_masks_result_t dt_masks_group_set_member_operation(dt_develop_t *dev, const int group_id, const int formid, const dt_masks_state_t operation, dt_masks_member_t *out)
Set a group member's combination operator, or toggle its inversion.
Definition masks.c:1785
gboolean dt_masks_form_get_info(const dt_masks_form_t *form, dt_masks_form_info_t *out)
Definition masks.c:1664
guint dt_masks_group_list(dt_develop_t *dev, dt_masks_form_info_t *out, const guint out_max)
Describe every group of the live list, in dev->forms order, into caller storage.
Definition masks.c:1887
int dt_masks_group_find_holder(dt_develop_t *dev, const int formid)
Which group references formid, searching every group in dev->forms depth-first.
Definition masks.c:1878
dt_masks_result_t dt_masks_group_first_use(dt_develop_t *dev, const int root_id, const int formid, int *holder_id, guint *index, char *holder_name, const size_t holder_name_size)
Where the mask rooted at root_id FIRST applies formid.
Definition masks.c:2013
dt_masks_result_t dt_masks_group_set_name_from_module(dt_develop_t *dev, const int group_id, dt_iop_module_t *module)
Name a group after the module that renders it, by id.
Definition masks.c:1808
dt_masks_result_t dt_masks_group_contains(dt_develop_t *dev, const int container_id, const int needle_id)
Whether container_id holds needle_id, at any depth (and trivially when they are the same id).
Definition masks.c:1942
dt_masks_result_t dt_masks_group_covers_shapes(dt_develop_t *dev, const int group_id, const int target_id, gboolean *has_shapes)
Whether every shape group_id ultimately holds is also held by target_id.
Definition masks.c:1973
int dt_masks_form_duplicate_in_group(dt_develop_t *dev, int group_id, int form_id)
Duplicate a shape (dt_masks_form_duplicate) and, if group_id names a valid group, attach the duplicat...
Definition masks.c:210
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
Definition masks.c:885
void dt_masks_cleanup_unused(dt_develop_t *dev)
Cleanup unused masks and refresh the current forms snapshot.
Definition masks.c:1587
dt_masks_event_t
Definition masks.h:143
@ DT_MASKS_EVENT_NONE
Definition masks.h:144
@ DT_MASKS_EVENT_UPDATE
Definition masks.h:147
@ DT_MASKS_EVENT_RESET
Definition masks.h:150
@ DT_MASKS_EVENT_ADD
Definition masks.h:145
@ DT_MASKS_EVENT_DELETE
Definition masks.h:148
@ DT_MASKS_EVENT_REMOVE
Definition masks.h:146
@ DT_MASKS_EVENT_CHANGE
Definition masks.h:149
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:851
void dt_masks_form_move(dt_masks_form_t *grp, int formid, int up)
Definition masks.c:1349
void dt_masks_form_delete(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *grp, dt_masks_form_t *form)
Definition masks.c:1196
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
Ask the masks module about a shape or a group, instead of reading its structs.
void dt_masks_group_ungroup(dt_develop_t *dev, dt_masks_form_t *dest_group, dt_masks_form_t *group_form)
Definition masks_gui.c:5356
void dt_masks_gui_populate_interaction_sliders(GtkWidget *menu, dt_develop_t *dev, dt_masks_form_t *form, const int group_id, dt_masks_form_gui_t *gui, dt_iop_module_t *module)
Append the full set of shape-parameter sliders (size/fading/rotation or curvature/fade/rotation depen...
Definition masks_gui.c:798
GtkWidget * dt_masks_shape_menu_item_new(GtkWidget *menu, const dt_masks_type_t type, GCallback activate_callback, gpointer user_data)
Append a menu entry for one shape type: its toolbar icon, then its toolbar label.
Definition masks_gui.c:210
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
gboolean dt_masks_gui_confirm_permanent_delete(const char *form_name)
Ask the user to confirm a permanent shape deletion, gated by the "ask_before_delete_mask_shape" pref....
Definition masks_gui.c:602
void dt_masks_append_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
Definition masks_gui.c:2505
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:113
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:308
dt_masks_form_group_t * dt_masks_group_add_form(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
Definition masks_gui.c:5347
void dt_masks_reset_show_masks_icons(dt_develop_t *dev)
Definition masks_gui.c:4909
void dt_masks_set_visible_form(dt_develop_t *dev, dt_masks_form_t *form)
Definition masks_gui.c:1414
int dt_masks_center_view_on_form(dt_develop_t *dev, const dt_masks_form_t *mask_form)
Center the darkroom ROI on a mask form gravity center.
Definition masks_gui.c:5177
gboolean dt_masks_creation_mode_enter(dt_develop_t *dev, dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
Definition masks_gui.c:5762
dt_masks_form_group_t * dt_masks_group_add_form_with_state(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form, const int parentid, const dt_masks_state_t state, const float opacity)
Definition masks_gui.c:5317
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
@ DT_MASKS_SHAPE_BUTTONS_NONE
Definition masks_gui.h:486
@ DT_MASKS_SHAPE_BUTTONS_ALL
Definition masks_gui.h:498
void dt_masks_form_unref(dt_masks_form_t *form)
dt_masks_form_t * dt_masks_cow_touch(dt_develop_t *dev, dt_masks_form_t *form)
#define DT_MASKS_FORM_NAME_LEN
Definition masks_types.h:46
@ DT_MASKS_EDIT_FULL
dt_masks_state_t
Definition masks_types.h:87
@ DT_MASKS_STATE_DIFFERENCE
Definition masks_types.h:94
@ DT_MASKS_STATE_INVERSE
Definition masks_types.h:91
@ DT_MASKS_STATE_INTERSECTION
Definition masks_types.h:93
@ DT_MASKS_STATE_EXCLUSION
Definition masks_types.h:95
@ DT_MASKS_STATE_NONE
Definition masks_types.h:88
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
@ DT_MASKS_STATE_USE
Definition masks_types.h:89
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_POLYGON
Definition masks_types.h:65
@ DT_MASKS_BRUSH
Definition masks_types.h:70
@ DT_MASKS_ELLIPSE
Definition masks_types.h:69
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_GRADIENT
Definition masks_types.h:68
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
@ DT_MASKS_OK
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
const char * name
Definition pdf.h:90
Asking the interface to repaint itself.
void dt_ui_scroll_wrap_reserve_trailing_row(GtkWidget *content_widget)
Make a DT_UI_RESIZE_DYNAMIC list or text view ask for one blank row past its content.
GtkWidget * dt_ui_scroll_wrap_get_scrolled_window(GtkWidget *wrapper)
Return the inner scrolled window of a dt_ui_scroll_wrap() wrapper, or NULL.
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:42
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:315
@ DT_SIGNAL_DEVELOP_MODULE_MOVED
This signal is raised when order of modules in pipeline is changed.
Definition signal.h:221
@ DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED
This signal is raised by viewmanager when a view has changed. 1 : dt_view_t * the old view 2 : dt_vie...
Definition signal.h:84
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float uint32_t state[4]
dt_iop_module_t *int gstate
dt_masks_form_t * form
void(* list_update)(struct dt_lib_module_t *self)
Definition develop.h:413
GList * iop
Definition develop.h:269
void(* list_remove)(struct dt_lib_module_t *self, int formid, int parentid)
Definition develop.h:412
struct dt_lib_module_t *void(* list_change)(struct dt_lib_module_t *self)
Definition develop.h:411
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
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:415
gboolean(* is_visible)(struct dt_lib_module_t *self)
Definition develop.h:418
struct dt_develop_t::@14::@22 masks
struct dt_develop_t::@14 proxy
void * blend_params
Definition develop.h:297
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
dt_dev_operation_t op
Definition imageop.h:259
GModule *void * data
Definition lib.h:79
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
What a form IS, BY VALUE: identity, kind, and – for a group – how many members.
dt_masks_type_t type
dt_masks_type_t type
Definition masks.h:254
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
One shape's membership of one group, BY VALUE.
dt_masks_state_t state
struct dt_develop_t * dev
Definition masks_gui.h:514
GtkTreeViewColumn * add_col
GtkTreeViewColumn * action_col
GtkTreeViewColumn * assign_col
GtkTreeViewColumn * name_col
GtkCellRenderer * name_renderer
dt_shape_manager_list_t lists[DT_SHAPE_LIST_COUNT]
dt_shape_list_t name_edit_which
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@61 module_toolbox
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Telling the user something happened.
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
Definition view.c:1329
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
@ DT_VIEW_DARKROOM
Definition view.h:79
GtkWidget * dt_widget_root_window(void)
void dt_widget_store_int(const char *key, int value)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
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)
@ DT_UI_CONTAINER_SIZE