X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkmenushell.c;h=faa6a4078eb54e2d31678c8bbaa451cb65c9e43c;hb=be19be61b7c7b10a14b3105e0ff48398080b9463;hp=333e1c12978887ee8267437dd71bef1d317b0801;hpb=4af7480f8f64bd7709500bc27af91e2243898969;p=~andy%2Fgtk diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c index 333e1c129..faa6a4078 100644 --- a/gtk/gtkmenushell.c +++ b/gtk/gtkmenushell.c @@ -2,32 +2,42 @@ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public + * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. + * Lesser General Public License for more details. * - * You should have received a copy of the GNU Library General Public + * 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. */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#define GTK_MENU_INTERNALS + #include "gdk/gdkkeysyms.h" #include "gtkbindings.h" #include "gtkmain.h" +#include "gtkmarshalers.h" +#include "gtkmenubar.h" #include "gtkmenuitem.h" -#include "gtktearoffmenuitem.h" /* FIXME */ #include "gtkmenushell.h" #include "gtksignal.h" - +#include "gtktearoffmenuitem.h" +#include "gtkwindow.h" #define MENU_SHELL_TIMEOUT 500 -#define MENU_SHELL_CLASS(w) GTK_MENU_SHELL_CLASS (GTK_OBJECT (w)->klass) - enum { DEACTIVATE, @@ -35,6 +45,7 @@ enum { MOVE_CURRENT, ACTIVATE_CURRENT, CANCEL, + CYCLE_FOCUS, LAST_SIGNAL }; @@ -102,7 +113,6 @@ typedef void (*GtkMenuShellSignal2) (GtkObject *object, static void gtk_menu_shell_class_init (GtkMenuShellClass *klass); static void gtk_menu_shell_init (GtkMenuShell *menu_shell); -static void gtk_menu_shell_map (GtkWidget *widget); static void gtk_menu_shell_realize (GtkWidget *widget); static gint gtk_menu_shell_button_press (GtkWidget *widget, GdkEventButton *event); @@ -118,22 +128,30 @@ static void gtk_menu_shell_add (GtkContainer *container, GtkWidget *widget); static void gtk_menu_shell_remove (GtkContainer *container, GtkWidget *widget); -static void gtk_menu_shell_foreach (GtkContainer *container, +static void gtk_menu_shell_forall (GtkContainer *container, + gboolean include_internals, GtkCallback callback, gpointer callback_data); +static void gtk_menu_shell_real_insert (GtkMenuShell *menu_shell, + 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); static GtkWidget *gtk_menu_shell_get_item (GtkMenuShell *menu_shell, GdkEvent *event); static GtkType gtk_menu_shell_child_type (GtkContainer *container); +static void gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell, + GtkWidget *menu_item); +static void gtk_menu_shell_select_submenu_first (GtkMenuShell *menu_shell); -static void gtk_menu_shell_deselect (GtkMenuShell *menu_shell); static void gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction); static void gtk_real_menu_shell_activate_current (GtkMenuShell *menu_shell, 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); static GtkContainerClass *parent_class = NULL; static guint menu_shell_signals[LAST_SIGNAL] = { 0 }; @@ -146,24 +164,56 @@ gtk_menu_shell_get_type (void) if (!menu_shell_type) { - GtkTypeInfo menu_shell_info = + static const GTypeInfo menu_shell_info = { - "GtkMenuShell", - sizeof (GtkMenuShell), sizeof (GtkMenuShellClass), - (GtkClassInitFunc) gtk_menu_shell_class_init, - (GtkObjectInitFunc) gtk_menu_shell_init, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, + 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 = gtk_type_unique (gtk_container_get_type (), &menu_shell_info); + menu_shell_type = g_type_register_static (GTK_TYPE_CONTAINER, "GtkMenuShell", + &menu_shell_info, G_TYPE_FLAG_ABSTRACT); } return menu_shell_type; } +static guint +binding_signal_new (const gchar *signal_name, + GType itype, + GSignalFlags signal_flags, + GCallback handler, + GSignalAccumulator accumulator, + gpointer accu_data, + GSignalCMarshaller c_marshaller, + GType return_type, + guint n_params, + ...) +{ + va_list args; + guint signal_id; + + g_return_val_if_fail (signal_name != NULL, 0); + + va_start (args, n_params); + + signal_id = g_signal_new_valist (signal_name, itype, signal_flags, + g_cclosure_new (handler, NULL, NULL), + accumulator, accu_data, c_marshaller, + return_type, n_params, args); + + va_end (args); + + return signal_id; +} + static void gtk_menu_shell_class_init (GtkMenuShellClass *klass) { @@ -179,65 +229,74 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass) parent_class = gtk_type_class (gtk_container_get_type ()); + widget_class->realize = gtk_menu_shell_realize; + widget_class->button_press_event = gtk_menu_shell_button_press; + widget_class->button_release_event = gtk_menu_shell_button_release; + widget_class->key_press_event = gtk_menu_shell_key_press; + widget_class->enter_notify_event = gtk_menu_shell_enter_notify; + widget_class->leave_notify_event = gtk_menu_shell_leave_notify; + + container_class->add = gtk_menu_shell_add; + container_class->remove = gtk_menu_shell_remove; + container_class->forall = gtk_menu_shell_forall; + container_class->child_type = gtk_menu_shell_child_type; + + klass->submenu_placement = GTK_TOP_BOTTOM; + klass->deactivate = gtk_real_menu_shell_deactivate; + klass->selection_done = NULL; + klass->move_current = gtk_real_menu_shell_move_current; + klass->activate_current = gtk_real_menu_shell_activate_current; + klass->cancel = gtk_real_menu_shell_cancel; + klass->select_item = gtk_menu_shell_real_select_item; + klass->insert = gtk_menu_shell_real_insert; + menu_shell_signals[DEACTIVATE] = gtk_signal_new ("deactivate", GTK_RUN_FIRST, - object_class->type, + GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMenuShellClass, deactivate), - gtk_marshal_NONE__NONE, + _gtk_marshal_VOID__VOID, GTK_TYPE_NONE, 0); menu_shell_signals[SELECTION_DONE] = gtk_signal_new ("selection-done", GTK_RUN_FIRST, - object_class->type, + GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMenuShellClass, selection_done), - gtk_marshal_NONE__NONE, + _gtk_marshal_VOID__VOID, GTK_TYPE_NONE, 0); menu_shell_signals[MOVE_CURRENT] = gtk_signal_new ("move_current", GTK_RUN_LAST | GTK_RUN_ACTION, - object_class->type, + GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMenuShellClass, move_current), - gtk_marshal_NONE__ENUM, + _gtk_marshal_VOID__ENUM, GTK_TYPE_NONE, 1, GTK_TYPE_MENU_DIRECTION_TYPE); menu_shell_signals[ACTIVATE_CURRENT] = gtk_signal_new ("activate_current", GTK_RUN_LAST | GTK_RUN_ACTION, - object_class->type, + GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMenuShellClass, activate_current), - gtk_marshal_NONE__BOOL, + _gtk_marshal_VOID__BOOLEAN, GTK_TYPE_NONE, 1, GTK_TYPE_BOOL); menu_shell_signals[CANCEL] = gtk_signal_new ("cancel", GTK_RUN_LAST | GTK_RUN_ACTION, - object_class->type, + GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMenuShellClass, cancel), - gtk_marshal_NONE__NONE, + _gtk_marshal_VOID__VOID, GTK_TYPE_NONE, 0); - - gtk_object_class_add_signals (object_class, menu_shell_signals, LAST_SIGNAL); + menu_shell_signals[CYCLE_FOCUS] = + binding_signal_new ("cycle_focus", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | GTK_RUN_ACTION, + G_CALLBACK (gtk_real_menu_shell_cycle_focus), + NULL, NULL, + _gtk_marshal_VOID__ENUM, + GTK_TYPE_NONE, 1, + GTK_TYPE_DIRECTION_TYPE); - widget_class->map = gtk_menu_shell_map; - widget_class->realize = gtk_menu_shell_realize; - widget_class->button_press_event = gtk_menu_shell_button_press; - widget_class->button_release_event = gtk_menu_shell_button_release; - widget_class->key_press_event = gtk_menu_shell_key_press; - widget_class->enter_notify_event = gtk_menu_shell_enter_notify; - widget_class->leave_notify_event = gtk_menu_shell_leave_notify; - - container_class->add = gtk_menu_shell_add; - container_class->remove = gtk_menu_shell_remove; - container_class->foreach = gtk_menu_shell_foreach; - container_class->child_type = gtk_menu_shell_child_type; - - klass->submenu_placement = GTK_TOP_BOTTOM; - klass->deactivate = gtk_real_menu_shell_deactivate; - klass->selection_done = NULL; - klass->move_current = gtk_real_menu_shell_move_current; - klass->activate_current = gtk_real_menu_shell_activate_current; - klass->cancel = gtk_real_menu_shell_cancel; binding_set = gtk_binding_set_by_class (klass); gtk_binding_entry_add_signal (binding_set, @@ -248,11 +307,29 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass) "activate_current", 1, GTK_TYPE_BOOL, TRUE); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Enter, 0, + "activate_current", 1, + GTK_TYPE_BOOL, + TRUE); gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "activate_current", 1, GTK_TYPE_BOOL, FALSE); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Space, 0, + "activate_current", 1, + GTK_TYPE_BOOL, + FALSE); + gtk_binding_entry_add_signal (binding_set, + GDK_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, + GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD); } static GtkType @@ -295,97 +372,41 @@ gtk_menu_shell_insert (GtkMenuShell *menu_shell, GtkWidget *child, gint position) { - GList *tmp_list; - GList *new_list; - gint nchildren; + GtkMenuShellClass *class; - g_return_if_fail (menu_shell != NULL); g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); - g_return_if_fail (child != NULL); g_return_if_fail (GTK_IS_MENU_ITEM (child)); - gtk_widget_set_parent (child, GTK_WIDGET (menu_shell)); - - if (GTK_WIDGET_VISIBLE (child->parent)) - { - if (GTK_WIDGET_REALIZED (child->parent) && - !GTK_WIDGET_REALIZED (child)) - gtk_widget_realize (child); + class = GTK_MENU_SHELL_GET_CLASS (menu_shell); - if (GTK_WIDGET_MAPPED (child->parent) && - !GTK_WIDGET_MAPPED (child)) - gtk_widget_map (child); - } - - nchildren = g_list_length (menu_shell->children); - if ((position < 0) || (position > nchildren)) - position = nchildren; + if (class->insert) + class->insert (menu_shell, child, position); +} - if (position == nchildren) - { - menu_shell->children = g_list_append (menu_shell->children, child); - } - else - { - tmp_list = g_list_nth (menu_shell->children, position); - new_list = g_list_alloc (); - new_list->data = child; - - if (tmp_list->prev) - tmp_list->prev->next = new_list; - new_list->next = tmp_list; - new_list->prev = tmp_list->prev; - tmp_list->prev = new_list; - - if (tmp_list == menu_shell->children) - menu_shell->children = new_list; - } +static void +gtk_menu_shell_real_insert (GtkMenuShell *menu_shell, + GtkWidget *child, + gint position) +{ + menu_shell->children = g_list_insert (menu_shell->children, child, position); - if (GTK_WIDGET_VISIBLE (menu_shell)) - gtk_widget_queue_resize (GTK_WIDGET (menu_shell)); + gtk_widget_set_parent (child, GTK_WIDGET (menu_shell)); } void gtk_menu_shell_deactivate (GtkMenuShell *menu_shell) { - g_return_if_fail (menu_shell != NULL); g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[DEACTIVATE]); } -static void -gtk_menu_shell_map (GtkWidget *widget) -{ - GtkMenuShell *menu_shell; - GtkWidget *child; - GList *children; - - g_return_if_fail (widget != NULL); - g_return_if_fail (GTK_IS_MENU_SHELL (widget)); - - menu_shell = GTK_MENU_SHELL (widget); - GTK_WIDGET_SET_FLAGS (menu_shell, GTK_MAPPED); - gdk_window_show (widget->window); - - children = menu_shell->children; - while (children) - { - child = children->data; - children = children->next; - - if (GTK_WIDGET_VISIBLE (child) && !GTK_WIDGET_MAPPED (child)) - gtk_widget_map (child); - } -} - static void gtk_menu_shell_realize (GtkWidget *widget) { GdkWindowAttr attributes; gint attributes_mask; - g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_MENU_SHELL (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); @@ -414,6 +435,17 @@ gtk_menu_shell_realize (GtkWidget *widget) gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); } +void +_gtk_menu_shell_activate (GtkMenuShell *menu_shell) +{ + if (!menu_shell->active) + { + gtk_grab_add (GTK_WIDGET (menu_shell)); + menu_shell->have_grab = TRUE; + menu_shell->active = TRUE; + } +} + static gint gtk_menu_shell_button_press (GtkWidget *widget, GdkEventButton *event) @@ -421,7 +453,6 @@ gtk_menu_shell_button_press (GtkWidget *widget, GtkMenuShell *menu_shell; GtkWidget *menu_item; - g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); @@ -432,33 +463,26 @@ gtk_menu_shell_button_press (GtkWidget *widget, if (menu_shell->parent_menu_shell) { - gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); + return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); } else if (!menu_shell->active || !menu_shell->button) { - if (!menu_shell->active) - { - gtk_grab_add (GTK_WIDGET (widget)); - menu_shell->have_grab = TRUE; - menu_shell->active = TRUE; - } + _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_WIDGET_IS_SENSITIVE (menu_item)) + if (menu_item && _gtk_menu_item_is_selectable (menu_item)) { if ((menu_item->parent == widget) && (menu_item != menu_shell->active_menu_item)) { - if (menu_shell->active_menu_item) - gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item)); - - menu_shell->active_menu_item = menu_item; - gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item), - MENU_SHELL_CLASS (menu_shell)->submenu_placement); - gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item)); + if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM) + g_object_set_data (G_OBJECT (menu_shell), + "gtk-menushell-just-activated", + GUINT_TO_POINTER (1)); + gtk_menu_shell_select_item (menu_shell, menu_item); } } } @@ -483,29 +507,45 @@ gtk_menu_shell_button_release (GtkWidget *widget, GtkWidget *menu_item; gint deactivate; - g_return_val_if_fail (widget != NULL, FALSE); 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) { + gboolean deactivate_immediately = FALSE; + if (menu_shell->button && (event->button != menu_shell->button)) { menu_shell->button = 0; if (menu_shell->parent_menu_shell) - gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); - return TRUE; + return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event); } - + menu_shell->button = 0; menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event); deactivate = TRUE; + if (menu_item + && GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM) + { + if (g_object_get_data (G_OBJECT (menu_shell), "gtk-menushell-just-activated")) + g_object_set_data (G_OBJECT (menu_shell), "gtk-menushell-just-activated", NULL); + else + deactivate_immediately = TRUE; + } + if ((event->time - menu_shell->activate_time) > MENU_SHELL_TIMEOUT) { - if (menu_item && (menu_shell->active_menu_item == menu_item)) + if (deactivate_immediately) + { + gtk_menu_shell_deactivate (menu_shell); + return TRUE; + } + + 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) { @@ -513,6 +553,8 @@ gtk_menu_shell_button_release (GtkWidget *widget, return TRUE; } } + else if (menu_item && !_gtk_menu_item_is_selectable (menu_item)) + deactivate = FALSE; else if (menu_shell->parent_menu_shell) { menu_shell->active = TRUE; @@ -521,7 +563,17 @@ gtk_menu_shell_button_release (GtkWidget *widget, } } else - deactivate = FALSE; + { + /* 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; + } /* If the button click was very fast, or we ended up on a submenu, * leave the menu up @@ -550,8 +602,8 @@ gtk_menu_shell_key_press (GtkWidget *widget, GdkEventKey *event) { GtkMenuShell *menu_shell; + GtkWidget *toplevel; - g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); @@ -560,12 +612,12 @@ gtk_menu_shell_key_press (GtkWidget *widget, 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 (GTK_OBJECT (widget), - event->keyval, - event->state)) + if (_gtk_bindings_activate_event (GTK_OBJECT (widget), event)) return TRUE; - if (gtk_accel_groups_activate (GTK_OBJECT (widget), event->keyval, event->state)) + toplevel = gtk_widget_get_toplevel (widget); + if (GTK_IS_WINDOW (toplevel) && + _gtk_window_activate_key (GTK_WINDOW (toplevel), event)) return TRUE; return FALSE; @@ -578,23 +630,27 @@ gtk_menu_shell_enter_notify (GtkWidget *widget, GtkMenuShell *menu_shell; GtkWidget *menu_item; - g_return_val_if_fail (widget != NULL, FALSE); 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_shell->ignore_enter) + if (menu_shell->active) { menu_item = gtk_get_event_widget ((GdkEvent*) event); - if (!menu_item || !GTK_WIDGET_IS_SENSITIVE (menu_item)) + 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)) { @@ -618,7 +674,6 @@ gtk_menu_shell_leave_notify (GtkWidget *widget, GtkMenuItem *menu_item; GtkWidget *event_widget; - g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); @@ -638,7 +693,7 @@ gtk_menu_shell_leave_notify (GtkWidget *widget, return TRUE; } - if (!GTK_WIDGET_IS_SENSITIVE (menu_item)) + if (!_gtk_menu_item_is_selectable (event_widget)) return TRUE; if ((menu_shell->active_menu_item == event_widget) && @@ -673,15 +728,19 @@ gtk_menu_shell_remove (GtkContainer *container, GtkMenuShell *menu_shell; gint was_visible; - g_return_if_fail (container != NULL); g_return_if_fail (GTK_IS_MENU_SHELL (container)); - g_return_if_fail (widget != NULL); 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) + { + gtk_item_deselect (GTK_ITEM (menu_shell->active_menu_item)); + menu_shell->active_menu_item = NULL; + } + gtk_widget_unparent (widget); /* queue resize regardless of GTK_WIDGET_VISIBLE (container), @@ -692,15 +751,15 @@ gtk_menu_shell_remove (GtkContainer *container, } static void -gtk_menu_shell_foreach (GtkContainer *container, - GtkCallback callback, - gpointer callback_data) +gtk_menu_shell_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) { GtkMenuShell *menu_shell; GtkWidget *child; GList *children; - g_return_if_fail (container != NULL); g_return_if_fail (GTK_IS_MENU_SHELL (container)); g_return_if_fail (callback != NULL); @@ -720,9 +779,6 @@ gtk_menu_shell_foreach (GtkContainer *container, static void gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell) { - g_return_if_fail (menu_shell != NULL); - g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); - if (menu_shell->active) { menu_shell->button = 0; @@ -741,9 +797,11 @@ gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell) } if (menu_shell->have_xgrab) { + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (menu_shell)); + menu_shell->have_xgrab = FALSE; - gdk_pointer_ungrab (GDK_CURRENT_TIME); - gdk_keyboard_ungrab (GDK_CURRENT_TIME); + gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME); + gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); } } } @@ -754,7 +812,6 @@ gtk_menu_shell_is_item (GtkMenuShell *menu_shell, { GtkWidget *parent; - g_return_val_if_fail (menu_shell != NULL, FALSE); g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE); g_return_val_if_fail (child != NULL, FALSE); @@ -769,7 +826,7 @@ gtk_menu_shell_is_item (GtkMenuShell *menu_shell, return FALSE; } -static GtkWidget * +static GtkWidget* gtk_menu_shell_get_item (GtkMenuShell *menu_shell, GdkEvent *event) { @@ -789,20 +846,35 @@ gtk_menu_shell_get_item (GtkMenuShell *menu_shell, /* Handlers for action signals */ void -gtk_menu_shell_select_item (GtkMenuShell *menu_shell, - GtkWidget *menu_item) +gtk_menu_shell_select_item (GtkMenuShell *menu_shell, + GtkWidget *menu_item) { - g_return_if_fail (menu_shell != NULL); + GtkMenuShellClass *class; + g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); - g_return_if_fail (menu_item != NULL); g_return_if_fail (GTK_IS_MENU_ITEM (menu_item)); - if (menu_shell->active_menu_item) - gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item)); - + class = GTK_MENU_SHELL_GET_CLASS (menu_shell); + + if (class->select_item) + class->select_item (menu_shell, menu_item); +} + +void _gtk_menu_item_set_placement (GtkMenuItem *menu_item, + GtkSubmenuPlacement placement); + +static void +gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell, + GtkWidget *menu_item) +{ + gtk_menu_shell_deselect (menu_shell); + + if (!_gtk_menu_item_is_selectable (menu_item)) + return; + menu_shell->active_menu_item = menu_item; - gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item), - MENU_SHELL_CLASS (menu_shell)->submenu_placement); + _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)); /* This allows the bizarre radio buttons-with-submenus-display-history @@ -812,11 +884,16 @@ gtk_menu_shell_select_item (GtkMenuShell *menu_shell, gtk_widget_activate (menu_shell->active_menu_item); } -static void -gtk_menu_shell_deselect (GtkMenuShell *menu_shell) +void +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; + g_return_if_fail (GTK_IS_MENU_SHELL (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; + } } void @@ -824,31 +901,48 @@ gtk_menu_shell_activate_item (GtkMenuShell *menu_shell, GtkWidget *menu_item, gboolean force_deactivate) { + GSList *slist, *shells = NULL; gboolean deactivate = force_deactivate; - g_return_if_fail (menu_shell != NULL); g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); - g_return_if_fail (menu_item != NULL); g_return_if_fail (GTK_IS_MENU_ITEM (menu_item)); if (!deactivate) - { - deactivate = GTK_MENU_ITEM_CLASS (GTK_OBJECT (menu_item)->klass)->hide_on_activate; - } + deactivate = GTK_MENU_ITEM_GET_CLASS (menu_item)->hide_on_activate; + + gtk_widget_ref (GTK_WIDGET (menu_shell)); if (deactivate) { + GtkMenuShell *parent_menu_shell = menu_shell; + + do + { + gtk_widget_ref (GTK_WIDGET (parent_menu_shell)); + shells = g_slist_prepend (shells, parent_menu_shell); + parent_menu_shell = (GtkMenuShell*) parent_menu_shell->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 * the menu is actually taken down */ - gdk_flush (); + gdk_display_sync (gtk_widget_get_display (menu_item)); } + gtk_widget_activate (menu_item); - if (deactivate) - gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]); + for (slist = shells; slist; slist = slist->next) + { + gtk_signal_emit (slist->data, menu_shell_signals[SELECTION_DONE]); + gtk_widget_unref (slist->data); + } + g_slist_free (shells); + + gtk_widget_unref (GTK_WIDGET (menu_shell)); } /* Distance should be +/- 1 */ @@ -866,7 +960,7 @@ gtk_menu_shell_move_selected (GtkMenuShell *menu_shell, { node = node->next; while (node != start_node && - (!node || !GTK_WIDGET_SENSITIVE (node->data))) + (!node || !_gtk_menu_item_is_selectable (node->data))) { if (!node) node = menu_shell->children; @@ -878,7 +972,7 @@ gtk_menu_shell_move_selected (GtkMenuShell *menu_shell, { node = node->prev; while (node != start_node && - (!node || !GTK_WIDGET_SENSITIVE (node->data))) + (!node || !_gtk_menu_item_is_selectable (node->data))) { if (!node) node = g_list_last (menu_shell->children); @@ -892,11 +986,92 @@ gtk_menu_shell_move_selected (GtkMenuShell *menu_shell, } } +/** + * _gtk_menu_shell_select_first: + * @menu_shell: a #GtkMenuShell + * @search_sensitive: if %TRUE, search for the first selectable + * menu item, otherwise select nothing if + * 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. + **/ +void +_gtk_menu_shell_select_first (GtkMenuShell *menu_shell, + gboolean search_sensitive) +{ + GtkWidget *to_select = NULL; + GList *tmp_list; + + tmp_list = menu_shell->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; + } + + tmp_list = tmp_list->next; + } + + if (to_select) + gtk_menu_shell_select_item (menu_shell, to_select); +} + +static void +gtk_menu_shell_select_last (GtkMenuShell *menu_shell, + gboolean search_sensitive) +{ + GtkWidget *to_select = NULL; + GList *tmp_list; + + tmp_list = g_list_last (menu_shell->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; + } + + tmp_list = tmp_list->prev; + } + + if (to_select) + gtk_menu_shell_select_item (menu_shell, to_select); +} + +static void +gtk_menu_shell_select_submenu_first (GtkMenuShell *menu_shell) +{ + GtkMenuItem *menu_item; + + menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item); + + if (menu_item->submenu) + _gtk_menu_shell_select_first (GTK_MENU_SHELL (menu_item->submenu), TRUE); +} + static void gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction) { GtkMenuShell *parent_menu_shell = NULL; + gboolean had_selection; + + had_selection = menu_shell->active_menu_item != NULL; if (menu_shell->parent_menu_shell) parent_menu_shell = GTK_MENU_SHELL (menu_shell->parent_menu_shell); @@ -906,42 +1081,63 @@ gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell, case GTK_MENU_DIR_PARENT: if (parent_menu_shell) { - if (GTK_MENU_SHELL_CLASS (GTK_OBJECT (parent_menu_shell)->klass)->submenu_placement == - GTK_MENU_SHELL_CLASS (GTK_OBJECT (menu_shell)->klass)->submenu_placement) + 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 - 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_CHILD: - if (GTK_BIN (menu_shell->active_menu_item)->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) { - menu_shell = GTK_MENU_SHELL (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu); - if (menu_shell->children) - gtk_menu_shell_select_item (menu_shell, menu_shell->children->data); + gtk_menu_shell_select_submenu_first (menu_shell); } else { /* Try to find a menu running the opposite direction */ while (parent_menu_shell && - (GTK_MENU_SHELL_CLASS (GTK_OBJECT (parent_menu_shell)->klass)->submenu_placement == - GTK_MENU_SHELL_CLASS (GTK_OBJECT (menu_shell)->klass)->submenu_placement)) - parent_menu_shell = GTK_MENU_SHELL (parent_menu_shell->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; + } + if (parent_menu_shell) - gtk_menu_shell_move_selected (parent_menu_shell, 1); + { + 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); 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); break; } + } static void @@ -949,6 +1145,7 @@ 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) && GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL) { gtk_menu_shell_activate_item (menu_shell, @@ -960,7 +1157,26 @@ gtk_real_menu_shell_activate_current (GtkMenuShell *menu_shell, static void gtk_real_menu_shell_cancel (GtkMenuShell *menu_shell) { + /* 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); gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]); } +static void +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); + else + menu_shell = NULL; + } + + if (menu_shell) + _gtk_menu_bar_cycle_focus (GTK_MENU_BAR (menu_shell), dir); +}