Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
menu.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2023, 2025 Aurélien PIERRE.
4 Copyright (C) 2023 Luca Zulberti.
5 Copyright (C) 2026 Guillaume Stutin.
6
7 Ansel is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 Ansel is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
19*/
20
21#include "common/darktable.h"
22#include "common/act_on.h"
23#include "common/debug.h"
24#include "common/collection.h"
25#include "common/selection.h"
26#include "control/conf.h"
27#include "develop/develop.h"
28#include "gui/gtk.h"
29#include "views/view.h"
30#include "math.h"
31#include "menu.h"
32
33#ifdef GDK_WINDOWING_QUARTZ
34#include "osx/osx.h"
35#endif
36
38
52// Wrapper to match menuitem "activate" signal to our generic accel callback
53static void _activate_callback_to_action_callback(GtkMenuItem* menu_item, gpointer user_data)
54{
55 dt_menu_entry_t *entry = (dt_menu_entry_t *)user_data;
56 GtkWindow *window = GTK_WINDOW(gtk_widget_get_ancestor(GTK_WIDGET(menu_item), GTK_TYPE_WINDOW));
57 entry->action_callback(entry->accel_group, G_OBJECT(window), 0, 0, menu_item);
58}
59
60static gboolean _menu_icon_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
61{
62 dt_menu_icon_data_t *data = (dt_menu_icon_data_t *)user_data;
63 if(IS_NULL_PTR(data) || data->shape == DT_MENU_ICON_NONE) return FALSE;
64
65 GtkStyleContext *context = gtk_widget_get_style_context(widget);
66 GdkRGBA color;
67 gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &color);
68 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
69 cairo_set_line_width(cr, 1.2);
70
71 GtkAllocation alloc;
72 gtk_widget_get_allocation(widget, &alloc);
73 const double pad = 1.0;
74 const double w = MAX(0.0, (double)alloc.width - 2.0 * pad);
75 const double h = MAX(0.0, (double)alloc.height - 2.0 * pad);
76 const double size = fmin(w, h);
77 const double x = ((double)alloc.width - size) * 0.5;
78 const double y = ((double)alloc.height - size) * 0.5;
79
80 if(data->shape == DT_MENU_ICON_CIRCLE)
81 {
82 cairo_arc(cr, x + size * 0.5, y + size * 0.5, MAX(0.0, size * 0.5 - 0.5), 0.0, 2.0 * M_PI);
83 cairo_stroke(cr);
84 }
85 else if(data->shape == DT_MENU_ICON_SQUARE)
86 {
87 cairo_rectangle(cr, x, y, size, size);
88 cairo_stroke(cr);
89 }
90
91 return FALSE;
92}
93
94static void _menu_entry_destroy(GtkWidget *widget, gpointer user_data)
95{
96 dt_menu_entry_t *entry = (dt_menu_entry_t *)user_data;
97 dt_free(entry);
98}
99
101 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
102 gpointer user_data, dt_menu_icon_t icon)
103{
104 GtkWidget *menu_item = gtk_menu_item_new();
105 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
106 GtkWidget *icon_widget = gtk_drawing_area_new();
107 GtkWidget *label_widget = gtk_label_new(NULL);
108
109 gtk_widget_set_size_request(icon_widget, 10, 10);
110 gtk_label_set_markup(GTK_LABEL(label_widget), label);
111
112 if(icon != DT_MENU_ICON_NONE)
113 {
114 dt_menu_icon_data_t *data = g_malloc0(sizeof(dt_menu_icon_data_t));
115 data->shape = icon;
116 g_signal_connect_data(icon_widget, "draw", G_CALLBACK(_menu_icon_draw), data, (GClosureNotify)g_free, 0);
117 }
118
119 gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0f);
120 gtk_box_pack_start(GTK_BOX(box), label_widget, FALSE, FALSE, 0);
121 gtk_box_pack_start(GTK_BOX(box), icon_widget, FALSE, FALSE, 2);
122 gtk_container_add(GTK_CONTAINER(menu_item), box);
123 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
124
125 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
126
127 return menu_item;
128}
129
130GtkWidget *ctx_gtk_menu_item_new_with_icon_and_shortcut(const char *label, const char *shortcut,
131 GtkWidget *menu,
132 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
133 gpointer user_data, dt_menu_icon_t icon)
134{
135 GtkWidget *menu_item = gtk_menu_item_new();
136 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
137 GtkWidget *left_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
138 GtkWidget *icon_widget = gtk_drawing_area_new();
139 GtkWidget *label_widget = gtk_label_new(NULL);
140 GtkWidget *shortcut_widget = gtk_label_new(shortcut);
141
142 gtk_widget_set_size_request(icon_widget, 10, 10);
143 gtk_label_set_markup(GTK_LABEL(label_widget), label);
144 gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0f);
145 gtk_widget_set_halign(label_widget, GTK_ALIGN_START);
146 gtk_widget_set_hexpand(label_widget, TRUE);
147
148 if(icon != DT_MENU_ICON_NONE)
149 {
150 dt_menu_icon_data_t *data = g_malloc0(sizeof(dt_menu_icon_data_t));
151 data->shape = icon;
152 g_signal_connect_data(icon_widget, "draw", G_CALLBACK(_menu_icon_draw), data, (GClosureNotify)g_free, 0);
153 }
154
155 gtk_label_set_xalign(GTK_LABEL(shortcut_widget), 1.0f);
156 gtk_widget_set_halign(shortcut_widget, GTK_ALIGN_END);
157 gtk_style_context_add_class(gtk_widget_get_style_context(shortcut_widget), "accelerator");
158
159 gtk_box_pack_start(GTK_BOX(left_box), label_widget, TRUE, TRUE, 0);
160 gtk_box_pack_start(GTK_BOX(left_box), icon_widget, FALSE, FALSE, 2);
161 gtk_box_pack_start(GTK_BOX(box), left_box, TRUE, TRUE, 0);
162 gtk_box_pack_end(GTK_BOX(box), shortcut_widget, FALSE, FALSE, 0);
163
164 gtk_container_add(GTK_CONTAINER(menu_item), box);
165 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
166 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
167
168 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
169
170 return menu_item;
171}
172
174 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
175 gpointer user_data)
176{
177 GtkWidget *menu_item = gtk_menu_item_new_with_label("");
178 GtkWidget *child = gtk_bin_get_child(GTK_BIN(menu_item));
179 gtk_label_set_markup(GTK_LABEL(child), label);
180 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
181 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
182
183 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
184
185 return menu_item;
186}
187
188GtkWidget *ctx_gtk_menu_item_new_with_markup_and_shortcut(const char *label, const char *shortcut,
189 GtkWidget *menu,
190 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
191 gpointer user_data)
192{
193 GtkWidget *menu_item = gtk_menu_item_new();
194 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
195 GtkWidget *label_widget = gtk_label_new(NULL);
196 GtkWidget *shortcut_widget = gtk_label_new(shortcut);
197
198 gtk_label_set_markup(GTK_LABEL(label_widget), label);
199 gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0f);
200 gtk_widget_set_halign(label_widget, GTK_ALIGN_START);
201 gtk_widget_set_hexpand(label_widget, TRUE);
202
203 gtk_label_set_xalign(GTK_LABEL(shortcut_widget), 1.0f);
204 gtk_widget_set_halign(shortcut_widget, GTK_ALIGN_END);
205 gtk_style_context_add_class(gtk_widget_get_style_context(shortcut_widget), "accelerator");
206
207 gtk_box_pack_start(GTK_BOX(box), label_widget, TRUE, TRUE, 0);
208 gtk_box_pack_end(GTK_BOX(box), shortcut_widget, FALSE, FALSE, 0);
209 gtk_container_add(GTK_CONTAINER(menu_item), box);
210 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
211 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
212
213 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
214
215 return menu_item;
216}
217
218GtkWidget *ctx_gtk_menu_item_new_with_markup_and_pixbuf(const char *label, GdkPixbuf *icon,
219 GtkWidget *menu,
220 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
221 gpointer user_data)
222{
223 GtkWidget *menu_item = gtk_menu_item_new();
224 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
225 GtkWidget *image_widget = icon ? gtk_image_new_from_pixbuf(icon) : gtk_image_new();
226 GtkWidget *label_widget = gtk_label_new(NULL);
227
228 gtk_label_set_markup(GTK_LABEL(label_widget), label);
229 gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0f);
230 gtk_widget_set_halign(label_widget, GTK_ALIGN_START);
231 gtk_widget_set_hexpand(label_widget, TRUE);
232
233 gtk_box_pack_start(GTK_BOX(box), label_widget, TRUE, TRUE, 0);
234 gtk_box_pack_end(GTK_BOX(box), image_widget, FALSE, FALSE, 0);
235 gtk_container_add(GTK_CONTAINER(menu_item), box);
236 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
237 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
238
239 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
240
241 return menu_item;
242}
243
245 GtkWidget *menu,
246 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
247 gpointer user_data,
248 const gboolean checked,
249 const gboolean show_checkbox)
250{
251 GtkWidget *menu_item = gtk_check_menu_item_new();
252 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
253 GtkWidget *label_widget = gtk_label_new(NULL);
254
255 gtk_label_set_markup(GTK_LABEL(label_widget), label);
256 gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0f);
257 gtk_widget_set_halign(label_widget, GTK_ALIGN_START);
258 gtk_widget_set_hexpand(label_widget, TRUE);
259 gtk_widget_set_hexpand(box, TRUE);
260 gtk_widget_set_halign(box, GTK_ALIGN_FILL);
261
262 gtk_box_pack_start(GTK_BOX(box), label_widget, TRUE, TRUE, 0);
263 gtk_container_add(GTK_CONTAINER(menu_item), box);
264 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), checked);
265 if(show_checkbox)
266 gtk_style_context_add_class(gtk_widget_get_style_context(menu_item), "dt-masks-inverse-item");
267 else
268 gtk_style_context_add_class(gtk_widget_get_style_context(menu_item), "dt-masks-hide-check");
269
270 if(checked)
271 {
272 gtk_style_context_add_class(gtk_widget_get_style_context(menu_item), "dt-masks-checked");
273 gtk_style_context_add_class(gtk_widget_get_style_context(label_widget), "dt-masks-checked-label");
274 }
275 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
276 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
277
278 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
279
280 return menu_item;
281}
282
284 GtkWidget *menu,
285 void (*activate_callback)(GtkWidget *widget, gpointer user_data),
286 gpointer user_data,
287 const gboolean checked,
288 const gboolean show_checkbox)
289{
290 GtkWidget *menu_item = gtk_check_menu_item_new();
291 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
292 GtkWidget *left_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
293 GtkWidget *image_widget = icon ? gtk_image_new_from_pixbuf(icon) : gtk_image_new();
294 GtkWidget *label_widget = gtk_label_new(NULL);
295
296 gtk_label_set_markup(GTK_LABEL(label_widget), label);
297 gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0f);
298 gtk_widget_set_halign(label_widget, GTK_ALIGN_START);
299 gtk_widget_set_hexpand(label_widget, TRUE);
300 gtk_widget_set_hexpand(left_box, TRUE);
301 gtk_widget_set_halign(left_box, GTK_ALIGN_FILL);
302 gtk_widget_set_hexpand(box, TRUE);
303 gtk_widget_set_halign(box, GTK_ALIGN_FILL);
304 gtk_widget_set_halign(image_widget, GTK_ALIGN_END);
305
306 gtk_box_pack_start(GTK_BOX(left_box), label_widget, TRUE, TRUE, 0);
307 gtk_box_pack_start(GTK_BOX(box), left_box, TRUE, TRUE, 0);
308 gtk_box_pack_end(GTK_BOX(box), image_widget, FALSE, FALSE, 0);
309 gtk_container_add(GTK_CONTAINER(menu_item), box);
310 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), checked);
311 if(show_checkbox)
312 gtk_style_context_add_class(gtk_widget_get_style_context(menu_item), "dt-masks-inverse-item");
313 else
314 gtk_style_context_add_class(gtk_widget_get_style_context(menu_item), "dt-masks-hide-check");
315
316 if(checked)
317 {
318 gtk_style_context_add_class(gtk_widget_get_style_context(menu_item), "dt-masks-checked");
319 gtk_style_context_add_class(gtk_widget_get_style_context(label_widget), "dt-masks-checked-label");
320 }
321 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
322 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
323
324 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), user_data);
325
326 return menu_item;
327}
328
329
330dt_menu_entry_t *set_menu_entry(GtkWidget **menus, GList **items_list,
331 const gchar *label, dt_menus_t menu_index,
332 GtkMenu *parent,
333 void *data,
334 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data),
335 gboolean (*checked_callback)(GtkWidget *widget),
336 gboolean (*active_callback)(GtkWidget *widget),
337 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val,
338 GdkModifierType mods, GtkAccelGroup *accel_group)
339{
340 if(IS_NULL_PTR(label)) label = "";
341
342 // Alloc and set to 0
343 dt_menu_entry_t *entry = calloc(1, sizeof(dt_menu_entry_t));
344
345 // Main widget
346 if(checked_callback)
347 {
348 entry->widget = gtk_check_menu_item_new_with_label("");
350 }
351 else
352 {
353 entry->widget = gtk_menu_item_new_with_label("");
355 }
356
357 // Set the text label allowing markup
358 GtkWidget *child = gtk_bin_get_child(GTK_BIN(entry->widget));
359 gtk_label_set_markup(GTK_LABEL(child), label);
360
361 // Store arbitrary data in the GtkWidget if needed
362 if(data)
363 g_object_set_data(G_OBJECT(entry->widget), "custom-data", data);
364
365 gtk_widget_show_all(GTK_WIDGET(entry->widget));
366
367 entry->menu = menu_index;
368 entry->accel_group = accel_group;
369 entry->action_callback = action_callback;
370 entry->checked_callback = checked_callback;
371 entry->sensitive_callback = sensitive_callback;
372 entry->active_callback = active_callback;
373
374 // Wire the accelerator
375 // Publish a new accel to the global map and attach it to the menu entry widget
376 if(!IS_NULL_PTR(action_callback))
377 {
378 // Register accel only if requested
379 if(!IS_NULL_PTR(accel_group))
380 {
381 gchar *clean_label = strip_markup(label);
382 // Slash is not allowed in control names because that makes accel paths fail.
383 // Keep the visible label intact, but sanitize the accelerator name.
384 if(g_strrstr(clean_label, "/") != NULL)
385 g_strdelimit(clean_label, "/", '-');
386
387 const gchar *parent_path = gtk_menu_get_accel_path(parent);
388
390 darktable.gui->accels, action_callback, entry->widget, accel_group,
391 parent_path, clean_label,
392 key_val, mods, FALSE, _("Triggers the action"));
393
394 gchar *path = dt_accels_build_path(parent_path, clean_label);
395 gtk_widget_set_accel_path(entry->widget, path, (!IS_NULL_PTR(action_callback)) ? accel_group : NULL);
396 dt_free(path);
397 dt_free(clean_label);
398 }
399 else
400 {
401 // Show a fake shortcut
402 gtk_accel_label_set_accel(GTK_ACCEL_LABEL(child), key_val, mods);
403 }
404
405 g_signal_connect(G_OBJECT(entry->widget), "activate", G_CALLBACK(_activate_callback_to_action_callback), entry);
406 }
407
408 // Add it to the list of menus items for easy sequential access later
409 *items_list = g_list_append(*items_list, entry);
410 g_signal_connect(G_OBJECT(entry->widget), "destroy", G_CALLBACK(_menu_entry_destroy), entry);
411 //fprintf(stdout, "menu %s, ref is at %p, list it as %p\n", label, items_list, *items_list);
412
413 return entry;
414}
415
416
418{
419 // Use the callbacks functions to update the visual properties of the menu entry
420 if(entry->style > DT_MENU_ENTRY_DEFAULT)
421 {
422 // Set the visible state of the checkbox without actually triggering the callback running on activation
423 // Gtk has no concept of "set costmetic active state" for checkboxes.
424 g_signal_handlers_block_matched(G_OBJECT(entry->widget), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _activate_callback_to_action_callback, entry);
425 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->widget), entry->checked_callback(entry->widget));
426 g_signal_handlers_unblock_matched(G_OBJECT(entry->widget), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _activate_callback_to_action_callback, entry);
427 }
428
429 if(entry->sensitive_callback)
430 gtk_widget_set_sensitive(entry->widget, entry->sensitive_callback(entry->widget));
431
432 if(entry->active_callback)
433 {
434 if(entry->active_callback(entry->widget))
435 dt_gui_add_class(entry->widget, "menu-active");
436 else
437 dt_gui_remove_class(entry->widget, "menu-active");
438 }
439}
440
441void update_menu_entries(GtkWidget *widget, gpointer user_data)
442{
443 // Update the graphic state of all sub-menu entries from the current top-level menu
444 // but only when it is opened.
445 GList **lists = (GList **)user_data;
446 //fprintf(stdout, "ref is at %p, list it at %p\n", lists, *lists);
447
448 if(lists && *lists)
449 {
450 GList *entries = *lists;
451 for(GList *entry_iter = g_list_first(entries); entry_iter; entry_iter = g_list_next(entry_iter))
452 {
453 dt_menu_entry_t *entry = (dt_menu_entry_t *)(entry_iter->data);
454 if(entry) update_entry(entry);
455 }
456 }
457}
458
459// Use for first-level entries in any menubar
460void add_generic_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index,
461 gchar *label, GtkAccelGroup *accel_group, const char *accel_path_prefix)
462{
463 // Top menus belong to menu bar : file, edit, display, etc.
464 menus[index] = gtk_menu_new();
465 gtk_menu_set_accel_group(GTK_MENU(menus[index]), accel_group);
466
467 gchar *clean_label = strip_markup(label);
468
469 // slash is not allowed in control names because that makes accel pathes fail
470 assert(g_strrstr(clean_label, "/") == NULL);
471
472 gchar *accel_path = dt_accels_build_path(accel_path_prefix, clean_label);
473 gtk_menu_set_accel_path(GTK_MENU(menus[index]), accel_path);
474 dt_free(clean_label);
475 dt_free(accel_path);
476
477 GtkWidget *menu_label = gtk_menu_item_new_with_mnemonic(label);
478 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_label), menus[index]);
479 gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_label);
480 dt_gui_add_class(menu_label, "top-level-item");
481 g_signal_connect(G_OBJECT(menu_label), "activate", G_CALLBACK(update_menu_entries), lists);
482}
483
484
485// Use for first-level entries in the global menubar
486void add_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label)
487{
488 // Top menus belong to menu bar : file, edit, display, etc.
489 add_generic_top_menu_entry(menu_bar, menus, lists, index, label, darktable.gui->accels->global_accels, "Global/Menu");
490}
491
492// Special submenus entries that only open a sub-submenu
493void add_generic_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, GtkAccelGroup *accel_group)
494{
495 GtkWidget *submenu = gtk_menu_new();
496 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
497
498 gchar *clean_label = strip_markup(label);
499
500 // slash is not allowed in control names because that makes accel pathes fail
501 assert(g_strrstr(clean_label, "/") == NULL);
502
503 gchar *accel_path = dt_accels_build_path(gtk_menu_get_accel_path(GTK_MENU(menus[index])), clean_label);
504 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path);
505 dt_free(clean_label);
506 dt_free(accel_path);
507
508 dt_menu_entry_t *entry = set_menu_entry(menus, lists, label, index, GTK_MENU(menus[index]), NULL, NULL, NULL, NULL, NULL, 0, 0, accel_group);
509 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->widget), submenu);
510 gtk_menu_shell_append(GTK_MENU_SHELL(menus[index]), entry->widget);
511 // We don't take callbacks for top submenus, they do nothing more than opening sub-submenues.
512}
513
514// Global menu only
515void add_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index)
516{
517 add_generic_top_submenu_entry(menus, lists, label, index, darktable.gui->accels->global_accels);
518}
519
520
521void add_generic_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index,
522 void *data,
523 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable,
524 guint keyval, GdkModifierType mods, gpointer user_data),
525 gboolean (*checked_callback)(GtkWidget *widget),
526 gboolean (*active_callback)(GtkWidget *widget),
527 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val,
528 GdkModifierType mods, GtkAccelGroup *accel_group)
529{
530 // Default submenu entries
531 dt_menu_entry_t *entry = set_menu_entry(menus, lists, label, index, GTK_MENU(menus[index]),
532 data,
533 action_callback, checked_callback,
534 active_callback, sensitive_callback,
535 key_val, mods, accel_group);
536
537 gtk_menu_shell_append(GTK_MENU_SHELL(menus[index]), entry->widget);
538 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(entry->widget), TRUE);
539}
540
541
542void add_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, void *data,
543 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
544 GdkModifierType mods, gpointer user_data),
545 gboolean (*checked_callback)(GtkWidget *widget),
546 gboolean (*active_callback)(GtkWidget *widget),
547 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods)
548{
549 add_generic_sub_menu_entry(menus, lists, label, index, data, action_callback, checked_callback, active_callback,
550 sensitive_callback, key_val, mods, darktable.gui->accels->global_accels);
551}
552
553void add_no_accel_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index,
554 void *data,
555 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable,
556 guint keyval, GdkModifierType mods, gpointer user_data),
557 gboolean (*checked_callback)(GtkWidget *widget),
558 gboolean (*active_callback)(GtkWidget *widget),
559 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val,
560 GdkModifierType mods)
561{
562 add_generic_sub_menu_entry(menus, lists, label, index, data, action_callback, checked_callback, active_callback,
563 sensitive_callback, key_val, mods, NULL);
564}
565
566void add_generic_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label,
567 const dt_menus_t index, void *data,
568 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable,
569 guint keyval, GdkModifierType mods,
570 gpointer user_data),
571 gboolean (*checked_callback)(GtkWidget *widget),
572 gboolean (*active_callback)(GtkWidget *widget),
573 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val,
574 GdkModifierType mods, GtkAccelGroup *accel_group)
575{
576 // Submenu of submenus entries
578 menus, lists, label, index, GTK_MENU(gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent))), data,
579 action_callback, checked_callback, active_callback, sensitive_callback, key_val, mods, accel_group);
580
581 gtk_menu_shell_append(GTK_MENU_SHELL(gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent))), entry->widget);
582}
583
584void add_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label,
585 const dt_menus_t index, void *data,
586 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
587 GdkModifierType mods, gpointer user_data),
588 gboolean (*checked_callback)(GtkWidget *widget),
589 gboolean (*active_callback)(GtkWidget *widget),
590 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods)
591{
592 add_generic_sub_sub_menu_entry(menus, parent, lists, label, index, data, action_callback, checked_callback,
593 active_callback, sensitive_callback, key_val, mods, darktable.gui->accels->global_accels);
594}
595
596// We don't go further than 3 levels of menus. This is not a Dassault Systems software.
597
599{
600 GtkWidget *sep = gtk_separator_menu_item_new();
601 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
602 gtk_widget_show(sep);
603}
604
606{
607 GtkWidget *sep = gtk_separator_menu_item_new();
608 gtk_menu_shell_append(GTK_MENU_SHELL(gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent))), sep);
609 gtk_widget_show(sep);
610}
611
613{
614 // Grab custom data optionnaly passed by pointer to the menuitem widget
615 return g_object_get_data(G_OBJECT(widget), "custom-data");
616}
617
619{
620 GList *last_entry = g_list_last(*list);
621 GtkWidget *w = NULL;
622 if(last_entry)
623 {
624 dt_menu_entry_t *entry = (dt_menu_entry_t *)(last_entry)->data;
625 if(entry && entry->widget) w = entry->widget;
626 }
627 return w;
628}
629
631{
632 // Can be used to set menu items sensitivity when image(s) is/are selected
634}
635
637{
639}
640
642{
644 return cv && !g_strcmp0(cv->module_name, "lighttable");
645}
646
647gboolean _is_darkroom()
648{
650 return cv && !g_strcmp0(cv->module_name, "darkroom");
651}
652
654{
655 const gboolean image = has_active_images();
656 const gboolean lighttable = _is_lighttable();
657 return image && lighttable;
658}
659
660gboolean dt_menu_is_image_in_dev(GList *imgs)
661{
663 && g_list_find(imgs, GINT_TO_POINTER(darktable.develop->image_storage.id));
664}
665
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
void dt_accels_new_action_shortcut(dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name, guint key_val, GdkModifierType accel_mods, const gboolean lock, const char *description)
Register a new shortcut for a generic action, setting up its path, default keys and accel group....
int dt_act_on_get_images_nb(const gboolean only_visible, const gboolean force)
Definition act_on.c:54
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
darktable_t darktable
Definition darktable.c:181
static gchar * strip_markup(const char *s)
Remove Pango/Gtk markup and accels mnemonics from text labels. If the markup parsing fails,...
Definition darktable.h:1095
#define dt_free(ptr)
Definition darktable.h:456
#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
void dt_dev_history_pixelpipe_update(dt_develop_t *dev, gboolean rebuild)
Rebuild or resync pixelpipes after backend history changes.
void dt_dev_history_gui_update(dt_develop_t *dev)
Apply history-loaded params to module GUIs.
gboolean dt_dev_reload_history_items(dt_develop_t *dev, const int32_t imgid)
Reload history from DB and rebuild pipelines/GUI state.
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:143
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
static const float x
#define M_PI
Definition math.h:45
gboolean has_active_image_in_lighttable()
Definition menu.c:653
GtkWidget * ctx_gtk_menu_item_new_with_icon_and_shortcut(const char *label, const char *shortcut, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, dt_menu_icon_t icon)
Definition menu.c:130
gboolean has_selection()
Definition menu.c:630
void add_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods)
Definition menu.c:584
void add_no_accel_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods)
Definition menu.c:553
void add_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label)
Definition menu.c:486
gboolean _is_lighttable()
Definition menu.c:641
gboolean dt_menu_is_image_in_dev(GList *imgs)
Definition menu.c:660
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
static void _menu_entry_destroy(GtkWidget *widget, gpointer user_data)
Definition menu.c:94
void add_generic_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods, GtkAccelGroup *accel_group)
Definition menu.c:521
void add_generic_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods, GtkAccelGroup *accel_group)
Definition menu.c:566
GtkWidget * ctx_gtk_check_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, const gboolean checked, const gboolean show_checkbox)
Definition menu.c:283
GtkWidget * ctx_gtk_check_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, const gboolean checked, const gboolean show_checkbox)
Definition menu.c:244
void dt_menu_apply_dev_history_update(dt_develop_t *dev)
Reload the current darkroom history and refresh every dependent GUI.
Definition menu.c:666
void update_menu_entries(GtkWidget *widget, gpointer user_data)
Definition menu.c:441
dt_menu_entry_t * set_menu_entry(GtkWidget **menus, GList **items_list, const gchar *label, dt_menus_t menu_index, GtkMenu *parent, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods, GtkAccelGroup *accel_group)
Definition menu.c:330
GtkWidget * get_last_widget(GList **list)
Definition menu.c:618
void add_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods)
Definition menu.c:542
static gboolean _menu_icon_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition menu.c:60
void add_sub_menu_separator(GtkWidget *parent)
Definition menu.c:605
void * get_custom_data(GtkWidget *widget)
Definition menu.c:612
gboolean _is_darkroom()
Definition menu.c:647
GtkWidget * ctx_gtk_menu_item_new_with_icon(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, dt_menu_icon_t icon)
Definition menu.c:100
void add_generic_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label, GtkAccelGroup *accel_group, const char *accel_path_prefix)
Definition menu.c:460
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
void add_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index)
Definition menu.c:515
void add_menu_separator(GtkWidget *menu)
Definition menu.c:598
static void _activate_callback_to_action_callback(GtkMenuItem *menu_item, gpointer user_data)
Definition menu.c:53
void add_generic_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, GtkAccelGroup *accel_group)
Definition menu.c:493
GtkWidget * ctx_gtk_menu_item_new_with_markup_and_shortcut(const char *label, const char *shortcut, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:188
void update_entry(dt_menu_entry_t *entry)
Definition menu.c:417
gboolean has_active_images()
Definition menu.c:636
@ DT_MENU_ENTRY_CHECKBUTTON
Definition menu.h:58
@ DT_MENU_ENTRY_DEFAULT
Definition menu.h:57
dt_menu_icon_t
Definition menu.h:30
@ DT_MENU_ICON_CIRCLE
Definition menu.h:32
@ DT_MENU_ICON_NONE
Definition menu.h:31
@ DT_MENU_ICON_SQUARE
Definition menu.h:33
dt_menus_t
Definition menu.h:42
size_t size
Definition mipmap_cache.c:3
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:179
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:204
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_selection_t * selection
Definition darktable.h:782
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_view_manager_t * view_manager
Definition darktable.h:772
GtkAccelGroup * global_accels
dt_image_t image_storage
Definition develop.h:259
dt_accels_t * accels
Definition gtk.h:194
int32_t id
Definition image.h:319
Definition menu.h:65
GtkAccelGroup * accel_group
Definition menu.h:74
dt_menu_entry_style_t style
Definition menu.h:73
gboolean(* sensitive_callback)(GtkWidget *widget)
Definition menu.h:69
dt_menus_t menu
Definition menu.h:72
gboolean(* checked_callback)(GtkWidget *widget)
Definition menu.h:70
gboolean(* action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
Definition menu.h:68
gboolean(* active_callback)(GtkWidget *widget)
Definition menu.h:71
GtkWidget * widget
Definition menu.h:66
dt_menu_icon_t shape
Definition menu.h:38
char module_name[64]
Definition view.h:153
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:140