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