Ctrl K
ComunidadGuías…

src/libs/masks_flexi_host.c

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
    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