From d3a9ed8c553618a2da59458b66f156317702f4e2 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 6 Jul 2026 23:11:36 +0200 Subject: [PATCH 1/5] Minor code clean-up, use dt_iop_get_module. --- src/develop/develop.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/develop/develop.c b/src/develop/develop.c index eef9b34d3596..7825417ae404 100644 --- a/src/develop/develop.c +++ b/src/develop/develop.c @@ -1464,17 +1464,9 @@ void dt_dev_add_masks_history_item_ext(dt_develop_t *dev, gboolean enable = _enable; // no module means that is called from the mask manager, so find the iop - if(module == NULL) + if (module == NULL) { - for(GList *modules = dev->iop; modules; modules = g_list_next(modules)) - { - dt_iop_module_t *mod = modules->data; - if(dt_iop_module_is(mod, "mask_manager")) - { - module = mod; - break; - } - } + module = dt_iop_get_module("mask_manager"); enable = FALSE; } if(module) From a929a39cdfc87d9d9b2bbeb5dd4f4855d61871b5 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 6 Jul 2026 23:12:51 +0200 Subject: [PATCH 2/5] Minor reformatting & const. --- src/develop/imageop.c | 3 ++- src/libs/lib.c | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/develop/imageop.c b/src/develop/imageop.c index 6fe982d0bbb6..f9736ddfc46c 100644 --- a/src/develop/imageop.c +++ b/src/develop/imageop.c @@ -2816,7 +2816,8 @@ gboolean dt_iop_show_hide_header_buttons(dt_iop_module_t *module, return TRUE; } -static void _display_mask_indicator_callback(GtkToggleButton *bt, dt_iop_module_t *module) +static void _display_mask_indicator_callback(GtkToggleButton *bt, + dt_iop_module_t *module) { DT_GUARD_GUI_UPDATE(); diff --git a/src/libs/lib.c b/src/libs/lib.c index f0cf40a65b3b..54f0584d6ad5 100644 --- a/src/libs/lib.c +++ b/src/libs/lib.c @@ -129,7 +129,7 @@ static void _set_module_preset_label(dt_lib_module_t *module, { if(!module->expander || !module->has_preset_label(module)) return; - + const gchar *current_preset_label_text = gtk_label_get_text(GTK_LABEL(module->preset_label)); gchar *preset_label_text = (*preset_name == '\0' || (!dt_conf_get_bool("darkroom/ui/auto_module_name_update")))? @@ -382,8 +382,8 @@ gboolean dt_lib_presets_apply(const gchar *preset, if(sqlite3_step(stmt) == SQLITE_ROW) { const void *blob = sqlite3_column_blob(stmt, 0); - int length = sqlite3_column_bytes(stmt, 0); - int writeprotect = sqlite3_column_int(stmt, 1); + const int length = sqlite3_column_bytes(stmt, 0); + const int writeprotect = sqlite3_column_int(stmt, 1); if(blob) { for(const GList *it = darktable.lib->plugins; it; it = g_list_next(it)) @@ -987,7 +987,7 @@ static gboolean _presets_popup_callback(GtkButton *button, return TRUE; } -void dt_lib_gui_set_expanded(dt_lib_module_t *module, gboolean expanded) +void dt_lib_gui_set_expanded(dt_lib_module_t *module, const gboolean expanded) { if(!module->expander || !module->arrow) return; From d221b27ece1b98de432b3a280d91cf1732d85339 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 6 Jul 2026 23:13:42 +0200 Subject: [PATCH 3/5] lib_api: Add new optional hook expanded_state. Part of #21436. --- src/libs/lib.c | 4 ++++ src/libs/lib_api.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/libs/lib.c b/src/libs/lib.c index 54f0584d6ad5..31dc578adeb7 100644 --- a/src/libs/lib.c +++ b/src/libs/lib.c @@ -1000,6 +1000,10 @@ void dt_lib_gui_set_expanded(dt_lib_module_t *module, const gboolean expanded) darktable.lib->gui_module = expanded ? module : NULL; + /* if expanded, make sure the module is visible */ + if(module->expanded_state) + module->expanded_state(module, expanded); + /* store expanded state of module */ char var[1024]; const dt_view_t *current_view = dt_view_manager_get_current_view(darktable.view_manager); diff --git a/src/libs/lib_api.h b/src/libs/lib_api.h index f35230975188..6621a320d1e9 100644 --- a/src/libs/lib_api.h +++ b/src/libs/lib_api.h @@ -53,6 +53,8 @@ REQUIRED(uint32_t, container, struct dt_lib_module_t *self); if not the module will always be shown without the expander. */ DEFAULT(gboolean, expandable, struct dt_lib_module_t *self); +OPTIONAL(void, expanded_state, struct dt_lib_module_t *self, const gboolean expanded); + /** constructor */ OPTIONAL(void, init, struct dt_lib_module_t *self); /** callback methods for gui. */ From fc9a7a7874148822d4e2bea58dca383e63accc81 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 6 Jul 2026 23:16:38 +0200 Subject: [PATCH 4/5] masks: Add new routine dt_masks_is_in_module. Returns TRUE is a given mask is used by module. Part of #21436. --- src/develop/masks.h | 2 ++ src/develop/masks/masks.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/develop/masks.h b/src/develop/masks.h index 786a1a80dbfd..e710ce743f2a 100644 --- a/src/develop/masks.h +++ b/src/develop/masks.h @@ -560,6 +560,8 @@ void dt_masks_replace_current_forms(dt_develop_t *dev, GList *forms); dt_masks_form_t *dt_masks_get_from_id_ext(GList *forms, dt_mask_id_t id); /** returns a form with formid == id from dev->forms */ dt_masks_form_t *dt_masks_get_from_id(const dt_develop_t *dev, dt_mask_id_t id); +/** check if a form is used by a given module (directly or as a child of its group) */ +gboolean dt_masks_is_in_module(dt_mask_id_t maskid, const struct dt_iop_module_t *module); /** register forms into the mask manager */ void dt_masks_register_forms(dt_develop_t *dev, GList *forms); diff --git a/src/develop/masks/masks.c b/src/develop/masks/masks.c index 031eb52e104e..49bad8cc2cfd 100644 --- a/src/develop/masks/masks.c +++ b/src/develop/masks/masks.c @@ -319,6 +319,37 @@ static dt_masks_form_t *_group_from_module(const dt_develop_t *dev, return dt_masks_get_from_id(dev, module->blend_params->mask_id); } +static gboolean _form_is_in_group(const dt_develop_t *dev, + const dt_masks_form_t *group, + const dt_mask_id_t maskid) +{ + for(const GList *iter = group->points; iter; iter = g_list_next(iter)) + { + const dt_masks_point_group_t *pt = iter->data; + if(pt->formid == maskid) return TRUE; + + const dt_masks_form_t *child = dt_masks_get_from_id(dev, pt->formid); + if(child && (child->type & DT_MASKS_GROUP)) + { + if(_form_is_in_group(dev, child, maskid)) return TRUE; + } + } + return FALSE; +} + +gboolean dt_masks_is_in_module(const dt_mask_id_t maskid, const dt_iop_module_t *module) +{ + if(!dt_is_valid_maskid(maskid) || !module) return FALSE; + + if(maskid == module->blend_params->mask_id) return TRUE; + + const dt_masks_form_t *root = dt_masks_get_from_id(module->dev, module->blend_params->mask_id); + if(root && (root->type & DT_MASKS_GROUP)) + return _form_is_in_group(module->dev, root, maskid); + + return FALSE; +} + void dt_masks_register_forms(dt_develop_t *dev, GList *forms) { From bd513f41b72bcab748ba5d2ebac8bbf503db440b Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Mon, 6 Jul 2026 23:18:17 +0200 Subject: [PATCH 5/5] masks: Rework the mask handling/edting when collapsing mask manager. This makes it possible to simplify the code in darkroom and moreover is cleaner as the actual check is done at the right place. This fixes the scroll, click, release events when mask manager is closed. Fixes #21436 --- src/develop/masks/masks.c | 3 +- src/libs/masks.c | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/develop/masks/masks.c b/src/develop/masks/masks.c index 49bad8cc2cfd..c69382e4d7a6 100644 --- a/src/develop/masks/masks.c +++ b/src/develop/masks/masks.c @@ -1160,8 +1160,7 @@ gboolean dt_masks_events_mouse_moved(dt_iop_module_t *module, 1. a module and it's not enabled 2. the mask manager and it is not expanded */ - const gboolean skipped = (module && !module->enabled) - && !dt_lib_gui_get_expanded(dt_lib_get_module("masks")); + const gboolean skipped = (module && !module->enabled); dt_print(DT_DEBUG_VERBOSE, "[dt_masks_events_mouse_moved] %s %s", diff --git a/src/libs/masks.c b/src/libs/masks.c index bdbb82415853..0cc282b06893 100644 --- a/src/libs/masks.c +++ b/src/libs/masks.c @@ -36,6 +36,11 @@ DT_MODULE(1) static void _lib_masks_recreate_list(dt_lib_module_t *self); static void _lib_masks_update_list(dt_lib_module_t *self); +static void _lib_masks_get_values(GtkTreeModel *model, + GtkTreeIter *iter, + dt_iop_module_t **module, + dt_mask_id_t *groupid, + dt_mask_id_t *formid); typedef struct dt_lib_masks_t { @@ -82,6 +87,68 @@ int position(const dt_lib_module_t *self) return 10; } +static gboolean _selected_masks_are_used(GtkTreeModel *model, + GList *selected, + dt_iop_module_t *module) +{ + for(GList *item = selected; item; item = g_list_next(item)) + { + GtkTreePath *ipath = (GtkTreePath *)item->data; + GtkTreeIter iter; + if(gtk_tree_model_get_iter(model, &iter, ipath)) + { + dt_mask_id_t id = INVALID_MASKID; + _lib_masks_get_values(model, &iter, NULL, NULL, &id); + + if(dt_is_valid_maskid(id) + && dt_masks_is_in_module(id, module)) + { + return TRUE; + } + } + } + + return FALSE; +} + +void expanded_state(dt_lib_module_t *self, + const gboolean expanded) +{ + // if not expanded we may want to disable the mask + + if (!expanded) + { + dt_develop_t *dev = darktable.develop; + dt_iop_module_t *mod = dev->gui_module; + const dt_iop_gui_blend_data_t *bd = mod ? mod->blend_data : NULL; + + /* + We hide the masks if: + - If the active module is not enabled + - If the active module is enabled but has no mask + - There is some selected masks in mask manage + - If the active module is enabled but the selected masks are not used by the + module. + */ + + dt_lib_masks_t *lm = self->data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview)); + GtkTreeModel *model = NULL; + GList *selected = gtk_tree_selection_get_selected_rows(selection, &model); + + if (!(mod + && mod->enabled + && (mod->blend_params->mask_mode & DEVELOP_MASK_MASK) + && bd->masks_shown != DT_MASKS_EDIT_OFF + && selected + && _selected_masks_are_used(model, selected, mod))) + { + dt_masks_change_form_gui(NULL); + dt_control_queue_redraw_center(); + } + } +} + typedef enum dt_masks_tree_cols_t { TREE_TEXT = 0,