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