X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkmenu.c;h=e8ced9faba19f85a8f9db74ab1e57029fdbd3083;hb=fe0b83b722a54165761c2545f1edb505fd547d6b;hp=47db2b26bf9cc00b7b97eee3ea83011206da38ae;hpb=7678a1ed1606f3cd696b1402f449d33de6856636;p=~andy%2Fgtk diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c index 47db2b26b..e8ced9fab 100644 --- a/gtk/gtkmenu.c +++ b/gtk/gtkmenu.c @@ -24,6 +24,8 @@ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ +#define GTK_MENU_INTERNALS + #include /* memset */ #include "gdk/gdkkeysyms.h" #include "gtkaccelmap.h" @@ -32,7 +34,6 @@ #include "gtkmain.h" #include "gtkmenu.h" #include "gtkmenuitem.h" -#include "gtksignal.h" #include "gtkwindow.h" #include "gtkhbox.h" #include "gtkvscrollbar.h" @@ -43,8 +44,12 @@ #define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_GET_CLASS (w) #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag -#define SUBMENU_NAV_REGION_PADDING 2 -#define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333 +#define DEFAULT_POPUP_DELAY 225 +#define DEFAULT_POPDOWN_DELAY 1000 + +#define NAVIGATION_REGION_OVERSHOOT 50 /* How much the navigation region + * extends below the submenu + */ #define MENU_SCROLL_STEP 10 #define MENU_SCROLL_ARROW_HEIGHT 16 @@ -53,6 +58,7 @@ #define MENU_SCROLL_TIMEOUT2 50 typedef struct _GtkMenuAttachData GtkMenuAttachData; +typedef struct _GtkMenuPrivate GtkMenuPrivate; struct _GtkMenuAttachData { @@ -60,6 +66,13 @@ struct _GtkMenuAttachData GtkMenuDetachFunc detacher; }; +struct _GtkMenuPrivate +{ + gboolean have_position; + gint x; + gint y; +}; + enum { PROP_0, PROP_TEAROFF_TITLE @@ -113,6 +126,12 @@ static void gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter); static void gtk_menu_set_tearoff_hints (GtkMenu *menu, gint width); +static void gtk_menu_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static gboolean gtk_menu_focus (GtkWidget *widget, + GtkDirectionType direction); +static gint gtk_menu_get_popup_delay (GtkMenuShell *menu_shell); + static void gtk_menu_stop_navigating_submenu (GtkMenu *menu); static gboolean gtk_menu_stop_navigating_submenu_cb (gpointer user_data); @@ -144,26 +163,51 @@ static void _gtk_menu_refresh_accel_paths (GtkMenu *menu, static GtkMenuShellClass *parent_class = NULL; static const gchar *attach_data_key = "gtk-menu-attach-data"; -GtkType +GtkMenuPrivate * +gtk_menu_get_private (GtkMenu *menu) +{ + GtkMenuPrivate *private; + static GQuark private_quark = 0; + + if (!private_quark) + private_quark = g_quark_from_static_string ("gtk-menu-private"); + + private = g_object_get_qdata (G_OBJECT (menu), private_quark); + + if (!private) + { + private = g_new0 (GtkMenuPrivate, 1); + private->have_position = FALSE; + + g_object_set_qdata_full (G_OBJECT (menu), private_quark, + private, g_free); + } + + return private; +} + +GType gtk_menu_get_type (void) { - static GtkType menu_type = 0; + static GType menu_type = 0; if (!menu_type) { - static const GtkTypeInfo menu_info = + static const GTypeInfo menu_info = { - "GtkMenu", - sizeof (GtkMenu), sizeof (GtkMenuClass), - (GtkClassInitFunc) gtk_menu_class_init, - (GtkObjectInitFunc) gtk_menu_init, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_menu_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkMenu), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_menu_init, }; - menu_type = gtk_type_unique (gtk_menu_shell_get_type (), &menu_info); + menu_type = g_type_register_static (GTK_TYPE_MENU_SHELL, "GtkMenu", + &menu_info, 0); } return menu_type; @@ -189,9 +233,10 @@ gtk_menu_class_init (GtkMenuClass *class) PROP_TEAROFF_TITLE, g_param_spec_string ("tearoff-title", _("Tearoff Title"), - _("A title that may be displayed by the window manager when this menu is torn-off."), + _("A title that may be displayed by the window manager when this menu is torn-off"), "", G_PARAM_READABLE | G_PARAM_WRITABLE)); + object_class->destroy = gtk_menu_destroy; widget_class->realize = gtk_menu_realize; @@ -206,6 +251,8 @@ gtk_menu_class_init (GtkMenuClass *class) widget_class->hide_all = gtk_menu_hide_all; widget_class->enter_notify_event = gtk_menu_enter_notify; widget_class->leave_notify_event = gtk_menu_leave_notify; + widget_class->style_set = gtk_menu_style_set; + widget_class->focus = gtk_menu_focus; container_class->remove = gtk_menu_remove; @@ -213,6 +260,7 @@ gtk_menu_class_init (GtkMenuClass *class) menu_shell_class->deactivate = gtk_menu_deactivate; menu_shell_class->select_item = gtk_menu_select_item; menu_shell_class->insert = gtk_menu_real_insert; + menu_shell_class->get_popup_delay = gtk_menu_get_popup_delay; binding_set = gtk_binding_set_by_class (class); gtk_binding_entry_add_signal (binding_set, @@ -258,9 +306,26 @@ gtk_menu_class_init (GtkMenuClass *class) gtk_settings_install_property (g_param_spec_boolean ("gtk-can-change-accels", _("Can change accelerators"), - _("Whether menu accelerators can be changed by pressing a key over the menu item."), + _("Whether menu accelerators can be changed by pressing a key over the menu item"), FALSE, G_PARAM_READWRITE)); + + gtk_settings_install_property (g_param_spec_int ("gtk-menu-popup-delay", + _("Delay before submenus appear"), + _("Minimum time the pointer must stay over a menu item before the submenu appear"), + 0, + G_MAXINT, + DEFAULT_POPUP_DELAY, + G_PARAM_READWRITE)); + + gtk_settings_install_property (g_param_spec_int ("gtk-menu-popdown-delay", + _("Delay before hiding a submenu"), + _("The time before hiding a submenu when the pointer is moving towards the submenu"), + 0, + G_MAXINT, + DEFAULT_POPDOWN_DELAY, + G_PARAM_READWRITE)); + } @@ -313,8 +378,8 @@ gtk_menu_window_event (GtkWidget *window, { gboolean handled = FALSE; - gtk_widget_ref (window); - gtk_widget_ref (menu); + g_object_ref (window); + g_object_ref (menu); switch (event->type) { @@ -326,12 +391,28 @@ gtk_menu_window_event (GtkWidget *window, break; } - gtk_widget_unref (window); - gtk_widget_unref (menu); + g_object_unref (window); + g_object_unref (menu); return handled; } +static void +gtk_menu_window_size_request (GtkWidget *window, + GtkRequisition *requisition, + GtkMenu *menu) +{ + GtkMenuPrivate *private = gtk_menu_get_private (menu); + + if (private->have_position) + { + gint screen_height = gdk_screen_height (); + + if (private->y + requisition->height > screen_height) + requisition->height = screen_height - private->y; + } +} + static void gtk_menu_init (GtkMenu *menu) { @@ -342,15 +423,15 @@ gtk_menu_init (GtkMenu *menu) menu->position_func_data = NULL; menu->toggle_size = 0; - menu->toplevel = g_object_connect (gtk_widget_new (GTK_TYPE_WINDOW, - "type", GTK_WINDOW_POPUP, - "child", menu, - NULL), + menu->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW, + "type", GTK_WINDOW_POPUP, + "child", menu, + NULL), "signal::event", gtk_menu_window_event, menu, + "signal::size_request", gtk_menu_window_size_request, menu, "signal::destroy", gtk_widget_destroyed, &menu->toplevel, NULL); - gtk_window_set_policy (GTK_WINDOW (menu->toplevel), - FALSE, FALSE, TRUE); + gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE); gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0); /* Refloat the menu, so that reference counting for the menu isn't @@ -394,7 +475,7 @@ gtk_menu_destroy (GtkObject *object) gtk_menu_stop_scrolling (menu); - data = gtk_object_get_data (object, attach_data_key); + data = g_object_get_data (G_OBJECT (object), attach_data_key); if (data) gtk_menu_detach (menu); @@ -402,7 +483,7 @@ gtk_menu_destroy (GtkObject *object) if (menu->old_active_menu_item) { - gtk_widget_unref (menu->old_active_menu_item); + g_object_unref (menu->old_active_menu_item); menu->old_active_menu_item = NULL; } @@ -410,7 +491,7 @@ gtk_menu_destroy (GtkObject *object) if (menu->needs_destruction_ref_count) { menu->needs_destruction_ref_count = FALSE; - gtk_object_ref (object); + g_object_ref (object); } if (menu->accel_group) @@ -451,21 +532,21 @@ gtk_menu_attach_to_widget (GtkMenu *menu, /* keep this function in sync with gtk_widget_set_parent() */ - data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key); + data = g_object_get_data (G_OBJECT (menu), attach_data_key); if (data) { g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s", - gtk_type_name (GTK_OBJECT_TYPE (data->attach_widget))); + g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget))); return; } - gtk_object_ref (GTK_OBJECT (menu)); + g_object_ref (menu); gtk_object_sink (GTK_OBJECT (menu)); data = g_new (GtkMenuAttachData, 1); data->attach_widget = attach_widget; data->detacher = detacher; - gtk_object_set_data (GTK_OBJECT (menu), attach_data_key, data); + g_object_set_data (G_OBJECT (menu), attach_data_key, data); if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL) gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL); @@ -485,7 +566,7 @@ gtk_menu_get_attach_widget (GtkMenu *menu) g_return_val_if_fail (GTK_IS_MENU (menu), NULL); - data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key); + data = g_object_get_data (G_OBJECT (menu), attach_data_key); if (data) return data->attach_widget; return NULL; @@ -500,13 +581,13 @@ gtk_menu_detach (GtkMenu *menu) /* keep this function in sync with gtk_widget_unparent() */ - data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key); + data = g_object_get_data (G_OBJECT (menu), attach_data_key); if (!data) { g_warning ("gtk_menu_detach(): menu is not attached"); return; } - gtk_object_remove_data (GTK_OBJECT (menu), attach_data_key); + g_object_set_data (G_OBJECT (menu), attach_data_key, NULL); data->detacher (data->attach_widget, menu); @@ -518,7 +599,7 @@ gtk_menu_detach (GtkMenu *menu) /* Fallback title for menu comes from attach widget */ gtk_menu_update_title (menu); - gtk_widget_unref (GTK_WIDGET (menu)); + g_object_unref (menu); } static void @@ -535,7 +616,7 @@ gtk_menu_remove (GtkContainer *container, */ if (menu->old_active_menu_item == widget) { - gtk_widget_unref (menu->old_active_menu_item); + g_object_unref (menu->old_active_menu_item); menu->old_active_menu_item = NULL; } @@ -546,7 +627,7 @@ gtk_menu_remove (GtkContainer *container, GtkWidget* gtk_menu_new (void) { - return GTK_WIDGET (gtk_type_new (gtk_menu_get_type ())); + return g_object_new (GTK_TYPE_MENU, NULL); } static void @@ -556,7 +637,7 @@ gtk_menu_real_insert (GtkMenuShell *menu_shell, { if (GTK_WIDGET_REALIZED (menu_shell)) gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window); - + GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position); } @@ -581,24 +662,24 @@ gtk_menu_tearoff_bg_copy (GtkMenu *menu) gc = gdk_gc_new_with_values (widget->window, &gc_values, GDK_GC_SUBWINDOW); - gdk_window_get_size (menu->tearoff_window->window, &width, &height); + gdk_drawable_get_size (menu->tearoff_window->window, &width, &height); pixmap = gdk_pixmap_new (menu->tearoff_window->window, width, height, -1); - gdk_draw_pixmap (pixmap, gc, - menu->tearoff_window->window, - 0, 0, 0, 0, -1, -1); - gdk_gc_unref (gc); + gdk_draw_drawable (pixmap, gc, + menu->tearoff_window->window, + 0, 0, 0, 0, -1, -1); + g_object_unref (gc); - gtk_widget_set_usize (menu->tearoff_window, - width, - height); + gtk_widget_set_size_request (menu->tearoff_window, + width, + height); gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE); - gdk_pixmap_unref (pixmap); + g_object_unref (pixmap); } } @@ -617,7 +698,8 @@ popup_grab_on_window (GdkWindow *window, return TRUE; else { - gdk_pointer_ungrab (activate_time); + gdk_display_pointer_ungrab (gdk_drawable_get_display (window), + activate_time); return FALSE; } } @@ -625,6 +707,32 @@ popup_grab_on_window (GdkWindow *window, return FALSE; } +/** + * gtk_menu_popup: + * @menu: a #GtkMenu. + * @parent_menu_shell: the menu shell containing the triggering menu item, or %NULL + * @parent_menu_item: the menu item whose activation triggered the popup, or %NULL + * @func: a user supplied function used to position the menu, or %NULL + * @data: user supplied data to be passed to @func. + * @button: the mouse button which was pressed to initiate the event. + * @activate_time: the time at which the activation event occurred. + * + * Displays a menu and makes it available for selection. Applications can use + * this function to display context-sensitive menus, and will typically supply + * %NULL for the @parent_menu_shell, @parent_menu_item, @func and @data + * parameters. The default menu positioning function will position the menu + * at the current mouse cursor position. + * + * The @button parameter should be the mouse button pressed to initiate + * the menu popup. If the menu popup was initiated by something other than + * a mouse button press, such as a mouse button release or a keypress, + * @button should be 0. + * + * The @activate_time parameter should be the time stamp of the event that + * initiated the popup. If such an event is not available, use + * gtk_get_current_event_time() instead. + * + */ void gtk_menu_popup (GtkMenu *menu, GtkWidget *parent_menu_shell, @@ -639,6 +747,7 @@ gtk_menu_popup (GtkMenu *menu, GtkWidget *parent; GdkEvent *current_event; GtkMenuShell *menu_shell; + GtkMenuAttachData *attach_data; g_return_if_fail (GTK_IS_MENU (menu)); @@ -646,6 +755,21 @@ gtk_menu_popup (GtkMenu *menu, menu_shell = GTK_MENU_SHELL (menu); menu_shell->parent_menu_shell = parent_menu_shell; + + if (!g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen")) + { + /* The screen was not set explicitly, if the menu is + * attached to a widget, try to get screen from its + * toplevel window else go with the default + */ + attach_data = g_object_get_data (G_OBJECT (menu), attach_data_key); + if (attach_data) + { + if (!GTK_WIDGET_REALIZED (menu)) + gtk_window_set_screen (GTK_WINDOW (menu->toplevel), + gtk_widget_get_screen (attach_data->attach_widget)); + } + } /* Find the last viewable ancestor, and make an X grab on it */ @@ -709,6 +833,7 @@ gtk_menu_popup (GtkMenu *menu, * try again. */ menu_shell->parent_menu_shell = NULL; + menu_grab_transfer_window_destroy (menu); return; } @@ -748,8 +873,12 @@ gtk_menu_popup (GtkMenu *menu, */ gtk_widget_show (GTK_WIDGET (menu)); + /* Position the menu, possibly changing the size request + */ + gtk_menu_position (menu); + /* Compute the size of the toplevel and realize it so we - * can position and scroll correctly. + * can scroll correctly. */ { GtkRequisition tmp_request; @@ -765,8 +894,6 @@ gtk_menu_popup (GtkMenu *menu, gtk_widget_realize (GTK_WIDGET (menu)); } - gtk_menu_position (menu); - gtk_menu_scroll_to (menu, menu->scroll_offset); /* Once everything is set up correctly, map the toplevel window on @@ -783,16 +910,20 @@ gtk_menu_popup (GtkMenu *menu, void gtk_menu_popdown (GtkMenu *menu) { + GtkMenuPrivate *private; GtkMenuShell *menu_shell; g_return_if_fail (GTK_IS_MENU (menu)); menu_shell = GTK_MENU_SHELL (menu); + private = gtk_menu_get_private (menu); menu_shell->parent_menu_shell = NULL; menu_shell->active = FALSE; menu_shell->ignore_enter = FALSE; + private->have_position = FALSE; + gtk_menu_stop_scrolling (menu); gtk_menu_stop_navigating_submenu (menu); @@ -800,9 +931,9 @@ gtk_menu_popdown (GtkMenu *menu) if (menu_shell->active_menu_item) { if (menu->old_active_menu_item) - gtk_widget_unref (menu->old_active_menu_item); + g_object_unref (menu->old_active_menu_item); menu->old_active_menu_item = menu_shell->active_menu_item; - gtk_widget_ref (menu->old_active_menu_item); + g_object_ref (menu->old_active_menu_item); } gtk_menu_shell_deselect (menu_shell); @@ -813,11 +944,7 @@ gtk_menu_popdown (GtkMenu *menu) if (menu->torn_off) { - gint width, height; - gdk_window_get_size (menu->tearoff_window->window, &width, &height); - gtk_widget_set_usize (menu->tearoff_window, - -1, - height); + gtk_widget_set_size_request (menu->tearoff_window, -1, -1); if (GTK_BIN (menu->toplevel)->child) { @@ -830,8 +957,10 @@ gtk_menu_popdown (GtkMenu *menu) */ if (menu_shell->have_xgrab) { - gdk_pointer_ungrab (GDK_CURRENT_TIME); - gdk_keyboard_ungrab (GDK_CURRENT_TIME); + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (menu)); + + gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME); + gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); } } @@ -877,7 +1006,7 @@ gtk_menu_get_active (GtkMenu *menu) menu->old_active_menu_item = child; if (menu->old_active_menu_item) - gtk_widget_ref (menu->old_active_menu_item); + g_object_ref (menu->old_active_menu_item); } return menu->old_active_menu_item; @@ -899,9 +1028,9 @@ gtk_menu_set_active (GtkMenu *menu, if (GTK_BIN (child)->child) { if (menu->old_active_menu_item) - gtk_widget_unref (menu->old_active_menu_item); + g_object_unref (menu->old_active_menu_item); menu->old_active_menu_item = child; - gtk_widget_ref (menu->old_active_menu_item); + g_object_ref (menu->old_active_menu_item); } } } @@ -915,10 +1044,10 @@ gtk_menu_set_accel_group (GtkMenu *menu, if (menu->accel_group != accel_group) { if (menu->accel_group) - gtk_accel_group_unref (menu->accel_group); + g_object_unref (menu->accel_group); menu->accel_group = accel_group; if (menu->accel_group) - gtk_accel_group_ref (menu->accel_group); + g_object_ref (menu->accel_group); _gtk_menu_refresh_accel_paths (menu, TRUE); } } @@ -1041,6 +1170,7 @@ gtk_menu_set_tearoff_hints (GtkMenu *menu, geometry_hints.min_height = 0; geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height; + gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window), NULL, &geometry_hints, @@ -1092,35 +1222,28 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, if (!menu->tearoff_window) { - menu->tearoff_window = g_object_connect (gtk_widget_new (GTK_TYPE_WINDOW, - "type", GTK_WINDOW_TOPLEVEL, - NULL), - "signal::destroy", gtk_widget_destroyed, &menu->tearoff_window, - NULL); + menu->tearoff_window = gtk_widget_new (GTK_TYPE_WINDOW, + "type", GTK_WINDOW_TOPLEVEL, + "screen", gtk_widget_get_screen (menu->toplevel), + "app_paintable", TRUE, + NULL); + gtk_window_set_type_hint (GTK_WINDOW (menu->tearoff_window), GDK_WINDOW_TYPE_HINT_MENU); gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->tearoff_window), 0); - gtk_widget_set_app_paintable (menu->tearoff_window, TRUE); - gtk_signal_connect (GTK_OBJECT (menu->tearoff_window), - "event", - GTK_SIGNAL_FUNC (gtk_menu_window_event), - GTK_OBJECT (menu)); + g_signal_connect (menu->tearoff_window, "destroy", + G_CALLBACK (gtk_widget_destroyed), &menu->tearoff_window); + g_signal_connect (menu->tearoff_window, "event", + G_CALLBACK (gtk_menu_window_event), menu); gtk_menu_update_title (menu); gtk_widget_realize (menu->tearoff_window); - gdk_window_set_decorations (menu->tearoff_window->window, - GDK_DECOR_ALL | - GDK_DECOR_RESIZEH | - GDK_DECOR_MINIMIZE | - GDK_DECOR_MAXIMIZE); - gtk_window_set_resizable (GTK_WINDOW (menu->tearoff_window), FALSE); - menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE); gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox); - gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height); + gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); menu->tearoff_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, @@ -1128,7 +1251,7 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, MENU_SCROLL_STEP, height/2, height)); - g_object_connect (GTK_OBJECT (menu->tearoff_adjustment), + g_object_connect (menu->tearoff_adjustment, "signal::value_changed", gtk_menu_scrollbar_changed, menu, NULL); menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment); @@ -1145,7 +1268,7 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE); - gdk_window_get_size (GTK_WIDGET (menu)->window, &width, NULL); + gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, NULL); /* Update menu->requisition */ @@ -1218,14 +1341,15 @@ gtk_menu_set_title (GtkMenu *menu, * Returns the title of the menu. See gtk_menu_set_title(). * * Return value: the title of the menu, or %NULL if the menu has no - * title set on it. + * title set on it. This string is owned by the widget and should + * not be modified or freed. **/ G_CONST_RETURN gchar * gtk_menu_get_title (GtkMenu *menu) { g_return_val_if_fail (GTK_IS_MENU (menu), NULL); - return gtk_object_get_data (GTK_OBJECT (menu), "gtk-menu-title"); + return g_object_get_data (G_OBJECT (menu), "gtk-menu-title"); } void @@ -1246,6 +1370,20 @@ gtk_menu_reorder_child (GtkMenu *menu, } } +static void +gtk_menu_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + if (GTK_WIDGET_REALIZED (widget)) + { + GtkMenu *menu = GTK_MENU (widget); + + gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL); + gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + } +} + static void gtk_menu_realize (GtkWidget *widget) { @@ -1326,6 +1464,16 @@ gtk_menu_realize (GtkWidget *widget) gdk_window_show (menu->view_window); } +static gboolean +gtk_menu_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + /* + * A menu or its menu items cannot have focus + */ + return FALSE; +} + /* See notes in gtk_menu_popup() for information about the "grab transfer window" */ static GdkWindow * @@ -1348,7 +1496,8 @@ menu_grab_transfer_window_get (GtkMenu *menu) attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR; - window = gdk_window_new (NULL, &attributes, attributes_mask); + window = gdk_window_new (gtk_widget_get_root_window (GTK_WIDGET (menu)), + &attributes, attributes_mask); gdk_window_set_user_data (window, menu); gdk_window_show (window); @@ -1453,11 +1602,9 @@ gtk_menu_size_request (GtkWidget *widget, menu->toggle_size = max_toggle_size; - /* If the requested width was different than the allocated width, we need to change - * the geometry hints for the tear off window so that the window can actually be resized. - * Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap). + /* Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap). */ - if ((requisition->width != GTK_WIDGET (menu)->allocation.width) && menu->tearoff_active) + if (menu->tearoff_active) gtk_menu_set_tearoff_hints (menu, requisition->width); } @@ -1486,6 +1633,9 @@ gtk_menu_size_allocate (GtkWidget *widget, width = MAX (1, allocation->width - x * 2); height = MAX (1, allocation->height - y * 2); + + if (menu_shell->active) + gtk_menu_scroll_to (menu, menu->scroll_offset); if (menu->upper_arrow_visible && !menu->tearoff_active) { @@ -1554,7 +1704,6 @@ gtk_menu_size_allocate (GtkWidget *widget, { gtk_widget_hide (menu->tearoff_scrollbar); gtk_menu_set_tearoff_hints (menu, allocation->width); - gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height); gtk_menu_scroll_to (menu, 0); } @@ -1580,7 +1729,6 @@ gtk_menu_size_allocate (GtkWidget *widget, { gtk_widget_show (menu->tearoff_scrollbar); gtk_menu_set_tearoff_hints (menu, allocation->width); - gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height); } } } @@ -1601,7 +1749,7 @@ gtk_menu_paint (GtkWidget *widget, border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness; border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness; - gdk_window_get_size (widget->window, &width, &height); + gdk_drawable_get_size (widget->window, &width, &height); if (event->window == widget->window) { @@ -1665,35 +1813,6 @@ gtk_menu_paint (GtkWidget *widget, MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2); } } - else if (event->window == menu->view_window) - { - gint menu_height; - gint top_pos; - - if (menu->scroll_offset < 0) - gtk_paint_box (widget->style, - menu->view_window, - GTK_STATE_ACTIVE, - GTK_SHADOW_IN, - NULL, widget, "menu", - 0, 0, - -1, - -menu->scroll_offset); - - menu_height = widget->requisition.height - 2*border_y; - top_pos = height - 2*border_y - (menu->upper_arrow_visible ? MENU_SCROLL_ARROW_HEIGHT : 0); - - if (menu_height - menu->scroll_offset < top_pos) - gtk_paint_box (widget->style, - menu->view_window, - GTK_STATE_ACTIVE, - GTK_SHADOW_IN, - NULL, widget, "menu", - 0, - menu_height - menu->scroll_offset, - -1, - top_pos - (menu_height - menu->scroll_offset)); - } } static gboolean @@ -1734,6 +1853,7 @@ gtk_menu_key_press (GtkWidget *widget, gchar *accel = NULL; guint accel_key, accel_mods; GdkModifierType consumed_modifiers; + GdkDisplay *display; g_return_val_if_fail (GTK_IS_MENU (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); @@ -1745,10 +1865,12 @@ gtk_menu_key_press (GtkWidget *widget, if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event)) return TRUE; + + display = gtk_widget_get_display (widget); - g_object_get (G_OBJECT (gtk_settings_get_default ()), - "gtk-menu-bar-accel", - &accel, + g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), + "gtk-menu-bar-accel", &accel, + "gtk-can-change-accels", &can_change_accels, NULL); if (accel) @@ -1769,7 +1891,7 @@ gtk_menu_key_press (GtkWidget *widget, if (event->keyval == keyval && (mods & event->state) == mods) { - gtk_signal_emit_by_name (GTK_OBJECT (menu), "cancel"); + g_signal_emit_by_name (menu, "cancel", 0); } g_free (accel); @@ -1789,12 +1911,8 @@ gtk_menu_key_press (GtkWidget *widget, break; } - g_object_get (G_OBJECT (gtk_settings_get_default ()), - "gtk-can-change-accels", &can_change_accels, - NULL); - /* Figure out what modifiers went into determining the key symbol */ - gdk_keymap_translate_keyboard_state (gdk_keymap_get_default (), + gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display), event->hardware_keycode, event->state, event->group, NULL, NULL, NULL, &consumed_modifiers); @@ -1812,20 +1930,20 @@ gtk_menu_key_press (GtkWidget *widget, menu_shell->active_menu_item && GTK_BIN (menu_shell->active_menu_item)->child && /* no seperators */ GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL && /* no submenus */ - (delete || gtk_accelerator_valid (event->keyval, event->state))) + (delete || gtk_accelerator_valid (accel_key, accel_mods))) { GtkWidget *menu_item = menu_shell->active_menu_item; - gboolean replace_accels = TRUE; + gboolean locked, replace_accels = TRUE; const gchar *path; - path = _gtk_widget_get_accel_path (menu_item); - if (!path) + path = _gtk_widget_get_accel_path (menu_item, &locked); + if (!path || locked) { /* can't change accelerators on menu_items without paths * (basically, those items are accelerator-locked). */ - /* g_print("item has no path, menu prefix: %s\n", menu->accel_path); */ - gdk_beep (); + /* g_print("item has no path or is locked, menu prefix: %s\n", menu->accel_path); */ + gdk_display_beep (display); } else { @@ -1854,7 +1972,7 @@ gtk_menu_key_press (GtkWidget *widget, * locked already */ /* g_print("failed to change\n"); */ - gdk_beep (); + gdk_display_beep (display); } } } @@ -1874,7 +1992,7 @@ gtk_menu_motion_notify (GtkWidget *widget, if (GTK_IS_MENU (widget)) gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE); - + /* We received the event for one of two reasons: * * a) We are the active menu, and did gtk_grab_add() @@ -1909,21 +2027,20 @@ gtk_menu_motion_notify (GtkWidget *widget, menu_shell->ignore_enter = FALSE; - gdk_window_get_size (event->window, &width, &height); + gdk_drawable_get_size (event->window, &width, &height); if (event->x >= 0 && event->x < width && event->y >= 0 && event->y < height) { - GdkEvent send_event; - - memset (&send_event, 0, sizeof (send_event)); - send_event.crossing.type = GDK_ENTER_NOTIFY; - send_event.crossing.window = event->window; - send_event.crossing.time = event->time; - send_event.crossing.send_event = TRUE; - send_event.crossing.x_root = event->x_root; - send_event.crossing.y_root = event->y_root; - send_event.crossing.x = event->x; - send_event.crossing.y = event->y; + GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY); + gboolean result; + + send_event->crossing.window = g_object_ref (event->window); + send_event->crossing.time = event->time; + send_event->crossing.send_event = TRUE; + send_event->crossing.x_root = event->x_root; + send_event->crossing.y_root = event->y_root; + send_event->crossing.x = event->x; + send_event->crossing.y = event->y; /* We send the event to 'widget', the currently active menu, * instead of 'menu', the menu that the pointer is in. This @@ -1931,7 +2048,10 @@ gtk_menu_motion_notify (GtkWidget *widget, * menuitem is a child of the active menu or some parent * menu of the active menu. */ - return gtk_widget_event (widget, &send_event); + result = gtk_widget_event (widget, send_event); + gdk_event_free (send_event); + + return result; } } @@ -1965,7 +2085,7 @@ gtk_menu_scroll_timeout (gpointer data) if ((menu->scroll_offset >= 0) && (offset < 0)) offset = 0; - gdk_window_get_size (widget->window, &view_width, &view_height); + gdk_drawable_get_size (widget->window, &view_width, &view_height); /* Don't scroll past the bottom if we weren't before: */ if (menu->scroll_offset > 0) @@ -1996,7 +2116,7 @@ gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter) menu_shell = GTK_MENU_SHELL (menu); gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL); - gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height); + gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); border = GTK_CONTAINER (menu)->border_width + GTK_WIDGET (menu)->style->ythickness; @@ -2178,15 +2298,15 @@ gtk_menu_stop_navigating_submenu_cb (gpointer user_data) if (child_window) { - GdkEventCrossing send_event; + GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY); + + send_event->crossing.window = g_object_ref (child_window); + send_event->crossing.time = GDK_CURRENT_TIME; /* Bogus */ + send_event->crossing.send_event = TRUE; - memset (&send_event, 0, sizeof (send_event)); - send_event.window = child_window; - send_event.type = GDK_ENTER_NOTIFY; - send_event.time = GDK_CURRENT_TIME; /* Bogus */ - send_event.send_event = TRUE; + GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), (GdkEventCrossing *)send_event); - GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), &send_event); + gdk_event_free (send_event); } } @@ -2213,6 +2333,71 @@ gtk_menu_navigating_submenu (GtkMenu *menu, return FALSE; } +#undef DRAW_STAY_UP_TRIANGLE + +#ifdef DRAW_STAY_UP_TRIANGLE + +static void +draw_stay_up_triangle (GdkWindow *window, + GdkRegion *region) +{ + /* Draw ugly color all over the stay-up triangle */ + GdkColor ugly_color = { 0, 50000, 10000, 10000 }; + GdkGCValues gc_values; + GdkGC *ugly_gc; + GdkRectangle clipbox; + + gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS; + ugly_gc = gdk_gc_new_with_values (window, &gc_values, 0 | GDK_GC_SUBWINDOW); + gdk_gc_set_rgb_fg_color (ugly_gc, &ugly_color); + gdk_gc_set_clip_region (ugly_gc, region); + + gdk_region_get_clipbox (region, &clipbox); + + gdk_draw_rectangle (window, + ugly_gc, + TRUE, + clipbox.x, clipbox.y, + clipbox.width, clipbox.height); + + g_object_unref (G_OBJECT (ugly_gc)); +} +#endif + +static GdkRegion * +flip_region (GdkRegion *region, + gboolean flip_x, + gboolean flip_y) +{ + gint n_rectangles; + GdkRectangle *rectangles; + GdkRectangle clipbox; + GdkRegion *new_region; + gint i; + + new_region = gdk_region_new (); + + gdk_region_get_rectangles (region, &rectangles, &n_rectangles); + gdk_region_get_clipbox (region, &clipbox); + + for (i = 0; i < n_rectangles; ++i) + { + GdkRectangle rect = rectangles[i]; + + if (flip_y) + rect.y -= 2 * (rect.y - clipbox.y) + rect.height; + + if (flip_x) + rect.x -= 2 * (rect.x - clipbox.x) + rect.width; + + gdk_region_union_with_rect (new_region, &rect); + } + + g_free (rectangles); + + return new_region; +} + static void gtk_menu_set_submenu_navigation_region (GtkMenu *menu, GtkMenuItem *menu_item, @@ -2233,56 +2418,80 @@ gtk_menu_set_submenu_navigation_region (GtkMenu *menu, event_widget = gtk_get_event_widget ((GdkEvent*) event); gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top); - gdk_window_get_size (menu_item->submenu->window, &width, &height); + gdk_drawable_get_size (menu_item->submenu->window, &width, &height); + submenu_right = submenu_left + width; submenu_bottom = submenu_top + height; - gdk_window_get_size (event_widget->window, &width, &height); + gdk_drawable_get_size (event_widget->window, &width, &height); if (event->x >= 0 && event->x < width) { - /* Set navigation region */ - /* We fudge/give a little padding in case the user - * ``misses the vertex'' of the triangle/is off by a pixel or two. - */ - if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT) - point[0].x = event->x_root - SUBMENU_NAV_REGION_PADDING; - else - point[0].x = event->x_root + SUBMENU_NAV_REGION_PADDING; + gint popdown_delay; + gboolean flip_y = FALSE; + gboolean flip_x = FALSE; - /* Exiting the top or bottom? */ + gtk_menu_stop_navigating_submenu (menu); + + if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT) + { + /* right */ + point[0].x = event->x_root; + point[1].x = submenu_left; + } + else + { + /* left */ + point[0].x = event->x_root + 1; + point[1].x = 2 * (event->x_root + 1) - submenu_right; + + flip_x = TRUE; + } + if (event->y < 0) - { /* top */ + { + /* top */ point[0].y = event->y_root + 1; - point[1].y = submenu_top; + point[1].y = 2 * (event->y_root + 1) - submenu_top + NAVIGATION_REGION_OVERSHOOT; - if (point[0].y <= point[1].y) + if (point[0].y >= point[1].y - NAVIGATION_REGION_OVERSHOOT) return; + + flip_y = TRUE; } else - { /* bottom */ + { + /* bottom */ point[0].y = event->y_root; - point[1].y = submenu_bottom; + point[1].y = submenu_bottom + NAVIGATION_REGION_OVERSHOOT; - if (point[0].y >= point[1].y) + if (point[0].y >= submenu_bottom) return; } - - /* Submenu is to the left or right? */ - if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT) - point[1].x = submenu_left; /* right */ - else - point[1].x = submenu_right; /* left */ - + point[2].x = point[1].x; point[2].y = point[0].y; - gtk_menu_stop_navigating_submenu (menu); - menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE); - menu->navigation_timeout = gtk_timeout_add (SUBMENU_NAV_HYSTERESIS_TIMEOUT, + if (flip_x || flip_y) + { + GdkRegion *new_region = flip_region (menu->navigation_region, flip_x, flip_y); + gdk_region_destroy (menu->navigation_region); + menu->navigation_region = new_region; + } + + g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (menu))), + "gtk-menu-popdown-delay", &popdown_delay, + NULL); + + menu->navigation_timeout = gtk_timeout_add (popdown_delay, gtk_menu_stop_navigating_submenu_cb, menu); + +#ifdef DRAW_STAY_UP_TRIANGLE + draw_stay_up_triangle (gdk_get_default_root_window(), + menu->navigation_region); +#endif } } @@ -2307,21 +2516,27 @@ gtk_menu_position (GtkMenu *menu) { GtkWidget *widget; GtkRequisition requisition; + GtkMenuPrivate *private; gint x, y; - gint screen_width; - gint screen_height; gint scroll_offset; gint menu_height; gboolean push_in; - + GdkScreen *screen; + GdkRectangle monitor; + gint monitor_num; + g_return_if_fail (GTK_IS_MENU (menu)); widget = GTK_WIDGET (menu); - screen_width = gdk_screen_width (); - screen_height = gdk_screen_height (); + gdk_window_get_pointer (gtk_widget_get_root_window (widget), + &x, &y, NULL); - gdk_window_get_pointer (NULL, &x, &y, NULL); + screen = gtk_widget_get_screen (widget); + monitor_num = gdk_screen_get_monitor_at_point (screen, x, y); + if (monitor_num < 0) + monitor_num = 0; + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); /* We need the requisition to figure out the right place to * popup the menu. In fact, we always need to ask here, since @@ -2336,8 +2551,8 @@ gtk_menu_position (GtkMenu *menu) (* menu->position_func) (menu, &x, &y, &push_in, menu->position_func_data); else { - x = CLAMP (x - 2, 0, MAX (0, screen_width - requisition.width)); - y = CLAMP (y - 2, 0, MAX (0, screen_height - requisition.height)); + x = CLAMP (x - 2, monitor.x, MAX (monitor.x, monitor.x + monitor.width - requisition.width)); + y = CLAMP (y - 2, monitor.y, MAX (monitor.y, monitor.y + monitor.height - requisition.height)); } scroll_offset = 0; @@ -2346,27 +2561,30 @@ gtk_menu_position (GtkMenu *menu) { menu_height = GTK_WIDGET (menu)->requisition.height; - if (y + menu_height > screen_height) + if (y + menu_height > monitor.y + monitor.height) { - scroll_offset -= y + menu_height - screen_height; - y = screen_height - menu_height; + scroll_offset -= y + menu_height - (monitor.y + monitor.height); + y = (monitor.y + monitor.height) - menu_height; } - if (y < 0) + if (y < monitor.y) { scroll_offset -= y; - y = 0; + y = monitor.y; } } - if (y + requisition.height > screen_height) - requisition.height = screen_height - y; + /* FIXME: should this be done in the various position_funcs ? */ + x = CLAMP (x, monitor.x, MAX (monitor.x, monitor.x + monitor.width - requisition.width)); + + if (y + requisition.height > monitor.y + monitor.height) + requisition.height = (monitor.y + monitor.height) - y; - if (y < 0) + if (y < monitor.y) { scroll_offset -= y; requisition.height -= -y; - y = 0; + y = monitor.y; } if (scroll_offset > 0) @@ -2374,9 +2592,22 @@ gtk_menu_position (GtkMenu *menu) gtk_window_move (GTK_WINDOW (GTK_MENU_SHELL (menu)->active ? menu->toplevel : menu->tearoff_window), x, y); - gtk_widget_set_usize (GTK_MENU_SHELL (menu)->active ? - menu->toplevel : menu->tearoff_hbox, - -1, requisition.height); + + if (GTK_MENU_SHELL (menu)->active) + { + private = gtk_menu_get_private (menu); + private->have_position = TRUE; + private->x = x; + private->y = y; + + gtk_widget_queue_resize (menu->toplevel); + } + else + { + gtk_window_resize (GTK_WINDOW (menu->tearoff_window), + requisition.width, requisition.height); + } + menu->scroll_offset = scroll_offset; } @@ -2412,11 +2643,9 @@ gtk_menu_scroll_to (GtkMenu *menu, gtk_adjustment_value_changed (menu->tearoff_adjustment); } - /* Scroll the menu: */ - gdk_window_move (menu->bin_window, 0, -offset); - /* Move/resize the viewport according to arrows: */ - gdk_window_get_size (widget->window, &view_width, &view_height); + view_width = widget->allocation.width; + view_height = widget->allocation.height; border_width = GTK_CONTAINER (menu)->border_width; view_width -= (border_width + widget->style->xthickness) * 2; @@ -2430,7 +2659,7 @@ gtk_menu_scroll_to (GtkMenu *menu, { last_visible = menu->upper_arrow_visible; menu->upper_arrow_visible = (offset > 0); - + if (menu->upper_arrow_visible) view_height -= MENU_SCROLL_ARROW_HEIGHT; @@ -2463,12 +2692,19 @@ gtk_menu_scroll_to (GtkMenu *menu, if (menu->upper_arrow_visible) y += MENU_SCROLL_ARROW_HEIGHT; } - - gdk_window_move_resize (menu->view_window, - x, - y, - view_width, - view_height); + + offset = CLAMP (offset, 0, menu_height - view_height); + + /* Scroll the menu: */ + if (GTK_WIDGET_REALIZED (menu)) + gdk_window_move (menu->bin_window, 0, -offset); + + if (GTK_WIDGET_REALIZED (menu)) + gdk_window_move_resize (menu->view_window, + x, + y, + view_width, + view_height); menu->scroll_offset = offset; } @@ -2520,7 +2756,7 @@ gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell, if (child == menu_item) { y = menu->scroll_offset; - gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height); + gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness; @@ -2614,15 +2850,15 @@ gtk_menu_reparent (GtkMenu *menu, GtkWidget *widget = GTK_WIDGET (menu); gboolean was_floating = GTK_OBJECT_FLOATING (object); - gtk_object_ref (object); + g_object_ref (object); gtk_object_sink (object); if (unrealize) { - gtk_object_ref (object); + g_object_ref (object); gtk_container_remove (GTK_CONTAINER (widget->parent), widget); gtk_container_add (GTK_CONTAINER (new_parent), widget); - gtk_object_unref (object); + g_object_unref (object); } else gtk_widget_reparent (GTK_WIDGET (menu), new_parent); @@ -2630,7 +2866,7 @@ gtk_menu_reparent (GtkMenu *menu, if (was_floating) GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING); else - gtk_object_unref (object); + g_object_unref (object); } static void @@ -2652,4 +2888,33 @@ gtk_menu_hide_all (GtkWidget *widget) gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL); } +/** + * gtk_menu_set_screen: + * @menu: a #GtkMenu. + * @screen: a #GtkScreen. + * + * Sets the #GtkScreen on which the GtkMenu will be displayed. + * This function can only be called before @menu is realized. + **/ +void +gtk_menu_set_screen (GtkMenu *menu, + GdkScreen *screen) +{ + g_return_if_fail (GTK_IS_MENU (menu)); + gtk_window_set_screen (GTK_WINDOW (menu->toplevel), + screen); + g_object_set_data (G_OBJECT (menu), "gtk-menu-explicit-screen", screen); +} + +static gint +gtk_menu_get_popup_delay (GtkMenuShell *menu_shell) +{ + gint popup_delay; + + g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (menu_shell))), + "gtk-menu-popup-delay", &popup_delay, + NULL); + + return popup_delay; +}