]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkmenushell.c
Updated Norwegian bokmål translation
[~andy/gtk] / gtk / gtkmenushell.c
index 79422f95873bd5d606f3001498c20830305f3915..3fb8ee6a9610f13c3b86750d4c7ec652dd73c108 100644 (file)
 
 #define GTK_MENU_INTERNALS
 
-#include <config.h>
+#include "config.h"
 #include "gdk/gdkkeysyms.h"
 #include "gtkbindings.h"
 #include "gtkkeyhash.h"
+#include "gtklabel.h"
 #include "gtkmain.h"
 #include "gtkmarshalers.h"
 #include "gtkmenu.h"
@@ -41,7 +42,6 @@
 #include "gtkwindow.h"
 #include "gtkprivate.h"
 #include "gtkintl.h"
-#include "gtkalias.h"
 
 #define MENU_SHELL_TIMEOUT   500
 
@@ -83,7 +83,7 @@ enum {
  *
  * There is also is a concept of the current menu and a current
  * menu item. The current menu item is the selected menu item
- * that is furthest down in the heirarchy. (Every active menu_shell
+ * that is furthest down in the hierarchy. (Every active menu_shell
  * does not necessarily contain a selected menu item, but if
  * it does, then menu_shell->parent_menu_shell must also contain
  * a selected menu item. The current menu is the menu that 
@@ -134,8 +134,14 @@ struct _GtkMenuShellPrivate
   GtkMnemonicHash *mnemonic_hash;
   GtkKeyHash *key_hash;
 
+  GdkDevice *grab_pointer;
+
   guint take_focus : 1;
   guint activated_submenu : 1;
+  /* This flag is a crutch to keep mnemonics in the same menu
+   * if the user moves the mouse over an unselectable menuitem.
+   */
+  guint in_unselectable_item : 1;
 };
 
 static void gtk_menu_shell_set_property      (GObject           *object,
@@ -199,7 +205,7 @@ static gboolean gtk_menu_shell_real_move_selected (GtkMenuShell  *menu_shell,
 
 static guint menu_shell_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (GtkMenuShell, gtk_menu_shell, GTK_TYPE_CONTAINER)
+G_DEFINE_ABSTRACT_TYPE (GtkMenuShell, gtk_menu_shell, GTK_TYPE_CONTAINER)
 
 static void
 gtk_menu_shell_class_init (GtkMenuShellClass *klass)
@@ -250,6 +256,7 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass)
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
+
   menu_shell_signals[SELECTION_DONE] =
     g_signal_new (I_("selection-done"),
                  G_OBJECT_CLASS_TYPE (object_class),
@@ -258,24 +265,27 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass)
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
+
   menu_shell_signals[MOVE_CURRENT] =
-    g_signal_new (I_("move_current"),
+    g_signal_new (I_("move-current"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkMenuShellClass, move_current),
                  NULL, NULL,
                  _gtk_marshal_VOID__ENUM,
-                 G_TYPE_NONE, 1, 
+                 G_TYPE_NONE, 1,
                  GTK_TYPE_MENU_DIRECTION_TYPE);
+
   menu_shell_signals[ACTIVATE_CURRENT] =
-    g_signal_new (I_("activate_current"),
+    g_signal_new (I_("activate-current"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkMenuShellClass, activate_current),
                  NULL, NULL,
                  _gtk_marshal_VOID__BOOLEAN,
-                 G_TYPE_NONE, 1, 
+                 G_TYPE_NONE, 1,
                  G_TYPE_BOOLEAN);
+
   menu_shell_signals[CANCEL] =
     g_signal_new (I_("cancel"),
                  G_OBJECT_CLASS_TYPE (object_class),
@@ -284,17 +294,31 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass)
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
+
   menu_shell_signals[CYCLE_FOCUS] =
-    _gtk_binding_signal_new (I_("cycle_focus"),
-                            G_OBJECT_CLASS_TYPE (object_class),
-                            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-                            G_CALLBACK (gtk_real_menu_shell_cycle_focus),
-                            NULL, NULL,
-                            _gtk_marshal_VOID__ENUM,
-                            G_TYPE_NONE, 1,
-                            GTK_TYPE_DIRECTION_TYPE);
+    g_signal_new_class_handler (I_("cycle-focus"),
+                                G_OBJECT_CLASS_TYPE (object_class),
+                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                G_CALLBACK (gtk_real_menu_shell_cycle_focus),
+                                NULL, NULL,
+                                _gtk_marshal_VOID__ENUM,
+                                G_TYPE_NONE, 1,
+                                GTK_TYPE_DIRECTION_TYPE);
+
+  /**
+   * GtkMenuShell::move-selected:
+   * @menu_shell: the object on which the signal is emitted
+   * @distance: +1 to move to the next item, -1 to move to the previous
+   *
+   * The ::move-selected signal is emitted to move the selection to
+   * another item.
+   *
+   * Returns: %TRUE to stop the signal emission, %FALSE to continue
+   *
+   * Since: 2.12
+   */
   menu_shell_signals[MOVE_SELECTED] =
-    g_signal_new (I_("move_selected"),
+    g_signal_new (I_("move-selected"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkMenuShellClass, move_selected),
@@ -309,31 +333,36 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass)
                                "cancel", 0);
   gtk_binding_entry_add_signal (binding_set,
                                GDK_Return, 0,
-                               "activate_current", 1,
+                               "activate-current", 1,
+                               G_TYPE_BOOLEAN,
+                               TRUE);
+  gtk_binding_entry_add_signal (binding_set,
+                               GDK_ISO_Enter, 0,
+                               "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                TRUE);
   gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Enter, 0,
-                               "activate_current", 1,
+                               "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                TRUE);
   gtk_binding_entry_add_signal (binding_set,
                                GDK_space, 0,
-                               "activate_current", 1,
+                               "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                FALSE);
   gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Space, 0,
-                               "activate_current", 1,
+                               "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                FALSE);
   gtk_binding_entry_add_signal (binding_set,
                                GDK_F10, 0,
-                               "cycle_focus", 1,
+                               "cycle-focus", 1,
                                 GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_FORWARD);
   gtk_binding_entry_add_signal (binding_set,
                                GDK_F10, GDK_SHIFT_MASK,
-                               "cycle_focus", 1,
+                               "cycle-focus", 1,
                                 GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD);
 
   /**
@@ -489,9 +518,7 @@ gtk_menu_shell_realize (GtkWidget *widget)
   GdkWindowAttr attributes;
   gint attributes_mask;
 
-  g_return_if_fail (GTK_IS_MENU_SHELL (widget));
-
-  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+  gtk_widget_set_realized (widget, TRUE);
 
   attributes.x = widget->allocation.x;
   attributes.y = widget->allocation.y;
@@ -522,7 +549,13 @@ _gtk_menu_shell_activate (GtkMenuShell *menu_shell)
 {
   if (!menu_shell->active)
     {
-      gtk_grab_add (GTK_WIDGET (menu_shell));
+      GdkDevice *device;
+
+      device = gtk_get_current_event_device ();
+
+      _gtk_menu_shell_set_grab_device (menu_shell, device);
+      gtk_device_grab_add (GTK_WIDGET (menu_shell), device, TRUE);
+
       menu_shell->have_grab = TRUE;
       menu_shell->active = TRUE;
     }
@@ -563,18 +596,45 @@ gtk_menu_shell_button_press (GtkWidget      *widget,
 
   if (!menu_shell->active || !menu_shell->button)
     {
-      _gtk_menu_shell_activate (menu_shell);
+      gboolean initially_active = menu_shell->active;
 
       menu_shell->button = event->button;
 
-      if (menu_item && _gtk_menu_item_is_selectable (menu_item) &&
-         menu_item->parent == widget &&
-          menu_item != menu_shell->active_menu_item)
+      if (menu_item)
+        {
+          if (_gtk_menu_item_is_selectable (menu_item) &&
+              menu_item->parent == widget &&
+              menu_item != menu_shell->active_menu_item)
+            {
+              _gtk_menu_shell_activate (menu_shell);
+              menu_shell->button = event->button;
+
+              if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM)
+                {
+                  menu_shell->activate_time = event->time;
+                  gtk_menu_shell_select_item (menu_shell, menu_item);
+                }
+            }
+        }
+      else
         {
-          if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM)
+          if (!initially_active)
             {
-              menu_shell->activate_time = event->time;
-              gtk_menu_shell_select_item (menu_shell, menu_item);
+              gboolean window_drag = FALSE;
+
+              gtk_widget_style_get (widget,
+                                    "window-dragging", &window_drag,
+                                    NULL);
+
+              if (window_drag)
+                {
+                  gtk_menu_shell_deactivate (menu_shell);
+                  gtk_window_begin_move_drag (GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+                                              event->button,
+                                              event->x_root,
+                                              event->y_root,
+                                              event->time);
+                }
             }
         }
     }
@@ -590,7 +650,7 @@ gtk_menu_shell_button_press (GtkWidget      *widget,
 
   if (menu_item && _gtk_menu_item_is_selectable (menu_item) &&
       GTK_MENU_ITEM (menu_item)->submenu != NULL &&
-      !GTK_WIDGET_VISIBLE (GTK_MENU_ITEM (menu_item)->submenu))
+      !gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->submenu))
     {
       GtkMenuShellPrivate *priv;
 
@@ -752,21 +812,94 @@ gtk_menu_shell_button_release (GtkWidget      *widget,
   return TRUE;
 }
 
+void
+_gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell,
+                                   gboolean      keyboard_mode)
+{
+  menu_shell->keyboard_mode = keyboard_mode;
+}
+
+gboolean
+_gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell)
+{
+  return menu_shell->keyboard_mode;
+}
+
+void
+_gtk_menu_shell_update_mnemonics (GtkMenuShell *menu_shell)
+{
+  GtkMenuShell *target;
+  gboolean auto_mnemonics;
+  gboolean found;
+  gboolean mnemonics_visible;
+
+  g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
+                "gtk-auto-mnemonics", &auto_mnemonics, NULL);
+
+  if (!auto_mnemonics)
+    return;
+
+  target = menu_shell;
+  found = FALSE;
+  while (target)
+    {
+      GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (target);
+      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (target));
+
+      /* The idea with keyboard mode is that once you start using
+       * the keyboard to navigate the menus, we show mnemonics
+       * until the menu navigation is over. To that end, we spread
+       * the keyboard mode upwards in the menu hierarchy here.
+       * Also see gtk_menu_popup, where we inherit it downwards.
+       */
+      if (menu_shell->keyboard_mode)
+        target->keyboard_mode = TRUE;
+
+      /* While navigating menus, the first parent menu with an active
+       * item is the one where mnemonics are effective, as can be seen
+       * in gtk_menu_shell_key_press below.
+       * We also show mnemonics in context menus. The grab condition is
+       * necessary to ensure we remove underlines from menu bars when
+       * dismissing menus.
+       */
+      mnemonics_visible = target->keyboard_mode &&
+                          (((target->active_menu_item || priv->in_unselectable_item) && !found) ||
+                           (target == menu_shell &&
+                            !target->parent_menu_shell &&
+                            gtk_widget_has_grab (GTK_WIDGET (target))));
+
+      /* While menus are up, only show underlines inside the menubar,
+       * not in the entire window.
+       */
+      if (GTK_IS_MENU_BAR (target))
+        {
+          gtk_window_set_mnemonics_visible (GTK_WINDOW (toplevel), FALSE);
+          _gtk_label_mnemonics_visible_apply_recursively (GTK_WIDGET (target),
+                                                          mnemonics_visible);
+        }
+      else
+        gtk_window_set_mnemonics_visible (GTK_WINDOW (toplevel), mnemonics_visible);
+
+      if (target->active_menu_item || priv->in_unselectable_item)
+        found = TRUE;
+
+      target = GTK_MENU_SHELL (target->parent_menu_shell);
+    }
+}
+
 static gint
 gtk_menu_shell_key_press (GtkWidget   *widget,
                          GdkEventKey *event)
 {
-  GtkMenuShell *menu_shell;
+  GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
+  GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
   gboolean enable_mnemonics;
-  
-  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-      
-  menu_shell = GTK_MENU_SHELL (widget);
 
-  if (!menu_shell->active_menu_item && menu_shell->parent_menu_shell)
+  menu_shell->keyboard_mode = TRUE;
+
+  if (!(menu_shell->active_menu_item || priv->in_unselectable_item) && menu_shell->parent_menu_shell)
     return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent *)event);
-  
+
   if (gtk_bindings_activate_event (GTK_OBJECT (widget), event))
     return TRUE;
 
@@ -784,12 +917,12 @@ static gint
 gtk_menu_shell_enter_notify (GtkWidget        *widget,
                             GdkEventCrossing *event)
 {
-  GtkMenuShell *menu_shell;
-
-  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
 
-  menu_shell = GTK_MENU_SHELL (widget);
+  if (event->mode == GDK_CROSSING_GTK_GRAB ||
+      event->mode == GDK_CROSSING_GTK_UNGRAB ||
+      event->mode == GDK_CROSSING_STATE_CHANGED)
+    return TRUE;
 
   if (menu_shell->active)
     {
@@ -797,10 +930,19 @@ gtk_menu_shell_enter_notify (GtkWidget        *widget,
 
       menu_item = gtk_get_event_widget ((GdkEvent*) event);
 
-      if (!menu_item ||
-         (GTK_IS_MENU_ITEM (menu_item) && 
-          !_gtk_menu_item_is_selectable (menu_item)))
-       return TRUE;
+      if (!menu_item)
+        return TRUE;
+
+      if (GTK_IS_MENU_ITEM (menu_item) &&
+          !_gtk_menu_item_is_selectable (menu_item))
+        {
+          GtkMenuShellPrivate *priv;
+
+          priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
+          priv->in_unselectable_item = TRUE;
+
+          return TRUE;
+        }
 
       if (menu_item->parent == widget &&
          GTK_IS_MENU_ITEM (menu_item))
@@ -810,7 +952,7 @@ gtk_menu_shell_enter_notify (GtkWidget        *widget,
 
          if (event->detail != GDK_NOTIFY_INFERIOR)
             {
-             if (GTK_WIDGET_STATE (menu_item) != GTK_STATE_PRELIGHT)
+             if (gtk_widget_get_state (menu_item) != GTK_STATE_PRELIGHT)
                 gtk_menu_shell_select_item (menu_shell, menu_item);
 
               /* If any mouse button is down, and there is a submenu
@@ -829,7 +971,7 @@ gtk_menu_shell_enter_notify (GtkWidget        *widget,
                   priv = GTK_MENU_SHELL_GET_PRIVATE (menu_item->parent);
                   priv->activated_submenu = TRUE;
 
-                  if (!GTK_WIDGET_VISIBLE (GTK_MENU_ITEM (menu_item)->submenu))
+                  if (!gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->submenu))
                     {
                       gboolean touchscreen_mode;
 
@@ -856,17 +998,16 @@ static gint
 gtk_menu_shell_leave_notify (GtkWidget        *widget,
                             GdkEventCrossing *event)
 {
-  GtkMenuShell *menu_shell;
-  GtkMenuItem *menu_item;
-  GtkWidget *event_widget;
-
-  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  if (event->mode == GDK_CROSSING_GTK_GRAB ||
+      event->mode == GDK_CROSSING_GTK_GRAB ||
+      event->mode == GDK_CROSSING_STATE_CHANGED)
+    return TRUE;
 
-  if (GTK_WIDGET_VISIBLE (widget))
+  if (gtk_widget_get_visible (widget))
     {
-      menu_shell = GTK_MENU_SHELL (widget);
-      event_widget = gtk_get_event_widget ((GdkEvent*) event);
+      GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
+      GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent*) event);
+      GtkMenuItem *menu_item;
 
       if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
        return TRUE;
@@ -874,13 +1015,20 @@ gtk_menu_shell_leave_notify (GtkWidget        *widget,
       menu_item = GTK_MENU_ITEM (event_widget);
 
       if (!_gtk_menu_item_is_selectable (event_widget))
-       return TRUE;
+        {
+          GtkMenuShellPrivate *priv;
+
+          priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
+          priv->in_unselectable_item = TRUE;
+
+          return TRUE;
+        }
 
       if ((menu_shell->active_menu_item == event_widget) &&
          (menu_item->submenu == NULL))
        {
          if ((event->detail != GDK_NOTIFY_INFERIOR) &&
-             (GTK_WIDGET_STATE (menu_item) != GTK_STATE_NORMAL))
+             (gtk_widget_get_state (GTK_WIDGET (menu_item)) != GTK_STATE_NORMAL))
            {
              gtk_menu_shell_deselect (menu_shell);
            }
@@ -912,14 +1060,10 @@ static void
 gtk_menu_shell_remove (GtkContainer *container,
                       GtkWidget    *widget)
 {
-  GtkMenuShell *menu_shell;
+  GtkMenuShell *menu_shell = GTK_MENU_SHELL (container);
   gint was_visible;
-  
-  g_return_if_fail (GTK_IS_MENU_SHELL (container));
-  g_return_if_fail (GTK_IS_MENU_ITEM (widget));
-  
-  was_visible = GTK_WIDGET_VISIBLE (widget);
-  menu_shell = GTK_MENU_SHELL (container);
+
+  was_visible = gtk_widget_get_visible (widget);
   menu_shell->children = g_list_remove (menu_shell->children, widget);
   
   if (widget == menu_shell->active_menu_item)
@@ -930,7 +1074,7 @@ gtk_menu_shell_remove (GtkContainer *container,
 
   gtk_widget_unparent (widget);
   
-  /* queue resize regardless of GTK_WIDGET_VISIBLE (container),
+  /* queue resize regardless of gtk_widget_get_visible (container),
    * since that's what is needed by toplevels.
    */
   if (was_visible)
@@ -943,15 +1087,10 @@ gtk_menu_shell_forall (GtkContainer *container,
                       GtkCallback   callback,
                       gpointer      callback_data)
 {
-  GtkMenuShell *menu_shell;
+  GtkMenuShell *menu_shell = GTK_MENU_SHELL (container);
   GtkWidget *child;
   GList *children;
 
-  g_return_if_fail (GTK_IS_MENU_SHELL (container));
-  g_return_if_fail (callback != NULL);
-
-  menu_shell = GTK_MENU_SHELL (container);
-
   children = menu_shell->children;
   while (children)
     {
@@ -968,6 +1107,8 @@ gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
 {
   if (menu_shell->active)
     {
+      GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
+
       menu_shell->button = 0;
       menu_shell->active = FALSE;
       menu_shell->activate_time = 0;
@@ -981,16 +1122,25 @@ gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
       if (menu_shell->have_grab)
        {
          menu_shell->have_grab = FALSE;
-         gtk_grab_remove (GTK_WIDGET (menu_shell));
+          gtk_device_grab_remove (GTK_WIDGET (menu_shell), priv->grab_pointer);
        }
       if (menu_shell->have_xgrab)
        {
-         GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (menu_shell));
-         
-         menu_shell->have_xgrab = FALSE;
-         gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
-         gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
+          GdkDevice *keyboard;
+
+          gdk_device_ungrab (priv->grab_pointer, GDK_CURRENT_TIME);
+          keyboard = gdk_device_get_associated_device (priv->grab_pointer);
+
+          if (keyboard)
+            gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
+
+          menu_shell->have_xgrab = FALSE;
        }
+
+      menu_shell->keyboard_mode = FALSE;
+      _gtk_menu_shell_set_grab_device (menu_shell, NULL);
+
+      _gtk_menu_shell_update_mnemonics (menu_shell);
     }
 }
 
@@ -1004,7 +1154,7 @@ gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
   g_return_val_if_fail (child != NULL, FALSE);
 
   parent = child->parent;
-  while (parent && GTK_IS_MENU_SHELL (parent))
+  while (GTK_IS_MENU_SHELL (parent))
     {
       if (parent == (GtkWidget*) menu_shell)
        return TRUE;
@@ -1059,10 +1209,21 @@ gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell,
 {
   GtkPackDirection pack_dir = PACK_DIRECTION (menu_shell);
 
-  gtk_menu_shell_deselect (menu_shell);
+  if (menu_shell->active_menu_item)
+    {
+      gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
+      menu_shell->active_menu_item = NULL;
+    }
 
   if (!_gtk_menu_item_is_selectable (menu_item))
-    return;
+    {
+      GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
+
+      priv->in_unselectable_item = TRUE;
+      _gtk_menu_shell_update_mnemonics (menu_shell);
+
+      return;
+    }
 
   menu_shell->active_menu_item = menu_item;
   if (pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT)
@@ -1073,6 +1234,8 @@ gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell,
                                  GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement);
   gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
 
+  _gtk_menu_shell_update_mnemonics (menu_shell);
+
   /* This allows the bizarre radio buttons-with-submenus-display-history
    * behavior
    */
@@ -1089,6 +1252,7 @@ gtk_menu_shell_deselect (GtkMenuShell *menu_shell)
     {
       gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
       menu_shell->active_menu_item = NULL;
+      _gtk_menu_shell_update_mnemonics (menu_shell);
     }
 }
 
@@ -1239,7 +1403,7 @@ gtk_menu_shell_select_first (GtkMenuShell *menu_shell,
     {
       GtkWidget *child = tmp_list->data;
       
-      if ((!search_sensitive && GTK_WIDGET_VISIBLE (child)) ||
+      if ((!search_sensitive && gtk_widget_get_visible (child)) ||
          _gtk_menu_item_is_selectable (child))
        {
          to_select = child;
@@ -1266,7 +1430,7 @@ _gtk_menu_shell_select_last (GtkMenuShell *menu_shell,
     {
       GtkWidget *child = tmp_list->data;
       
-      if ((!search_sensitive && GTK_WIDGET_VISIBLE (child)) ||
+      if ((!search_sensitive && gtk_widget_get_visible (child)) ||
          _gtk_menu_item_is_selectable (child))
        {
          to_select = child;
@@ -1306,29 +1470,46 @@ static void
 gtk_real_menu_shell_move_current (GtkMenuShell         *menu_shell,
                                  GtkMenuDirectionType  direction)
 {
+  GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
   GtkMenuShell *parent_menu_shell = NULL;
   gboolean had_selection;
+  gboolean touchscreen_mode;
+
+  priv->in_unselectable_item = FALSE;
 
   had_selection = menu_shell->active_menu_item != NULL;
 
+  g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
+                "gtk-touchscreen-mode", &touchscreen_mode,
+                NULL);
+
   if (menu_shell->parent_menu_shell)
     parent_menu_shell = GTK_MENU_SHELL (menu_shell->parent_menu_shell);
 
   switch (direction)
     {
     case GTK_MENU_DIR_PARENT:
-      if (parent_menu_shell)
+      if (touchscreen_mode &&
+          menu_shell->active_menu_item &&
+          GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu &&
+          gtk_widget_get_visible (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu))
+        {
+          /* if we are on a menu item that has an open submenu but the
+           * focus is not in that submenu (e.g. because it's empty or
+           * has only insensitive items), close that submenu instead
+           * of running into the code below which would close *this*
+           * menu.
+           */
+          _gtk_menu_item_popdown_submenu (menu_shell->active_menu_item);
+          _gtk_menu_shell_update_mnemonics (menu_shell);
+        }
+      else if (parent_menu_shell)
        {
-          gboolean touchscreen_mode;
-
-          g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
-                        "gtk-touchscreen-mode", &touchscreen_mode,
-                        NULL);
-
           if (touchscreen_mode)
             {
               /* close menu when returning from submenu. */
               _gtk_menu_item_popdown_submenu (GTK_MENU (menu_shell)->parent_menu_item);
+              _gtk_menu_shell_update_mnemonics (parent_menu_shell);
               break;
             }
 
@@ -1603,6 +1784,37 @@ _gtk_menu_shell_remove_mnemonic (GtkMenuShell *menu_shell,
   gtk_menu_shell_reset_key_hash (menu_shell);
 }
 
+void
+_gtk_menu_shell_set_grab_device (GtkMenuShell *menu_shell,
+                                 GdkDevice    *device)
+{
+  GtkMenuShellPrivate *priv;
+
+  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+  g_return_if_fail (!device || GDK_IS_DEVICE (device));
+
+  priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
+
+  if (!device)
+    priv->grab_pointer = NULL;
+  else if (device->source == GDK_SOURCE_KEYBOARD)
+    priv->grab_pointer = gdk_device_get_associated_device (device);
+  else
+    priv->grab_pointer = device;
+}
+
+GdkDevice *
+_gtk_menu_shell_get_grab_device (GtkMenuShell  *menu_shell)
+{
+  GtkMenuShellPrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE);
+
+  priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell);
+
+  return priv->grab_pointer;
+}
+
 /**
  * gtk_menu_shell_get_take_focus:
  * @menu_shell: a #GtkMenuShell
@@ -1674,6 +1886,3 @@ gtk_menu_shell_set_take_focus (GtkMenuShell *menu_shell,
       g_object_notify (G_OBJECT (menu_shell), "take-focus");
     }
 }
-
-#define __GTK_MENU_SHELL_C__
-#include "gtkaliasdef.c"