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