Fix #1240: Regression: Option to open new full-function window directly is missing
[geeqie.git] / src / ui-menu.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program 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 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program 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 along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "ui-menu.h"
23
24 #include <cstddef>
25
26 #include <pango/pango.h>
27
28 #include "layout.h"
29
30 /*
31  *-----------------------------------------------------------------------------
32  * menu items
33  *-----------------------------------------------------------------------------
34  */
35
36 /**
37  * @brief Add accelerator key to a window popup menu
38  * @param menu
39  * @param accel_group
40  * @param window_keys
41  *
42  * This is used only so that the user can see the applicable
43  * shortcut key displayed in the menu. The actual handling of
44  * the keystroke is done elsewhere in the code.
45  */
46 static void menu_item_add_accelerator(GtkWidget *menu, GtkAccelGroup *accel_group, hard_coded_window_keys *window_keys)
47 {
48         gchar *label;
49         gchar *label_text;
50         gchar **label_stripped;
51         gint i = 0;
52
53         label = g_strdup(gtk_menu_item_get_label(GTK_MENU_ITEM(menu)));
54
55         pango_parse_markup(label, -1, '_', nullptr, &label_text, nullptr, nullptr);
56
57         label_stripped = g_strsplit(label_text, "...", 2);
58
59         while (window_keys[i].text != nullptr)
60                 {
61                 if (g_strcmp0(window_keys[i].text, label_stripped[0]) == 0)
62                         {
63                         gtk_widget_add_accelerator(menu, "activate", accel_group, window_keys[i].key_value, window_keys[i].mask, GTK_ACCEL_VISIBLE);
64
65                         break;
66                         }
67                 i++;
68                 }
69
70         g_free(label);
71         g_free(label_text);
72         g_strfreev(label_stripped);
73 }
74
75 /**
76  * @brief Callback for the actions GList sort function
77  * @param a
78  * @param b
79  * @returns
80  *
81  * Sort the action entries so that the non-shifted and non-control
82  * entries are at the start of the list. The user then sees the basic
83  * non-modified key shortcuts displayed in the menus.
84  */
85 static gint actions_sort_cb(gconstpointer a, gconstpointer b)
86 {
87         const gchar *accel_path_a;
88         GtkAccelKey key_a;
89         const gchar *accel_path_b;
90         GtkAccelKey key_b;
91
92         accel_path_a = gtk_action_get_accel_path(GTK_ACTION(a));
93         accel_path_b = gtk_action_get_accel_path(GTK_ACTION(b));
94
95         if (accel_path_a && gtk_accel_map_lookup_entry(accel_path_a, &key_a) && accel_path_b && gtk_accel_map_lookup_entry(accel_path_b, &key_b))
96                 {
97                 if (key_a.accel_mods < key_b.accel_mods) return -1;
98                 if (key_a.accel_mods > key_b.accel_mods) return 1;
99                 }
100
101         return 0;
102 }
103
104 /**
105  * @brief Add accelerator key to main window popup menu
106  * @param menu
107  * @param accel_group
108  *
109  * This is used only so that the user can see the applicable
110  * shortcut key displayed in the menu. The actual handling of
111  * the keystroke is done elsewhere in the code.
112  */
113 static void menu_item_add_main_window_accelerator(GtkWidget *menu, GtkAccelGroup *accel_group)
114 {
115         gchar *menu_label;
116         gchar *menu_label_text;
117         gchar *action_label;
118         gchar *action_label_text;
119         LayoutWindow *lw;
120         GList *groups;
121         GList *actions;
122         GtkAction *action;
123         const gchar *accel_path;
124         GtkAccelKey key;
125
126         menu_label = g_strdup(gtk_menu_item_get_label(GTK_MENU_ITEM(menu)));
127
128         pango_parse_markup(menu_label, -1, '_', nullptr, &menu_label_text, nullptr, nullptr);
129
130         lw = static_cast<LayoutWindow *>(layout_window_list->data); /* get the actions from the first window, it should not matter, they should be the same in all windows */
131
132         g_assert(lw && lw->ui_manager);
133         groups = gtk_ui_manager_get_action_groups(lw->ui_manager);
134
135         while (groups)
136                 {
137                 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
138                 actions = g_list_sort(actions, actions_sort_cb);
139
140                 while (actions)
141                         {
142                         action = GTK_ACTION(actions->data);
143                         accel_path = gtk_action_get_accel_path(action);
144                         if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key))
145                                 {
146                                 g_object_get(action, "label", &action_label, NULL);
147
148                                 pango_parse_markup(action_label, -1, '_', nullptr, &action_label_text, nullptr, nullptr);
149
150                                 if (g_strcmp0(action_label_text, menu_label_text) == 0)
151                                         {
152                                         if (key.accel_key != 0)
153                                                 {
154                                                 gtk_widget_add_accelerator(menu, "activate", accel_group, key.accel_key, key.accel_mods, GTK_ACCEL_VISIBLE);
155
156                                                 break;
157                                                 }
158                                         }
159                                 g_free(action_label_text);
160                                 g_free(action_label);
161                                 }
162                         actions = actions->next;
163                         }
164                 groups = groups->next;
165                 }
166
167         g_free(menu_label);
168         g_free(menu_label_text);
169 }
170
171 static void menu_item_finish(GtkWidget *menu, GtkWidget *item, GCallback func, gpointer data)
172 {
173         if (func) g_signal_connect(G_OBJECT(item), "activate", func, data);
174         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
175         gtk_widget_show(item);
176 }
177
178 GtkWidget *menu_item_add(GtkWidget *menu, const gchar *label,
179                          GCallback func, gpointer data)
180 {
181         GtkWidget *item;
182         GtkAccelGroup *accel_group;
183         hard_coded_window_keys *window_keys;
184
185         item = gtk_menu_item_new_with_mnemonic(label);
186         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
187         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
188
189         if (accel_group && window_keys)
190                 {
191                 menu_item_add_accelerator(item, accel_group, window_keys);
192                 }
193         else if (accel_group)
194                 {
195                 menu_item_add_main_window_accelerator(item, accel_group);
196                 }
197
198         menu_item_finish(menu, item, func, data);
199
200         return item;
201 }
202
203 GtkWidget *menu_item_add_stock(GtkWidget *menu, const gchar *label, const gchar *stock_id,
204                                GCallback func, gpointer data)
205 {
206         GtkWidget *item;
207         GtkWidget *image;
208         GtkAccelGroup *accel_group;
209         hard_coded_window_keys *window_keys;
210
211         item = gtk_image_menu_item_new_with_mnemonic(label);
212         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
213         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
214
215         image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
216         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
217
218         if (accel_group && window_keys)
219                 {
220                 menu_item_add_accelerator(item, accel_group, window_keys);
221                 }
222         else if (accel_group)
223                 {
224                 menu_item_add_main_window_accelerator(item, accel_group);
225                 }
226
227         gtk_widget_show(image);
228         menu_item_finish(menu, item, func, data);
229
230         return item;
231 }
232
233 GtkWidget *menu_item_add_icon(GtkWidget *menu, const gchar *label, const gchar *icon_name,
234                                GCallback func, gpointer data)
235 {
236         GtkWidget *item;
237         GtkWidget *image;
238         GtkAccelGroup *accel_group;
239         hard_coded_window_keys *window_keys;
240
241         item = gtk_image_menu_item_new_with_mnemonic(label);
242         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
243         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
244
245         image = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
246         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
247
248         if (accel_group && window_keys)
249                 {
250                 menu_item_add_accelerator(item, accel_group, window_keys);
251                 }
252         else if (accel_group)
253                 {
254                 menu_item_add_main_window_accelerator(item, accel_group);
255                 }
256
257         gtk_widget_show(image);
258         menu_item_finish(menu, item, func, data);
259
260         return item;
261 }
262
263 GtkWidget *menu_item_add_sensitive(GtkWidget *menu, const gchar *label, gboolean sensitive,
264                                    GCallback func, gpointer data)
265 {
266         GtkWidget *item;
267         GtkAccelGroup *accel_group;
268         hard_coded_window_keys *window_keys;
269
270         item = menu_item_add(menu, label, func, data);
271         gtk_widget_set_sensitive(item, sensitive);
272         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
273         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
274         if (accel_group && window_keys)
275                 {
276                 menu_item_add_accelerator(item, accel_group, window_keys);
277                 }
278         else if (accel_group)
279                 {
280                 menu_item_add_main_window_accelerator(item, accel_group);
281                 }
282
283         return item;
284 }
285
286 GtkWidget *menu_item_add_stock_sensitive(GtkWidget *menu, const gchar *label, const gchar *stock_id, gboolean sensitive,
287                                          GCallback func, gpointer data)
288 {
289         GtkWidget *item;
290         GtkAccelGroup *accel_group;
291         hard_coded_window_keys *window_keys;
292
293         item = menu_item_add_stock(menu, label, stock_id, func, data);
294         gtk_widget_set_sensitive(item, sensitive);
295         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
296         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
297         if (accel_group && window_keys)
298                 {
299                 menu_item_add_accelerator(item, accel_group, window_keys);
300                 }
301         else if (accel_group)
302                 {
303                 menu_item_add_main_window_accelerator(item, accel_group);
304                 }
305
306         return item;
307 }
308
309 GtkWidget *menu_item_add_icon_sensitive(GtkWidget *menu, const gchar *label, const gchar *icon_name, gboolean sensitive,
310                                          GCallback func, gpointer data)
311 {
312         GtkWidget *item;
313         GtkAccelGroup *accel_group;
314         hard_coded_window_keys *window_keys;
315
316         item = menu_item_add_icon(menu, label, icon_name, func, data);
317         gtk_widget_set_sensitive(item, sensitive);
318         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
319         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
320         if (accel_group && window_keys)
321                 {
322                 menu_item_add_accelerator(item, accel_group, window_keys);
323                 }
324         else if (accel_group)
325                 {
326                 menu_item_add_main_window_accelerator(item, accel_group);
327                 }
328
329         return item;
330 }
331
332 GtkWidget *menu_item_add_check(GtkWidget *menu, const gchar *label, gboolean active,
333                                GCallback func, gpointer data)
334 {
335         GtkWidget *item;
336         GtkAccelGroup *accel_group;
337         hard_coded_window_keys *window_keys;
338
339         item = gtk_check_menu_item_new_with_mnemonic(label);
340         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
341         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
342
343         if (accel_group && window_keys)
344                 {
345                 menu_item_add_accelerator(item, accel_group, window_keys);
346                 }
347         else if (accel_group)
348                 {
349                 menu_item_add_main_window_accelerator(item, accel_group);
350                 }
351
352         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), active);
353         menu_item_finish(menu, item, func, data);
354
355         return item;
356 }
357
358 GtkWidget *menu_item_add_radio(GtkWidget *menu, const gchar *label, gpointer item_data, gboolean active,
359                                GCallback func, gpointer data)
360 {
361         GtkAccelGroup *accel_group;
362         hard_coded_window_keys *window_keys;
363
364         GtkWidget *item = menu_item_add_check(menu, label, active, func, data);
365         g_object_set_data(G_OBJECT(item), "menu_item_radio_data", item_data);
366         g_object_set(G_OBJECT(item), "draw-as-radio", TRUE, NULL);
367
368         window_keys = static_cast<hard_coded_window_keys *>(g_object_get_data(G_OBJECT(menu), "window_keys"));
369         accel_group = static_cast<GtkAccelGroup *>(g_object_get_data(G_OBJECT(menu), "accel_group"));
370         if (accel_group && window_keys)
371                 {
372                 menu_item_add_accelerator(item, accel_group, window_keys);
373                 }
374         else if (accel_group)
375                 {
376                 menu_item_add_main_window_accelerator(item, accel_group);
377                 }
378
379         return item;
380 }
381
382 void menu_item_add_divider(GtkWidget *menu)
383 {
384         GtkWidget *item = gtk_separator_menu_item_new();
385         gtk_widget_set_sensitive(item, FALSE);
386         gtk_menu_shell_append(GTK_MENU_SHELL(menu),item);
387         gtk_widget_show(item);
388 }
389
390 GtkWidget *menu_item_add_simple(GtkWidget *menu, const gchar *label,
391                                 GCallback func, gpointer data)
392 {
393         GtkWidget *item = gtk_menu_item_new_with_label(label);
394         menu_item_finish(menu, item, func, data);
395
396         return item;
397 }
398
399 /*
400  *-----------------------------------------------------------------------------
401  * popup menus
402  *-----------------------------------------------------------------------------
403  */
404
405 static void popup_menu_short_lived_cb(GtkWidget *, gpointer data)
406 {
407         /* destroy the menu */
408         g_object_unref(G_OBJECT(data));
409 }
410
411 GtkWidget *popup_menu_short_lived()
412 {
413         GtkWidget *menu;
414
415         menu = gtk_menu_new();
416
417         /* take ownership of menu */
418 #ifdef GTK_OBJECT_FLOATING
419         /* GTK+ < 2.10 */
420         g_object_ref(G_OBJECT(menu));
421         gtk_object_sink(GTK_OBJECT(menu));
422 #else
423         /* GTK+ >= 2.10 */
424         g_object_ref_sink(G_OBJECT(menu));
425 #endif
426
427         g_signal_connect(G_OBJECT(menu), "selection_done",
428                          G_CALLBACK(popup_menu_short_lived_cb), menu);
429         return menu;
430 }
431
432 // @todo Used only in vd_menu_position_cb_unused(). Remove?
433 gboolean popup_menu_position_clamp(GtkMenu *menu, gint *x, gint *y, gint height)
434 {
435         gboolean adjusted = FALSE;
436         gint w;
437         gint h;
438         gint xw;
439         gint xh;
440         GtkRequisition requisition;
441
442         G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
443         gtk_widget_get_requisition(GTK_WIDGET(menu), &requisition);
444         w = requisition.width;
445         h = requisition.height;
446         xw = gdk_screen_width();
447         xh = gdk_screen_height();
448         G_GNUC_END_IGNORE_DEPRECATIONS;
449
450         if (*x + w > xw)
451                 {
452                 *x = xw - w;
453                 adjusted = TRUE;
454                 }
455         if (*y + h > xh)
456                 {
457                 if (height)
458                         {
459                         *y = MAX(0, *y - h - height);
460                         }
461                 else
462                         {
463                         *y = xh - h;
464                         }
465                 adjusted = TRUE;
466                 };
467
468         if (*x < 0)
469                 {
470                 *x = 0;
471                 adjusted = TRUE;
472                 }
473         if (*y < 0)
474                 {
475                 *y = 0;
476                 adjusted = TRUE;
477                 }
478
479         return adjusted;
480 }
481 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */