X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkmenushell.c;h=ae36459d43f981feae370333b014a5f46b5df0f8;hb=ca2368dba958d6fecf2fce417307bb9cbbe6eb6b;hp=123f00911b91523e6bdb5d4b6989b6e7d85d740a;hpb=9b356ea0d8ee1992ad9b3559aadad0c4e2b56f2c;p=~andy%2Fgtk diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c index 123f00911..ae36459d4 100644 --- a/gtk/gtkmenushell.c +++ b/gtk/gtkmenushell.c @@ -12,9 +12,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -24,23 +22,41 @@ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ -#define GTK_MENU_INTERNALS +/** + * SECTION:gtkmenushell + * @Title: GtkMenuShell + * @Short_description: A base class for menu objects + * + * A #GtkMenuShell is the abstract base class used to derive the + * #GtkMenu and #GtkMenuBar subclasses. + * + * A #GtkMenuShell is a container of #GtkMenuItem objects arranged + * in a list which can be navigated, selected, and activated by the + * user to perform application functions. A #GtkMenuItem can have a + * submenu associated with it, allowing for nested hierarchical menus. + */ +#include "config.h" -#include -#include "gdk/gdkkeysyms.h" #include "gtkbindings.h" #include "gtkkeyhash.h" +#include "gtklabel.h" #include "gtkmain.h" #include "gtkmarshalers.h" #include "gtkmenubar.h" -#include "gtkmenuitem.h" -#include "gtkmenushell.h" +#include "gtkmenuitemprivate.h" +#include "gtkmenushellprivate.h" +#include "gtkmenuprivate.h" #include "gtkmnemonichash.h" -#include "gtktearoffmenuitem.h" #include "gtkwindow.h" #include "gtkprivate.h" +#include "gtkmain.h" #include "gtkintl.h" -#include "gtkalias.h" +#include "gtktypebuiltins.h" + +#include "deprecated/gtktearoffmenuitem.h" + +#include "a11y/gtkmenushellaccessible.h" + #define MENU_SHELL_TIMEOUT 500 @@ -56,6 +72,8 @@ enum { ACTIVATE_CURRENT, CANCEL, CYCLE_FOCUS, + MOVE_SELECTED, + INSERT, LAST_SIGNAL }; @@ -64,13 +82,6 @@ enum { PROP_TAKE_FOCUS }; -typedef void (*GtkMenuShellSignal1) (GtkObject *object, - GtkMenuDirectionType arg1, - gpointer data); -typedef void (*GtkMenuShellSignal2) (GtkObject *object, - gboolean arg1, - gpointer data); - /* Terminology: * * A menu item can be "selected", this means that it is displayed @@ -88,7 +99,7 @@ typedef void (*GtkMenuShellSignal2) (GtkObject *object, * * 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 @@ -130,20 +141,7 @@ typedef void (*GtkMenuShellSignal2) (GtkObject *object, * Cancels the current selection */ -#define GTK_MENU_SHELL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_MENU_SHELL, GtkMenuShellPrivate)) - -typedef struct _GtkMenuShellPrivate GtkMenuShellPrivate; -struct _GtkMenuShellPrivate -{ - GtkMnemonicHash *mnemonic_hash; - GtkKeyHash *key_hash; - - 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, @@ -154,85 +152,59 @@ static void gtk_menu_shell_get_property (GObject *object, GParamSpec *pspec); static void gtk_menu_shell_realize (GtkWidget *widget); static void gtk_menu_shell_finalize (GObject *object); +static void gtk_menu_shell_dispose (GObject *object); static gint gtk_menu_shell_button_press (GtkWidget *widget, - GdkEventButton *event); + GdkEventButton *event); static gint gtk_menu_shell_button_release (GtkWidget *widget, - GdkEventButton *event); -static gint gtk_menu_shell_key_press (GtkWidget *widget, - GdkEventKey *event); + GdkEventButton *event); +static gint gtk_menu_shell_key_press (GtkWidget *widget, + GdkEventKey *event); static gint gtk_menu_shell_enter_notify (GtkWidget *widget, - GdkEventCrossing *event); + GdkEventCrossing *event); static gint gtk_menu_shell_leave_notify (GtkWidget *widget, - GdkEventCrossing *event); + GdkEventCrossing *event); static void gtk_menu_shell_screen_changed (GtkWidget *widget, - GdkScreen *previous_screen); + GdkScreen *previous_screen); static gboolean gtk_menu_shell_grab_broken (GtkWidget *widget, - GdkEventGrabBroken *event); + GdkEventGrabBroken *event); static void gtk_menu_shell_add (GtkContainer *container, - GtkWidget *widget); + GtkWidget *widget); static void gtk_menu_shell_remove (GtkContainer *container, - GtkWidget *widget); + GtkWidget *widget); static void gtk_menu_shell_forall (GtkContainer *container, - gboolean include_internals, - GtkCallback callback, - gpointer callback_data); + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); static void gtk_menu_shell_real_insert (GtkMenuShell *menu_shell, - GtkWidget *child, - gint position); + GtkWidget *child, + gint position); static void gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell); static gint gtk_menu_shell_is_item (GtkMenuShell *menu_shell, - GtkWidget *child); + GtkWidget *child); static GtkWidget *gtk_menu_shell_get_item (GtkMenuShell *menu_shell, - GdkEvent *event); + GdkEvent *event); static GType gtk_menu_shell_child_type (GtkContainer *container); static void gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell, - GtkWidget *menu_item); + GtkWidget *menu_item); static gboolean gtk_menu_shell_select_submenu_first (GtkMenuShell *menu_shell); static void gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell, - GtkMenuDirectionType direction); + GtkMenuDirectionType direction); static void gtk_real_menu_shell_activate_current (GtkMenuShell *menu_shell, - gboolean force_hide); + gboolean force_hide); static void gtk_real_menu_shell_cancel (GtkMenuShell *menu_shell); static void gtk_real_menu_shell_cycle_focus (GtkMenuShell *menu_shell, - GtkDirectionType dir); + GtkDirectionType dir); static void gtk_menu_shell_reset_key_hash (GtkMenuShell *menu_shell); static gboolean gtk_menu_shell_activate_mnemonic (GtkMenuShell *menu_shell, - GdkEventKey *event); + GdkEventKey *event); +static gboolean gtk_menu_shell_real_move_selected (GtkMenuShell *menu_shell, + gint distance); -static GtkContainerClass *parent_class = NULL; static guint menu_shell_signals[LAST_SIGNAL] = { 0 }; - -GType -gtk_menu_shell_get_type (void) -{ - static GType menu_shell_type = 0; - - if (!menu_shell_type) - { - static const GTypeInfo menu_shell_info = - { - sizeof (GtkMenuShellClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) gtk_menu_shell_class_init, - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (GtkMenuShell), - 0, /* n_preallocs */ - (GInstanceInitFunc) gtk_menu_shell_init, - NULL, /* value_table */ - }; - - menu_shell_type = - g_type_register_static (GTK_TYPE_CONTAINER, "GtkMenuShell", - &menu_shell_info, G_TYPE_FLAG_ABSTRACT); - } - - return menu_shell_type; -} +G_DEFINE_ABSTRACT_TYPE (GtkMenuShell, gtk_menu_shell, GTK_TYPE_CONTAINER) static void gtk_menu_shell_class_init (GtkMenuShellClass *klass) @@ -247,11 +219,10 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass) widget_class = (GtkWidgetClass*) klass; 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; + object_class->dispose = gtk_menu_shell_dispose; widget_class->realize = gtk_menu_shell_realize; widget_class->button_press_event = gtk_menu_shell_button_press; @@ -275,91 +246,192 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass) klass->cancel = gtk_real_menu_shell_cancel; klass->select_item = gtk_menu_shell_real_select_item; klass->insert = gtk_menu_shell_real_insert; + klass->move_selected = gtk_menu_shell_real_move_selected; + /** + * GtkMenuShell::deactivate: + * @menushell: the object which received the signal + * + * This signal is emitted when a menu shell is deactivated. + */ menu_shell_signals[DEACTIVATE] = - g_signal_new ("deactivate", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (GtkMenuShellClass, deactivate), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); + g_signal_new (I_("deactivate"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkMenuShellClass, deactivate), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkMenuShell::selection-done: + * @menushell: the object which received the signal + * + * This signal is emitted when a selection has been + * completed within a menu shell. + */ menu_shell_signals[SELECTION_DONE] = - g_signal_new ("selection-done", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (GtkMenuShellClass, selection_done), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); + g_signal_new (I_("selection-done"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkMenuShellClass, selection_done), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkMenuShell::move-current: + * @menushell: the object which received the signal + * @direction: the direction to move + * + * An keybinding signal which moves the current menu item + * in the direction specified by @direction. + */ menu_shell_signals[MOVE_CURRENT] = - g_signal_new ("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, - GTK_TYPE_MENU_DIRECTION_TYPE); + 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, + GTK_TYPE_MENU_DIRECTION_TYPE); + + /** + * GtkMenuShell::activate-current: + * @menushell: the object which received the signal + * @force_hide: if %TRUE, hide the menu after activating the menu item + * + * An action signal that activates the current menu item within + * the menu shell. + */ menu_shell_signals[ACTIVATE_CURRENT] = - g_signal_new ("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_BOOLEAN); + 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_BOOLEAN); + + /** + * GtkMenuShell::cancel: + * @menushell: the object which received the signal + * + * An action signal which cancels the selection within the menu shell. + * Causes the #GtkMenuShell::selection-done signal to be emitted. + */ menu_shell_signals[CANCEL] = - g_signal_new ("cancel", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkMenuShellClass, cancel), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); + g_signal_new (I_("cancel"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkMenuShellClass, cancel), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkMenuShell::cycle-focus: + * @menushell: the object which received the signal + * @direction: the direction to cycle in + * + * A keybinding signal which moves the focus in the + * given @direction. + */ menu_shell_signals[CYCLE_FOCUS] = - _gtk_binding_signal_new ("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_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkMenuShellClass, move_selected), + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__INT, + G_TYPE_BOOLEAN, 1, + G_TYPE_INT); + + /** + * GtkMenuShell::insert: + * @menu_shell: the object on which the signal is emitted + * @child: the #GtkMenuItem that is being inserted + * @position: the position at which the insert occurs + * + * The ::insert signal is emitted when a new #GtkMenuItem is added to + * a #GtkMenuShell. A separate signal is used instead of + * GtkContainer::add because of the need for an additional position + * parameter. + * + * The inverse of this signal is the GtkContainer::removed signal. + * + * Since: 3.2 + **/ + menu_shell_signals[INSERT] = + g_signal_new (I_("insert"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkMenuShellClass, insert), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_INT, + G_TYPE_NONE, 2, GTK_TYPE_WIDGET, G_TYPE_INT); binding_set = gtk_binding_set_by_class (klass); gtk_binding_entry_add_signal (binding_set, - GDK_Escape, 0, - "cancel", 0); + GDK_KEY_Escape, 0, + "cancel", 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_Return, 0, + "activate-current", 1, + G_TYPE_BOOLEAN, + TRUE); gtk_binding_entry_add_signal (binding_set, - GDK_Return, 0, - "activate_current", 1, - G_TYPE_BOOLEAN, - TRUE); + GDK_KEY_ISO_Enter, 0, + "activate-current", 1, + G_TYPE_BOOLEAN, + TRUE); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Enter, 0, - "activate_current", 1, - G_TYPE_BOOLEAN, - TRUE); + GDK_KEY_KP_Enter, 0, + "activate-current", 1, + G_TYPE_BOOLEAN, + TRUE); gtk_binding_entry_add_signal (binding_set, - GDK_space, 0, - "activate_current", 1, - G_TYPE_BOOLEAN, - FALSE); + GDK_KEY_space, 0, + "activate-current", 1, + G_TYPE_BOOLEAN, + FALSE); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Space, 0, - "activate_current", 1, - G_TYPE_BOOLEAN, - FALSE); + GDK_KEY_KP_Space, 0, + "activate-current", 1, + G_TYPE_BOOLEAN, + FALSE); gtk_binding_entry_add_signal (binding_set, - GDK_F10, 0, - "cycle_focus", 1, + GDK_KEY_F10, 0, + "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, + GDK_KEY_F10, GDK_SHIFT_MASK, + "cycle-focus", 1, GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD); /** @@ -374,16 +446,18 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass) 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, - GTK_PARAM_READWRITE)); + P_("Take Focus"), + P_("A boolean that determines whether the menu grabs the keyboard focus"), + TRUE, + GTK_PARAM_READWRITE)); + + gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_MENU_SHELL_ACCESSIBLE); g_type_class_add_private (object_class, sizeof (GtkMenuShellPrivate)); } static GType -gtk_menu_shell_child_type (GtkContainer *container) +gtk_menu_shell_child_type (GtkContainer *container) { return GTK_TYPE_MENU_ITEM; } @@ -391,19 +465,12 @@ gtk_menu_shell_child_type (GtkContainer *container) static void gtk_menu_shell_init (GtkMenuShell *menu_shell) { - GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell); - - menu_shell->children = NULL; - menu_shell->active_menu_item = NULL; - menu_shell->parent_menu_shell = NULL; - menu_shell->active = FALSE; - menu_shell->have_grab = FALSE; - menu_shell->have_xgrab = FALSE; - menu_shell->button = 0; - menu_shell->activate_time = 0; - - priv->mnemonic_hash = NULL; - priv->key_hash = NULL; + GtkMenuShellPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE (menu_shell, + GTK_TYPE_MENU_SHELL, + GtkMenuShellPrivate); + menu_shell->priv = priv; priv->take_focus = TRUE; } @@ -427,10 +494,10 @@ gtk_menu_shell_set_property (GObject *object, } static void -gtk_menu_shell_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +gtk_menu_shell_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { GtkMenuShell *menu_shell = GTK_MENU_SHELL (object); @@ -449,159 +516,263 @@ static void gtk_menu_shell_finalize (GObject *object) { GtkMenuShell *menu_shell = GTK_MENU_SHELL (object); - GtkMenuShellPrivate *priv = GTK_MENU_SHELL_GET_PRIVATE (menu_shell); + GtkMenuShellPrivate *priv = menu_shell->priv; if (priv->mnemonic_hash) _gtk_mnemonic_hash_free (priv->mnemonic_hash); if (priv->key_hash) _gtk_key_hash_free (priv->key_hash); - G_OBJECT_CLASS (parent_class)->finalize (object); + G_OBJECT_CLASS (gtk_menu_shell_parent_class)->finalize (object); } +static void +gtk_menu_shell_dispose (GObject *object) +{ + gtk_menu_shell_deactivate (GTK_MENU_SHELL (object)); + + G_OBJECT_CLASS (gtk_menu_shell_parent_class)->dispose (object); +} + +/** + * gtk_menu_shell_append: + * @menu_shell: a #GtkMenuShell + * @child: The #GtkMenuItem to add + * + * Adds a new #GtkMenuItem to the end of the menu shell's + * item list. + */ void gtk_menu_shell_append (GtkMenuShell *menu_shell, - GtkWidget *child) + GtkWidget *child) { gtk_menu_shell_insert (menu_shell, child, -1); } +/** + * gtk_menu_shell_prepend: + * @menu_shell: a #GtkMenuShell + * @child: The #GtkMenuItem to add + * + * Adds a new #GtkMenuItem to the beginning of the menu shell's + * item list. + */ void gtk_menu_shell_prepend (GtkMenuShell *menu_shell, - GtkWidget *child) + GtkWidget *child) { gtk_menu_shell_insert (menu_shell, child, 0); } +/** + * gtk_menu_shell_insert: + * @menu_shell: a #GtkMenuShell + * @child: The #GtkMenuItem to add + * @position: The position in the item list where @child + * is added. Positions are numbered from 0 to n-1 + * + * Adds a new #GtkMenuItem to the menu shell's item list + * at the position indicated by @position. + */ void gtk_menu_shell_insert (GtkMenuShell *menu_shell, - GtkWidget *child, - gint position) + GtkWidget *child, + gint position) { - GtkMenuShellClass *class; - g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); g_return_if_fail (GTK_IS_MENU_ITEM (child)); - class = GTK_MENU_SHELL_GET_CLASS (menu_shell); - - if (class->insert) - class->insert (menu_shell, child, position); + g_signal_emit (menu_shell, menu_shell_signals[INSERT], 0, child, position); } static void gtk_menu_shell_real_insert (GtkMenuShell *menu_shell, - GtkWidget *child, - gint position) + GtkWidget *child, + gint position) { - menu_shell->children = g_list_insert (menu_shell->children, child, position); + GtkMenuShellPrivate *priv = menu_shell->priv; + + priv->children = g_list_insert (priv->children, child, position); gtk_widget_set_parent (child, GTK_WIDGET (menu_shell)); } +/** + * gtk_menu_shell_deactivate: + * @menu_shell: a #GtkMenuShell + * + * Deactivates the menu shell. + * + * Typically this results in the menu shell being erased + * from the screen. + */ void gtk_menu_shell_deactivate (GtkMenuShell *menu_shell) { g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); - g_signal_emit (menu_shell, menu_shell_signals[DEACTIVATE], 0); + if (menu_shell->priv->active) + g_signal_emit (menu_shell, menu_shell_signals[DEACTIVATE], 0); } static void gtk_menu_shell_realize (GtkWidget *widget) { + GtkAllocation allocation; + GdkWindow *window; GdkWindowAttr attributes; gint attributes_mask; + GtkStyleContext *context; - g_return_if_fail (GTK_IS_MENU_SHELL (widget)); + gtk_widget_set_realized (widget, TRUE); - GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + gtk_widget_get_allocation (widget, &allocation); - attributes.x = widget->allocation.x; - attributes.y = widget->allocation.y; - attributes.width = widget->allocation.width; - attributes.height = widget->allocation.height; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); - attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = gtk_widget_get_events (widget); attributes.event_mask |= (GDK_EXPOSURE_MASK | - GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_KEY_PRESS_MASK | - GDK_ENTER_NOTIFY_MASK | - GDK_LEAVE_NOTIFY_MASK); - - attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; - widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); - gdk_window_set_user_data (widget->window, widget); - - widget->style = gtk_style_attach (widget->style, widget->window); - gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; + + window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gtk_widget_set_window (widget, window); + gtk_widget_register_window (widget, window); + + context = gtk_widget_get_style_context (widget); + gtk_style_context_set_background (context, window); } -void -_gtk_menu_shell_activate (GtkMenuShell *menu_shell) +static void +gtk_menu_shell_activate (GtkMenuShell *menu_shell) { - if (!menu_shell->active) + GtkMenuShellPrivate *priv = menu_shell->priv; + + if (!priv->active) { - gtk_grab_add (GTK_WIDGET (menu_shell)); - menu_shell->have_grab = TRUE; - menu_shell->active = TRUE; + 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); + + priv->have_grab = TRUE; + priv->active = TRUE; } } static gint gtk_menu_shell_button_press (GtkWidget *widget, - GdkEventButton *event) + GdkEventButton *event) { GtkMenuShell *menu_shell; + GtkMenuShellPrivate *priv; GtkWidget *menu_item; - - g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + GtkWidget *parent; if (event->type != GDK_BUTTON_PRESS) return FALSE; menu_shell = GTK_MENU_SHELL (widget); + priv = menu_shell->priv; + + if (priv->parent_menu_shell) + return gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event); - if (menu_shell->parent_menu_shell) + menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent *)event); + + if (menu_item && _gtk_menu_item_is_selectable (menu_item)) { - return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); + parent = gtk_widget_get_parent (menu_item); + + if (menu_item != GTK_MENU_SHELL (parent)->priv->active_menu_item) + { + /* select the menu item *before* activating the shell, so submenus + * which might be open are closed the friendly way. If we activate + * (and thus grab) this menu shell first, we might get grab_broken + * events which will close the entire menu hierarchy. Selecting the + * menu item also fixes up the state as if enter_notify() would + * have run before (which normally selects the item). + */ + if (GTK_MENU_SHELL_GET_CLASS (parent)->submenu_placement != GTK_TOP_BOTTOM) + gtk_menu_shell_select_item (GTK_MENU_SHELL (parent), menu_item); + } } - else if (!menu_shell->active || !menu_shell->button) + + if (!priv->active || !priv->button) { - _gtk_menu_shell_activate (menu_shell); - - 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; - } - - gtk_menu_shell_select_item (menu_shell, menu_item); - } - } + gboolean initially_active = priv->active; + + priv->button = event->button; + + if (menu_item) + { + if (_gtk_menu_item_is_selectable (menu_item) && + gtk_widget_get_parent (menu_item) == widget && + menu_item != priv->active_menu_item) + { + gtk_menu_shell_activate (menu_shell); + priv->button = event->button; + + if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM) + { + priv->activate_time = event->time; + gtk_menu_shell_select_item (menu_shell, menu_item); + } + } + } + else + { + if (!initially_active) + { + 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); + } + } + } } 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); + } + } + + if (menu_item && + _gtk_menu_item_is_selectable (menu_item) && + GTK_MENU_ITEM (menu_item)->priv->submenu != NULL && + !gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu)) + { + _gtk_menu_item_popup_submenu (menu_item, FALSE); + priv->activated_submenu = TRUE; } return TRUE; @@ -609,18 +780,15 @@ gtk_menu_shell_button_press (GtkWidget *widget, static gboolean gtk_menu_shell_grab_broken (GtkWidget *widget, - GdkEventGrabBroken *event) + GdkEventGrabBroken *event) { - GtkMenuShell *menu_shell; - GtkWidget *menu_item; - gint deactivate; - - 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); + GtkMenuShellPrivate *priv = menu_shell->priv; - menu_shell = GTK_MENU_SHELL (widget); - if (menu_shell->active) + if (priv->have_xgrab && event->grab_window == NULL) { + /* Unset the active menu item so gtk_menu_popdown() doesn't see it. */ + gtk_menu_shell_deselect (menu_shell); gtk_menu_shell_deactivate (menu_shell); g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0); } @@ -630,148 +798,305 @@ gtk_menu_shell_grab_broken (GtkWidget *widget, static gint gtk_menu_shell_button_release (GtkWidget *widget, - GdkEventButton *event) + GdkEventButton *event) { - GtkMenuShell *menu_shell; - GtkWidget *menu_item; - gint deactivate; + GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget); + GtkMenuShellPrivate *priv = menu_shell->priv; - 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) + if (priv->active) { - if (menu_shell->button && (event->button != menu_shell->button)) - { - menu_shell->button = 0; - if (menu_shell->parent_menu_shell) - return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); - } - - menu_shell->button = 0; + GtkWidget *menu_item; + gboolean deactivate = TRUE; + + if (priv->button && (event->button != priv->button)) + { + priv->button = 0; + if (priv->parent_menu_shell) + return gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event); + } + + priv->button = 0; menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event); - deactivate = TRUE; - - if ((event->time - menu_shell->activate_time) > MENU_SHELL_TIMEOUT) - { - if (menu_item && (menu_shell->active_menu_item == menu_item) && - _gtk_menu_item_is_selectable (menu_item)) - { - if (GTK_MENU_ITEM (menu_item)->submenu == NULL) - { - 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 (menu_item && - !_gtk_menu_item_is_selectable (menu_item) && - GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) - { - deactivate = FALSE; - } - else if (menu_shell->parent_menu_shell) - { - menu_shell->active = TRUE; - gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); - return TRUE; - } - - /* If we ended up on an item with a submenu, leave the menu up. - */ - if (menu_item && (menu_shell->active_menu_item == menu_item) && - GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) - { - deactivate = FALSE; - } - } + if ((event->time - priv->activate_time) > MENU_SHELL_TIMEOUT) + { + if (menu_item && (priv->active_menu_item == menu_item) && + _gtk_menu_item_is_selectable (menu_item)) + { + GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu; + + if (submenu == NULL) + { + gtk_menu_shell_activate_item (menu_shell, menu_item, TRUE); + deactivate = FALSE; + } + else if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM || + priv->activated_submenu) + { + gint popdown_delay; + GTimeVal *popup_time; + gint64 usec_since_popup = 0; + + g_object_get (gtk_widget_get_settings (widget), + "gtk-menu-popdown-delay", &popdown_delay, + NULL); + + popup_time = g_object_get_data (G_OBJECT (submenu), + "gtk-menu-exact-popup-time"); + + if (popup_time) + { + GTimeVal current_time; + + g_get_current_time (¤t_time); + + usec_since_popup = ((gint64) current_time.tv_sec * 1000 * 1000 + + (gint64) current_time.tv_usec - + (gint64) popup_time->tv_sec * 1000 * 1000 - + (gint64) popup_time->tv_usec); + + g_object_set_data (G_OBJECT (submenu), + "gtk-menu-exact-popup-time", NULL); + } + + /* Only close the submenu on click if we opened the + * menu explicitely (usec_since_popup == 0) or + * enough time has passed since it was opened by + * GtkMenuItem's timeout (usec_since_popup > delay). + */ + if (!priv->activated_submenu && + (usec_since_popup == 0 || + usec_since_popup > popdown_delay * 1000)) + { + _gtk_menu_item_popdown_submenu (menu_item); + } + else + { + gtk_menu_item_select (GTK_MENU_ITEM (menu_item)); + } + + deactivate = FALSE; + } + } + else if (menu_item && + !_gtk_menu_item_is_selectable (menu_item) && + GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) + { + deactivate = FALSE; + } + else if (priv->parent_menu_shell) + { + priv->active = TRUE; + gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event); + deactivate = FALSE; + } + + /* If we ended up on an item with a submenu, leave the menu up. */ + if (menu_item && + (priv->active_menu_item == menu_item) && + GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) + { + deactivate = FALSE; + } + } else /* a very fast press-release */ - { - /* We only ever want to prevent deactivation on the first + { + /* We only ever want to prevent deactivation on the first * press/release. Setting the time to zero is a bit of a - * hack, since we could be being triggered in the first - * few fractions of a second after a server time wraparound. - * the chances of that happening are ~1/10^6, without - * serious harm if we lose. - */ - menu_shell->activate_time = 0; - deactivate = FALSE; - } - + * hack, since we could be being triggered in the first + * few fractions of a second after a server time wraparound. + * the chances of that happening are ~1/10^6, without + * serious harm if we lose. + */ + priv->activate_time = 0; + deactivate = FALSE; + } + if (deactivate) - { - 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); + } + + priv->activated_submenu = FALSE; } return TRUE; } +void +_gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell, + gboolean keyboard_mode) +{ + menu_shell->priv->keyboard_mode = keyboard_mode; +} + +gboolean +_gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell) +{ + return menu_shell->priv->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 = target->priv; + 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->priv->keyboard_mode) + target->priv->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->priv->keyboard_mode && + (((target->priv->active_menu_item || priv->in_unselectable_item) && !found) || + (target == menu_shell && + !target->priv->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->priv->active_menu_item || priv->in_unselectable_item) + found = TRUE; + + target = GTK_MENU_SHELL (target->priv->parent_menu_shell); + } +} + static gint gtk_menu_shell_key_press (GtkWidget *widget, - GdkEventKey *event) + GdkEventKey *event) { - GtkMenuShell *menu_shell; - - 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); + GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget); + GtkMenuShellPrivate *priv = menu_shell->priv; + gboolean enable_mnemonics; - if (!menu_shell->active_menu_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)) + priv->keyboard_mode = TRUE; + + if (!(priv->active_menu_item || priv->in_unselectable_item) && + priv->parent_menu_shell) + return gtk_widget_event (priv->parent_menu_shell, (GdkEvent *)event); + + if (gtk_bindings_activate_event (G_OBJECT (widget), event)) return TRUE; - return gtk_menu_shell_activate_mnemonic (menu_shell, event); + g_object_get (gtk_widget_get_settings (widget), + "gtk-enable-mnemonics", &enable_mnemonics, + NULL); + + if (enable_mnemonics) + return gtk_menu_shell_activate_mnemonic (menu_shell, event); + + return FALSE; } static gint gtk_menu_shell_enter_notify (GtkWidget *widget, - GdkEventCrossing *event) + GdkEventCrossing *event) { - GtkMenuShell *menu_shell; - GtkWidget *menu_item; + GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget); + GtkMenuShellPrivate *priv = menu_shell->priv; - 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 (event->mode == GDK_CROSSING_GTK_GRAB || + event->mode == GDK_CROSSING_GTK_UNGRAB || + event->mode == GDK_CROSSING_STATE_CHANGED) + return TRUE; - if (menu_shell->active) + if (priv->active) { + GtkWidget *menu_item; + GtkWidget *parent; + 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->parent == widget) && - (menu_shell->active_menu_item != menu_item) && - GTK_IS_MENU_ITEM (menu_item)) - { - if (menu_shell->ignore_enter) - return TRUE; - - if ((event->detail != GDK_NOTIFY_INFERIOR) && - (GTK_WIDGET_STATE (menu_item) != GTK_STATE_PRELIGHT)) - { - gtk_menu_shell_select_item (menu_shell, menu_item); - } - } - else if (menu_shell->parent_menu_shell) - { - gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); - } + if (!menu_item) + return TRUE; + + if (GTK_IS_MENU_ITEM (menu_item) && + !_gtk_menu_item_is_selectable (menu_item)) + { + priv->in_unselectable_item = TRUE; + return TRUE; + } + + parent = gtk_widget_get_parent (menu_item); + if (parent == widget && + GTK_IS_MENU_ITEM (menu_item)) + { + if (priv->ignore_enter) + return TRUE; + + if (event->detail != GDK_NOTIFY_INFERIOR) + { + if ((gtk_widget_get_state_flags (menu_item) & GTK_STATE_FLAG_PRELIGHT) == 0) + gtk_menu_shell_select_item (menu_shell, menu_item); + + /* If any mouse button is down, and there is a submenu + * that is not yet visible, activate it. It's sufficient + * to check for any button's mask (not only the one + * matching menu_shell->button), because there is no + * situation a mouse button could be pressed while + * entering a menu item where we wouldn't want to show + * its submenu. + */ + if ((event->state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK)) && + GTK_MENU_ITEM (menu_item)->priv->submenu != NULL) + { + GTK_MENU_SHELL (parent)->priv->activated_submenu = TRUE; + + if (!gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu)) + { + GdkDevice *source_device; + + source_device = gdk_event_get_source_device ((GdkEvent *) event); + + if (gdk_device_get_source (source_device) == GDK_SOURCE_TOUCHSCREEN) + _gtk_menu_item_popup_submenu (menu_item, TRUE); + } + } + } + } + else if (priv->parent_menu_shell) + { + gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event); + } } return TRUE; @@ -779,41 +1104,44 @@ gtk_menu_shell_enter_notify (GtkWidget *widget, static gint gtk_menu_shell_leave_notify (GtkWidget *widget, - GdkEventCrossing *event) + 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_UNGRAB || + 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); + GtkMenuShellPrivate *priv = menu_shell->priv; + GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent*) event); + GtkMenuItem *menu_item; if (!event_widget || !GTK_IS_MENU_ITEM (event_widget)) - return TRUE; + return TRUE; menu_item = GTK_MENU_ITEM (event_widget); if (!_gtk_menu_item_is_selectable (event_widget)) - 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_menu_shell_deselect (menu_shell); - } - } - else if (menu_shell->parent_menu_shell) - { - gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); - } + { + priv->in_unselectable_item = TRUE; + return TRUE; + } + + if ((priv->active_menu_item == event_widget) && + (menu_item->priv->submenu == NULL)) + { + if ((event->detail != GDK_NOTIFY_INFERIOR) && + (gtk_widget_get_state_flags (GTK_WIDGET (menu_item)) & GTK_STATE_FLAG_PRELIGHT) != 0) + { + gtk_menu_shell_deselect (menu_shell); + } + } + else if (priv->parent_menu_shell) + { + gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event); + } } return TRUE; @@ -821,41 +1149,38 @@ gtk_menu_shell_leave_notify (GtkWidget *widget, static void gtk_menu_shell_screen_changed (GtkWidget *widget, - GdkScreen *previous_screen) + GdkScreen *previous_screen) { gtk_menu_shell_reset_key_hash (GTK_MENU_SHELL (widget)); } static void gtk_menu_shell_add (GtkContainer *container, - GtkWidget *widget) + GtkWidget *widget) { gtk_menu_shell_append (GTK_MENU_SHELL (container), widget); } static void gtk_menu_shell_remove (GtkContainer *container, - GtkWidget *widget) + GtkWidget *widget) { - GtkMenuShell *menu_shell; + GtkMenuShell *menu_shell = GTK_MENU_SHELL (container); + GtkMenuShellPrivate *priv = menu_shell->priv; 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); - menu_shell->children = g_list_remove (menu_shell->children, widget); - - if (widget == menu_shell->active_menu_item) + + was_visible = gtk_widget_get_visible (widget); + priv->children = g_list_remove (priv->children, widget); + + if (widget == priv->active_menu_item) { - gtk_item_deselect (GTK_ITEM (menu_shell->active_menu_item)); - menu_shell->active_menu_item = NULL; + g_signal_emit_by_name (priv->active_menu_item, "deselect"); + priv->active_menu_item = NULL; } 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) @@ -864,20 +1189,15 @@ gtk_menu_shell_remove (GtkContainer *container, static void gtk_menu_shell_forall (GtkContainer *container, - gboolean include_internals, - GtkCallback callback, - gpointer callback_data) + gboolean include_internals, + 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; + children = menu_shell->priv->children; while (children) { child = children->data; @@ -891,49 +1211,60 @@ gtk_menu_shell_forall (GtkContainer *container, static void gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell) { - if (menu_shell->active) + GtkMenuShellPrivate *priv = menu_shell->priv; + + if (priv->active) { - menu_shell->button = 0; - menu_shell->active = FALSE; - menu_shell->activate_time = 0; - - 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 (menu_shell->have_grab) - { - menu_shell->have_grab = FALSE; - gtk_grab_remove (GTK_WIDGET (menu_shell)); - } - 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); - } + priv->button = 0; + priv->active = FALSE; + priv->activate_time = 0; + + if (priv->active_menu_item) + { + gtk_menu_item_deselect (GTK_MENU_ITEM (priv->active_menu_item)); + priv->active_menu_item = NULL; + } + + if (priv->have_grab) + { + priv->have_grab = FALSE; + gtk_device_grab_remove (GTK_WIDGET (menu_shell), priv->grab_pointer); + } + if (priv->have_xgrab) + { + 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); + + priv->have_xgrab = FALSE; + } + + priv->keyboard_mode = FALSE; + _gtk_menu_shell_set_grab_device (menu_shell, NULL); + + _gtk_menu_shell_update_mnemonics (menu_shell); } } static gint gtk_menu_shell_is_item (GtkMenuShell *menu_shell, - GtkWidget *child) + GtkWidget *child) { GtkWidget *parent; g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE); g_return_val_if_fail (child != NULL, FALSE); - parent = child->parent; - while (parent && GTK_IS_MENU_SHELL (parent)) + parent = gtk_widget_get_parent (child); + while (GTK_IS_MENU_SHELL (parent)) { if (parent == (GtkWidget*) menu_shell) - return TRUE; - parent = GTK_MENU_SHELL (parent)->parent_menu_shell; + return TRUE; + parent = GTK_MENU_SHELL (parent)->priv->parent_menu_shell; } return FALSE; @@ -941,14 +1272,14 @@ gtk_menu_shell_is_item (GtkMenuShell *menu_shell, static GtkWidget* gtk_menu_shell_get_item (GtkMenuShell *menu_shell, - GdkEvent *event) + GdkEvent *event) { GtkWidget *menu_item; menu_item = gtk_get_event_widget ((GdkEvent*) event); - + while (menu_item && !GTK_IS_MENU_ITEM (menu_item)) - menu_item = menu_item->parent; + menu_item = gtk_widget_get_parent (menu_item); if (menu_item && gtk_menu_shell_is_item (menu_shell, menu_item)) return menu_item; @@ -958,10 +1289,18 @@ gtk_menu_shell_get_item (GtkMenuShell *menu_shell, /* Handlers for action signals */ +/** + * gtk_menu_shell_select_item: + * @menu_shell: a #GtkMenuShell + * @menu_item: The #GtkMenuItem to select + * + * Selects the menu item from the menu shell. + */ void gtk_menu_shell_select_item (GtkMenuShell *menu_shell, - GtkWidget *menu_item) + GtkWidget *menu_item) { + GtkMenuShellPrivate *priv = menu_shell->priv; GtkMenuShellClass *class; g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); @@ -970,57 +1309,89 @@ gtk_menu_shell_select_item (GtkMenuShell *menu_shell, class = GTK_MENU_SHELL_GET_CLASS (menu_shell); if (class->select_item && - !(menu_shell->active && - menu_shell->active_menu_item == menu_item)) + !(priv->active && + priv->active_menu_item == menu_item)) class->select_item (menu_shell, menu_item); } void _gtk_menu_item_set_placement (GtkMenuItem *menu_item, - GtkSubmenuPlacement placement); + GtkSubmenuPlacement placement); static void gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell, - GtkWidget *menu_item) + GtkWidget *menu_item) { + GtkMenuShellPrivate *priv = menu_shell->priv; GtkPackDirection pack_dir = PACK_DIRECTION (menu_shell); - gtk_menu_shell_deselect (menu_shell); + if (priv->active_menu_item) + { + gtk_menu_item_deselect (GTK_MENU_ITEM (priv->active_menu_item)); + priv->active_menu_item = NULL; + } if (!_gtk_menu_item_is_selectable (menu_item)) - return; + { + priv->in_unselectable_item = TRUE; + _gtk_menu_shell_update_mnemonics (menu_shell); + return; + } - menu_shell->active_menu_item = menu_item; + gtk_menu_shell_activate (menu_shell); + + priv->active_menu_item = menu_item; if (pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT) - _gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item), - GTK_LEFT_RIGHT); + _gtk_menu_item_set_placement (GTK_MENU_ITEM (priv->active_menu_item), + GTK_LEFT_RIGHT); else - _gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item), - GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement); - gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item)); + _gtk_menu_item_set_placement (GTK_MENU_ITEM (priv->active_menu_item), + GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement); + gtk_menu_item_select (GTK_MENU_ITEM (priv->active_menu_item)); + + _gtk_menu_shell_update_mnemonics (menu_shell); /* This allows the bizarre radio buttons-with-submenus-display-history * behavior */ - if (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu) - gtk_widget_activate (menu_shell->active_menu_item); + if (GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu) + gtk_widget_activate (priv->active_menu_item); } +/** + * gtk_menu_shell_deselect: + * @menu_shell: a #GtkMenuShell + * + * Deselects the currently selected item from the menu shell, + * if any. + */ void gtk_menu_shell_deselect (GtkMenuShell *menu_shell) { + GtkMenuShellPrivate *priv = menu_shell->priv; + g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); - if (menu_shell->active_menu_item) + if (priv->active_menu_item) { - gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item)); - menu_shell->active_menu_item = NULL; + gtk_menu_item_deselect (GTK_MENU_ITEM (priv->active_menu_item)); + priv->active_menu_item = NULL; + _gtk_menu_shell_update_mnemonics (menu_shell); } } +/** + * gtk_menu_shell_activate_item: + * @menu_shell: a #GtkMenuShell + * @menu_item: the #GtkMenuItem to activate + * @force_deactivate: if %TRUE, force the deactivation of the + * menu shell after the menu item is activated + * + * Activates the menu item within the menu shell. + */ void -gtk_menu_shell_activate_item (GtkMenuShell *menu_shell, - GtkWidget *menu_item, - gboolean force_deactivate) +gtk_menu_shell_activate_item (GtkMenuShell *menu_shell, + GtkWidget *menu_item, + gboolean force_deactivate) { GSList *slist, *shells = NULL; gboolean deactivate = force_deactivate; @@ -1039,17 +1410,17 @@ gtk_menu_shell_activate_item (GtkMenuShell *menu_shell, GtkMenuShell *parent_menu_shell = menu_shell; do - { - g_object_ref (parent_menu_shell); - shells = g_slist_prepend (shells, parent_menu_shell); - parent_menu_shell = (GtkMenuShell*) parent_menu_shell->parent_menu_shell; - } + { + g_object_ref (parent_menu_shell); + shells = g_slist_prepend (shells, parent_menu_shell); + parent_menu_shell = (GtkMenuShell*) parent_menu_shell->priv->parent_menu_shell; + } while (parent_menu_shell); shells = g_slist_reverse (shells); gtk_menu_shell_deactivate (menu_shell); - - /* flush the x-queue, so any grabs are removed and + + /* Flush the x-queue, so any grabs are removed and * the menu is actually taken down */ gdk_display_sync (gtk_widget_get_display (menu_item)); @@ -1069,44 +1440,73 @@ gtk_menu_shell_activate_item (GtkMenuShell *menu_shell, } /* Distance should be +/- 1 */ -static void -gtk_menu_shell_move_selected (GtkMenuShell *menu_shell, - gint distance) +static gboolean +gtk_menu_shell_real_move_selected (GtkMenuShell *menu_shell, + gint distance) { - if (menu_shell->active_menu_item) + GtkMenuShellPrivate *priv = menu_shell->priv; + + if (priv->active_menu_item) { - GList *node = g_list_find (menu_shell->children, - menu_shell->active_menu_item); + GList *node = g_list_find (priv->children, priv->active_menu_item); GList *start_node = node; - + gboolean wrap_around; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)), + "gtk-keynav-wrap-around", &wrap_around, + NULL); + if (distance > 0) - { - node = node->next; - while (node != start_node && - (!node || !_gtk_menu_item_is_selectable (node->data))) - { - if (!node) - node = menu_shell->children; - else - node = node->next; - } - } + { + node = node->next; + while (node != start_node && + (!node || !_gtk_menu_item_is_selectable (node->data))) + { + if (node) + node = node->next; + else if (wrap_around) + node = priv->children; + else + { + gtk_widget_error_bell (GTK_WIDGET (menu_shell)); + break; + } + } + } else - { - node = node->prev; - while (node != start_node && - (!node || !_gtk_menu_item_is_selectable (node->data))) - { - if (!node) - node = g_list_last (menu_shell->children); - else - node = node->prev; - } - } + { + node = node->prev; + while (node != start_node && + (!node || !_gtk_menu_item_is_selectable (node->data))) + { + if (node) + node = node->prev; + else if (wrap_around) + node = g_list_last (priv->children); + else + { + gtk_widget_error_bell (GTK_WIDGET (menu_shell)); + break; + } + } + } if (node) - gtk_menu_shell_select_item (menu_shell, node->data); + gtk_menu_shell_select_item (menu_shell, node->data); } + + return TRUE; +} + +/* Distance should be +/- 1 */ +static void +gtk_menu_shell_move_selected (GtkMenuShell *menu_shell, + gint distance) +{ + gboolean handled = FALSE; + + g_signal_emit (menu_shell, menu_shell_signals[MOVE_SELECTED], 0, + distance, &handled); } /** @@ -1117,33 +1517,34 @@ gtk_menu_shell_move_selected (GtkMenuShell *menu_shell, * the first item isn't sensitive. This * should be %FALSE if the menu is being * popped up initially. - * + * * Select the first visible or selectable child of the menu shell; * don't select tearoff items unless the only item is a tearoff * item. * * Since: 2.2 - **/ + */ void gtk_menu_shell_select_first (GtkMenuShell *menu_shell, - gboolean search_sensitive) + gboolean search_sensitive) { + GtkMenuShellPrivate *priv = menu_shell->priv; GtkWidget *to_select = NULL; GList *tmp_list; - tmp_list = menu_shell->children; + tmp_list = priv->children; while (tmp_list) { GtkWidget *child = tmp_list->data; - - if ((!search_sensitive && GTK_WIDGET_VISIBLE (child)) || - _gtk_menu_item_is_selectable (child)) - { - to_select = child; - if (!GTK_IS_TEAROFF_MENU_ITEM (child)) - break; - } - + + if ((!search_sensitive && gtk_widget_get_visible (child)) || + _gtk_menu_item_is_selectable (child)) + { + to_select = child; + if (!GTK_IS_TEAROFF_MENU_ITEM (child)) + break; + } + tmp_list = tmp_list->next; } @@ -1153,24 +1554,25 @@ gtk_menu_shell_select_first (GtkMenuShell *menu_shell, void _gtk_menu_shell_select_last (GtkMenuShell *menu_shell, - gboolean search_sensitive) + gboolean search_sensitive) { + GtkMenuShellPrivate *priv = menu_shell->priv; GtkWidget *to_select = NULL; GList *tmp_list; - tmp_list = g_list_last (menu_shell->children); + tmp_list = g_list_last (priv->children); while (tmp_list) { GtkWidget *child = tmp_list->data; - - if ((!search_sensitive && GTK_WIDGET_VISIBLE (child)) || - _gtk_menu_item_is_selectable (child)) - { - to_select = child; - if (!GTK_IS_TEAROFF_MENU_ITEM (child)) - break; - } - + + if ((!search_sensitive && gtk_widget_get_visible (child)) || + _gtk_menu_item_is_selectable (child)) + { + to_select = child; + if (!GTK_IS_TEAROFF_MENU_ITEM (child)) + break; + } + tmp_list = tmp_list->prev; } @@ -1179,156 +1581,154 @@ _gtk_menu_shell_select_last (GtkMenuShell *menu_shell, } static gboolean -gtk_menu_shell_select_submenu_first (GtkMenuShell *menu_shell) +gtk_menu_shell_select_submenu_first (GtkMenuShell *menu_shell) { + GtkMenuShellPrivate *priv = menu_shell->priv; GtkMenuItem *menu_item; - menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item); - - if (menu_item->submenu) + if (priv->active_menu_item == NULL) + return FALSE; + + menu_item = GTK_MENU_ITEM (priv->active_menu_item); + + if (menu_item->priv->submenu) { - _gtk_menu_item_popup_submenu (GTK_WIDGET (menu_item)); - gtk_menu_shell_select_first (GTK_MENU_SHELL (menu_item->submenu), TRUE); - if (GTK_MENU_SHELL (menu_item->submenu)->active_menu_item) - return TRUE; + _gtk_menu_item_popup_submenu (GTK_WIDGET (menu_item), FALSE); + gtk_menu_shell_select_first (GTK_MENU_SHELL (menu_item->priv->submenu), TRUE); + if (GTK_MENU_SHELL (menu_item->priv->submenu)->priv->active_menu_item) + return TRUE; } return FALSE; } static void -gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell, - GtkMenuDirectionType direction) +gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell, + GtkMenuDirectionType direction) { + GtkMenuShellPrivate *priv = menu_shell->priv; GtkMenuShell *parent_menu_shell = NULL; gboolean had_selection; - had_selection = menu_shell->active_menu_item != NULL; + priv->in_unselectable_item = FALSE; + + had_selection = priv->active_menu_item != NULL; - if (menu_shell->parent_menu_shell) - parent_menu_shell = GTK_MENU_SHELL (menu_shell->parent_menu_shell); + if (priv->parent_menu_shell) + parent_menu_shell = GTK_MENU_SHELL (priv->parent_menu_shell); switch (direction) { case GTK_MENU_DIR_PARENT: if (parent_menu_shell) - { - if (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement == - GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement) - gtk_menu_shell_deselect (menu_shell); - else - { - if (PACK_DIRECTION (parent_menu_shell) == GTK_PACK_DIRECTION_LTR) - gtk_menu_shell_move_selected (parent_menu_shell, -1); - else - gtk_menu_shell_move_selected (parent_menu_shell, 1); - gtk_menu_shell_select_submenu_first (parent_menu_shell); - } - } + { + if (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement == + GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement) + gtk_menu_shell_deselect (menu_shell); + else + { + if (PACK_DIRECTION (parent_menu_shell) == GTK_PACK_DIRECTION_LTR) + gtk_menu_shell_move_selected (parent_menu_shell, -1); + else + gtk_menu_shell_move_selected (parent_menu_shell, 1); + gtk_menu_shell_select_submenu_first (parent_menu_shell); + } + } /* If there is no parent and the submenu is in the opposite direction * to the menu, then make the PARENT direction wrap around to * the bottom of the submenu. */ - else if (menu_shell->active_menu_item && - _gtk_menu_item_is_selectable (menu_shell->active_menu_item) && - GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu) - { - GtkMenuShell *submenu = GTK_MENU_SHELL (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu); - - if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != - GTK_MENU_SHELL_GET_CLASS (submenu)->submenu_placement) - _gtk_menu_shell_select_last (submenu, TRUE); - } + else if (priv->active_menu_item && + _gtk_menu_item_is_selectable (priv->active_menu_item) && + GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu) + { + GtkMenuShell *submenu = GTK_MENU_SHELL (GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu); + + if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != + GTK_MENU_SHELL_GET_CLASS (submenu)->submenu_placement) + _gtk_menu_shell_select_last (submenu, TRUE); + } break; - + case GTK_MENU_DIR_CHILD: - if (menu_shell->active_menu_item && - _gtk_menu_item_is_selectable (menu_shell->active_menu_item) && - GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu) - { - if (gtk_menu_shell_select_submenu_first (menu_shell)) - break; - } + if (priv->active_menu_item && + _gtk_menu_item_is_selectable (priv->active_menu_item) && + GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu) + { + if (gtk_menu_shell_select_submenu_first (menu_shell)) + break; + } /* Try to find a menu running the opposite direction */ - while (parent_menu_shell && - (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement == - GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement)) - { - GtkWidget *tmp_widget = parent_menu_shell->parent_menu_shell; - - if (tmp_widget) - parent_menu_shell = GTK_MENU_SHELL (tmp_widget); - else - parent_menu_shell = NULL; - } - + while (parent_menu_shell && + (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement == + GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement)) + { + parent_menu_shell = GTK_MENU_SHELL (parent_menu_shell->priv->parent_menu_shell); + } + if (parent_menu_shell) - { - if (PACK_DIRECTION (parent_menu_shell) == GTK_PACK_DIRECTION_LTR) - gtk_menu_shell_move_selected (parent_menu_shell, 1); - else - gtk_menu_shell_move_selected (parent_menu_shell, -1); - - gtk_menu_shell_select_submenu_first (parent_menu_shell); - } + { + if (PACK_DIRECTION (parent_menu_shell) == GTK_PACK_DIRECTION_LTR) + gtk_menu_shell_move_selected (parent_menu_shell, 1); + else + gtk_menu_shell_move_selected (parent_menu_shell, -1); + + gtk_menu_shell_select_submenu_first (parent_menu_shell); + } break; - + case GTK_MENU_DIR_PREV: gtk_menu_shell_move_selected (menu_shell, -1); - if (!had_selection && - !menu_shell->active_menu_item && - menu_shell->children) - _gtk_menu_shell_select_last (menu_shell, TRUE); + if (!had_selection && !priv->active_menu_item && priv->children) + _gtk_menu_shell_select_last (menu_shell, TRUE); break; + case GTK_MENU_DIR_NEXT: gtk_menu_shell_move_selected (menu_shell, 1); - if (!had_selection && - !menu_shell->active_menu_item && - menu_shell->children) - gtk_menu_shell_select_first (menu_shell, TRUE); + if (!had_selection && !priv->active_menu_item && priv->children) + gtk_menu_shell_select_first (menu_shell, TRUE); break; } } static void -gtk_real_menu_shell_activate_current (GtkMenuShell *menu_shell, - gboolean force_hide) +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)) + GtkMenuShellPrivate *priv = menu_shell->priv; + + if (priv->active_menu_item && + _gtk_menu_item_is_selectable (priv->active_menu_item)) { - - if (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL) + if (GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu == NULL) gtk_menu_shell_activate_item (menu_shell, - menu_shell->active_menu_item, - force_hide); + priv->active_menu_item, + force_hide); else - _gtk_menu_item_popup_submenu (menu_shell->active_menu_item); + _gtk_menu_item_popup_submenu (priv->active_menu_item, FALSE); } } static void -gtk_real_menu_shell_cancel (GtkMenuShell *menu_shell) +gtk_real_menu_shell_cancel (GtkMenuShell *menu_shell) { - /* Unset the active menu item so gtk_menu_popdown() doesn't see it. - */ + /* Unset the active menu item so gtk_menu_popdown() doesn't see it. */ gtk_menu_shell_deselect (menu_shell); - gtk_menu_shell_deactivate (menu_shell); g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0); } static void -gtk_real_menu_shell_cycle_focus (GtkMenuShell *menu_shell, - GtkDirectionType dir) +gtk_real_menu_shell_cycle_focus (GtkMenuShell *menu_shell, + GtkDirectionType dir) { while (menu_shell && !GTK_IS_MENU_BAR (menu_shell)) { - if (menu_shell->parent_menu_shell) - menu_shell = GTK_MENU_SHELL (menu_shell->parent_menu_shell); + if (menu_shell->priv->parent_menu_shell) + menu_shell = GTK_MENU_SHELL (menu_shell->priv->parent_menu_shell); else - menu_shell = NULL; + menu_shell = NULL; } if (menu_shell) @@ -1348,11 +1748,11 @@ _gtk_menu_shell_get_popup_delay (GtkMenuShell *menu_shell) { gint popup_delay; GtkWidget *widget = GTK_WIDGET (menu_shell); - + g_object_get (gtk_widget_get_settings (widget), - "gtk-menu-popup-delay", &popup_delay, - NULL); - + "gtk-menu-popup-delay", &popup_delay, + NULL); + return popup_delay; } } @@ -1360,9 +1760,9 @@ _gtk_menu_shell_get_popup_delay (GtkMenuShell *menu_shell) /** * gtk_menu_shell_cancel: * @menu_shell: a #GtkMenuShell - * - * Cancels the selection within the menu shell. - * + * + * Cancels the selection within the menu shell. + * * Since: 2.4 */ void @@ -1375,20 +1775,20 @@ gtk_menu_shell_cancel (GtkMenuShell *menu_shell) static GtkMnemonicHash * gtk_menu_shell_get_mnemonic_hash (GtkMenuShell *menu_shell, - gboolean create) + gboolean create) { - GtkMenuShellPrivate *private = GTK_MENU_SHELL_GET_PRIVATE (menu_shell); + GtkMenuShellPrivate *priv = menu_shell->priv; - if (!private->mnemonic_hash && create) - private->mnemonic_hash = _gtk_mnemonic_hash_new (); + if (!priv->mnemonic_hash && create) + priv->mnemonic_hash = _gtk_mnemonic_hash_new (); - return private->mnemonic_hash; + return priv->mnemonic_hash; } static void -menu_shell_add_mnemonic_foreach (guint keyval, - GSList *targets, - gpointer data) +menu_shell_add_mnemonic_foreach (guint keyval, + GSList *targets, + gpointer data) { GtkKeyHash *key_hash = data; @@ -1397,45 +1797,45 @@ menu_shell_add_mnemonic_foreach (guint keyval, static GtkKeyHash * gtk_menu_shell_get_key_hash (GtkMenuShell *menu_shell, - gboolean create) + gboolean create) { - GtkMenuShellPrivate *private = GTK_MENU_SHELL_GET_PRIVATE (menu_shell); + GtkMenuShellPrivate *priv = menu_shell->priv; GtkWidget *widget = GTK_WIDGET (menu_shell); - if (!private->key_hash && create && gtk_widget_has_screen (widget)) + if (!priv->key_hash && create && gtk_widget_has_screen (widget)) { GtkMnemonicHash *mnemonic_hash = gtk_menu_shell_get_mnemonic_hash (menu_shell, FALSE); GdkScreen *screen = gtk_widget_get_screen (widget); GdkKeymap *keymap = gdk_keymap_get_for_display (gdk_screen_get_display (screen)); if (!mnemonic_hash) - return NULL; - - private->key_hash = _gtk_key_hash_new (keymap, NULL); + return NULL; + + priv->key_hash = _gtk_key_hash_new (keymap, NULL); _gtk_mnemonic_hash_foreach (mnemonic_hash, - menu_shell_add_mnemonic_foreach, - private->key_hash); + menu_shell_add_mnemonic_foreach, + priv->key_hash); } - - return private->key_hash; + + return priv->key_hash; } static void gtk_menu_shell_reset_key_hash (GtkMenuShell *menu_shell) { - GtkMenuShellPrivate *private = GTK_MENU_SHELL_GET_PRIVATE (menu_shell); + GtkMenuShellPrivate *priv = menu_shell->priv; - if (private->key_hash) + if (priv->key_hash) { - _gtk_key_hash_free (private->key_hash); - private->key_hash = NULL; + _gtk_key_hash_free (priv->key_hash); + priv->key_hash = NULL; } } static gboolean gtk_menu_shell_activate_mnemonic (GtkMenuShell *menu_shell, - GdkEventKey *event) + GdkEventKey *event) { GtkMnemonicHash *mnemonic_hash; GtkKeyHash *key_hash; @@ -1449,46 +1849,74 @@ gtk_menu_shell_activate_mnemonic (GtkMenuShell *menu_shell, key_hash = gtk_menu_shell_get_key_hash (menu_shell, TRUE); if (!key_hash) return FALSE; - + entries = _gtk_key_hash_lookup (key_hash, - event->hardware_keycode, - event->state, - gtk_accelerator_get_default_mod_mask (), - event->group); + event->hardware_keycode, + event->state, + gtk_accelerator_get_default_mod_mask (), + event->group); if (entries) - result = _gtk_mnemonic_hash_activate (mnemonic_hash, - GPOINTER_TO_UINT (entries->data)); + { + result = _gtk_mnemonic_hash_activate (mnemonic_hash, + GPOINTER_TO_UINT (entries->data)); + g_slist_free (entries); + } return result; } void _gtk_menu_shell_add_mnemonic (GtkMenuShell *menu_shell, - guint keyval, - GtkWidget *target) + guint keyval, + GtkWidget *target) { g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); g_return_if_fail (GTK_IS_WIDGET (target)); _gtk_mnemonic_hash_add (gtk_menu_shell_get_mnemonic_hash (menu_shell, TRUE), - keyval, target); + keyval, target); gtk_menu_shell_reset_key_hash (menu_shell); } void _gtk_menu_shell_remove_mnemonic (GtkMenuShell *menu_shell, - guint keyval, - GtkWidget *target) + guint keyval, + GtkWidget *target) { g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); g_return_if_fail (GTK_IS_WIDGET (target)); - + _gtk_mnemonic_hash_remove (gtk_menu_shell_get_mnemonic_hash (menu_shell, TRUE), - keyval, target); + keyval, target); gtk_menu_shell_reset_key_hash (menu_shell); } +void +_gtk_menu_shell_set_grab_device (GtkMenuShell *menu_shell, + GdkDevice *device) +{ + GtkMenuShellPrivate *priv = menu_shell->priv; + + g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); + g_return_if_fail (device == NULL || GDK_IS_DEVICE (device)); + + if (!device) + priv->grab_pointer = NULL; + else if (gdk_device_get_source (device) == 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) +{ + g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), NULL); + + return menu_shell->priv->grab_pointer; +} + /** * gtk_menu_shell_get_take_focus: * @menu_shell: a #GtkMenuShell @@ -1498,37 +1926,35 @@ _gtk_menu_shell_remove_mnemonic (GtkMenuShell *menu_shell, * 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; + return menu_shell->priv->take_focus; } /** * gtk_menu_shell_set_take_focus: * @menu_shell: a #GtkMenuShell - * @take_focus: %TRUE if the menu shell should take the keyboard focus on popup. + * @take_focus: %TRUE if the menu shell should take the keyboard + * focus on popup * - * If @take_focus is %TRUE (the default) the menu shell will take the keyboard - * focus so that it will receive all keyboard events which is needed to enable - * keyboard navigation in menus. + * If @take_focus is %TRUE (the default) the menu shell 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. + * 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: * @@ -1543,17 +1969,15 @@ gtk_menu_shell_get_take_focus (GtkMenuShell *menu_shell) * See also gdk_keyboard_grab() * * Since: 2.8 - **/ + */ void gtk_menu_shell_set_take_focus (GtkMenuShell *menu_shell, gboolean take_focus) { - GtkMenuShellPrivate *priv; + GtkMenuShellPrivate *priv = menu_shell->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; @@ -1561,5 +1985,41 @@ gtk_menu_shell_set_take_focus (GtkMenuShell *menu_shell, } } -#define __GTK_MENU_SHELL_C__ -#include "gtkaliasdef.c" +/** + * gtk_menu_shell_get_selected_item: + * @menu_shell: a #GtkMenuShell + * + * Gets the currently selected item. + * + * Returns: (transfer none): the currently selected item + * + * Since: 3.0 + */ +GtkWidget * +gtk_menu_shell_get_selected_item (GtkMenuShell *menu_shell) +{ + g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), NULL); + + return menu_shell->priv->active_menu_item; +} + +/** + * gtk_menu_shell_get_parent_shell: + * @menu_shell: a #GtkMenuShell + * + * Gets the parent menu shell. + * + * The parent menu shell of a submenu is the #GtkMenu or #GtkMenuBar + * from which it was opened up. + * + * Returns: (transfer none): the parent #GtkMenuShell + * + * Since: 3.0 + */ +GtkWidget * +gtk_menu_shell_get_parent_shell (GtkMenuShell *menu_shell) +{ + g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), NULL); + + return menu_shell->priv->parent_menu_shell; +}