src/libs/masks_flexi_host.c
Rama master · réplica de lectura del repositorio original.
/*
This file is part of darktable,
Copyright (C) 2026 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/
// the host lib of the flexi masks panel in the "utility module" position
// (MASKS_PANEL_POS_UTILITY, see develop/masks_gui_panel_host.c), in
// DT_UI_CONTAINER_PANEL_LEFT_CENTER and folded by its own expander (see
// expanded_state, which ties on-canvas mask editing to it). The canvas
// position does not use it: that is a grid column of src/gui/gtk.c
// (dt_ui_flexi_panel_*).
//
// The lib stays visible to dt_lib_is_visible (and is not in the modules list,
// see dt_masks_gui_is_host_lib): view.c builds the expander
// (dt_lib_gui_get_expander) only for libs visible when the view is entered,
// so a hidden lib could not take the panel until the next view change. In
// the other positions self->expander is hidden with gtk_widget_hide()
#include "control/conf.h"
#include "control/signal.h"
#include "develop/blend.h"
#include "develop/blend_gui_internal.h"
#include "develop/develop.h"
#include "dtgtk/expander.h"
#include "dtgtk/paint.h"
#include "dtgtk/togglebutton.h"
#include "gui/gtk.h"
#include "libs/lib.h"
#include "libs/lib_api.h"
DT_MODULE(1)
typedef struct dt_lib_masks_flexi_host_t
{
GtkBox *content_box;
GtkBox *toggle_box;
// the stand-in in toggle_box while no module is focused
GtkWidget *idle_toggle;
} dt_lib_masks_flexi_host_t;
static void _reconfigure(dt_lib_module_t *self);
// the modules list no longer offers this lib, but a hidden state stored from
// when it did would still keep view.c from building its expander
static gboolean _is_stale_visible_key(const gpointer key,
const gpointer value,
const gpointer suffix)
{
return g_str_has_prefix(key, "plugins/darkroom/")
&& g_str_has_suffix(key, (const char *)suffix);
}
const char *name(dt_lib_module_t *self)
{
return _("masking");
}
const char *description(dt_lib_module_t *self)
{
return _("blend mask panel of the focused module\n"
"(right-click the mask on/off toggle for its options)");
}
dt_view_type_flags_t views(dt_lib_module_t *self)
{
return DT_VIEW_DARKROOM;
}
uint32_t container(dt_lib_module_t *self)
{
return DT_UI_CONTAINER_PANEL_LEFT_CENTER;
}
int position(const dt_lib_module_t *self)
{
// first in its container: view.c packs the highest position at the top,
// past snapshots (1000), history (900), the duplicate manager (850) and the
// color picker (800). A position the user gives it by dragging is stored
// and wins over this one (_lib_position in libs/lib.c)
return 1100;
}
// show self->expander only in the "utility" position, by gtk_widget_show and
// hide (see the file comment). Only once self->expander exists (view_enter)
static void _reconfigure(dt_lib_module_t *self)
{
if(!self->expander) return;
const int pos = dt_masks_gui_panel_position();
gtk_widget_set_visible(self->expander, pos == MASKS_PANEL_POS_UTILITY);
}
// this lib's expander is the collapse control for the masking panel in the
// "utility module" position -- the counterpart of the canvas panel's edge
// strip and of the embedded position's in-header arrow. Folding it hides the shapes
// list, so the on-canvas editing it drives goes with it (and comes back on
// expand), and a fold the user asked for is recorded as the panel's shared
// collapse preference, the same as in the other two positions. Both are
// masks_gui_panel_host.c's business, not this lib's; forward and let it decide.
void expanded_state(dt_lib_module_t *self, const gboolean expanded)
{
if(!darktable.develop || !darktable.develop->proxy.masks_flexi_host.hosted_module)
{
if(expanded && self->expander)
dt_lib_gui_set_expanded(self, FALSE);
return;
}
dt_iop_gui_blend_masks_panel_host_expanded(expanded);
}
// the header's reset button resets the hosted mask, as "reset mask" in the
// mask group's menu does. Insensitive with nothing hosted or the mask locked
// (dt_iop_gui_blend_mask_header_sync), but a shortcut reaches it all the same
void gui_reset(dt_lib_module_t *self)
{
dt_iop_module_t *module =
darktable.develop ? darktable.develop->proxy.masks_flexi_host.hosted_module : NULL;
if(module) dt_iop_gui_blend_mask_reset(module);
}
/* a raster element's badge depends on its source module, which can be
* switched off, stop masking or be removed without touching this module's
* forms. Each of those adds a history item, so the badges of every module are
* refreshed on a history change: the badge belongs to the reader of the mask,
* not to the module touched. A module with no list returns at once. Here for
* the lib's darkroom-scoped lifecycle; it runs in every panel position */
static void _history_change_callback(gpointer instance, gpointer user_data)
{
if(!darktable.develop) return;
for(GList *m = darktable.develop->iop; m; m = g_list_next(m))
dt_iop_gui_blend_refresh_mask_badges((dt_iop_module_t *)m->data);
}
void gui_init(dt_lib_module_t *self)
{
dt_lib_masks_flexi_host_t *d = g_malloc0(sizeof(dt_lib_masks_flexi_host_t));
self->data = (void *)d;
d->content_box = GTK_BOX(dt_gui_vbox());
gtk_widget_set_name(GTK_WIDGET(d->content_box), "masks-host-content");
// it lives in the expander header, which view.c destroys on every view
// leave and rebuilds on enter: own it, and take it out of the header before
// it goes (see view_leave), or the next darkroom visit packs a dead widget
// and the mask's toggle vanishes with it
d->toggle_box = GTK_BOX(g_object_ref_sink(dt_gui_hbox()));
gtk_widget_set_valign(GTK_WIDGET(d->toggle_box), GTK_ALIGN_CENTER);
// the theme sizes the toggle this box takes in, and only it: the lib's own
// header buttons (its arrow above all) keep every lib's size
dt_gui_add_class(GTK_WIDGET(d->toggle_box), "dt_masks_host_controls");
gtk_widget_show(GTK_WIDGET(d->toggle_box));
// with no module focused there is no mask toggle to take in: a stand-in
// keeps the header's shape, insensitive with the whole lib then. No
// tooltip: the header's says why it does nothing (_utility_header_hosting
// in develop/masks_gui_panel_host.c). no_show_all: shown and hidden by the
// panel host alone
d->idle_toggle = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_panel, 0, NULL);
dt_masks_gui_header_toggle_style(d->idle_toggle);
gtk_widget_set_no_show_all(d->idle_toggle, TRUE);
dt_gui_box_add(d->toggle_box, d->idle_toggle);
self->widget = GTK_WIDGET(d->content_box);
gtk_widget_show_all(self->widget);
darktable.develop->proxy.masks_flexi_host.module = self;
darktable.develop->proxy.masks_flexi_host.content_box = d->content_box;
darktable.develop->proxy.masks_flexi_host.toggle_box = d->toggle_box;
darktable.develop->proxy.masks_flexi_host.idle_toggle = d->idle_toggle;
darktable.develop->proxy.masks_flexi_host.hosted_module = NULL;
darktable.develop->proxy.masks_flexi_host.reconfigure = _reconfigure;
gchar *visible_suffix = g_strdup_printf("/%s_visible", self->plugin_name);
g_hash_table_foreach_remove(darktable.conf->table, _is_stale_visible_key, visible_suffix);
g_free(visible_suffix);
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_HISTORY_CHANGE, _history_change_callback);
// deliberately NOT calling dt_lib_set_visible(self, FALSE) here even
// when the current position isn't utility -- see file comment.
// self->expander doesn't exist yet at this point anyway (view.c builds
// it after gui_init returns); the initial visual state is applied from
// view_enter() instead.
}
void view_enter(dt_lib_module_t *self,
struct dt_view_t *old_view,
struct dt_view_t *new_view)
{
// self->expander now exists (view.c just built it) -- apply the initial
// visual state for the current masks_panel_position
_reconfigure(self);
dt_lib_masks_flexi_host_t *d = (dt_lib_masks_flexi_host_t *)self->data;
if(self->expander && d)
{
GtkWidget *header = DTGTK_EXPANDER(self->expander)->header;
dt_gui_add_class(header, "dt_masks_host_header");
// a lib's own header, with the mask's on/off toggle last, where every
// other lib has its presets menu (hidden below, so the toggle takes its
// place). The mask's other header controls stay on the panel's own header,
// which leads the lib's body (see dt_iop_gui_blend_masks_panel_relocate)
if(!gtk_widget_get_parent(GTK_WIDGET(d->toggle_box)))
{
gtk_box_pack_end(GTK_BOX(header), GTK_WIDGET(d->toggle_box), FALSE, FALSE, 0);
gtk_box_reorder_child(GTK_BOX(header), GTK_WIDGET(d->toggle_box), 0);
}
}
// the lib has no presets or preferences, so lib.c's header hamburger has
// nothing to open; the masking options open on a right-click of the on/off
// toggle in this header (_blendop_mask_enable_toggled). no_show_all: the
// header gets a gtk_widget_show_all on every view enter
if(self->presets_button)
{
gtk_widget_set_no_show_all(self->presets_button, TRUE);
gtk_widget_hide(self->presets_button);
}
dt_iop_module_t *module = darktable.develop ? darktable.develop->gui_module : NULL;
if(module)
{
dt_iop_gui_blend_masks_panel_relocate(module);
}
else
{
if(self->expander) dt_lib_gui_set_expanded(self, FALSE);
dt_masks_gui_utility_header_unhosted(self);
}
}
void view_leave(dt_lib_module_t *self,
struct dt_view_t *old_view,
struct dt_view_t *new_view)
{
dt_lib_masks_flexi_host_t *d = (dt_lib_masks_flexi_host_t *)self->data;
if(!d) return;
GtkWidget *box = GTK_WIDGET(d->toggle_box);
GtkWidget *parent = box ? gtk_widget_get_parent(box) : NULL;
if(parent) gtk_container_remove(GTK_CONTAINER(parent), box);
}
void gui_cleanup(dt_lib_module_t *self)
{
DT_CONTROL_SIGNAL_DISCONNECT(_history_change_callback, self);
darktable.develop->proxy.masks_flexi_host.module = NULL;
darktable.develop->proxy.masks_flexi_host.content_box = NULL;
darktable.develop->proxy.masks_flexi_host.toggle_box = NULL;
darktable.develop->proxy.masks_flexi_host.idle_toggle = NULL;
darktable.develop->proxy.masks_flexi_host.hosted_module = NULL;
darktable.develop->proxy.masks_flexi_host.reconfigure = NULL;
dt_lib_masks_flexi_host_t *d = (dt_lib_masks_flexi_host_t *)self->data;
if(d) g_clear_object(&d->toggle_box);
g_free(self->data);
self->data = NULL;
}
// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on