--- gtk+-2.6.4/gtk/gtkmenushell.c 2005-02-09 18:46:54.000000000 +0200 +++ gtk+-2.6.4/gtk/gtkmenushell.c 2005-04-06 16:19:36.999913520 +0300 @@ -39,6 +39,8 @@ #include "gtkmnemonichash.h" #include "gtktearoffmenuitem.h" #include "gtkwindow.h" +#include "gtkprivate.h" +#include "gtkintl.h" #define MENU_SHELL_TIMEOUT 500 @@ -52,6 +54,11 @@ LAST_SIGNAL }; +enum { + PROP_0, + PROP_TAKE_FOCUS +}; + typedef void (*GtkMenuShellSignal1) (GtkObject *object, GtkMenuDirectionType arg1, gpointer data); @@ -122,10 +129,20 @@ { GtkMnemonicHash *mnemonic_hash; GtkKeyHash *key_hash; + gboolean activated_submenu; + gboolean take_focus; }; static void gtk_menu_shell_class_init (GtkMenuShellClass *klass); static void gtk_menu_shell_init (GtkMenuShell *menu_shell); +static void gtk_menu_shell_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_menu_shell_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); static void gtk_menu_shell_realize (GtkWidget *widget); static void gtk_menu_shell_finalize (GObject *object); static gint gtk_menu_shell_button_press (GtkWidget *widget, @@ -176,7 +193,6 @@ static GtkContainerClass *parent_class = NULL; static guint menu_shell_signals[LAST_SIGNAL] = { 0 }; - GType gtk_menu_shell_get_type (void) { @@ -220,6 +236,8 @@ container_class = (GtkContainerClass*) klass; parent_class = g_type_class_peek_parent (klass); + object_class->set_property = gtk_menu_shell_set_property; + object_class->get_property = gtk_menu_shell_get_property; object_class->finalize = gtk_menu_shell_finalize; @@ -299,9 +317,15 @@ binding_set = gtk_binding_set_by_class (klass); +/* Hildon : The following binding is commented out + * because we want the Escape key to only exit the + * currently opened submenu. Therefore, the handling + * of esc-key will be moved to gtkmenuitem.c */ +/* gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "cancel", 0); +*/ gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "activate_current", 1, @@ -330,7 +354,23 @@ GDK_F10, GDK_SHIFT_MASK, "cycle_focus", 1, GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD); - + /** + * GtkMenuShell:take-focus: + * + * A boolean that determines whether the menu and its submenus grab the + * keyboard focus. See gtk_menu_shell_set_take_focus() and + * gtk_menu_shell_get_take_focus(). + * + * Since: 2.8 + **/ + g_object_class_install_property (object_class, + PROP_TAKE_FOCUS, + g_param_spec_boolean ("take-focus", + P_("Take Focus"), + P_("A boolean that determines whether the menu grabs the keyboard focus"), + TRUE, + G_PARAM_READWRITE)); + g_type_class_add_private (object_class, sizeof (GtkMenuShellPrivate)); } @@ -356,6 +396,46 @@ priv->mnemonic_hash = NULL; priv->key_hash = NULL; + priv->take_focus = TRUE; + priv->activated_submenu = FALSE; +} + +static void +gtk_menu_shell_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkMenuShell *menu_shell = GTK_MENU_SHELL (object); + + switch (prop_id) + { + case PROP_TAKE_FOCUS: + gtk_menu_shell_set_take_focus (menu_shell, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_menu_shell_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkMenuShell *menu_shell = GTK_MENU_SHELL (object); + + switch (prop_id) + { + case PROP_TAKE_FOCUS: + g_value_set_boolean (value, gtk_menu_shell_get_take_focus (menu_shell)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } static void @@ -470,6 +550,7 @@ gtk_menu_shell_button_press (GtkWidget *widget, GdkEventButton *event) { + GtkMenuShellPrivate *priv; GtkMenuShell *menu_shell; GtkWidget *menu_item; @@ -479,7 +560,22 @@ if (event->type != GDK_BUTTON_PRESS) return FALSE; + priv = GTK_MENU_SHELL_GET_PRIVATE (widget); + menu_shell = GTK_MENU_SHELL (widget); + menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event); + + if (menu_shell->active && menu_item && + (menu_shell->active_menu_item == 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)) + { + /* Hildon : We want to be able to activate submenu items. */ + gtk_menu_shell_activate_item (menu_shell, menu_item, FALSE); + + priv->activated_submenu = TRUE; + } if (menu_shell->parent_menu_shell) { @@ -491,30 +587,29 @@ menu_shell->button = event->button; - menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent *)event); - if (menu_item && _gtk_menu_item_is_selectable (menu_item)) - { - if ((menu_item->parent == widget) && - (menu_item != menu_shell->active_menu_item)) - { - if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM) - { - menu_shell->activate_time = event->time; - } + { + + if ((menu_item->parent == widget) && + (menu_item != menu_shell->active_menu_item)) + { + 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); - } - } + gtk_menu_shell_select_item (menu_shell, menu_item); + } + } } else { widget = gtk_get_event_widget ((GdkEvent*) event); if (widget == GTK_WIDGET (menu_shell)) - { - gtk_menu_shell_deactivate (menu_shell); - g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0); - } + { + gtk_menu_shell_deactivate (menu_shell); + g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0); + } } return TRUE; @@ -524,13 +619,20 @@ gtk_menu_shell_button_release (GtkWidget *widget, GdkEventButton *event) { + GtkMenuShellPrivate *priv; GtkMenuShell *menu_shell; GtkWidget *menu_item; gint deactivate; + gboolean activated_submenu; g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); + priv = GTK_MENU_SHELL_GET_PRIVATE (widget); + + activated_submenu = priv->activated_submenu; + priv->activated_submenu = FALSE; + menu_shell = GTK_MENU_SHELL (widget); if (menu_shell->active) { @@ -556,11 +658,11 @@ gtk_menu_shell_activate_item (menu_shell, menu_item, TRUE); return TRUE; } - else if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) - { - gtk_menu_item_select (GTK_MENU_ITEM (menu_item)); - return TRUE; - } + else if (!activated_submenu) + { + /* popdown the submenu if we didn't pop it up in this click */ + _gtk_menu_item_popdown_submenu (menu_item); + } } else if (menu_item && !_gtk_menu_item_is_selectable (menu_item) && @@ -630,12 +732,14 @@ gtk_menu_shell_enter_notify (GtkWidget *widget, GdkEventCrossing *event) { + GtkMenuShellPrivate *priv; GtkMenuShell *menu_shell; GtkWidget *menu_item; g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); + priv = GTK_MENU_SHELL_GET_PRIVATE (widget); menu_shell = GTK_MENU_SHELL (widget); if (menu_shell->active) @@ -658,6 +762,17 @@ (GTK_WIDGET_STATE (menu_item) != GTK_STATE_PRELIGHT)) { gtk_menu_shell_select_item (menu_shell, menu_item); + + /* If the pen is down, and there is a submenu that is not + * yet visible, activate it */ + if ((event->state & GDK_BUTTON1_MASK) && + GTK_MENU_ITEM (menu_item)->submenu != NULL && + !GTK_WIDGET_VISIBLE (GTK_MENU_ITEM (menu_item)->submenu)) + { + gtk_menu_shell_activate_item (menu_shell, menu_item, FALSE); + + priv->activated_submenu = TRUE; + } } } else if (menu_shell->parent_menu_shell) @@ -887,8 +1002,14 @@ /* This allows the bizarre radio buttons-with-submenus-display-history * behavior */ + /* Hildon modification. We probably won't have those + * bizarre radio buttons-with-submenus so we don't + * need this. Also, this functionality interferes with + * other functionality. */ +/* if (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu) gtk_widget_activate (menu_shell->active_menu_item); +*/ } void @@ -919,7 +1040,9 @@ g_object_ref (menu_shell); - if (deactivate) + /* We don't want to deactivate if we're activating + * a submenu item. */ + if ((deactivate) && (GTK_MENU_ITEM (menu_item)->submenu == NULL)) { GtkMenuShell *parent_menu_shell = menu_shell; @@ -965,29 +1088,30 @@ if (distance > 0) { + /*Hildon: selection no longer wraps around at the + *bottom of the menu*/ + node = node->next; - while (node != start_node && - (!node || !_gtk_menu_item_is_selectable (node->data))) + while (node && node != start_node && + !_gtk_menu_item_is_selectable (node->data)) { - if (!node) - node = menu_shell->children; - else node = node->next; } } else { + /*Hildon: selection no longer wraps around at the top + *of the menu*/ + node = node->prev; - while (node != start_node && - (!node || !_gtk_menu_item_is_selectable (node->data))) + while (node && node != start_node && + !_gtk_menu_item_is_selectable (node->data)) { - if (!node) - node = g_list_last (menu_shell->children); - else node = node->prev; } } + /*note: gtk_menu_shell_select_item won't select non-selectable items*/ if (node) gtk_menu_shell_select_item (menu_shell, node->data); } @@ -1119,6 +1243,16 @@ switch (direction) { case GTK_MENU_DIR_PARENT: + + if(!parent_menu_shell || GTK_IS_MENU_BAR(parent_menu_shell)) + break; + + /* hildon-modification - menu should be closed when returning from submenu. + * WARNING: This function is from GtkMenu, which normally + * shouldn't be called from GtkMenuShell, but currently + * there are no better alternatives. */ + gtk_menu_popdown (GTK_MENU (menu_shell)); + if (parent_menu_shell) { if (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement == @@ -1151,10 +1285,14 @@ _gtk_menu_item_is_selectable (menu_shell->active_menu_item) && GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu) { + /* Hildon-modification -- submenu is not opened automatically but needs to be explicitly opened*/ + g_signal_emit (G_OBJECT (menu_shell), menu_shell_signals[ACTIVATE_CURRENT], 0, (gint) FALSE); + if (gtk_menu_shell_select_submenu_first (menu_shell)) break; } +#if 0 /* Try to find a menu running the opposite direction */ while (parent_menu_shell && (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement == @@ -1173,6 +1311,7 @@ gtk_menu_shell_move_selected (parent_menu_shell, 1); gtk_menu_shell_select_submenu_first (parent_menu_shell); } +#endif break; case GTK_MENU_DIR_PREV: @@ -1197,8 +1336,8 @@ gtk_real_menu_shell_activate_current (GtkMenuShell *menu_shell, gboolean force_hide) { - if (menu_shell->active_menu_item && - _gtk_menu_item_is_selectable (menu_shell->active_menu_item)) + if (menu_shell->active_menu_item)/* && + _gtk_menu_item_is_selectable (menu_shell->active_menu_item)) */ { if (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL) @@ -1390,4 +1529,73 @@ keyval, target); gtk_menu_shell_reset_key_hash (menu_shell); } +/** + * gtk_menu_shell_get_take_focus: + * @menu: a #GtkMenuShell + * + * @returns: %TRUE if the menu_shell will take the keyboard focus on popup. + * + * Since: 2.8 + **/ +gboolean +gtk_menu_shell_get_take_focus (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->take_focus; +} + +/** + * gtk_menu_shell_set_take_focus: + * @menu: a #GtkMenuShell + * @take_focus: %TRUE if the menu_shell should take the keyboard focus on popup. + * + * If @take_focus is %TRUE (the default) the menu will take the keyboard focus + * so that it will receive all keyboard events which is needed to enable + * keyboard navigation in menus. + * + * Setting @take_focus to %FALSE is useful only for special applications + * like virtual keyboard implementations which should not take keyboard + * focus. + * + * The @take_focus state of a menu or menu bar is automatically propagated + * to submenus whenever a submenu is popped up, so you don't have to worry + * about recursively setting it for your entire menu hierarchy. Only when + * programmatically picking a submenu and popping it up manually, the + * @take_focus property of the submenu needs to be set explicitely. + * + * Note that setting it to %FALSE has side-effects: + * + * If the focus is in some other app, it keeps the focus and keynav in + * the menu doesn't work. Consequently, keynav on the menu will only + * work if the focus is on some toplevel owned by the onscreen keyboard. + * + * To avoid confusing the user, menus with @take_focus set to %FALSE + * should not display mnemonics or accelerators, since it cannot be + * guaranteed that they will work. + * + * See also gdk_keyboard_grab() + * + * Since: 2.8 + **/ +void +gtk_menu_shell_set_take_focus (GtkMenuShell *menu_shell, + gboolean take_focus) +{ + GtkMenuShellPrivate *priv; + + g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); + + priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell); + + if (priv->take_focus != take_focus) + { + priv->take_focus = take_focus; + g_object_notify (G_OBJECT (menu_shell), "take-focus"); + } +}