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