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