Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
blending.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/* The left panel's home for the focused module's blending controls.
20 *
21 * It owns no blending state and builds no widget of its own beyond a container and a message
22 * label: dt_iop_gui_init_blending_body() fills self->widget with the focused module's own
23 * blending body, and this module's whole job is to decide which module that is, to tear the
24 * previous one down before the next is built, and to say why the panel is empty when no module
25 * can host one.
26 *
27 * The shape manager (libs/shape_manager.c) is a peer, not a parent: it lists the drawn shapes of
28 * the whole image in its own window, while the Drawn tab inside the body below speaks only for
29 * the focused module. Both rest on develop/masks/ for the shapes themselves.
30 */
31
32#include "common/module_versioning.h" // DT_MODULE()
33#include "develop/blend_gui.h" // dt_iop_gui_{init,cleanup}_blending_body(), dt_iop_gui_update_blending()
34#include "develop/develop.h" // dt_dev_get_global(), dt_history_item_get_name()
35#include "develop/imageop.h" // dt_iop_module_t, IOP_FLAGS_SUPPORTS_BLENDING
36#include "develop/imageop_gui.h" // dt_iop_module_gui_t, for module->gui->blend_data
37#include "control/signal.h" // DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
38#include "gui/window_manager.h" // DT_UI_CONTAINER_PANEL_LEFT_CENTER
39#include "libs/lib.h"
40#include "libs/lib_api.h"
41#include "system/macros.h" // IS_NULL_PTR()
42#include "system/mem_alloc.h" // dt_free(), dt_calloc_align(), dt_free_align()
43#include "widgets/widget_settings.h" // DT_GUI_BOX_SPACING, DT_PIXEL_APPLY_DPI()
44
45#include <glib.h>
46#include <gtk/gtk.h>
47
48DT_MODULE(1)
49
50typedef struct dt_lib_blending_t
51{
52 /* The module whose body is currently built into self->widget, and the one the last refresh
53 * ran for. They differ while a refresh is in flight, which is what tells it to tear the
54 * previous body down before building the next. */
58
59
60/* The name the panel section has always carried. The module hosts the whole blending body, whose
61 * Drawn / Parametric / Raster tabs are the three kinds of mask, so "masking" belongs in it -- and
62 * keeping the string spares every translation that already has it. */
63const char *name(struct dt_lib_module_t *self __attribute__((unused)))
64{
65 return _("Masking & Blending");
66}
67
68const char **views(dt_lib_module_t *self __attribute__((unused)))
69{
70 static const char *v[] = {"darkroom", NULL};
71 return v;
72}
73
74uint32_t container(dt_lib_module_t *self __attribute__((unused)))
75{
77}
78
80{
81 return 1;
82}
83
85{
86 return 850;
87}
88
89
90/* A module still in dev->iop. The focused module can be one this panel already tore down, or one
91 * from a dev that has since been replaced, and neither may be dereferenced. */
92static gboolean _module_is_current(const dt_iop_module_t *module)
93{
94 return dt_dev_get_global() && module && g_list_find(dt_dev_get_global()->iop, module);
95}
96
97static void _clear_box(dt_lib_module_t *self)
98{
99 if(IS_NULL_PTR(self) || !GTK_IS_WIDGET(self->widget)) return;
100
101 GList *children = gtk_container_get_children(GTK_CONTAINER(self->widget));
102 for(GList *iter = children; iter; iter = g_list_next(iter))
103 gtk_widget_destroy(GTK_WIDGET(iter->data));
104 g_list_free(children);
105 children = NULL;
106}
107
108static gboolean _can_host(const dt_iop_module_t *module)
109{
110 if(!_module_is_current(module) || !module->flags
111 || !(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || !module->gui || !module->gui->blend_data)
112 return FALSE;
113
114 return TRUE;
115}
116
117static void _release(dt_lib_module_t *self)
118{
119 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
121 dt_iop_module_t *hosted_module = d->hosted_module;
122 d->hosted_module = NULL;
123
124 if(_can_host(hosted_module))
126 else
127 _clear_box(self);
128}
129
130static void _show_message(dt_lib_module_t *self, gchar *markup)
131{
132 if(IS_NULL_PTR(self) || !GTK_IS_WIDGET(self->widget) || IS_NULL_PTR(markup)) return;
133
134 GtkWidget *label = gtk_label_new(NULL);
135 gtk_label_set_markup(GTK_LABEL(label), markup);
136 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
137 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
138 gtk_widget_set_margin_top(label, DT_PIXEL_APPLY_DPI(16));
139 gtk_widget_set_margin_bottom(label, DT_PIXEL_APPLY_DPI(16));
140 gtk_widget_set_sensitive(label, FALSE);
141 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
142 // self->widget is the expander body: its own visibility encodes the
143 // expanded/collapsed state persisted in conf, so show only the child.
144 gtk_widget_show_all(label);
145}
146
147static void _gui_changed_callback(gpointer instance __attribute__((unused)), dt_lib_module_t *self)
148{
149 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
150
152
153 /* dt_dev_get_global() is NULL outside the darkroom -- only the views that own that lifetime
154 * publish it -- so it is read once and tested before anything is read through it. Nothing
155 * reaches this callback that early today (dt_lib_init() runs after dt_view_manager_init(),
156 * which darktable.c refuses to continue without a develop), but the guard was written for a
157 * NULL it then dereferenced one line above it. */
159 dt_iop_module_t *module = IS_NULL_PTR(dev) ? NULL : dev->gui_module;
160
161 if(IS_NULL_PTR(dev) || !dev->history || !module)
162 {
163 _release(self);
164 _clear_box(self);
165
166 gchar *markup = g_markup_printf_escaped(_("<i>Select a module to edit its blending settings.</i>"));
167
168 _show_message(self, markup);
169 g_free(markup);
170
171 return;
172 }
173
174 if(!_can_host(module))
175 {
176 _release(self);
177 _clear_box(self);
178
179 gchar *module_label = dt_history_item_get_name(module);
180 gchar *markup = g_markup_printf_escaped(_("<i>Blending is not available for the <b>%s</b> module.</i>"), module_label);
181
182 _show_message(self, markup);
183 g_free(markup);
184 dt_free(module_label);
185
186 return;
187 }
188
189 const gboolean module_changed = (d->active_module != module);
190 d->active_module = module;
191
192 if(module_changed) _release(self);
193
194 if(!d->hosted_module) _clear_box(self);
195
196 // dt_iop_gui_init_blending_body() shows the children it packs; don't show
197 // self->widget itself, it is the expander body whose visibility encodes the
198 // expanded/collapsed state persisted in conf.
200 d->hosted_module = module;
201
203}
204
205
207{
209 self->data = (void *)d;
210
211 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
212
214 G_CALLBACK(_gui_changed_callback), self);
215
216 // Show the "select a module" message rather than an empty panel on the first display.
217 _gui_changed_callback(NULL, self);
218}
219
221{
223
224 if(!IS_NULL_PTR(self->data))
225 {
226 _release(self);
227 /* gui_init() takes this block from dt_calloc_align(), so it goes back to dt_free_align().
228 * The two families are only interchangeable on Linux, where both end at g_free(). On Windows
229 * the aligned one is _aligned_malloc()/_aligned_free(), and handing one of its blocks to
230 * g_free() is STATUS_HEAP_CORRUPTION (0xc0000374) -- which aborted dt_cleanup() here, before
231 * it ever reached dt_conf_cleanup() and the database teardown further down. That is why the
232 * window vanished but the process died in the background, anselrc kept the previous session's
233 * keys, and the next start found the database still locked (issue #1369).
234 * dt_free_align() NULLs self->data itself. */
235 dt_free_align(self->data);
236 }
237}
238
239// clang-format off
240// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
241// vim: shiftwidth=2 expandtab tabstop=2 cindent
242// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
243// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_iop_gui_init_blending_body(GtkWidget *container, dt_iop_module_t *module)
Definition blend_gui.c:4929
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4466
void dt_iop_gui_cleanup_blending_body(dt_iop_module_t *module)
Definition blend_gui.c:4834
The blending panel's GUI state and API, cut out of develop/blend.h.
static void _clear_box(dt_lib_module_t *self)
Definition blending.c:97
void gui_cleanup(dt_lib_module_t *self)
Definition blending.c:220
int expandable(dt_lib_module_t *self __attribute__((unused)))
Definition blending.c:79
static void _show_message(dt_lib_module_t *self, gchar *markup)
Definition blending.c:130
static void _release(dt_lib_module_t *self)
Definition blending.c:117
const char ** views(dt_lib_module_t *self __attribute__((unused)))
Definition blending.c:68
static gboolean _module_is_current(const dt_iop_module_t *module)
Definition blending.c:92
static gboolean _can_host(const dt_iop_module_t *module)
Definition blending.c:108
static void _gui_changed_callback(gpointer instance __attribute__((unused)), dt_lib_module_t *self)
Definition blending.c:147
uint32_t container(dt_lib_module_t *self __attribute__((unused)))
Definition blending.c:74
void gui_init(dt_lib_module_t *self)
Definition blending.c:206
int position()
Definition blending.c:84
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
#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
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
const char * name
Definition pdf.h:90
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:323
#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
GList * history
Definition develop.h:259
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
dt_iop_module_t * hosted_module
Definition blending.c:56
dt_iop_module_t * active_module
Definition blending.c:55
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER