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
380 const gchar *parent_path = gtk_menu_get_accel_path(parent);
381
383 dt_gui_get_accels(), action_callback, entry->widget, entry->widget, accel_group,
384 parent_path, clean_label,
385 key_val, mods, FALSE, _("Triggers the action"));
386
387 gchar *path = dt_accels_build_path(parent_path, clean_label);
388 gtk_widget_set_accel_path(entry->widget, path, (!IS_NULL_PTR(action_callback)) ? accel_group : NULL);
389
390 // GtkAccelLabel would otherwise read the accel path's live GtkAccelMap entry itself, but
391 // that entry must keep GDK_MOD2_MASK (needed for gtk_accel_map_save() to collapse it to
392 // the portable "<Primary>" token -- see widgets/accelerators.h's DT_PRIMARY_MASK doc
393 // comment), and GTK only ever renders the "⌘" glyph for GDK_META_MASK, never
394 // GDK_MOD2_MASK. update_entry() re-asserts the display-swapped accel on every menu open
395 // (gtk_accel_label_set_accel() overrides the accel-path-driven value), so this is just
396 // the initial value shown before the menu is ever opened.
397 GtkAccelKey initial_key = { 0 };
398 if(gtk_accel_map_lookup_entry(path, &initial_key) && initial_key.accel_key)
399 gtk_accel_label_set_accel(GTK_ACCEL_LABEL(child), initial_key.accel_key,
400 dt_accels_display_mods(initial_key.accel_mods));
401
402 entry->accel_path = path; // transfer ownership, see update_entry()
403 dt_free(clean_label);
404 }
405 else
406 {
407 // Show a fake shortcut
408 gtk_accel_label_set_accel(GTK_ACCEL_LABEL(child), key_val, dt_accels_display_mods(mods));
409 }
410
411 g_signal_connect(G_OBJECT(entry->widget), "activate", G_CALLBACK(_activate_callback_to_action_callback), entry);
412 }
413
414 // Add it to the list of menus items for easy sequential access later
415 *items_list = g_list_append(*items_list, entry);
416 g_signal_connect(G_OBJECT(entry->widget), "destroy", G_CALLBACK(_menu_entry_destroy), entry);
417 //fprintf(stdout, "menu %s, ref is at %p, list it as %p\n", label, items_list, *items_list);
418
419 return entry;
420}
421
422
424{
425 // Use the callbacks functions to update the visual properties of the menu entry
426 if(entry->style > DT_MENU_ENTRY_DEFAULT)
427 {
428 // Set the visible state of the checkbox without actually triggering the callback running on activation
429 // Gtk has no concept of "set costmetic active state" for checkboxes.
430 g_signal_handlers_block_matched(G_OBJECT(entry->widget), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _activate_callback_to_action_callback, entry);
431 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->widget), entry->checked_callback(entry->widget));
432 g_signal_handlers_unblock_matched(G_OBJECT(entry->widget), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _activate_callback_to_action_callback, entry);
433 }
434
435 if(entry->sensitive_callback)
436 gtk_widget_set_sensitive(entry->widget, entry->sensitive_callback(entry->widget));
437
438 if(entry->active_callback)
439 {
440 if(entry->active_callback(entry->widget))
441 dt_gui_add_class(entry->widget, "menu-active");
442 else
443 dt_gui_remove_class(entry->widget, "menu-active");
444 }
445
446 // Re-assert the display-swapped accelerator (see set_menu_entry()'s doc comment) every time
447 // the menu is opened, since the user may have rebound this shortcut since the last open, and
448 // gtk_accel_label_set_accel() does not itself track the live GtkAccelMap entry.
449 if(entry->accel_path)
450 {
451 GtkAccelKey key = { 0 };
452 GtkWidget *child = gtk_bin_get_child(GTK_BIN(entry->widget));
453 if(gtk_accel_map_lookup_entry(entry->accel_path, &key) && key.accel_key)
454 gtk_accel_label_set_accel(GTK_ACCEL_LABEL(child), key.accel_key, dt_accels_display_mods(key.accel_mods));
455 else
456 gtk_accel_label_set_accel(GTK_ACCEL_LABEL(child), 0, 0);
457 }
458}
459
460void update_menu_entries(GtkWidget *widget, gpointer user_data)
461{
462 // Update the graphic state of all sub-menu entries from the current top-level menu
463 // but only when it is opened.
464 GList **lists = (GList **)user_data;
465 //fprintf(stdout, "ref is at %p, list it at %p\n", lists, *lists);
466
467 if(lists && *lists)
468 {
469 GList *entries = *lists;
470 for(GList *entry_iter = g_list_first(entries); entry_iter; entry_iter = g_list_next(entry_iter))
471 {
472 dt_menu_entry_t *entry = (dt_menu_entry_t *)(entry_iter->data);
473 if(entry) update_entry(entry);
474 }
475 }
476}
477
478// Use for first-level entries in any menubar
479void add_generic_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index,
480 gchar *label, GtkAccelGroup *accel_group, const char *accel_path_prefix)
481{
482 // Top menus belong to menu bar : file, edit, display, etc.
483 menus[index] = gtk_menu_new();
484 gtk_menu_set_accel_group(GTK_MENU(menus[index]), accel_group);
485
486 gchar *clean_label = strip_markup(label);
487
488 gchar *accel_path = dt_accels_build_path(accel_path_prefix, clean_label);
489 gtk_menu_set_accel_path(GTK_MENU(menus[index]), accel_path);
490 dt_free(clean_label);
491 dt_free(accel_path);
492
493 GtkWidget *menu_label = gtk_menu_item_new_with_mnemonic(label);
494 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_label), menus[index]);
495 gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_label);
496 dt_gui_add_class(menu_label, "top-level-item");
497 g_signal_connect(G_OBJECT(menu_label), "activate", G_CALLBACK(update_menu_entries), lists);
498}
499
500
501// Use for first-level entries in the global menubar
502void add_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label)
503{
504 // Top menus belong to menu bar : file, edit, display, etc.
505 add_generic_top_menu_entry(menu_bar, menus, lists, index, label, dt_gui_get_accels()->global_accels, "Global/Menu");
506}
507
508// Special submenus entries that only open a sub-submenu
509void add_generic_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, GtkAccelGroup *accel_group)
510{
511 GtkWidget *submenu = gtk_menu_new();
512 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
513
514 gchar *clean_label = strip_markup(label);
515
516 gchar *accel_path = dt_accels_build_path(gtk_menu_get_accel_path(GTK_MENU(menus[index])), clean_label);
517 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path);
518 dt_free(clean_label);
519 dt_free(accel_path);
520
521 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);
522 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->widget), submenu);
523 gtk_menu_shell_append(GTK_MENU_SHELL(menus[index]), entry->widget);
524 // We don't take callbacks for top submenus, they do nothing more than opening sub-submenues.
525}
526
527// Global menu only
528void add_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index)
529{
530 add_generic_top_submenu_entry(menus, lists, label, index, dt_gui_get_accels()->global_accels);
531}
532
533
534void add_generic_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index,
535 void *data,
536 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable,
537 guint keyval, GdkModifierType mods, gpointer user_data),
538 gboolean (*checked_callback)(GtkWidget *widget),
539 gboolean (*active_callback)(GtkWidget *widget),
540 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val,
541 GdkModifierType mods, GtkAccelGroup *accel_group)
542{
543 // Default submenu entries
544 dt_menu_entry_t *entry = set_menu_entry(menus, lists, label, index, GTK_MENU(menus[index]),
545 data,
546 action_callback, checked_callback,
547 active_callback, sensitive_callback,
548 key_val, mods, accel_group);
549
550 gtk_menu_shell_append(GTK_MENU_SHELL(menus[index]), entry->widget);
551 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(entry->widget), TRUE);
552}
553
554
555void add_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, void *data,
556 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
557 GdkModifierType mods, gpointer user_data),
558 gboolean (*checked_callback)(GtkWidget *widget),
559 gboolean (*active_callback)(GtkWidget *widget),
560 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val, 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, dt_gui_get_accels()->global_accels);
564}
565
566void add_no_accel_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index,
567 void *data,
568 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable,
569 guint keyval, GdkModifierType mods, 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)
574{
575 add_generic_sub_menu_entry(menus, lists, label, index, data, action_callback, checked_callback, active_callback,
576 sensitive_callback, key_val, mods, NULL);
577}
578
579void add_generic_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label,
580 const dt_menus_t index, void *data,
581 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable,
582 guint keyval, GdkModifierType mods,
583 gpointer user_data),
584 gboolean (*checked_callback)(GtkWidget *widget),
585 gboolean (*active_callback)(GtkWidget *widget),
586 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val,
587 GdkModifierType mods, GtkAccelGroup *accel_group)
588{
589 // Submenu of submenus entries
591 menus, lists, label, index, GTK_MENU(gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent))), data,
592 action_callback, checked_callback, active_callback, sensitive_callback, key_val, mods, accel_group);
593
594 gtk_menu_shell_append(GTK_MENU_SHELL(gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent))), entry->widget);
595}
596
597void add_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label,
598 const dt_menus_t index, void *data,
599 gboolean (*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
600 GdkModifierType mods, gpointer user_data),
601 gboolean (*checked_callback)(GtkWidget *widget),
602 gboolean (*active_callback)(GtkWidget *widget),
603 gboolean (*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods)
604{
605 add_generic_sub_sub_menu_entry(menus, parent, lists, label, index, data, action_callback, checked_callback,
606 active_callback, sensitive_callback, key_val, mods, dt_gui_get_accels()->global_accels);
607}
608
609// We don't go further than 3 levels of menus. This is not a Dassault Systems software.
610
612{
613 GtkWidget *sep = gtk_separator_menu_item_new();
614 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
615 gtk_widget_show(sep);
616}
617
619{
620 GtkWidget *sep = gtk_separator_menu_item_new();
621 gtk_menu_shell_append(GTK_MENU_SHELL(gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent))), sep);
622 gtk_widget_show(sep);
623}
624
626{
627 // Grab custom data optionnaly passed by pointer to the menuitem widget
628 return g_object_get_data(G_OBJECT(widget), "custom-data");
629}
630
632{
633 GList *last_entry = g_list_last(*list);
634 GtkWidget *w = NULL;
635 if(last_entry)
636 {
637 dt_menu_entry_t *entry = (dt_menu_entry_t *)(last_entry)->data;
638 if(entry && entry->widget) w = entry->widget;
639 }
640 return w;
641}
642
644{
645 // Can be used to set menu items sensitivity when image(s) is/are selected
647}
648
650{
652}
653
655{
657 return cv && !g_strcmp0(cv->module_name, "lighttable");
658}
659
660gboolean _is_darkroom()
661{
663 return cv && !g_strcmp0(cv->module_name, "darkroom");
664}
665
667{
668 const gboolean image = has_active_images();
669 const gboolean lighttable = _is_lighttable();
670 return image && lighttable;
671}
672
673// dt_menu_is_image_in_dev() / dt_apply_dev_history_update() now live in
674// develop/dev_history.c -- they are develop/darkroom concerns, not menu concerns, and are
675// shared with common/history_actions.c and gui/actions/styles.c.
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
Build the accel path of feature under scope.
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:602
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:666
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:643
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:597
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:566
void add_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label)
Definition menu.c:502
gboolean _is_lighttable()
Definition menu.c:654
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:534
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:579
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:460
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:631
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:555
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:618
void * get_custom_data(GtkWidget *widget)
Definition menu.c:625
gboolean _is_darkroom()
Definition menu.c:660
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:479
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:528
void add_menu_separator(GtkWidget *menu)
Definition menu.c:611
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:509
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:423
gboolean has_active_images()
Definition menu.c:649
@ 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:144
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)