Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
masks_gui.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel
3 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2025-2026 Guillaume Stutin.
5
6 Ansel is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Ansel is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with darktable. If not, see <http://www.gnu.org/licenses/>.
18*/
19#include "common/darktable.h"
20#include "develop/masks.h"
21#include "bauhaus/bauhaus.h"
22#include "common/debug.h"
23#include "control/signal.h"
24#include "develop/imageop_gui.h"
25#include "dtgtk/button.h"
26#include "dtgtk/paint.h"
27#include "gui/actions/menu.h"
28#include "gui/draw.h"
29#include "gui/gtk.h"
30
31#include <math.h>
32#include <stdlib.h>
33
34#define DT_MASKS_SHAPE_BUTTON_COUNT 5
35
45
53
66
68{
69 if(IS_NULL_PTR(data)) return;
70
71 // Walk all buttons in this group so any caller can reset every masks shape toolbar through the shared signal.
72 for(int i = 0; i < DT_MASKS_SHAPE_BUTTON_COUNT; i++)
73 {
74 GtkWidget *button = data->buttons[i];
75 if(GTK_IS_TOGGLE_BUTTON(button) && button != active_button)
76 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
77 }
78}
79
80static void _masks_shape_buttons_deactivate_signal(gpointer instance, GtkWidget *active_button,
82{
83 _masks_shape_buttons_deactivate(active_button, data);
84}
85
90
92{
93 if(IS_NULL_PTR(data)) return -1;
94
95 // Search the stored button pointers because callers may keep their own storage arrays.
96 for(int i = 0; i < DT_MASKS_SHAPE_BUTTON_COUNT; i++)
97 if(data->buttons[i] == button) return i;
98
99 return -1;
100}
101
103 const int button_index)
104{
107
108 return !IS_NULL_PTR(mask_gui) && mask_gui->creation
109 && mask_gui->creation_module == data->config.creation_module
110 && !IS_NULL_PTR(visible_form)
111 && (visible_form->type & data->types[button_index]);
112}
113
114static gboolean _masks_shape_button_pressed(GtkWidget *button, GdkEventButton *event, gpointer user_data)
115{
116 if(darktable.gui->reset || event->button != GDK_BUTTON_PRIMARY) return TRUE;
117
119 (dt_masks_shape_buttons_data_t *)g_object_get_data(G_OBJECT(button), "dt-masks-shape-buttons-data");
120 const int button_index = _masks_shape_button_index(data, button);
121 if(button_index < 0) return FALSE;
122
123 dt_masks_type_t type = data->types[button_index];
124 dt_iop_module_t *module = data->config.creation_module;
126
127 if(_masks_shape_button_is_current_creation(data, button_index))
128 {
130 dt_masks_form_exit_creation(module, mask_gui);
131 if(data->config.exited) data->config.exited(button, module, type, data->config.user_data);
133 return TRUE;
134 }
135
136 if(data->config.can_start && !data->config.can_start(button, module, type, data->config.user_data))
137 {
140 return TRUE;
141 }
142
143 if(data->config.form_type) type = data->config.form_type(module, type, data->config.user_data);
144
146 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
147
149 {
150 if(data->config.started)
151 {
152 data->config.started(button, module, type, data->config.user_data);
153 // Force focus back to the drawing area after creation mode enabling
154 gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
155 }
156 }
157 else
158 {
160 }
161
163 return TRUE;
164}
165
171
181{
182 if(IS_NULL_PTR(config)) return NULL;
183
185 if(IS_NULL_PTR(data)) return NULL;
186
187 data->config = *config;
188 data->box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
189 gtk_widget_set_halign(data->box, GTK_ALIGN_END);
190 gtk_widget_set_valign(data->box, GTK_ALIGN_CENTER);
191
192 const char *action_section = config->action_section ? config->action_section : N_("shapes");
193 const size_t button_defs_count = sizeof(_masks_shape_button_defs) / sizeof(_masks_shape_button_defs[0]);
194
195 // Create buttons in the same visible order used by the module-local toolbars.
196 for(size_t i = 0; i < button_defs_count; i++)
197 {
199 if(!(config->flags & def->flag)) continue;
200
201 GtkWidget *button = NULL;
202 if(config->owner_module)
203 {
204 const gboolean register_button = (config->register_flags & def->flag);
205 if(register_button)
206 {
207 button = dt_iop_togglebutton_new(config->owner_module, action_section, def->label, def->ctrl_label,
208 G_CALLBACK(_masks_shape_button_pressed), config->local,
209 0, 0, def->paint, data->box);
210 }
211 else
212 {
213 button = dt_iop_togglebutton_new_no_register(config->owner_module, action_section, def->label, def->ctrl_label,
214 G_CALLBACK(_masks_shape_button_pressed), config->local,
215 0, 0, def->paint, data->box);
216 }
217 }
218 else
219 {
220 button = dtgtk_togglebutton_new(def->paint, 0, NULL);
221 gtk_widget_set_tooltip_text(button, _(def->label));
222 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
223 gtk_box_pack_end(GTK_BOX(data->box), button, FALSE, FALSE, 0);
224 g_signal_connect(G_OBJECT(button), "button-press-event", G_CALLBACK(_masks_shape_button_pressed), NULL);
225 }
226
227 gtk_widget_set_can_focus(button, FALSE);
228 g_object_set_data(G_OBJECT(button), "dt-masks-shape-buttons-data", data);
229
230 data->buttons[def->index] = button;
231 data->types[def->index] = def->type;
232 if(config->buttons) config->buttons[def->index] = button;
233 if(config->types) config->types[def->index] = def->type;
234 }
235
238 g_signal_connect(G_OBJECT(data->box), "destroy", G_CALLBACK(_masks_shape_buttons_destroy), data);
239
240 return data->box;
241}
242
253
254// Push the new value to history (so the pipeline re-renders) and refresh the mask
255// treeviews (opacity text, etc.).
256//
257// This is called from the slider "value-changed" handler. The bauhaus slider already
258// throttles that emission through dt_gui_throttle_queue() while dragging, so the commit
259// is debounced at the slider-value level: transient values do not flood the pipeline with
260// renders, yet the image updates without waiting for the context menu to be closed.
270
272{
273 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->form_group)) return;
274
275 if(data->increment == DT_MASKS_INCREMENT_ABSOLUTE) // aka opacity
276 {
278 data->increment, 1, data->gui, data->module);
279 data->last_value = value;
281 return;
282 }
283
284 const float delta = value - data->last_value;
285 if(fabsf(delta) < 1e-6f) return;
286
287 // Slider value is a log2 scale factor in [-3;3], so apply the delta in log space.
288 const float scale = exp2f(delta);
290 DT_MASKS_INCREMENT_SCALE, 1.f, data->gui, data->module);
291 data->last_value = value;
293}
294
295static void _masks_gui_menu_item_block_activate(GtkWidget *widget, gpointer user_data)
296{
297 g_signal_stop_emission_by_name(widget, "activate");
298}
299
300static gboolean _masks_gui_menu_item_forward_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
301{
303 if(IS_NULL_PTR(data) || !data->slider) return FALSE;
304
305 GdkEvent *copy = gdk_event_copy(event);
306 if(IS_NULL_PTR(copy)) return FALSE;
307
308 double x = 0.0, y = 0.0;
309 gboolean has_coords = FALSE;
310 switch(copy->type)
311 {
312 case GDK_BUTTON_PRESS:
313 case GDK_2BUTTON_PRESS:
314 case GDK_3BUTTON_PRESS:
315 case GDK_BUTTON_RELEASE:
316 x = copy->button.x;
317 y = copy->button.y;
318 has_coords = TRUE;
319 break;
320 case GDK_MOTION_NOTIFY:
321 x = copy->motion.x;
322 y = copy->motion.y;
323 has_coords = TRUE;
324 break;
325 case GDK_SCROLL:
326 x = copy->scroll.x;
327 y = copy->scroll.y;
328 has_coords = TRUE;
329 break;
330 default:
331 break;
332 }
333
334 if(has_coords)
335 {
336 int sx = 0, sy = 0;
337 if(gtk_widget_translate_coordinates(widget, data->slider, (int)x, (int)y, &sx, &sy))
338 {
339 switch(copy->type)
340 {
341 case GDK_BUTTON_PRESS:
342 case GDK_2BUTTON_PRESS:
343 case GDK_3BUTTON_PRESS:
344 case GDK_BUTTON_RELEASE:
345 copy->button.x = sx;
346 copy->button.y = sy;
347 break;
348 case GDK_MOTION_NOTIFY:
349 copy->motion.x = sx;
350 copy->motion.y = sy;
351 break;
352 case GDK_SCROLL:
353 copy->scroll.x = sx;
354 copy->scroll.y = sy;
355 break;
356 default:
357 break;
358 }
359 }
360 }
361
362 GdkWindow *slider_window = gtk_widget_get_window(data->slider);
363 if(slider_window)
364 {
365 if(copy->any.window) g_object_unref(copy->any.window);
366 copy->any.window = g_object_ref(slider_window);
367 copy->any.send_event = TRUE;
368 }
369
370 gtk_widget_event(data->slider, copy);
371 gdk_event_free(copy);
372 return TRUE;
373}
374
375static void _masks_gui_interaction_slider_changed(GtkWidget *widget, gpointer user_data)
376{
378 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->form_group)) return;
379
381}
382
384 dt_masks_interaction_t interaction, dt_masks_increment_t increment,
385 float min, float max, float step, float value, int digits,
386 const char *format, float factor,
388{
389 GtkWidget *menu_item = gtk_menu_item_new();
390 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
391
392 gtk_widget_set_can_focus(menu_item, FALSE);
393 g_signal_connect(G_OBJECT(menu_item), "activate",
394 G_CALLBACK(_masks_gui_menu_item_block_activate), NULL);
395 gtk_widget_add_events(menu_item, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
396 | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK);
397
399 min, max, step, value, digits);
400 dt_bauhaus_widget_set_label(slider, label);
401 dt_bauhaus_slider_set_digits(slider, digits);
402 if(format && format[0] != '\0') dt_bauhaus_slider_set_format(slider, format);
403 if(factor != 1.0f) dt_bauhaus_slider_set_factor(slider, factor);
405 DT_BAUHAUS_WIDGET(slider)->expand = TRUE;
406 gtk_widget_set_hexpand(slider, TRUE);
407 gtk_widget_set_halign(slider, GTK_ALIGN_FILL);
408 gtk_widget_set_valign(slider, GTK_ALIGN_CENTER);
409 gtk_widget_set_size_request(slider, DT_PIXEL_APPLY_DPI(220), DT_PIXEL_APPLY_DPI(28));
410 gtk_widget_set_can_focus(slider, TRUE);
411
413 data->form_group = form_group;
414 data->gui = gui;
415 data->module = module;
416 data->interaction = interaction;
417 data->increment = increment;
418 data->last_value = value;
419 data->slider = slider;
420 g_signal_connect_data(G_OBJECT(slider), "value-changed",
422 data, (GClosureNotify)g_free, 0);
423 g_signal_connect(G_OBJECT(menu_item), "button-press-event",
424 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
425 g_signal_connect(G_OBJECT(menu_item), "button-release-event",
426 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
427 g_signal_connect(G_OBJECT(menu_item), "motion-notify-event",
428 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
429 g_signal_connect(G_OBJECT(menu_item), "scroll-event",
430 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
431
432 gtk_box_pack_start(GTK_BOX(box), slider, TRUE, TRUE, 0);
433 gtk_container_add(GTK_CONTAINER(menu_item), box);
434 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
435
436 return menu_item;
437}
438
440{
441 if(IS_NULL_PTR(darktable.gui) || IS_NULL_PTR(darktable.gui->ui)) return GTK_RESPONSE_NO;
442
443 GtkWidget *dialog = gtk_message_dialog_new(
444 GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)),
445 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
446 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Delete the shape '%s' ?"), form_name);
447 gtk_message_dialog_format_secondary_text(
448 GTK_MESSAGE_DIALOG(dialog), "'%s' %s\n\n%s", form_name,
449 _("will no longer be used."),
450 _("Do you want to permanently delete it, or keep it unused for potential reuse?"));
451
452 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Delete shape"), GTK_RESPONSE_YES);
453 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Keep unused shape"), GTK_RESPONSE_NO);
454 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
455 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
456
457 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
458 gtk_widget_destroy(dialog);
459
460 return response;
461}
462
463static void _masks_gui_delete_form_callback(GtkWidget *menu, gpointer user_data)
464{
465 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
466 if(IS_NULL_PTR(gui)) return;
468 if(IS_NULL_PTR(forms)) return;
469
470 if(gui->group_selected >= 0)
471 {
472 // Delete shape from current group
474 if(IS_NULL_PTR(fpt)) return;
475 dt_iop_module_t *module = darktable.develop->gui_module;
476 if(IS_NULL_PTR(module)) return;
478 if(IS_NULL_PTR(sel)) return;
479
480 const int parentid = fpt->parentid;
481 const int formid = fpt->formid;
482
483 dt_masks_remove_or_delete(module, sel, parentid, gui, formid);
484
485 }
486}
487
488void _masks_gui_delete_node_callback(GtkWidget *menu, gpointer user_data)
489{
490 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
491 if(IS_NULL_PTR(gui)) return;
493 if(IS_NULL_PTR(forms)) return;
494
495 dt_iop_module_t *module = darktable.develop->gui_module;
496 if(IS_NULL_PTR(module)) return;
497
498 if(gui->creation)
499 {
500 // Minimum points to create a polygon
501 if(gui->node_dragging < 1)
502 {
503 dt_masks_form_exit_creation(module, gui);
504 return;
505 }
507 if(sel)
508 dt_masks_remove_node(module, sel, 0, gui, 0, gui->node_dragging);
509 gui->node_dragging -= 1;
510 }
511 else if(gui->group_selected >= 0)
512 {
513 // Delete shape from current group
514
516 if(IS_NULL_PTR(fpt)) return;
518 if(sel)
519 dt_masks_remove_node(module, sel, fpt->parentid, gui, gui->group_selected, gui->node_hovered);
520
522 }
523}
524
525static void _masks_gui_exit_creation_callback(GtkWidget *menu, gpointer user_data)
526{
527 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
528 dt_iop_module_t *module = darktable.develop->gui_module;
529 dt_masks_form_exit_creation(module, gui);
530}
531
532static void _masks_move_up_down_callback(gpointer user_data, const int up)
533{
534 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
535 if(IS_NULL_PTR(gui)) return;
536 if(gui->group_selected < 0) return;
537
538 dt_iop_module_t *module = darktable.develop->gui_module;
539 if(IS_NULL_PTR(module)) return;
540
542 if(IS_NULL_PTR(forms)) return;
544 if(IS_NULL_PTR(fpt)) return;
546 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
547
548 dt_masks_form_move(grp, fpt->formid, up);
549
551}
552
553static void _masks_moveup_callback(GtkWidget *menu, gpointer user_data)
554{
555 _masks_move_up_down_callback(user_data, 0);
556}
557
558static void _masks_movedown_callback(GtkWidget *menu, gpointer user_data)
559{
560 _masks_move_up_down_callback(user_data, 1);
561}
562
565static void _masks_operation_callback(GtkWidget *menu, gpointer user_data)
566{
567 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
568 if(IS_NULL_PTR(gui) || IS_NULL_PTR(menu)) return;
569
570 const guint form_pos = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(menu), "form_pos"));
571 const dt_masks_state_t state_op = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu), "state_op"));
572 // Advert the user if it will have no effect
573 if(form_pos == 0 && (state_op & DT_MASKS_STATE_IS_COMBINE_OP) != 0)
574 {
575 dt_control_log(_("Applying a boolean operation has no effect on the first shape of a group.\n"
576 "Move it to at least the 2nd position if you need to use boolean operations"));
577 }
578
579 dt_masks_form_group_t *form_op = (dt_masks_form_group_t *)g_object_get_data(G_OBJECT(menu), "op_form");
580 if(IS_NULL_PTR(form_op)) return;
581
582 apply_operation(form_op, state_op);
583
585}
586
587#define masks_gtk_menu_item_new_bold(label, selected, state, icon) \
588{ \
589 gchar *op_label = g_strdup(label); \
590 menu_item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(op_label, icon, \
591 sub_menu, \
592 _masks_operation_callback, gui, \
593 (selected != 0), \
594 ((state) == DT_MASKS_STATE_INVERSE)); \
595 dt_free(op_label); \
596 op_label = NULL; \
597 g_object_set_data(G_OBJECT(menu_item), "state_op", GINT_TO_POINTER(state)); \
598 g_object_set_data(G_OBJECT(menu_item), "op_form", op_form); \
599 g_object_set_data(G_OBJECT(menu_item), "form_pos", GINT_TO_POINTER(form_pos)); \
600}
601
602
604 const float pzx, const float pzy)
605{
606 assert(gui);
607 assert(form);
608 // Always re-create the menu when we show it because we don't bother updating info during the lifetime of the mask
609 GtkWidget *menu = gtk_menu_new();
610 gtk_style_context_add_class(gtk_widget_get_style_context(menu), "dt-masks-context-menu");
611
612 // Create an array of icons for the operations
613 const int bs2 = DT_PIXEL_APPLY_DPI(13);
614 GdkPixbuf *op_icon[DT_MASKS_STATE_EXCLUSION + 1] = { 0 };
615 int width = bs2 * 2;
621
622 // Get the current group to apply operations on it if needed
623 dt_masks_form_group_t *op_form = NULL;
624 dt_masks_form_t *grp = formgroup ? dt_masks_get_from_id(darktable.develop, formgroup->parentid) : NULL;
625 if(grp && (grp->type & DT_MASKS_GROUP))
626 op_form = dt_masks_form_group_from_parentid(grp->formid, form->formid);
627 if(IS_NULL_PTR(op_form) && !gui->creation)
628 {
629 for(size_t k = 0; k < G_N_ELEMENTS(op_icon); k++)
630 g_clear_object(&op_icon[k]);
631 gtk_widget_destroy(menu);
632 return NULL;
633 }
634
635 // Find the position of the current form in the group
636 guint form_pos = 0;
637 gboolean form_found = FALSE;
638 if(grp && (grp->type & DT_MASKS_GROUP))
639 {
640 for(GList *fpts = grp->points; fpts; fpts = g_list_next(fpts))
641 {
642 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
643 if(fpt->formid == form->formid)
644 {
645 form_found = TRUE;
646 break;
647 }
648 form_pos++;
649 }
650 }
651
652 // Get the number of shapes in the group
653 guint list_length = (form_found && grp) ? g_list_length(grp->points) : 0;
654
655
656 // Title
657 gchar *form_name = NULL;
658 if(form->name[0])
659 form_name = g_strdup(form->name);
660 else if(gui->creation)
661 {
662 // if no name, we are probably creating a new form, we create one based on the type
663 form_name = g_strdup(_("New "));
664 switch (form->type)
665 {
666 case DT_MASKS_CIRCLE:
667 form_name = g_strconcat(form_name, _("circle"), NULL);
668 break;
669 case DT_MASKS_ELLIPSE:
670 form_name = g_strconcat(form_name, _("ellipse"), NULL);
671 break;
672 case DT_MASKS_POLYGON:
673 form_name = g_strconcat(form_name, _("polygon"), NULL);
674 break;
675 case DT_MASKS_BRUSH:
676 form_name = g_strconcat(form_name, _("brush"), NULL);
677 break;
679 form_name = g_strconcat(form_name, _("gradient"), NULL);
680 break;
681 case DT_MASKS_GROUP:
682 form_name = g_strconcat(form_name, _("mask"), NULL);
683 break;
684 default:
685 dt_free(form_name); // Erase the "New " prefix
686 form_name = g_strdup(_("Unknown shape"));
687 break;
688 }
689 }
690
691 // Create the main label string
692 gchar *item_str = NULL;
693 if(gui->node_hovered >= 0 || gui->seg_hovered >= 0)
694 {
695 const int item_index = (gui->node_hovered >= 0) ? gui->node_hovered : gui->seg_hovered;
696 item_str = g_strdup_printf("%s %d - ", gui->node_hovered >= 0 ? _("Node") : _("Segment"), item_index);
697 }
698 else
699 item_str = g_strdup("");
700
701 // Create an assembled image if we have an inverse state to show
702 const dt_masks_state_t state = IS_NULL_PTR(op_form) ? 0 : op_form->state & DT_MASKS_STATE_IS_COMBINE_OP;
703 const gboolean has_inverse = !IS_NULL_PTR(op_form) && (op_form->state & DT_MASKS_STATE_INVERSE) != 0;
704 GdkPixbuf *icon = (state <= DT_MASKS_STATE_EXCLUSION) ? op_icon[state] : NULL;
705 GdkPixbuf *composed_icon = NULL;
706 if(has_inverse && op_icon[DT_MASKS_STATE_INVERSE])
707 {
708 if(icon)
709 {
710 const int base_w = gdk_pixbuf_get_width(icon);
711 const int base_h = gdk_pixbuf_get_height(icon);
712 const int inv_w = gdk_pixbuf_get_width(op_icon[DT_MASKS_STATE_INVERSE]);
713 const int inv_h = gdk_pixbuf_get_height(op_icon[DT_MASKS_STATE_INVERSE]);
714 const int out_w = base_w + inv_w;
715 const int out_h = MAX(base_h, inv_h);
716
717 composed_icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, out_w, out_h);
718 if(composed_icon)
719 {
720 gdk_pixbuf_fill(composed_icon, 0x00000000);
721 gdk_pixbuf_copy_area(icon, 0, 0, base_w, base_h, composed_icon, 0, 0);
722 gdk_pixbuf_copy_area(op_icon[DT_MASKS_STATE_INVERSE], 0, 0, inv_w, inv_h, composed_icon, base_w, 0);
723 icon = composed_icon;
724 }
725 }
726 else
727 icon = op_icon[DT_MASKS_STATE_INVERSE];
728 }
729
730 const gboolean draw_icon = !IS_NULL_PTR(op_form) && form_pos > 0;
731 gchar *title = g_strdup_printf("<b><big>%s%s</big></b>", item_str, form_name);
732 GtkWidget *menu_item = ctx_gtk_menu_item_new_with_markup_and_pixbuf(title, (draw_icon) ? icon : NULL, menu, NULL, gui);
733 gtk_widget_set_sensitive(menu_item, FALSE);
734 dt_free(item_str);
735 dt_free(title);
736 dt_free(form_name);
737
738 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
739
740 // Common menu items
741 if(!gui->creation && (gui->form_selected || gui->node_selected) && op_form)
742 {
745
747 DT_MASKS_INCREMENT_SCALE, -4.f, 4.0f, 0.01f, 0.0f, 2, "x", 1.0f,
750 DT_MASKS_INCREMENT_ABSOLUTE, 0.f, 1.0f, 0.01f,
751 isfinite(hardness) ? hardness : 1.0f, 3, "%", 100.0f,
754 DT_MASKS_INCREMENT_ABSOLUTE, 0.0f, 1.0f, 0.01f,
755 isfinite(opacity) ? opacity : 1.0f, 3, "%", 100.0f,
757
758 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
759 }
760
761 // Shape specific menu items
762 if(!IS_NULL_PTR(form) && form->functions && form->functions->populate_context_menu)
763 if(form->functions->populate_context_menu(menu, form, gui, pzx, pzy))
764 {
765 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
766 }
767
768
769 /* Module specific */
770 {
771 dt_iop_module_t *module = darktable.develop->gui_module;
772 if(!IS_NULL_PTR(module) && module->populate_masks_context_menu)
773 if(module->populate_masks_context_menu(module, menu, form->formid, pzx, pzy))
774 {
775 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
776 }
777 }
778
779 /* Operation */
780
781 if(!gui->creation && !(form->type & DT_MASKS_IS_RETOUCHE) && (op_form) && !gui->node_selected)
782 {
783 menu_item = ctx_gtk_menu_item_new_with_markup(_("Operation"), menu, NULL, gui);
784 GtkWidget *sub_menu = gtk_menu_new();
785 gtk_style_context_add_class(gtk_widget_get_style_context(sub_menu), "dt-masks-context-menu");
786 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
787
789 op_icon[DT_MASKS_STATE_INVERSE]);
790 gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), gtk_separator_menu_item_new());
792 op_icon[DT_MASKS_STATE_UNION]);
798 op_icon[DT_MASKS_STATE_EXCLUSION]);
799
800 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
801 }
802
803 if(!gui->creation && gui->form_selected)
804 {
805 menu_item = ctx_gtk_menu_item_new_with_markup(_("Move up"), menu, _masks_moveup_callback, gui);
806 gtk_widget_set_sensitive(menu_item, (form_pos > 0));
807 menu_item = ctx_gtk_menu_item_new_with_markup(_("Move down"), menu, _masks_movedown_callback, gui);
808 gtk_widget_set_sensitive(menu_item, (form_pos < list_length - 1));
809
810 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
811 }
812
813 // Risky stuff at the end
814 if(gui->creation)
815 {
816 menu_item = ctx_gtk_menu_item_new_with_markup(_("Done shape creation"), menu,
818 menu_item_set_fake_accel(menu_item, GDK_KEY_Escape, 0);
819 }
820 else
821 {
822 if(gui->node_hovered >= 0)
823 {
824 menu_item = ctx_gtk_menu_item_new_with_markup(_("Delete node"), menu, _masks_gui_delete_node_callback, gui);
825 menu_item_set_fake_accel(menu_item, GDK_KEY_Delete, 0);
826 }
827 else
828 {
829 menu_item = ctx_gtk_menu_item_new_with_markup(_("Remove shape from mask"), menu, _masks_gui_delete_form_callback, gui);
830 menu_item_set_fake_accel(menu_item, GDK_KEY_Delete, 0);
831 gtk_widget_set_sensitive(menu_item, gui->form_selected >= 0);
832 }
833 }
834
835 for(size_t k = 0; k < G_N_ELEMENTS(op_icon); k++)
836 g_clear_object(&op_icon[k]);
837 g_clear_object(&composed_icon);
838
839 gtk_widget_show_all(menu);
840 return menu;
841}
842
843// clang-format off
844// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
845// vim: shiftwidth=2 expandtab tabstop=2 cindent
846// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
847// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3611
#define DT_BAUHAUS_WIDGET(obj)
Definition bauhaus.h:60
int width
Definition bilateral.h:1
static const float const float const float min
const float max
const float delta
int type
void dt_control_log(const char *msg,...)
Definition control.c:761
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
darktable_t darktable
Definition darktable.c:181
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#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 darktable.h:281
#define dt_dev_add_history_item(dev, module, enable, redraw)
static GdkPixbuf * dt_draw_get_pixbuf_from_cairo(DTGTKCairoPaintIconFunc paint, const int width, const int height)
Definition draw.h:947
void dtgtk_cairo_paint_masks_circle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
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_brush(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_ellipse(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Paint a 45 deg-rotated ellipse that touches the unit square boundaries.
void dtgtk_cairo_paint_masks_gradient(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_exclusion(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_polygon(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)
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition dtgtk/paint.h:75
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
#define DT_GUI_MODULE(x)
GtkWidget * dt_iop_togglebutton_new_no_register(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
static const float x
float *const restrict const size_t k
gboolean dt_masks_remove_or_delete(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id)
If the form to remove is used once, ask to the user if he wants to delete it from the list or just re...
void apply_operation(struct dt_masks_form_group_t *pt, const dt_masks_state_t apply_state)
Apply a mask state operation on a group entry.
@ DT_MASKS_SHAPE_INDEX_BRUSH
Definition masks.h:935
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks.h:933
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks.h:934
@ DT_MASKS_SHAPE_INDEX_GRADIENT
Definition masks.h:931
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks.h:932
dt_masks_state_t
Definition masks.h:166
@ DT_MASKS_STATE_DIFFERENCE
Definition masks.h:173
@ DT_MASKS_STATE_INVERSE
Definition masks.h:170
@ DT_MASKS_STATE_INTERSECTION
Definition masks.h:172
@ DT_MASKS_STATE_IS_COMBINE_OP
Definition masks.h:177
@ DT_MASKS_STATE_EXCLUSION
Definition masks.h:174
@ DT_MASKS_STATE_UNION
Definition masks.h:171
float dt_masks_form_get_interaction_value(dt_masks_form_group_t *form_group, dt_masks_interaction_t interaction)
gboolean dt_masks_form_exit_creation(dt_iop_module_t *module, dt_masks_form_gui_t *gui)
dt_masks_type_t
Definition masks.h:129
@ DT_MASKS_POLYGON
Definition masks.h:132
@ DT_MASKS_BRUSH
Definition masks.h:137
@ DT_MASKS_ELLIPSE
Definition masks.h:136
@ DT_MASKS_GRADIENT
Definition masks.h:135
@ DT_MASKS_CIRCLE
Definition masks.h:131
@ DT_MASKS_GROUP
Definition masks.h:133
@ DT_MASKS_IS_RETOUCHE
Definition masks.h:146
dt_masks_interaction_t
Definition masks.h:302
@ DT_MASKS_INTERACTION_OPACITY
Definition masks.h:306
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:305
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:304
#define menu_item_set_fake_accel(menu_item, keyval, mods)
Definition masks.h:1522
dt_masks_form_group_t * dt_masks_form_get_selected_group(const struct dt_masks_form_t *form, const struct dt_masks_form_gui_t *gui)
@ DT_MASKS_EVENT_UPDATE
Definition masks.h:160
@ DT_MASKS_EVENT_CHANGE
Definition masks.h:162
@ DT_MASKS_SHAPE_BUTTONS_GRADIENT
Definition masks.h:951
@ DT_MASKS_SHAPE_BUTTONS_CIRCLE
Definition masks.h:943
@ DT_MASKS_SHAPE_BUTTONS_BRUSH
Definition masks.h:949
@ DT_MASKS_SHAPE_BUTTONS_ELLIPSE
Definition masks.h:945
@ DT_MASKS_SHAPE_BUTTONS_POLYGON
Definition masks.h:947
gboolean dt_masks_creation_mode_enter(dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
void dt_masks_form_move(dt_masks_form_t *grp, int formid, int up)
dt_masks_increment_t
Definition masks.h:193
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:195
@ DT_MASKS_INCREMENT_ABSOLUTE
Definition masks.h:194
float dt_masks_form_set_interaction_value(dt_masks_form_group_t *form_group, dt_masks_interaction_t interaction, float value, dt_masks_increment_t increment, int flow, struct dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
void dt_masks_remove_node(struct dt_iop_module_t *module, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index, int node_index)
dt_masks_form_group_t * dt_masks_form_group_from_parentid(int parentid, int formid)
Return the group entry for a (parent, form) pair.
static void _masks_gui_menu_item_block_activate(GtkWidget *widget, gpointer user_data)
Definition masks_gui.c:295
static gboolean _masks_gui_menu_item_forward_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition masks_gui.c:300
static void _masks_gui_delete_form_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:463
static void _masks_move_up_down_callback(gpointer user_data, const int up)
Definition masks_gui.c:532
static gboolean _masks_shape_button_pressed(GtkWidget *button, GdkEventButton *event, gpointer user_data)
Definition masks_gui.c:114
static int _masks_shape_button_index(const dt_masks_shape_buttons_data_t *data, GtkWidget *button)
Definition masks_gui.c:91
static gboolean _masks_shape_button_is_current_creation(const dt_masks_shape_buttons_data_t *data, const int button_index)
Definition masks_gui.c:102
static void _masks_gui_interaction_apply_value(dt_masks_gui_interaction_slider_t *data, float value)
Definition masks_gui.c:271
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:86
static void _masks_movedown_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:558
static void _masks_gui_interaction_slider_changed(GtkWidget *widget, gpointer user_data)
Definition masks_gui.c:375
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:180
static const dt_masks_shape_button_def_t _masks_shape_button_defs[]
Definition masks_gui.c:54
static void _masks_moveup_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:553
static void _masks_shape_buttons_destroy(GtkWidget *widget, dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:166
void _masks_gui_delete_node_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:488
#define masks_gtk_menu_item_new_bold(label, selected, state, icon)
Definition masks_gui.c:587
int dt_masks_gui_confirm_delete_form_dialog(const char *form_name)
Definition masks_gui.c:439
static void _masks_shape_buttons_deactivate(GtkWidget *active_button, dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:67
static void _masks_shape_buttons_deactivate_signal(gpointer instance, GtkWidget *active_button, dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:80
GtkWidget * dt_masks_create_menu(dt_masks_form_gui_t *gui, dt_masks_form_t *form, const dt_masks_form_group_t *formgroup, const float pzx, const float pzy)
Definition masks_gui.c:603
#define DT_MASKS_SHAPE_BUTTON_COUNT
Definition masks_gui.c:34
static void _masks_operation_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:565
static GtkWidget * _masks_gui_add_interaction_slider(GtkWidget *menu, const char *label, dt_masks_form_group_t *form_group, dt_masks_interaction_t interaction, dt_masks_increment_t increment, float min, float max, float step, float value, int digits, const char *format, float factor, dt_masks_form_gui_t *gui, dt_iop_module_t *module)
Definition masks_gui.c:383
static void _masks_gui_exit_creation_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:525
static void _masks_gui_interaction_commit(dt_masks_gui_interaction_slider_t *data)
Definition masks_gui.c:261
GtkWidget * ctx_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:173
GtkWidget * ctx_gtk_menu_item_new_with_markup_and_pixbuf(const char *label, GdkPixbuf *icon, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:218
const float factor
Definition pdf.h:90
void copy(double *dest, double *source, size_t num_el)
Copy a flat buffer.
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:303
@ DT_SIGNAL_MASK_SHAPE_BUTTONS_DEACTIVATE
Definition signal.h:304
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_iop_module_t * gui_module
Definition develop.h:165
struct dt_masks_form_gui_t * form_gui
Definition develop.h:327
int32_t reset
Definition gtk.h:172
dt_ui_t * ui
Definition gtk.h:164
gboolean node_selected
Definition masks.h:470
dt_iop_module_t * creation_module
Definition masks.h:505
gboolean creation
Definition masks.h:503
gboolean form_selected
Definition masks.h:476
const dt_masks_functions_t * functions
Definition masks.h:379
dt_masks_type_t type
Definition masks.h:378
char name[128]
Definition masks.h:396
GList * points
Definition masks.h:377
int(* populate_context_menu)(GtkWidget *menu, struct dt_masks_form_t *form, struct dt_masks_form_gui_t *gui, const float pzx, const float pzy)
Definition masks.h:371
dt_iop_module_t *dt_masks_interaction_t interaction
Definition masks_gui.c:248
dt_masks_form_gui_t * gui
Definition masks_gui.c:246
dt_masks_increment_t increment
Definition masks_gui.c:249
dt_masks_form_group_t * form_group
Definition masks_gui.c:245
DTGTKCairoPaintIconFunc paint
Definition masks_gui.c:43
dt_iop_module_t * creation_module
Definition masks.h:970
dt_masks_shape_buttons_flags_t register_flags
Definition masks.h:975
dt_masks_shape_buttons_type_f form_type
Definition masks.h:979
dt_masks_shape_buttons_start_f can_start
Definition masks.h:978
dt_masks_shape_buttons_notify_f exited
Definition masks.h:981
dt_masks_shape_buttons_notify_f started
Definition masks.h:980
dt_iop_module_t * owner_module
Definition masks.h:969
dt_masks_shape_buttons_flags_t flags
Definition masks.h:974
dt_masks_shape_buttons_config_t config
Definition masks_gui.c:51
#define MAX(a, b)
Definition thinplate.c:29
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)