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