Fix #934: Request to add image bookmarking/tagging within a folder
[geeqie.git] / src / ui_menu.c
index 4192dda..56426d2 100644 (file)
@@ -1,13 +1,22 @@
 /*
- * (SLIK) SimpLIstic sKin functions
- * (C) 2004 John Ellis
- * Copyright (C) 2008 The Geeqie Team
+ * Copyright (C) 2004 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
  *
  * Author: John Ellis
  *
- * This software is released under the GNU General Public License (GNU GPL).
- * Please read the included file COPYING for more information.
- * This software comes with no warranty of any kind, use at your own risk!
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -21,6 +30,8 @@
 
 #include <gtk/gtk.h>
 
+#include "main.h"
+#include "layout.h"
 #include "ui_menu.h"
 
 
  *-----------------------------------------------------------------------------
  */
 
+/**
+ * @brief Add accelerator key to a window popup menu
+ * @param menu
+ * @param accel_group
+ * @param window_keys
+ *
+ * This is used only so that the user can see the applicable
+ * shortcut key displayed in the menu. The actual handling of
+ * the keystroke is done elsewhere in the code.
+ */
+static void menu_item_add_accelerator(GtkWidget *menu, GtkAccelGroup *accel_group, hard_coded_window_keys *window_keys)
+{
+       gchar *label;
+       gchar *label_text;
+       gchar **label_stripped;
+       gint i = 0;
+
+       label = g_strdup(gtk_menu_item_get_label(GTK_MENU_ITEM(menu)));
+
+       pango_parse_markup(label, -1, '_', NULL, &label_text, NULL, NULL);
+
+       label_stripped = g_strsplit(label_text, "...", 2);
+
+       while (window_keys[i].text != NULL)
+               {
+               if (g_strcmp0(window_keys[i].text, label_stripped[0]) == 0)
+                       {
+                       gtk_widget_add_accelerator(menu, "activate", accel_group, window_keys[i].key_value, (GdkModifierType)window_keys[i].mask, GTK_ACCEL_VISIBLE);
+
+                       break;
+                       }
+               i++;
+               }
+
+       g_free(label);
+       g_free(label_text);
+       g_strfreev(label_stripped);
+}
+
+/**
+ * @brief Callback for the actions GList sort function
+ * @param a
+ * @param b
+ * @returns
+ *
+ * Sort the action entries so that the non-shifted and non-control
+ * entries are at the start of the list. The user then sees the basic
+ * non-modified key shortcuts displayed in the menus.
+ */
+static gint actions_sort_cb(gconstpointer a, gconstpointer b)
+{
+       const gchar *accel_path_a;
+       GtkAccelKey key_a;
+       const gchar *accel_path_b;
+       GtkAccelKey key_b;
+
+       accel_path_a = gtk_action_get_accel_path(GTK_ACTION(a));
+       accel_path_b = gtk_action_get_accel_path(GTK_ACTION(b));
+
+       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))
+               {
+               if (key_a.accel_mods < key_b.accel_mods) return -1;
+               if (key_a.accel_mods > key_b.accel_mods) return 1;
+               }
+
+       return 0;
+}
+
+/**
+ * @brief Add accelerator key to main window popup menu
+ * @param menu
+ * @param accel_group
+ *
+ * This is used only so that the user can see the applicable
+ * shortcut key displayed in the menu. The actual handling of
+ * the keystroke is done elsewhere in the code.
+ */
+static void menu_item_add_main_window_accelerator(GtkWidget *menu, GtkAccelGroup *accel_group)
+{
+       gchar *menu_label;
+       gchar *menu_label_text;
+       gchar *action_label;
+       gchar *action_label_text;
+       LayoutWindow *lw;
+       GList *groups;
+       GList *actions;
+       GtkAction *action;
+       const gchar *accel_path;
+       GtkAccelKey key;
+
+       menu_label = g_strdup(gtk_menu_item_get_label(GTK_MENU_ITEM(menu)));
+
+       pango_parse_markup(menu_label, -1, '_', NULL, &menu_label_text, NULL, NULL);
+
+       lw = layout_window_list->data; /* get the actions from the first window, it should not matter, they should be the same in all windows */
+
+       g_assert(lw && lw->ui_manager);
+       groups = gtk_ui_manager_get_action_groups(lw->ui_manager);
+       actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data)); // Only the first group required
+       actions = g_list_sort(actions, actions_sort_cb);
+
+       while (actions)
+               {
+               action = GTK_ACTION(actions->data);
+               accel_path = gtk_action_get_accel_path(action);
+               if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key))
+                       {
+                       g_object_get(action, "label", &action_label, NULL);
+
+                       pango_parse_markup(action_label, -1, '_', NULL, &action_label_text, NULL, NULL);
+
+                       if (g_strcmp0(action_label_text, menu_label_text) == 0)
+                               {
+                               if (key.accel_key != 0)
+                                       {
+                                       gtk_widget_add_accelerator(menu, "activate", accel_group, key.accel_key, key.accel_mods, GTK_ACCEL_VISIBLE);
+
+                                       break;
+                                       }
+                               }
+
+                       g_free(action_label_text);
+                       g_free(action_label);
+                       }
+               actions = actions->next;
+               }
+
+       g_free(menu_label);
+       g_free(menu_label_text);
+}
+
 static void menu_item_finish(GtkWidget *menu, GtkWidget *item, GCallback func, gpointer data)
 {
        if (func) g_signal_connect(G_OBJECT(item), "activate", func, data);
@@ -41,8 +183,22 @@ GtkWidget *menu_item_add(GtkWidget *menu, const gchar *label,
                         GCallback func, gpointer data)
 {
        GtkWidget *item;
+       GtkAccelGroup *accel_group;
+       hard_coded_window_keys *window_keys;
 
        item = gtk_menu_item_new_with_mnemonic(label);
+       window_keys = g_object_get_data(G_OBJECT(menu), "window_keys");
+       accel_group = g_object_get_data(G_OBJECT(menu), "accel_group");
+
+       if (accel_group && window_keys)
+               {
+               menu_item_add_accelerator(item, accel_group, window_keys);
+               }
+       else if (accel_group)
+               {
+               menu_item_add_main_window_accelerator(item, accel_group);
+               }
+
        menu_item_finish(menu, item, func, data);
 
        return item;
@@ -53,69 +209,130 @@ GtkWidget *menu_item_add_stock(GtkWidget *menu, const gchar *label, const gchar
 {
        GtkWidget *item;
        GtkWidget *image;
+       GtkAccelGroup *accel_group;
+       hard_coded_window_keys *window_keys;
 
        item = gtk_image_menu_item_new_with_mnemonic(label);
+       window_keys = g_object_get_data(G_OBJECT(menu), "window_keys");
+       accel_group = g_object_get_data(G_OBJECT(menu), "accel_group");
+
        image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
+
+       if (accel_group && window_keys)
+               {
+               menu_item_add_accelerator(item, accel_group, window_keys);
+               }
+       else if (accel_group)
+               {
+               menu_item_add_main_window_accelerator(item, accel_group);
+               }
+
        gtk_widget_show(image);
        menu_item_finish(menu, item, func, data);
 
        return item;
 }
 
-GtkWidget *menu_item_add_sensitive(GtkWidget *menu, const gchar *label, gint sensitive,
+GtkWidget *menu_item_add_sensitive(GtkWidget *menu, const gchar *label, gboolean sensitive,
                                   GCallback func, gpointer data)
 {
        GtkWidget *item;
+       GtkAccelGroup *accel_group;
+       hard_coded_window_keys *window_keys;
 
        item = menu_item_add(menu, label, func, data);
        gtk_widget_set_sensitive(item, sensitive);
+       window_keys = g_object_get_data(G_OBJECT(menu), "window_keys");
+       accel_group = g_object_get_data(G_OBJECT(menu), "accel_group");
+       if (accel_group && window_keys)
+               {
+               menu_item_add_accelerator(item, accel_group, window_keys);
+               }
+       else if (accel_group)
+               {
+               menu_item_add_main_window_accelerator(item, accel_group);
+               }
 
        return item;
 }
 
-GtkWidget *menu_item_add_stock_sensitive(GtkWidget *menu, const gchar *label, const gchar *stock_id, gint sensitive,
+GtkWidget *menu_item_add_stock_sensitive(GtkWidget *menu, const gchar *label, const gchar *stock_id, gboolean sensitive,
                                         GCallback func, gpointer data)
 {
        GtkWidget *item;
+       GtkAccelGroup *accel_group;
+       hard_coded_window_keys *window_keys;
 
        item = menu_item_add_stock(menu, label, stock_id, func, data);
        gtk_widget_set_sensitive(item, sensitive);
+       window_keys = g_object_get_data(G_OBJECT(menu), "window_keys");
+       accel_group = g_object_get_data(G_OBJECT(menu), "accel_group");
+       if (accel_group && window_keys)
+               {
+               menu_item_add_accelerator(item, accel_group, window_keys);
+               }
+       else if (accel_group)
+               {
+               menu_item_add_main_window_accelerator(item, accel_group);
+               }
 
        return item;
 }
 
-GtkWidget *menu_item_add_check(GtkWidget *menu, const gchar *label, gint active,
+GtkWidget *menu_item_add_check(GtkWidget *menu, const gchar *label, gboolean active,
                               GCallback func, gpointer data)
 {
        GtkWidget *item;
+       GtkAccelGroup *accel_group;
+       hard_coded_window_keys *window_keys;
 
        item = gtk_check_menu_item_new_with_mnemonic(label);
+       window_keys = g_object_get_data(G_OBJECT(menu), "window_keys");
+       accel_group = g_object_get_data(G_OBJECT(menu), "accel_group");
+
+       if (accel_group && window_keys)
+               {
+               menu_item_add_accelerator(item, accel_group, window_keys);
+               }
+       else if (accel_group)
+               {
+               menu_item_add_main_window_accelerator(item, accel_group);
+               }
+
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), active);
        menu_item_finish(menu, item, func, data);
 
        return item;
 }
 
-GtkWidget *menu_item_add_radio(GtkWidget *menu, GtkWidget *parent,
-                              const gchar *label, gint active,
+GtkWidget *menu_item_add_radio(GtkWidget *menu, const gchar *label, gpointer item_data, gboolean active,
                               GCallback func, gpointer data)
 {
-       GtkWidget *item;
-       GSList *group = NULL;
+       GtkAccelGroup *accel_group;
+       hard_coded_window_keys *window_keys;
 
-       if (parent) group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(parent));
+       GtkWidget *item = menu_item_add_check(menu, label, active, func, data);
+       g_object_set_data(G_OBJECT(item), "menu_item_radio_data", item_data);
+       g_object_set(G_OBJECT(item), "draw-as-radio", TRUE, NULL);
 
-       item = gtk_radio_menu_item_new_with_mnemonic(group, label);
-       if (active) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), active);
-       menu_item_finish(menu, item, func, data);
+       window_keys = g_object_get_data(G_OBJECT(menu), "window_keys");
+       accel_group = g_object_get_data(G_OBJECT(menu), "accel_group");
+       if (accel_group && window_keys)
+               {
+               menu_item_add_accelerator(item, accel_group, window_keys);
+               }
+       else if (accel_group)
+               {
+               menu_item_add_main_window_accelerator(item, accel_group);
+               }
 
        return item;
 }
 
 void menu_item_add_divider(GtkWidget *menu)
 {
-       GtkWidget *item = gtk_menu_item_new();
+       GtkWidget *item = gtk_separator_menu_item_new();
        gtk_widget_set_sensitive(item, FALSE);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu),item);
        gtk_widget_show(item);
@@ -163,14 +380,16 @@ GtkWidget *popup_menu_short_lived(void)
        return menu;
 }
 
-gint popup_menu_position_clamp(GtkMenu *menu, gint *x, gint *y, gint height)
+gboolean popup_menu_position_clamp(GtkMenu *menu, gint *x, gint *y, gint height)
 {
-       gint adjusted = FALSE;
+       gboolean adjusted = FALSE;
        gint w, h;
        gint xw, xh;
+       GtkRequisition requisition;
 
-       w = GTK_WIDGET(menu)->requisition.width;
-       h = GTK_WIDGET(menu)->requisition.height;
+       gtk_widget_get_requisition(GTK_WIDGET(menu), &requisition);
+       w = requisition.width;
+       h = requisition.height;
        xw = gdk_screen_width();
        xh = gdk_screen_height();