X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkmenu.c;h=068f2a338cb91769866edf08d1572741323e8b85;hb=9e661ed0e91a5709afcc46cdeaea4d711a885da7;hp=63bc5e3563abd8f46864f538ad685085168f99c4;hpb=e46eeab2a95bd4041cc32bb624a6c0a80334e84c;p=~andy%2Fgtk diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c index 63bc5e356..068f2a338 100644 --- a/gtk/gtkmenu.c +++ b/gtk/gtkmenu.c @@ -21,21 +21,21 @@ * 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/. + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ -#define GTK_MENU_INTERNALS -#include -#include /* memset */ +#include "config.h" +#include #include "gdk/gdkkeysyms.h" #include "gtkaccellabel.h" #include "gtkaccelmap.h" #include "gtkbindings.h" -#include "gtklabel.h" +#include "gtkcheckmenuitem.h" +#include #include "gtkmain.h" #include "gtkmarshalers.h" #include "gtkmenu.h" -#include "gtkmenuitem.h" +#include "gtkmenuprivate.h" #include "gtktearoffmenuitem.h" #include "gtkwindow.h" #include "gtkhbox.h" @@ -43,29 +43,26 @@ #include "gtksettings.h" #include "gtkprivate.h" #include "gtkintl.h" -#include "gtkalias.h" - -#define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_GET_CLASS (w) - -#define DEFAULT_POPUP_DELAY 225 +#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_STEP1 8 -#define MENU_SCROLL_STEP2 15 -#define MENU_SCROLL_FAST_ZONE 8 -#define MENU_SCROLL_TIMEOUT1 50 -#define MENU_SCROLL_TIMEOUT2 20 +#define MENU_SCROLL_STEP1 8 +#define MENU_SCROLL_STEP2 15 +#define MENU_SCROLL_FAST_ZONE 8 +#define MENU_SCROLL_TIMEOUT1 50 +#define MENU_SCROLL_TIMEOUT2 20 #define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key" #define ATTACHED_MENUS "gtk-attached-menus" typedef struct _GtkMenuAttachData GtkMenuAttachData; typedef struct _GtkMenuPrivate GtkMenuPrivate; +typedef struct _GtkMenuPopdownData GtkMenuPopdownData; struct _GtkMenuAttachData { @@ -75,31 +72,48 @@ struct _GtkMenuAttachData struct _GtkMenuPrivate { - gboolean seen_item_enter; - - gboolean have_position; gint x; gint y; + gboolean initially_pushed_in; + + GDestroyNotify position_func_data_destroy; /* info used for the table */ guint *heights; gint heights_length; + gint requested_height; gint monitor_num; /* Cached layout information */ - gboolean have_layout; gint n_rows; gint n_columns; + guint accel_size; + gchar *title; /* Arrow states */ GtkStateType lower_arrow_state; GtkStateType upper_arrow_state; - gboolean ignore_button_release; - gboolean initially_pushed_in; + /* navigation region */ + int navigation_x; + int navigation_y; + int navigation_width; + int navigation_height; + + guint have_layout : 1; + guint seen_item_enter : 1; + guint have_position : 1; + guint ignore_button_release : 1; + guint no_toggle_size : 1; +}; + +struct _GtkMenuPopdownData +{ + GtkMenu *menu; + GdkDevice *device; }; typedef struct @@ -127,7 +141,8 @@ enum { PROP_ATTACH_WIDGET, PROP_TEAROFF_STATE, PROP_TEAROFF_TITLE, - PROP_MONITOR + PROP_MONITOR, + PROP_RESERVE_TOGGLE_SIZE }; enum { @@ -156,18 +171,14 @@ static void gtk_menu_get_child_property(GtkContainer *container, guint property_id, GValue *value, GParamSpec *pspec); -static void gtk_menu_destroy (GtkObject *object); +static void gtk_menu_destroy (GtkWidget *widget); static void gtk_menu_realize (GtkWidget *widget); static void gtk_menu_unrealize (GtkWidget *widget); -static void gtk_menu_size_request (GtkWidget *widget, - GtkRequisition *requisition); static void gtk_menu_size_allocate (GtkWidget *widget, GtkAllocation *allocation); -static void gtk_menu_paint (GtkWidget *widget, - GdkEventExpose *expose); static void gtk_menu_show (GtkWidget *widget); -static gboolean gtk_menu_expose (GtkWidget *widget, - GdkEventExpose *event); +static gboolean gtk_menu_draw (GtkWidget *widget, + cairo_t *cr); static gboolean gtk_menu_key_press (GtkWidget *widget, GdkEventKey *event); static gboolean gtk_menu_scroll (GtkWidget *widget, @@ -248,7 +259,19 @@ static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget, static void _gtk_menu_refresh_accel_paths (GtkMenu *menu, gboolean group_changed); -static const gchar attach_data_key[] = "gtk-menu-attach-data"; +static void gtk_menu_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_menu_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_menu_get_preferred_height_for_width (GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size); + + +static const gchar attach_data_key[] = "gtk-menu-attach-data"; static guint menu_signals[LAST_SIGNAL] = { 0 }; @@ -414,7 +437,7 @@ get_effective_child_attach (GtkWidget *child, int *t, int *b) { - GtkMenu *menu = GTK_MENU (child->parent); + GtkMenu *menu = GTK_MENU (gtk_widget_get_parent (child)); AttachInfo *ai; menu_ensure_layout (menu); @@ -436,7 +459,6 @@ static void gtk_menu_class_init (GtkMenuClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); - GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); GtkMenuShellClass *menu_shell_class = GTK_MENU_SHELL_CLASS (class); @@ -445,14 +467,12 @@ gtk_menu_class_init (GtkMenuClass *class) gobject_class->set_property = gtk_menu_set_property; gobject_class->get_property = gtk_menu_get_property; - object_class->destroy = gtk_menu_destroy; - + widget_class->destroy = gtk_menu_destroy; widget_class->realize = gtk_menu_realize; widget_class->unrealize = gtk_menu_unrealize; - widget_class->size_request = gtk_menu_size_request; widget_class->size_allocate = gtk_menu_size_allocate; widget_class->show = gtk_menu_show; - widget_class->expose_event = gtk_menu_expose; + widget_class->draw = gtk_menu_draw; widget_class->scroll_event = gtk_menu_scroll; widget_class->key_press_event = gtk_menu_key_press; widget_class->button_press_event = gtk_menu_button_press; @@ -466,6 +486,9 @@ gtk_menu_class_init (GtkMenuClass *class) widget_class->focus = gtk_menu_focus; widget_class->can_activate_accel = gtk_menu_real_can_activate_accel; widget_class->grab_notify = gtk_menu_grab_notify; + widget_class->get_preferred_width = gtk_menu_get_preferred_width; + widget_class->get_preferred_height = gtk_menu_get_preferred_height; + widget_class->get_preferred_height_for_width = gtk_menu_get_preferred_height_for_width; container_class->remove = gtk_menu_remove; container_class->get_child_property = gtk_menu_get_child_property; @@ -479,29 +502,30 @@ gtk_menu_class_init (GtkMenuClass *class) menu_shell_class->move_current = gtk_menu_move_current; menu_signals[MOVE_SCROLL] = - _gtk_binding_signal_new (I_("move_scroll"), - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_CALLBACK (gtk_menu_real_move_scroll), - NULL, NULL, - _gtk_marshal_VOID__ENUM, - G_TYPE_NONE, 1, - GTK_TYPE_SCROLL_TYPE); + g_signal_new_class_handler (I_("move-scroll"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gtk_menu_real_move_scroll), + NULL, NULL, + _gtk_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_SCROLL_TYPE); /** * GtkMenu:active: * - * The currently selected menu item. + * The index of the currently selected menu item, or -1 if no + * menu item is selected. * * Since: 2.14 **/ g_object_class_install_property (gobject_class, PROP_ACTIVE, - g_param_spec_uint ("active", - P_("Active"), - P_("The currently selected menu item"), - 0, G_MAXUINT, 0, - GTK_PARAM_READWRITE)); + g_param_spec_int ("active", + P_("Active"), + P_("The currently selected menu item"), + -1, G_MAXINT, -1, + GTK_PARAM_READWRITE)); /** * GtkMenu:accel-group: @@ -536,16 +560,18 @@ gtk_menu_class_init (GtkMenuClass *class) /** * GtkMenu:attach-widget: * - * The widget the menu is attached to. + * The widget the menu is attached to. Setting this property attaches + * the menu without a #GtkMenuDetachFunc. If you need to use a detacher, + * use gtk_menu_attach_to_widget() directly. * * Since: 2.14 **/ g_object_class_install_property (gobject_class, - PROP_ACCEL_PATH, - g_param_spec_string ("attach-widget", + PROP_ATTACH_WIDGET, + g_param_spec_object ("attach-widget", P_("Attach Widget"), P_("The widget the menu is attached to"), - NULL, + GTK_TYPE_WIDGET, GTK_PARAM_READWRITE)); g_object_class_install_property (gobject_class, @@ -595,6 +621,27 @@ gtk_menu_class_init (GtkMenuClass *class) 1, GTK_PARAM_READABLE)); + /** + * GtkMenu:reserve-toggle-size: + * + * A boolean that indicates whether the menu reserves space for + * toggles and icons, regardless of their actual presence. + * + * This property should only be changed from its default value + * for special-purposes such as tabular menus. Regular menus that + * are connected to a menu bar or context menus should reserve + * toggle space for consistency. + * + * Since: 2.18 + */ + g_object_class_install_property (gobject_class, + PROP_RESERVE_TOGGLE_SIZE, + g_param_spec_boolean ("reserve-toggle-size", + P_("Reserve Toggle Size"), + P_("A boolean that indicates whether the menu reserves space for toggles and icons"), + TRUE, + GTK_PARAM_READWRITE)); + gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("horizontal-padding", P_("Horizontal Padding"), @@ -629,6 +676,20 @@ gtk_menu_class_init (GtkMenuClass *class) TRUE, GTK_PARAM_READABLE)); + /** + * GtkMenu:arrow-placement: + * + * Indicates where scroll arrows should be placed. + * + * Since: 2.16 + **/ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("arrow-placement", + P_("Arrow Placement"), + P_("Indicates where scroll arrows should be placed"), + GTK_TYPE_ARROW_PLACEMENT, + GTK_ARROWS_BOTH, + GTK_PARAM_READABLE)); gtk_container_class_install_child_property (container_class, CHILD_PROP_LEFT_ATTACH, @@ -662,85 +723,99 @@ gtk_menu_class_init (GtkMenuClass *class) -1, INT_MAX, -1, GTK_PARAM_READWRITE)); + /** + * GtkMenu::arrow-scaling + * + * Arbitrary constant to scale down the size of the scroll arrow. + * + * Since: 2.16 + */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_float ("arrow-scaling", + P_("Arrow Scaling"), + P_("Arbitrary constant to scale down the size of the scroll arrow"), + 0.0, 1.0, 0.7, + GTK_PARAM_READABLE)); + binding_set = gtk_binding_set_by_class (class); gtk_binding_entry_add_signal (binding_set, - GDK_Up, 0, - I_("move_current"), 1, + GDK_KEY_Up, 0, + I_("move-current"), 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_PREV); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Up, 0, - "move_current", 1, + GDK_KEY_KP_Up, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_PREV); gtk_binding_entry_add_signal (binding_set, - GDK_Down, 0, - "move_current", 1, + GDK_KEY_Down, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_NEXT); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Down, 0, - "move_current", 1, + GDK_KEY_KP_Down, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_NEXT); gtk_binding_entry_add_signal (binding_set, - GDK_Left, 0, - "move_current", 1, + GDK_KEY_Left, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_PARENT); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Left, 0, - "move_current", 1, + GDK_KEY_KP_Left, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_PARENT); gtk_binding_entry_add_signal (binding_set, - GDK_Right, 0, - "move_current", 1, + GDK_KEY_Right, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_CHILD); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Right, 0, - "move_current", 1, + GDK_KEY_KP_Right, 0, + "move-current", 1, GTK_TYPE_MENU_DIRECTION_TYPE, GTK_MENU_DIR_CHILD); gtk_binding_entry_add_signal (binding_set, - GDK_Home, 0, - "move_scroll", 1, + GDK_KEY_Home, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_START); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Home, 0, - "move_scroll", 1, + GDK_KEY_KP_Home, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_START); gtk_binding_entry_add_signal (binding_set, - GDK_End, 0, - "move_scroll", 1, + GDK_KEY_End, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_END); gtk_binding_entry_add_signal (binding_set, - GDK_KP_End, 0, - "move_scroll", 1, + GDK_KEY_KP_End, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_END); gtk_binding_entry_add_signal (binding_set, - GDK_Page_Up, 0, - "move_scroll", 1, + GDK_KEY_Page_Up, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_UP); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Page_Up, 0, - "move_scroll", 1, + GDK_KEY_KP_Page_Up, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_UP); gtk_binding_entry_add_signal (binding_set, - GDK_Page_Down, 0, - "move_scroll", 1, + GDK_KEY_Page_Down, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_DOWN); gtk_binding_entry_add_signal (binding_set, - GDK_KP_Page_Down, 0, - "move_scroll", 1, + GDK_KEY_KP_Page_Down, 0, + "move-scroll", 1, GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_DOWN); @@ -770,20 +845,18 @@ gtk_menu_class_init (GtkMenuClass *class) } -static void +static void gtk_menu_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { - GtkMenu *menu; - - menu = GTK_MENU (object); - + GtkMenu *menu = GTK_MENU (object); + switch (prop_id) { case PROP_ACTIVE: - gtk_menu_set_active (menu, g_value_get_uint (value)); + gtk_menu_set_active (menu, g_value_get_int (value)); break; case PROP_ACCEL_GROUP: gtk_menu_set_accel_group (menu, g_value_get_object (value)); @@ -792,7 +865,17 @@ gtk_menu_set_property (GObject *object, gtk_menu_set_accel_path (menu, g_value_get_string (value)); break; case PROP_ATTACH_WIDGET: - gtk_menu_attach (menu, g_value_get_object (value), 0, 0, 0, 0); + { + GtkWidget *widget; + + widget = gtk_menu_get_attach_widget (menu); + if (widget) + gtk_menu_detach (menu); + + widget = (GtkWidget*) g_value_get_object (value); + if (widget) + gtk_menu_attach_to_widget (menu, widget, NULL); + } break; case PROP_TEAROFF_STATE: gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value)); @@ -803,26 +886,27 @@ gtk_menu_set_property (GObject *object, case PROP_MONITOR: gtk_menu_set_monitor (menu, g_value_get_int (value)); break; + case PROP_RESERVE_TOGGLE_SIZE: + gtk_menu_set_reserve_toggle_size (menu, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } -static void +static void gtk_menu_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { - GtkMenu *menu; - - menu = GTK_MENU (object); - + GtkMenu *menu = GTK_MENU (object); + switch (prop_id) { case PROP_ACTIVE: - g_value_set_boolean (value, gtk_menu_get_active (menu)); + g_value_set_int (value, g_list_index (GTK_MENU_SHELL (menu)->children, gtk_menu_get_active (menu))); break; case PROP_ACCEL_GROUP: g_value_set_object (value, gtk_menu_get_accel_group (menu)); @@ -842,6 +926,9 @@ gtk_menu_get_property (GObject *object, case PROP_MONITOR: g_value_set_int (value, gtk_menu_get_monitor (menu)); break; + case PROP_RESERVE_TOGGLE_SIZE: + g_value_set_boolean (value, gtk_menu_get_reserve_toggle_size (menu)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -937,28 +1024,6 @@ gtk_menu_window_event (GtkWidget *window, 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) - { - GdkScreen *screen = gtk_widget_get_screen (window); - GdkRectangle monitor; - - gdk_screen_get_monitor_geometry (screen, private->monitor_num, &monitor); - - if (private->y + requisition->height > monitor.y + monitor.height) - requisition->height = monitor.y + monitor.height - private->y; - - if (private->y < monitor.y) - requisition->height -= monitor.y - private->y; - } -} - static void gtk_menu_init (GtkMenu *menu) { @@ -976,7 +1041,6 @@ gtk_menu_init (GtkMenu *menu) "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_resizable (GTK_WINDOW (menu->toplevel), FALSE); @@ -1012,18 +1076,19 @@ gtk_menu_init (GtkMenu *menu) priv->lower_arrow_state = GTK_STATE_NORMAL; priv->have_layout = FALSE; + priv->monitor_num = -1; } static void -gtk_menu_destroy (GtkObject *object) +gtk_menu_destroy (GtkWidget *widget) { - GtkMenu *menu = GTK_MENU (object); + GtkMenu *menu = GTK_MENU (widget); GtkMenuAttachData *data; GtkMenuPrivate *priv; gtk_menu_remove_scroll_timeout (menu); - data = g_object_get_data (G_OBJECT (object), attach_data_key); + data = g_object_get_data (G_OBJECT (widget), attach_data_key); if (data) gtk_menu_detach (menu); @@ -1039,7 +1104,7 @@ gtk_menu_destroy (GtkObject *object) if (menu->needs_destruction_ref_count) { menu->needs_destruction_ref_count = FALSE; - g_object_ref (object); + g_object_ref (widget); } if (menu->accel_group) @@ -1068,7 +1133,14 @@ gtk_menu_destroy (GtkObject *object) priv->title = NULL; } - GTK_OBJECT_CLASS (gtk_menu_parent_class)->destroy (object); + if (priv->position_func_data_destroy) + { + priv->position_func_data_destroy (menu->position_func_data); + menu->position_func_data = NULL; + priv->position_func_data_destroy = NULL; + } + + GTK_WIDGET_CLASS (gtk_menu_parent_class)->destroy (widget); } static void @@ -1132,7 +1204,7 @@ gtk_menu_attach_to_widget (GtkMenu *menu, data = g_slice_new (GtkMenuAttachData); data->attach_widget = attach_widget; - g_signal_connect (attach_widget, "screen_changed", + g_signal_connect (attach_widget, "screen-changed", G_CALLBACK (attach_widget_screen_changed), menu); attach_widget_screen_changed (attach_widget, NULL, menu); @@ -1146,7 +1218,7 @@ gtk_menu_attach_to_widget (GtkMenu *menu, g_object_set_data_full (G_OBJECT (attach_widget), I_(ATTACHED_MENUS), list, (GDestroyNotify) g_list_free); - if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL) + if (gtk_widget_get_state (GTK_WIDGET (menu)) != GTK_STATE_NORMAL) gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL); /* we don't need to set the style here, since @@ -1155,6 +1227,8 @@ gtk_menu_attach_to_widget (GtkMenu *menu, /* Fallback title for menu comes from attach widget */ gtk_menu_update_title (menu); + + g_object_notify (G_OBJECT (menu), "attach-widget"); } GtkWidget* @@ -1202,7 +1276,7 @@ gtk_menu_detach (GtkMenu *menu) else g_object_set_data (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), NULL); - if (GTK_WIDGET_REALIZED (menu)) + if (gtk_widget_get_realized (GTK_WIDGET (menu))) gtk_widget_unrealize (GTK_WIDGET (menu)); g_slice_free (GtkMenuAttachData, data); @@ -1254,7 +1328,7 @@ gtk_menu_real_insert (GtkMenuShell *menu_shell, ai->top_attach = -1; ai->bottom_attach = -1; - if (GTK_WIDGET_REALIZED (menu_shell)) + if (gtk_widget_get_realized (GTK_WIDGET (menu_shell))) gtk_widget_set_parent_window (child, menu->bin_window); GTK_MENU_SHELL_CLASS (gtk_menu_parent_class)->insert (menu_shell, child, position); @@ -1272,79 +1346,94 @@ gtk_menu_tearoff_bg_copy (GtkMenu *menu) if (menu->torn_off) { - GdkPixmap *pixmap; - GdkGC *gc; - GdkGCValues gc_values; + GdkWindow *window; + cairo_surface_t *surface; + cairo_pattern_t *pattern; + cairo_t *cr; menu->tearoff_active = FALSE; menu->saved_scroll_offset = menu->scroll_offset; - - gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS; - gc = gdk_gc_new_with_values (widget->window, - &gc_values, GDK_GC_SUBWINDOW); - - gdk_drawable_get_size (menu->tearoff_window->window, &width, &height); - - pixmap = gdk_pixmap_new (menu->tearoff_window->window, - width, - height, - -1); - gdk_draw_drawable (pixmap, gc, - menu->tearoff_window->window, - 0, 0, 0, 0, -1, -1); - g_object_unref (gc); + window = gtk_widget_get_window (menu->tearoff_window); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); + + surface = gdk_window_create_similar_surface (window, + CAIRO_CONTENT_COLOR, + width, + height); + + cr = cairo_create (surface); + gdk_cairo_set_source_window (cr, + window, + 0, 0); + cairo_paint (cr); + cairo_destroy (cr); gtk_widget_set_size_request (menu->tearoff_window, width, height); - gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE); - g_object_unref (pixmap); + pattern = cairo_pattern_create_for_surface (surface); + gdk_window_set_background_pattern (window, pattern); + + cairo_pattern_destroy (pattern); + cairo_surface_destroy (surface); } } static gboolean popup_grab_on_window (GdkWindow *window, - guint32 activate_time, - gboolean grab_keyboard) + GdkDevice *keyboard, + GdkDevice *pointer, + guint32 activate_time) { - if ((gdk_pointer_grab (window, TRUE, - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | - GDK_POINTER_MOTION_MASK, - NULL, NULL, activate_time) == 0)) + if (keyboard && + gdk_device_grab (keyboard, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL, activate_time) != GDK_GRAB_SUCCESS) + return FALSE; + + if (pointer && + gdk_device_grab (pointer, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK, + NULL, activate_time) != GDK_GRAB_SUCCESS) { - if (!grab_keyboard || - gdk_keyboard_grab (window, TRUE, - activate_time) == 0) - return TRUE; - else - { - gdk_display_pointer_ungrab (gdk_drawable_get_display (window), - activate_time); - return FALSE; - } + if (keyboard) + gdk_device_ungrab (keyboard, activate_time); + + return FALSE; } - return FALSE; + return TRUE; } /** - * gtk_menu_popup: + * gtk_menu_popup_for_device: * @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. + * @device: (allow-none): a #GdkDevice + * @parent_menu_shell: (allow-none): the menu shell containing the triggering + * menu item, or %NULL + * @parent_menu_item: (allow-none): the menu item whose activation triggered + * the popup, or %NULL + * @func: (allow-none): a user supplied function used to position the menu, + * or %NULL + * @data: (allow-none): user supplied data to be passed to @func + * @destroy: (allow-none): destroy notify for @data + * @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. + * 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, @data and @destroy parameters. The default + * menu positioning function will position the menu at the current position + * of @device (or its corresponding pointer). * * The @button parameter should be the mouse button pressed to initiate * the menu popup. If the menu popup was initiated by something other than @@ -1357,15 +1446,19 @@ popup_grab_on_window (GdkWindow *window, * a mouse click or key press) that caused the initiation of the popup. * Only if no such event is available, gtk_get_current_event_time() can * be used instead. + * + * Since: 3.0 */ void -gtk_menu_popup (GtkMenu *menu, - GtkWidget *parent_menu_shell, - GtkWidget *parent_menu_item, - GtkMenuPositionFunc func, - gpointer data, - guint button, - guint32 activate_time) +gtk_menu_popup_for_device (GtkMenu *menu, + GdkDevice *device, + GtkWidget *parent_menu_shell, + GtkWidget *parent_menu_item, + GtkMenuPositionFunc func, + gpointer data, + GDestroyNotify destroy, + guint button, + guint32 activate_time) { GtkWidget *widget; GtkWidget *xgrab_shell; @@ -1375,13 +1468,44 @@ gtk_menu_popup (GtkMenu *menu, gboolean grab_keyboard; GtkMenuPrivate *priv; GtkWidget *parent_toplevel; + GdkDevice *keyboard, *pointer; g_return_if_fail (GTK_IS_MENU (menu)); + g_return_if_fail (device == NULL || GDK_IS_DEVICE (device)); + + if (device == NULL) + device = gtk_get_current_event_device (); + + if (device == NULL) + { + GdkDisplay *display; + GdkDeviceManager *device_manager; + GList *devices; + + display = gtk_widget_get_display (GTK_WIDGET (menu)); + device_manager = gdk_display_get_device_manager (display); + devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER); + + device = devices->data; + + g_list_free (devices); + } widget = GTK_WIDGET (menu); menu_shell = GTK_MENU_SHELL (menu); priv = gtk_menu_get_private (menu); + if (device->source == GDK_SOURCE_KEYBOARD) + { + keyboard = device; + pointer = gdk_device_get_associated_device (device); + } + else + { + pointer = device; + keyboard = gdk_device_get_associated_device (device); + } + menu_shell->parent_menu_shell = parent_menu_shell; priv->seen_item_enter = FALSE; @@ -1397,12 +1521,12 @@ gtk_menu_popup (GtkMenu *menu, while (tmp) { - if (!GTK_WIDGET_MAPPED (tmp)) + if (!gtk_widget_get_mapped (tmp)) { viewable = FALSE; break; } - tmp = tmp->parent; + tmp = gtk_widget_get_parent (tmp); } if (viewable) @@ -1429,10 +1553,16 @@ gtk_menu_popup (GtkMenu *menu, grab_keyboard = gtk_menu_shell_get_take_focus (menu_shell); gtk_window_set_accept_focus (GTK_WINDOW (menu->toplevel), grab_keyboard); + if (!grab_keyboard) + keyboard = NULL; + if (xgrab_shell && xgrab_shell != widget) { - if (popup_grab_on_window (xgrab_shell->window, activate_time, grab_keyboard)) - GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE; + if (popup_grab_on_window (gtk_widget_get_window (xgrab_shell), keyboard, pointer, activate_time)) + { + _gtk_menu_shell_set_grab_device (GTK_MENU_SHELL (xgrab_shell), pointer); + GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE; + } } else { @@ -1440,8 +1570,11 @@ gtk_menu_popup (GtkMenu *menu, xgrab_shell = widget; transfer_window = menu_grab_transfer_window_get (menu); - if (popup_grab_on_window (transfer_window, activate_time, grab_keyboard)) - GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE; + if (popup_grab_on_window (transfer_window, keyboard, pointer, activate_time)) + { + _gtk_menu_shell_set_grab_device (GTK_MENU_SHELL (xgrab_shell), pointer); + GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE; + } } if (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab) @@ -1455,6 +1588,7 @@ gtk_menu_popup (GtkMenu *menu, return; } + _gtk_menu_shell_set_grab_device (GTK_MENU_SHELL (menu), pointer); menu_shell->active = TRUE; menu_shell->button = button; @@ -1500,11 +1634,12 @@ gtk_menu_popup (GtkMenu *menu, menu->parent_menu_item = parent_menu_item; menu->position_func = func; menu->position_func_data = data; + priv->position_func_data_destroy = destroy; menu_shell->activate_time = activate_time; /* We need to show the menu here rather in the init function because * code expects to be able to tell if the menu is onscreen by - * looking at the GTK_WIDGET_VISIBLE (menu) + * looking at the gtk_widget_get_visible (menu) */ gtk_widget_show (GTK_WIDGET (menu)); @@ -1519,7 +1654,10 @@ gtk_menu_popup (GtkMenu *menu, GtkRequisition tmp_request; GtkAllocation tmp_allocation = { 0, }; - gtk_widget_size_request (menu->toplevel, &tmp_request); + /* Instead of trusting the menu position function to queue a resize when the + * menu goes out of bounds, invalidate the cached size here. */ + gtk_widget_queue_resize (GTK_WIDGET (menu)); + gtk_widget_get_preferred_size (menu->toplevel, &tmp_request, NULL); tmp_allocation.width = tmp_request.width; tmp_allocation.height = tmp_request.height; @@ -1550,8 +1688,68 @@ gtk_menu_popup (GtkMenu *menu, gtk_widget_show (menu->toplevel); if (xgrab_shell == widget) - popup_grab_on_window (widget->window, activate_time, grab_keyboard); /* Should always succeed */ - gtk_grab_add (GTK_WIDGET (menu)); + popup_grab_on_window (gtk_widget_get_window (widget), keyboard, pointer, activate_time); /* Should always succeed */ + + gtk_device_grab_add (GTK_WIDGET (menu), pointer, TRUE); + + if (parent_menu_shell) + { + gboolean keyboard_mode; + + keyboard_mode = _gtk_menu_shell_get_keyboard_mode (GTK_MENU_SHELL (parent_menu_shell)); + _gtk_menu_shell_set_keyboard_mode (menu_shell, keyboard_mode); + } + else if (menu_shell->button == 0) /* a keynav-activated context menu */ + _gtk_menu_shell_set_keyboard_mode (menu_shell, TRUE); + + _gtk_menu_shell_update_mnemonics (menu_shell); +} + +/** + * gtk_menu_popup: + * @menu: a #GtkMenu. + * @parent_menu_shell: (allow-none): the menu shell containing the triggering menu item, or %NULL + * @parent_menu_item: (allow-none): the menu item whose activation triggered the popup, or %NULL + * @func: (allow-none): a user supplied function used to position the menu, or %NULL + * @data: (allow-none): 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 is used to conflict-resolve initiation of + * concurrent requests for mouse/keyboard grab requests. To function + * properly, this needs to be the time stamp of the user event (such as + * a mouse click or key press) that caused the initiation of the popup. + * Only if no such event is available, gtk_get_current_event_time() can + * be used instead. + */ +void +gtk_menu_popup (GtkMenu *menu, + GtkWidget *parent_menu_shell, + GtkWidget *parent_menu_item, + GtkMenuPositionFunc func, + gpointer data, + guint button, + guint32 activate_time) +{ + g_return_if_fail (GTK_IS_MENU (menu)); + + gtk_menu_popup_for_device (menu, + NULL, + parent_menu_shell, + parent_menu_item, + func, data, NULL, + button, activate_time); } void @@ -1559,6 +1757,7 @@ gtk_menu_popdown (GtkMenu *menu) { GtkMenuPrivate *private; GtkMenuShell *menu_shell; + GdkDevice *pointer; g_return_if_fail (GTK_IS_MENU (menu)); @@ -1590,26 +1789,31 @@ gtk_menu_popdown (GtkMenu *menu) gtk_widget_hide (menu->toplevel); gtk_window_set_transient_for (GTK_WINDOW (menu->toplevel), NULL); + pointer = _gtk_menu_shell_get_grab_device (menu_shell); + if (menu->torn_off) { gtk_widget_set_size_request (menu->tearoff_window, -1, -1); - if (GTK_BIN (menu->toplevel)->child) + if (gtk_bin_get_child (GTK_BIN (menu->toplevel))) { gtk_menu_reparent (menu, menu->tearoff_hbox, TRUE); } else { - /* We popped up the menu from the tearoff, so we need to + /* We popped up the menu from the tearoff, so we need to * release the grab - we aren't actually hiding the menu. */ - if (menu_shell->have_xgrab) - { - 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); - } + if (menu_shell->have_xgrab && pointer) + { + GdkDevice *keyboard; + + gdk_device_ungrab (pointer, GDK_CURRENT_TIME); + keyboard = gdk_device_get_associated_device (pointer); + + if (keyboard) + gdk_device_ungrab (keyboard, GDK_CURRENT_TIME); + } } /* gtk_menu_popdown is called each time a menu item is selected from @@ -1624,7 +1828,11 @@ gtk_menu_popdown (GtkMenu *menu) gtk_widget_hide (GTK_WIDGET (menu)); menu_shell->have_xgrab = FALSE; - gtk_grab_remove (GTK_WIDGET (menu)); + + if (pointer) + gtk_device_grab_remove (GTK_WIDGET (menu), pointer); + + _gtk_menu_shell_set_grab_device (menu_shell, NULL); menu_grab_transfer_window_destroy (menu); } @@ -1647,7 +1855,7 @@ gtk_menu_get_active (GtkMenu *menu) child = children->data; children = children->next; - if (GTK_BIN (child)->child) + if (gtk_bin_get_child (GTK_BIN (child))) break; child = NULL; } @@ -1673,7 +1881,7 @@ gtk_menu_set_active (GtkMenu *menu, if (tmp_list) { child = tmp_list->data; - if (GTK_BIN (child)->child) + if (gtk_bin_get_child (GTK_BIN (child))) { if (menu->old_active_menu_item) g_object_unref (menu->old_active_menu_item); @@ -1683,6 +1891,11 @@ gtk_menu_set_active (GtkMenu *menu, } } + +/** + * gtk_menu_set_accel_group: + * @accel_group: (allow-none): + */ void gtk_menu_set_accel_group (GtkMenu *menu, GtkAccelGroup *accel_group) @@ -1723,13 +1936,13 @@ gtk_menu_real_can_activate_accel (GtkWidget *widget, if (awidget) return gtk_widget_can_activate_accel (awidget, signal_id); else - return GTK_WIDGET_IS_SENSITIVE (widget); + return gtk_widget_is_sensitive (widget); } /** * gtk_menu_set_accel_path * @menu: a valid #GtkMenu - * @accel_path: a valid accelerator path + * @accel_path: (allow-none): a valid accelerator path * * Sets an accelerator path for this menu from which accelerator paths * for its immediate children, its menu items, can be constructed. @@ -1771,12 +1984,14 @@ gtk_menu_set_accel_path (GtkMenu *menu, * * Retrieves the accelerator path set on the menu. * + * Returns: the accelerator path set on the menu. + * * Since: 2.14 */ const gchar* -gtk_menu_get_accel_path (GtkMenu *menu) +gtk_menu_get_accel_path (GtkMenu *menu) { - g_return_if_fail (GTK_IS_MENU (menu)); + g_return_val_if_fail (GTK_IS_MENU (menu), NULL); return menu->accel_path; } @@ -1800,11 +2015,11 @@ refresh_accel_paths_foreach (GtkWidget *widget, } static void -_gtk_menu_refresh_accel_paths (GtkMenu *menu, - gboolean group_changed) +_gtk_menu_refresh_accel_paths (GtkMenu *menu, + gboolean group_changed) { g_return_if_fail (GTK_IS_MENU (menu)); - + if (menu->accel_path && menu->accel_group) { AccelPropagation prop; @@ -1822,7 +2037,7 @@ gtk_menu_reposition (GtkMenu *menu) { g_return_if_fail (GTK_IS_MENU (menu)); - if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off) + if (!menu->torn_off && gtk_widget_is_drawable (GTK_WIDGET (menu))) gtk_menu_position (menu); } @@ -1841,21 +2056,27 @@ gtk_menu_set_tearoff_hints (GtkMenu *menu, gint width) { GdkGeometry geometry_hints; - + GtkMenuPrivate *priv; + if (!menu->tearoff_window) return; - if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar)) + priv = gtk_menu_get_private (menu); + + if (gtk_widget_get_visible (menu->tearoff_scrollbar)) { - gtk_widget_size_request (menu->tearoff_scrollbar, NULL); - width += menu->tearoff_scrollbar->requisition.width; + GtkRequisition requisition; + + gtk_widget_get_preferred_size (menu->tearoff_scrollbar, + &requisition, NULL); + width += requisition.width; } geometry_hints.min_width = width; geometry_hints.max_width = width; geometry_hints.min_height = 0; - geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height; + geometry_hints.max_height = priv->requested_height; gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window), NULL, @@ -1877,7 +2098,7 @@ gtk_menu_update_title (GtkMenu *menu) attach_widget = gtk_menu_get_attach_widget (menu); if (GTK_IS_MENU_ITEM (attach_widget)) { - GtkWidget *child = GTK_BIN (attach_widget)->child; + GtkWidget *child = gtk_bin_get_child (GTK_BIN (attach_widget)); if (GTK_IS_LABEL (child)) title = gtk_label_get_text (GTK_LABEL (child)); } @@ -1896,14 +2117,14 @@ gtk_menu_get_toplevel (GtkWidget *menu) attach = gtk_menu_get_attach_widget (GTK_MENU (menu)); if (GTK_IS_MENU_ITEM (attach)) - attach = attach->parent; + attach = gtk_widget_get_parent (attach); if (GTK_IS_MENU (attach)) return gtk_menu_get_toplevel (attach); else if (GTK_IS_WIDGET (attach)) { toplevel = gtk_widget_get_toplevel (attach); - if (GTK_WIDGET_TOPLEVEL (toplevel)) + if (gtk_widget_is_toplevel (toplevel)) return toplevel; } @@ -1921,10 +2142,13 @@ void gtk_menu_set_tearoff_state (GtkMenu *menu, gboolean torn_off) { - gint width, height; + gint height; + GtkMenuPrivate *priv; g_return_if_fail (GTK_IS_MENU (menu)); + priv = gtk_menu_get_private (menu); + if (menu->torn_off != torn_off) { menu->torn_off = torn_off; @@ -1932,7 +2156,7 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, if (menu->torn_off) { - if (GTK_WIDGET_VISIBLE (menu)) + if (gtk_widget_get_visible (GTK_WIDGET (menu))) gtk_menu_popdown (menu); if (!menu->tearoff_window) @@ -1965,16 +2189,14 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE); gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox); - gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); - menu->tearoff_adjustment = - GTK_ADJUSTMENT (gtk_adjustment_new (0, - 0, - GTK_WIDGET (menu)->requisition.height, - MENU_SCROLL_STEP2, - height/2, - height)); + height = gdk_window_get_height (gtk_widget_get_window (GTK_WIDGET (menu))); + menu->tearoff_adjustment = gtk_adjustment_new (0, + 0, priv->requested_height, + MENU_SCROLL_STEP2, + height/2, + height); g_object_connect (menu->tearoff_adjustment, - "signal::value_changed", gtk_menu_scrollbar_changed, menu, + "signal::value-changed", gtk_menu_scrollbar_changed, menu, NULL); menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment); @@ -1990,13 +2212,12 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE); - gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, NULL); - /* Update menu->requisition */ - gtk_widget_size_request (GTK_WIDGET (menu), NULL); - - gtk_menu_set_tearoff_hints (menu, width); + gtk_widget_get_preferred_size (GTK_WIDGET (menu), + NULL, NULL); + + gtk_menu_set_tearoff_hints (menu, gdk_window_get_width (gtk_widget_get_window (GTK_WIDGET (menu)))); gtk_widget_realize (menu->tearoff_window); gtk_menu_position (menu); @@ -2118,27 +2339,50 @@ static void gtk_menu_style_set (GtkWidget *widget, GtkStyle *previous_style) { - if (GTK_WIDGET_REALIZED (widget)) + if (gtk_widget_get_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); + GtkStyle *style; + + style = gtk_widget_get_style (widget); + + gtk_style_set_background (style, menu->bin_window, GTK_STATE_NORMAL); + gtk_style_set_background (style, menu->view_window, GTK_STATE_NORMAL); + gtk_style_set_background (style, gtk_widget_get_window (widget), GTK_STATE_NORMAL); } } static void -get_arrows_border (GtkMenu *menu, GtkBorder *border) +get_arrows_border (GtkMenu *menu, + GtkBorder *border) { guint scroll_arrow_height; + GtkArrowPlacement arrow_placement; gtk_widget_style_get (GTK_WIDGET (menu), "scroll-arrow-vlength", &scroll_arrow_height, + "arrow_placement", &arrow_placement, NULL); - border->top = menu->upper_arrow_visible ? scroll_arrow_height : 0; - border->bottom = menu->lower_arrow_visible ? scroll_arrow_height : 0; + switch (arrow_placement) + { + case GTK_ARROWS_BOTH: + border->top = menu->upper_arrow_visible ? scroll_arrow_height : 0; + border->bottom = menu->lower_arrow_visible ? scroll_arrow_height : 0; + break; + + case GTK_ARROWS_START: + border->top = (menu->upper_arrow_visible || + menu->lower_arrow_visible) ? scroll_arrow_height : 0; + border->bottom = 0; + break; + + case GTK_ARROWS_END: + border->top = 0; + border->bottom = (menu->upper_arrow_visible || + menu->lower_arrow_visible) ? scroll_arrow_height : 0; + break; + } border->left = border->right = 0; } @@ -2146,10 +2390,14 @@ get_arrows_border (GtkMenu *menu, GtkBorder *border) static void gtk_menu_realize (GtkWidget *widget) { + GtkAllocation allocation; + GtkStyle *style; + GdkWindow *window; GdkWindowAttr attributes; gint attributes_mask; gint border_width; GtkMenu *menu; + GtkMenuPrivate *priv; GtkWidget *child; GList *children; guint vertical_padding; @@ -2159,53 +2407,63 @@ gtk_menu_realize (GtkWidget *widget) g_return_if_fail (GTK_IS_MENU (widget)); menu = GTK_MENU (widget); - - GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); - + priv = gtk_menu_get_private (menu); + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + attributes.window_type = GDK_WINDOW_CHILD; - 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.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_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); - - border_width = GTK_CONTAINER (widget)->border_width; + 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); + gdk_window_set_user_data (window, widget); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + style = gtk_widget_get_style (widget); gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, "horizontal-padding", &horizontal_padding, NULL); - attributes.x = border_width + widget->style->xthickness + horizontal_padding; - attributes.y = border_width + widget->style->ythickness + vertical_padding; - attributes.width = MAX (1, widget->allocation.width - attributes.x * 2); - attributes.height = MAX (1, widget->allocation.height - attributes.y * 2); + gtk_widget_get_allocation (widget, &allocation); + + attributes.x = border_width + style->xthickness + horizontal_padding; + attributes.y = border_width + style->ythickness + vertical_padding; + attributes.width = MAX (1, allocation.width - attributes.x * 2); + attributes.height = MAX (1, allocation.height - attributes.y * 2); get_arrows_border (menu, &arrow_border); attributes.y += arrow_border.top; attributes.height -= arrow_border.top; attributes.height -= arrow_border.bottom; - menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask); + menu->view_window = gdk_window_new (window, + &attributes, attributes_mask); gdk_window_set_user_data (menu->view_window, menu); + gtk_widget_get_allocation (widget, &allocation); + attributes.x = 0; attributes.y = 0; - attributes.width = MAX (1, widget->allocation.width - (border_width + widget->style->xthickness + horizontal_padding) * 2); - attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness + vertical_padding) * 2); - - menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask); + attributes.width = MAX (1, allocation.width - (border_width + style->xthickness + horizontal_padding) * 2); + attributes.height = MAX (1, priv->requested_height - (border_width + style->ythickness + vertical_padding) * 2); + + menu->bin_window = gdk_window_new (menu->view_window, + &attributes, attributes_mask); gdk_window_set_user_data (menu->bin_window, menu); children = GTK_MENU_SHELL (menu)->children; @@ -2216,11 +2474,11 @@ gtk_menu_realize (GtkWidget *widget) gtk_widget_set_parent_window (child, menu->bin_window); } - - widget->style = gtk_style_attach (widget->style, widget->window); - 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); + + gtk_widget_style_attach (widget); + gtk_style_set_background (style, menu->bin_window, GTK_STATE_NORMAL); + gtk_style_set_background (style, menu->view_window, GTK_STATE_NORMAL); + gtk_style_set_background (style, window, GTK_STATE_NORMAL); if (GTK_MENU_SHELL (widget)->active_menu_item) gtk_menu_scroll_item_visible (GTK_MENU_SHELL (widget), @@ -2301,100 +2559,84 @@ gtk_menu_unrealize (GtkWidget *widget) gdk_window_destroy (menu->bin_window); menu->bin_window = NULL; - (* GTK_WIDGET_CLASS (gtk_menu_parent_class)->unrealize) (widget); + GTK_WIDGET_CLASS (gtk_menu_parent_class)->unrealize (widget); } -static void -gtk_menu_size_request (GtkWidget *widget, - GtkRequisition *requisition) + +static gint +calculate_line_heights (GtkMenu *menu, + gint for_width, + guint **ret_min_heights, + guint **ret_nat_heights) { - gint i; - GtkMenu *menu; - GtkMenuShell *menu_shell; - GtkWidget *child; - GList *children; - guint max_toggle_size; - guint max_accel_width; - guint vertical_padding; - guint horizontal_padding; - GtkRequisition child_requisition; + GtkMenuShell *menu_shell; GtkMenuPrivate *priv; + GtkWidget *child, *widget; + GList *children; + guint horizontal_padding; + guint border_width; + guint n_columns; + gint n_heights; + guint *min_heights; + guint *nat_heights; + gint avail_width; + + widget = GTK_WIDGET (menu); + menu_shell = GTK_MENU_SHELL (widget); + priv = gtk_menu_get_private (menu); - g_return_if_fail (GTK_IS_MENU (widget)); - g_return_if_fail (requisition != NULL); - - menu = GTK_MENU (widget); - menu_shell = GTK_MENU_SHELL (widget); - priv = gtk_menu_get_private (menu); - - requisition->width = 0; - requisition->height = 0; - - max_toggle_size = 0; - max_accel_width = 0; - - g_free (priv->heights); - priv->heights = g_new0 (guint, gtk_menu_get_n_rows (menu)); - priv->heights_length = gtk_menu_get_n_rows (menu); + min_heights = g_new0 (guint, gtk_menu_get_n_rows (menu)); + nat_heights = g_new0 (guint, gtk_menu_get_n_rows (menu)); + n_heights = gtk_menu_get_n_rows (menu); + n_columns = gtk_menu_get_n_columns (menu); + avail_width = for_width - (2 * menu->toggle_size + priv->accel_size) * n_columns; - children = menu_shell->children; - while (children) + gtk_widget_style_get (GTK_WIDGET (menu), + "horizontal-padding", &horizontal_padding, + NULL); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); + avail_width -= (border_width + horizontal_padding + gtk_widget_get_style (widget)->xthickness) * 2; + + for (children = menu_shell->children; children; children = children->next) { gint part; gint toggle_size; gint l, r, t, b; + gint child_min, child_nat; child = children->data; - children = children->next; - if (! GTK_WIDGET_VISIBLE (child)) + if (! gtk_widget_get_visible (child)) continue; get_effective_child_attach (child, &l, &r, &t, &b); - /* It's important to size_request the child - * before doing the toggle size request, in - * case the toggle size request depends on the size - * request of a child of the child (e.g. for ImageMenuItem) - */ + part = avail_width / (r - l); - GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE; - gtk_widget_size_request (child, &child_requisition); - - gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size); - max_toggle_size = MAX (max_toggle_size, toggle_size); - max_accel_width = MAX (max_accel_width, - GTK_MENU_ITEM (child)->accelerator_width); + gtk_widget_get_preferred_height_for_width (child, part, + &child_min, &child_nat); - part = child_requisition.width / (r - l); - requisition->width = MAX (requisition->width, part); + gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size); + + part = MAX (child_min, toggle_size) / (b - t); + min_heights[t] = MAX (min_heights[t], part); - part = MAX (child_requisition.height, toggle_size) / (b - t); - priv->heights[t] = MAX (priv->heights[t], part); + part = MAX (child_nat, toggle_size) / (b - t); + nat_heights[t] = MAX (nat_heights[t], part); } - for (i = 0; i < gtk_menu_get_n_rows (menu); i++) - requisition->height += priv->heights[i]; - - requisition->width += max_toggle_size + max_accel_width; - requisition->width *= gtk_menu_get_n_columns (menu); - - gtk_widget_style_get (GTK_WIDGET (menu), - "vertical-padding", &vertical_padding, - "horizontal-padding", &horizontal_padding, - NULL); + if (ret_min_heights) + *ret_min_heights = min_heights; + else + g_free (min_heights); - requisition->width += (GTK_CONTAINER (menu)->border_width + horizontal_padding + - widget->style->xthickness) * 2; - requisition->height += (GTK_CONTAINER (menu)->border_width + vertical_padding + - widget->style->ythickness) * 2; + if (ret_nat_heights) + *ret_nat_heights = nat_heights; + else + g_free (nat_heights); - menu->toggle_size = max_toggle_size; - - /* Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap). - */ - if (menu->tearoff_active) - gtk_menu_set_tearoff_hints (menu, requisition->width); + return n_heights; } static void @@ -2405,11 +2647,12 @@ gtk_menu_size_allocate (GtkWidget *widget, GtkMenuShell *menu_shell; GtkWidget *child; GtkAllocation child_allocation; - GtkRequisition child_requisition; GtkMenuPrivate *priv; + GtkStyle *style; GList *children; - gint x, y; + gint x, y, i; gint width, height; + guint border_width; guint vertical_padding; guint horizontal_padding; @@ -2420,23 +2663,34 @@ gtk_menu_size_allocate (GtkWidget *widget, menu_shell = GTK_MENU_SHELL (widget); priv = gtk_menu_get_private (menu); - widget->allocation = *allocation; - gtk_widget_get_child_requisition (GTK_WIDGET (menu), &child_requisition); + gtk_widget_set_allocation (widget, allocation); + + style = gtk_widget_get_style (widget); gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, "horizontal-padding", &horizontal_padding, NULL); + border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); - x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness + horizontal_padding; - y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness + vertical_padding; + g_free (priv->heights); + priv->heights_length = + calculate_line_heights (menu, + allocation->width, + &priv->heights, + NULL); + + /* refresh our cached height request */ + priv->requested_height = (border_width + vertical_padding + style->ythickness) * 2; + for (i = 0; i < priv->heights_length; i++) + priv->requested_height += priv->heights[i]; + + x = border_width + style->xthickness + horizontal_padding; + y = border_width + style->ythickness + vertical_padding; width = MAX (1, allocation->width - x * 2); height = MAX (1, allocation->height - y * 2); - child_requisition.width -= x * 2; - child_requisition.height -= y * 2; - if (menu_shell->active) gtk_menu_scroll_to (menu, menu->scroll_offset); @@ -2450,9 +2704,9 @@ gtk_menu_size_allocate (GtkWidget *widget, height -= arrow_border.bottom; } - if (GTK_WIDGET_REALIZED (widget)) + if (gtk_widget_get_realized (widget)) { - gdk_window_move_resize (widget->window, + gdk_window_move_resize (gtk_widget_get_window (widget), allocation->x, allocation->y, allocation->width, allocation->height); @@ -2473,7 +2727,7 @@ gtk_menu_size_allocate (GtkWidget *widget, child = children->data; children = children->next; - if (GTK_WIDGET_VISIBLE (child)) + if (gtk_widget_get_visible (child)) { gint i; gint l, r, t, b; @@ -2510,7 +2764,7 @@ gtk_menu_size_allocate (GtkWidget *widget, } /* Resize the item window */ - if (GTK_WIDGET_REALIZED (widget)) + if (gtk_widget_get_realized (widget)) { gint i; gint width, height; @@ -2525,9 +2779,9 @@ gtk_menu_size_allocate (GtkWidget *widget, if (menu->tearoff_active) { - if (allocation->height >= widget->requisition.height) + if (height >= priv->requested_height) { - if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar)) + if (gtk_widget_get_visible (menu->tearoff_scrollbar)) { gtk_widget_hide (menu->tearoff_scrollbar); gtk_menu_set_tearoff_hints (menu, allocation->width); @@ -2537,7 +2791,7 @@ gtk_menu_size_allocate (GtkWidget *widget, } else { - menu->tearoff_adjustment->upper = widget->requisition.height; + menu->tearoff_adjustment->upper = priv->requested_height; menu->tearoff_adjustment->page_size = allocation->height; if (menu->tearoff_adjustment->value + menu->tearoff_adjustment->page_size > @@ -2552,7 +2806,7 @@ gtk_menu_size_allocate (GtkWidget *widget, gtk_adjustment_changed (menu->tearoff_adjustment); - if (!GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar)) + if (!gtk_widget_get_visible (menu->tearoff_scrollbar)) { gtk_widget_show (menu->tearoff_scrollbar); gtk_menu_set_tearoff_hints (menu, allocation->width); @@ -2563,166 +2817,397 @@ gtk_menu_size_allocate (GtkWidget *widget, } static void -get_arrows_visible_area (GtkMenu *menu, +get_arrows_visible_area (GtkMenu *menu, GdkRectangle *border, GdkRectangle *upper, GdkRectangle *lower, - gint *arrow_space) + gint *arrow_space) { + GtkArrowPlacement arrow_placement; + GtkStyle *style; GtkWidget *widget = GTK_WIDGET (menu); + guint border_width; guint vertical_padding; guint horizontal_padding; gint scroll_arrow_height; - + + style = gtk_widget_get_style (widget); + gtk_widget_style_get (widget, "vertical-padding", &vertical_padding, "horizontal-padding", &horizontal_padding, "scroll-arrow-vlength", &scroll_arrow_height, + "arrow-placement", &arrow_placement, NULL); - border->x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness + horizontal_padding; - border->y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness + vertical_padding; - gdk_drawable_get_size (widget->window, &border->width, &border->height); + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + border->x = border_width + style->xthickness + horizontal_padding; + border->y = border_width + style->ythickness + vertical_padding; + border->width = gdk_window_get_width (gtk_widget_get_window (widget)); + border->height = gdk_window_get_height (gtk_widget_get_window (widget)); + + switch (arrow_placement) + { + case GTK_ARROWS_BOTH: + upper->x = border->x; + upper->y = border->y; + upper->width = border->width - 2 * border->x; + upper->height = scroll_arrow_height; + + lower->x = border->x; + lower->y = border->height - border->y - scroll_arrow_height; + lower->width = border->width - 2 * border->x; + lower->height = scroll_arrow_height; + break; - upper->x = border->x; - upper->y = border->y; - upper->width = border->width - 2 * border->x; - upper->height = scroll_arrow_height; + case GTK_ARROWS_START: + upper->x = border->x; + upper->y = border->y; + upper->width = (border->width - 2 * border->x) / 2; + upper->height = scroll_arrow_height; - lower->x = border->x; - lower->y = border->height - border->y - scroll_arrow_height; - lower->width = border->width - 2 * border->x; - lower->height = scroll_arrow_height; + lower->x = border->x + upper->width; + lower->y = border->y; + lower->width = (border->width - 2 * border->x) / 2; + lower->height = scroll_arrow_height; + break; - *arrow_space = scroll_arrow_height - 2 * widget->style->ythickness; + case GTK_ARROWS_END: + upper->x = border->x; + upper->y = border->height - border->y - scroll_arrow_height; + upper->width = (border->width - 2 * border->x) / 2; + upper->height = scroll_arrow_height; + + lower->x = border->x + upper->width; + lower->y = border->height - border->y - scroll_arrow_height; + lower->width = (border->width - 2 * border->x) / 2; + lower->height = scroll_arrow_height; + break; + + default: + g_assert_not_reached(); + upper->x = upper->y = upper->width = upper->height = 0; + lower->x = lower->y = lower->width = lower->height = 0; + } + + *arrow_space = scroll_arrow_height - 2 * style->ythickness; } -static void -gtk_menu_paint (GtkWidget *widget, - GdkEventExpose *event) +static gboolean +gtk_menu_draw (GtkWidget *widget, + cairo_t *cr) { GtkMenu *menu; GtkMenuPrivate *priv; + GtkStyle *style; GdkRectangle border; GdkRectangle upper; GdkRectangle lower; + GdkWindow *window; gint arrow_space; - g_return_if_fail (GTK_IS_MENU (widget)); - menu = GTK_MENU (widget); priv = gtk_menu_get_private (menu); + style = gtk_widget_get_style (widget); + window = gtk_widget_get_window (widget); + get_arrows_visible_area (menu, &border, &upper, &lower, &arrow_space); - if (event->window == widget->window) + if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget))) { - gint arrow_size = 0.7 * arrow_space; + gfloat arrow_scaling; + gint arrow_size; + + gtk_widget_style_get (widget, "arrow-scaling", &arrow_scaling, NULL); + arrow_size = arrow_scaling * arrow_space; - gtk_paint_box (widget->style, - widget->window, + gtk_paint_box (style, + cr, GTK_STATE_NORMAL, GTK_SHADOW_OUT, - &event->area, widget, "menu", - 0, 0, -1, -1); + widget, "menu", + 0, 0, + gtk_widget_get_allocated_width (widget), + gtk_widget_get_allocated_height (widget)); if (menu->upper_arrow_visible && !menu->tearoff_active) { - gtk_paint_box (widget->style, - widget->window, + gtk_paint_box (style, + cr, priv->upper_arrow_state, GTK_SHADOW_OUT, - &event->area, widget, "menu_scroll_arrow_up", + widget, "menu_scroll_arrow_up", upper.x, upper.y, upper.width, upper.height); - gtk_paint_arrow (widget->style, - widget->window, + gtk_paint_arrow (style, + cr, priv->upper_arrow_state, GTK_SHADOW_OUT, - &event->area, widget, "menu_scroll_arrow_up", + widget, "menu_scroll_arrow_up", GTK_ARROW_UP, TRUE, upper.x + (upper.width - arrow_size) / 2, - upper.y + widget->style->ythickness + (arrow_space - arrow_size) / 2, + upper.y + style->ythickness + (arrow_space - arrow_size) / 2, + arrow_size, arrow_size); + } + + if (menu->lower_arrow_visible && !menu->tearoff_active) + { + gtk_paint_box (style, + cr, + priv->lower_arrow_state, + GTK_SHADOW_OUT, + widget, "menu_scroll_arrow_down", + lower.x, + lower.y, + lower.width, + lower.height); + + gtk_paint_arrow (style, + cr, + priv->lower_arrow_state, + GTK_SHADOW_OUT, + widget, "menu_scroll_arrow_down", + GTK_ARROW_DOWN, + TRUE, + lower.x + (lower.width - arrow_size) / 2, + lower.y + style->ythickness + (arrow_space - arrow_size) / 2, arrow_size, arrow_size); } + } + + if (gtk_cairo_should_draw_window (cr, menu->bin_window)) + { + gint y = -border.y + menu->scroll_offset; + + cairo_save (cr); + gtk_cairo_transform_to_window (cr, widget, menu->bin_window); + + if (!menu->tearoff_active) + { + GtkBorder arrow_border; + + get_arrows_border (menu, &arrow_border); + y -= arrow_border.top; + } + + gtk_paint_box (style, + cr, + GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + widget, "menu", + - border.x, y, + border.width, border.height); + + cairo_restore (cr); + } + + GTK_WIDGET_CLASS (gtk_menu_parent_class)->draw (widget, cr); + + return FALSE; +} + +static void +gtk_menu_show (GtkWidget *widget) +{ + GtkMenu *menu = GTK_MENU (widget); + + _gtk_menu_refresh_accel_paths (menu, FALSE); + + GTK_WIDGET_CLASS (gtk_menu_parent_class)->show (widget); +} + + +static void +gtk_menu_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkMenu *menu; + GtkMenuShell *menu_shell; + GtkMenuPrivate *priv; + GtkStyle *style; + GtkWidget *child; + GList *children; + guint max_toggle_size; + guint max_accel_width; + guint horizontal_padding; + guint border_width; + gint child_min, child_nat; + gint min_width, nat_width; + + menu = GTK_MENU (widget); + menu_shell = GTK_MENU_SHELL (widget); + priv = gtk_menu_get_private (menu); + + style = gtk_widget_get_style (GTK_WIDGET (widget)); + + min_width = nat_width = 0; + + max_toggle_size = 0; + max_accel_width = 0; + + children = menu_shell->children; + while (children) + { + gint part; + gint toggle_size; + gint l, r, t, b; + + child = children->data; + children = children->next; + + if (! gtk_widget_get_visible (child)) + continue; + + get_effective_child_attach (child, &l, &r, &t, &b); + + /* It's important to size_request the child + * before doing the toggle size request, in + * case the toggle size request depends on the size + * request of a child of the child (e.g. for ImageMenuItem) + */ + + GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE; + gtk_widget_get_preferred_width (child, &child_min, &child_nat); + + gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size); + max_toggle_size = MAX (max_toggle_size, toggle_size); + max_accel_width = MAX (max_accel_width, + GTK_MENU_ITEM (child)->accelerator_width); - if (menu->lower_arrow_visible && !menu->tearoff_active) - { - gtk_paint_box (widget->style, - widget->window, - priv->lower_arrow_state, - GTK_SHADOW_OUT, - &event->area, widget, "menu_scroll_arrow_down", - lower.x, - lower.y, - lower.width, - lower.height); + part = child_min / (r - l); + min_width = MAX (min_width, part); - gtk_paint_arrow (widget->style, - widget->window, - priv->lower_arrow_state, - GTK_SHADOW_OUT, - &event->area, widget, "menu_scroll_arrow_down", - GTK_ARROW_DOWN, - TRUE, - lower.x + (lower.width - arrow_size) / 2, - lower.y + widget->style->ythickness + (arrow_space - arrow_size) / 2, - arrow_size, arrow_size); - } + part = child_nat / (r - l); + nat_width = MAX (nat_width, part); } - else if (event->window == menu->bin_window) - { - gint y = -border.y + menu->scroll_offset; - if (!menu->tearoff_active) - { - GtkBorder arrow_border; + /* If the menu doesn't include any images or check items + * reserve the space so that all menus are consistent. + * We only do this for 'ordinary' menus, not for combobox + * menus or multi-column menus + */ + if (max_toggle_size == 0 && + gtk_menu_get_n_columns (menu) == 1 && + !priv->no_toggle_size) + { + guint toggle_spacing; + guint indicator_size; - get_arrows_border (menu, &arrow_border); - y -= arrow_border.top; - } + gtk_style_get (style, + GTK_TYPE_CHECK_MENU_ITEM, + "toggle-spacing", &toggle_spacing, + "indicator-size", &indicator_size, + NULL); - gtk_paint_box (widget->style, - menu->bin_window, - GTK_STATE_NORMAL, - GTK_SHADOW_OUT, - &event->area, widget, "menu", - - border.x, y, - border.width, border.height); + max_toggle_size = indicator_size + toggle_spacing; } + + min_width += 2 * max_toggle_size + max_accel_width; + min_width *= gtk_menu_get_n_columns (menu); + + nat_width += 2 * max_toggle_size + max_accel_width; + nat_width *= gtk_menu_get_n_columns (menu); + + gtk_widget_style_get (GTK_WIDGET (menu), + "horizontal-padding", &horizontal_padding, + NULL); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); + min_width += (border_width + horizontal_padding + style->xthickness) * 2; + nat_width += (border_width + horizontal_padding + style->xthickness) * 2; + + menu->toggle_size = max_toggle_size; + priv->accel_size = max_accel_width; + + if (minimum_size) + *minimum_size = min_width; + + if (natural_size) + *natural_size = nat_width; + + /* Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap). + */ + if (menu->tearoff_active) + gtk_menu_set_tearoff_hints (menu, min_width); } -static gboolean -gtk_menu_expose (GtkWidget *widget, - GdkEventExpose *event) +static void +gtk_menu_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) { - g_return_val_if_fail (GTK_IS_MENU (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + gint min_width; - if (GTK_WIDGET_DRAWABLE (widget)) - { - gtk_menu_paint (widget, event); - - (* GTK_WIDGET_CLASS (gtk_menu_parent_class)->expose_event) (widget, event); - } - - return FALSE; + /* Menus are height-for-width only, just return the height for the minimum width */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_width, minimum_size, natural_size); } static void -gtk_menu_show (GtkWidget *widget) +gtk_menu_get_preferred_height_for_width (GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) { - GtkMenu *menu = GTK_MENU (widget); + GtkMenu *menu = GTK_MENU (widget); + GtkMenuPrivate *private = gtk_menu_get_private (menu); + guint *min_heights, *nat_heights; + guint vertical_padding, border_width; + gint n_heights, i; + gint min_height, nat_height; - _gtk_menu_refresh_accel_paths (menu, FALSE); + gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, NULL); + border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); - GTK_WIDGET_CLASS (gtk_menu_parent_class)->show (widget); + min_height = nat_height = (border_width + vertical_padding + gtk_widget_get_style (GTK_WIDGET (widget))->ythickness) * 2; + + n_heights = + calculate_line_heights (menu, for_size, &min_heights, &nat_heights); + + for (i = 0; i < n_heights; i++) + { + min_height += min_heights[i]; + nat_height += nat_heights[i]; + } + + if (private->have_position) + { + GdkScreen *screen = gtk_widget_get_screen (menu->toplevel); + GdkRectangle monitor; + + gdk_screen_get_monitor_geometry (screen, private->monitor_num, &monitor); + + if (private->y + min_height > monitor.y + monitor.height) + min_height = monitor.y + monitor.height - private->y; + + if (private->y + nat_height > monitor.y + monitor.height) + nat_height = monitor.y + monitor.height - private->y; + + if (private->y < monitor.y) + { + min_height -= monitor.y - private->y; + nat_height -= monitor.y - private->y; + } + } + + if (minimum_size) + *minimum_size = min_height; + + if (natural_size) + *natural_size = nat_height; + + g_free (min_heights); + g_free (nat_heights); } + + static gboolean gtk_menu_button_scroll (GtkMenu *menu, GdkEventButton *event) @@ -2752,17 +3237,20 @@ pointer_in_menu_window (GtkWidget *widget, gdouble x_root, gdouble y_root) { + GtkAllocation allocation; GtkMenu *menu = GTK_MENU (widget); - if (GTK_WIDGET_MAPPED (menu->toplevel)) + if (gtk_widget_get_mapped (menu->toplevel)) { GtkMenuShell *menu_shell; gint window_x, window_y; - gdk_window_get_position (menu->toplevel->window, &window_x, &window_y); + gdk_window_get_position (gtk_widget_get_window (menu->toplevel), + &window_x, &window_y); - if (x_root >= window_x && x_root < window_x + widget->allocation.width && - y_root >= window_y && y_root < window_y + widget->allocation.height) + gtk_widget_get_allocation (widget, &allocation); + if (x_root >= window_x && x_root < window_x + allocation.width && + y_root >= window_y && y_root < window_y + allocation.height) return TRUE; menu_shell = GTK_MENU_SHELL (widget); @@ -2859,7 +3347,7 @@ get_accel_path (GtkWidget *menu_item, { *locked = TRUE; - label = GTK_BIN (menu_item)->child; + label = gtk_bin_get_child (GTK_BIN (menu_item)); if (GTK_IS_ACCEL_LABEL (label)) { @@ -2870,7 +3358,7 @@ get_accel_path (GtkWidget *menu_item, { accel_group = gtk_accel_group_from_accel_closure (accel_closure); - *locked = accel_group->lock_count > 0; + *locked = gtk_accel_group_get_is_locked (accel_group); } } } @@ -2936,9 +3424,9 @@ gtk_menu_key_press (GtkWidget *widget, switch (event->keyval) { - case GDK_Delete: - case GDK_KP_Delete: - case GDK_BackSpace: + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: delete = TRUE; break; default: @@ -2962,7 +3450,7 @@ gtk_menu_key_press (GtkWidget *widget, /* Modify the accelerators */ if (can_change_accels && menu_shell->active_menu_item && - GTK_BIN (menu_shell->active_menu_item)->child && /* no separators */ + gtk_bin_get_child (GTK_BIN (menu_shell->active_menu_item)) && /* no separators */ GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL && /* no submenus */ (delete || gtk_accelerator_valid (accel_key, accel_mods))) { @@ -3016,8 +3504,10 @@ gtk_menu_key_press (GtkWidget *widget, static gboolean check_threshold (GtkWidget *widget, - int start_x, int start_y, - int x, int y) + gint start_x, + gint start_y, + gint x, + gint y) { #define THRESHOLD 8 @@ -3027,14 +3517,15 @@ check_threshold (GtkWidget *widget, } static gboolean -definitely_within_item (GtkWidget *widget, - int x, - int y) +definitely_within_item (GtkWidget *widget, + gint x, + gint y) { GdkWindow *window = GTK_MENU_ITEM (widget)->event_window; int w, h; - gdk_drawable_get_size (window, &w, &h); + w = gdk_window_get_width (window); + h = gdk_window_get_height (window); return check_threshold (widget, 0, 0, x, y) && @@ -3044,12 +3535,23 @@ definitely_within_item (GtkWidget *widget, } static gboolean -gtk_menu_motion_notify (GtkWidget *widget, - GdkEventMotion *event) +gtk_menu_has_navigation_triangle (GtkMenu *menu) +{ + GtkMenuPrivate *priv; + + priv = gtk_menu_get_private (menu); + + return priv->navigation_height && priv->navigation_width; +} + +static gboolean +gtk_menu_motion_notify (GtkWidget *widget, + GdkEventMotion *event) { GtkWidget *menu_item; GtkMenu *menu; GtkMenuShell *menu_shell; + GtkWidget *parent; gboolean need_enter; @@ -3074,17 +3576,18 @@ gtk_menu_motion_notify (GtkWidget *widget, * which may be different from 'widget'. */ menu_item = gtk_get_event_widget ((GdkEvent*) event); + parent = gtk_widget_get_parent (menu_item); if (!GTK_IS_MENU_ITEM (menu_item) || - !GTK_IS_MENU (menu_item->parent)) + !GTK_IS_MENU (parent)) return FALSE; - menu_shell = GTK_MENU_SHELL (menu_item->parent); + menu_shell = GTK_MENU_SHELL (parent); menu = GTK_MENU (menu_shell); if (definitely_within_item (menu_item, event->x, event->y)) menu_shell->activate_time = 0; - need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter); + need_enter = (gtk_menu_has_navigation_triangle (menu) || menu_shell->ignore_enter); /* Check to see if we are within an active submenu's navigation region */ @@ -3096,7 +3599,10 @@ gtk_menu_motion_notify (GtkWidget *widget, */ if (!_gtk_menu_item_is_selectable (menu_item)) { - gtk_menu_shell_deselect (menu_shell); + /* We really want to deselect, but this gives the menushell code + * a chance to do some bookkeeping about the menuitem. + */ + gtk_menu_shell_select_item (menu_shell, menu_item); return FALSE; } @@ -3105,13 +3611,10 @@ gtk_menu_motion_notify (GtkWidget *widget, /* The menu is now sensitive to enter events on its items, but * was previously sensitive. So we fake an enter event. */ - gint width, height; - menu_shell->ignore_enter = FALSE; - gdk_drawable_get_size (event->window, &width, &height); - if (event->x >= 0 && event->x < width && - event->y >= 0 && event->y < height) + if (event->x >= 0 && event->x < gdk_window_get_width (event->window) && + event->y >= 0 && event->y < gdk_window_get_height (event->window)) { GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY); gboolean result; @@ -3124,6 +3627,7 @@ gtk_menu_motion_notify (GtkWidget *widget, send_event->crossing.x = event->x; send_event->crossing.y = event->y; send_event->crossing.state = event->state; + gdk_event_set_device (send_event, gdk_event_get_device ((GdkEvent *) event)); /* We send the event to 'widget', the currently active menu, * instead of 'menu', the menu that the pointer is in. This @@ -3144,13 +3648,18 @@ gtk_menu_motion_notify (GtkWidget *widget, static gboolean get_double_arrows (GtkMenu *menu) { - GtkMenuPrivate *priv = gtk_menu_get_private (menu); - gboolean double_arrows; + GtkMenuPrivate *priv = gtk_menu_get_private (menu); + gboolean double_arrows; + GtkArrowPlacement arrow_placement; gtk_widget_style_get (GTK_WIDGET (menu), "double-arrows", &double_arrows, + "arrow-placement", &arrow_placement, NULL); + if (arrow_placement != GTK_ARROWS_BOTH) + return TRUE; + return double_arrows || (priv->initially_pushed_in && menu->scroll_offset != 0); } @@ -3159,14 +3668,16 @@ static void gtk_menu_scroll_by (GtkMenu *menu, gint step) { + GtkMenuPrivate *priv; + GtkBorder arrow_border; GtkWidget *widget; gint offset; - gint view_width, view_height; + gint view_height; gboolean double_arrows; - GtkBorder arrow_border; - + widget = GTK_WIDGET (menu); offset = menu->scroll_offset + step; + priv = gtk_menu_get_private (menu); get_arrows_border (menu, &arrow_border); @@ -3185,10 +3696,10 @@ gtk_menu_scroll_by (GtkMenu *menu, if ((menu->scroll_offset >= 0) && (offset < 0)) offset = 0; - gdk_drawable_get_size (widget->window, &view_width, &view_height); + view_height = gdk_window_get_height (gtk_widget_get_window (widget)); if (menu->scroll_offset == 0 && - view_height >= widget->requisition.height) + view_height >= priv->requested_height) return; /* Don't scroll past the bottom if we weren't before: */ @@ -3201,9 +3712,9 @@ gtk_menu_scroll_by (GtkMenu *menu, if (double_arrows) view_height -= arrow_border.bottom; - if ((menu->scroll_offset + view_height <= widget->requisition.height) && - (offset + view_height > widget->requisition.height)) - offset = widget->requisition.height - view_height; + if ((menu->scroll_offset + view_height <= priv->requested_height) && + (offset + view_height > priv->requested_height)) + offset = priv->requested_height - view_height; if (offset != menu->scroll_offset) gtk_menu_scroll_to (menu, offset); @@ -3316,42 +3827,89 @@ gtk_menu_scroll (GtkWidget *widget, } static void -get_arrows_sensitive_area (GtkMenu *menu, +get_arrows_sensitive_area (GtkMenu *menu, GdkRectangle *upper, GdkRectangle *lower) { + GtkArrowPlacement arrow_placement; + GtkWidget *widget = GTK_WIDGET (menu); + GdkWindow *window; gint width, height; - gint border; + guint border; guint vertical_padding; gint win_x, win_y; gint scroll_arrow_height; - gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); + window = gtk_widget_get_window (widget); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); - gtk_widget_style_get (GTK_WIDGET (menu), + gtk_widget_style_get (widget, "vertical-padding", &vertical_padding, "scroll-arrow-vlength", &scroll_arrow_height, + "arrow-placement", &arrow_placement, NULL); - border = GTK_CONTAINER (menu)->border_width + - GTK_WIDGET (menu)->style->ythickness + vertical_padding; + border = gtk_container_get_border_width (GTK_CONTAINER (menu)) + + gtk_widget_get_style (widget)->ythickness + vertical_padding; - gdk_window_get_position (GTK_WIDGET (menu)->window, &win_x, &win_y); + gdk_window_get_position (window, &win_x, &win_y); - if (upper) + switch (arrow_placement) { - upper->x = win_x; - upper->y = win_y; - upper->width = width; - upper->height = scroll_arrow_height + border; - } + case GTK_ARROWS_BOTH: + if (upper) + { + upper->x = win_x; + upper->y = win_y; + upper->width = width; + upper->height = scroll_arrow_height + border; + } - if (lower) - { - lower->x = win_x; - lower->y = win_y + height - border - scroll_arrow_height; - lower->width = width; - lower->height = scroll_arrow_height + border; + if (lower) + { + lower->x = win_x; + lower->y = win_y + height - border - scroll_arrow_height; + lower->width = width; + lower->height = scroll_arrow_height + border; + } + break; + + case GTK_ARROWS_START: + if (upper) + { + upper->x = win_x; + upper->y = win_y; + upper->width = width / 2; + upper->height = scroll_arrow_height + border; + } + + if (lower) + { + lower->x = win_x + width / 2; + lower->y = win_y; + lower->width = width / 2; + lower->height = scroll_arrow_height + border; + } + break; + + case GTK_ARROWS_END: + if (upper) + { + upper->x = win_x; + upper->y = win_y + height - border - scroll_arrow_height; + upper->width = width / 2; + upper->height = scroll_arrow_height + border; + } + + if (lower) + { + lower->x = win_x + width / 2; + lower->y = win_y + height - border - scroll_arrow_height; + lower->width = width / 2; + lower->height = scroll_arrow_height + border; + } + break; } } @@ -3379,7 +3937,8 @@ gtk_menu_handle_scrolling (GtkMenu *menu, "gtk-touchscreen-mode", &touchscreen_mode, NULL); - gdk_window_get_position (menu->toplevel->window, &top_x, &top_y); + gdk_window_get_position (gtk_widget_get_window (menu->toplevel), + &top_x, &top_y); x -= top_x; y -= top_y; @@ -3485,7 +4044,7 @@ gtk_menu_handle_scrolling (GtkMenu *menu, { priv->upper_arrow_state = arrow_state; - gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, + gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (menu)), &rect, FALSE); } } @@ -3593,7 +4152,7 @@ gtk_menu_handle_scrolling (GtkMenu *menu, { priv->lower_arrow_state = arrow_state; - gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, + gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (menu)), &rect, FALSE); } } @@ -3605,8 +4164,14 @@ gtk_menu_enter_notify (GtkWidget *widget, GdkEventCrossing *event) { GtkWidget *menu_item; + GtkWidget *parent; gboolean touchscreen_mode; + if (event->mode == GDK_CROSSING_GTK_GRAB || + event->mode == GDK_CROSSING_GTK_UNGRAB || + event->mode == GDK_CROSSING_STATE_CHANGED) + return TRUE; + g_object_get (gtk_widget_get_settings (widget), "gtk-touchscreen-mode", &touchscreen_mode, NULL); @@ -3623,7 +4188,7 @@ gtk_menu_enter_notify (GtkWidget *widget, if (!touchscreen_mode && GTK_IS_MENU_ITEM (menu_item)) { - GtkWidget *menu = menu_item->parent; + GtkWidget *menu = gtk_widget_get_parent (menu_item); if (GTK_IS_MENU (menu)) { @@ -3661,8 +4226,9 @@ gtk_menu_enter_notify (GtkWidget *widget, * will not correspond to the event widget's parent. Check to see * if we are in the parent's navigation region. */ - if (GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) && - gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), + parent = gtk_widget_get_parent (menu_item); + if (GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (parent) && + gtk_menu_navigating_submenu (GTK_MENU (parent), event->x_root, event->y_root)) return TRUE; @@ -3678,6 +4244,11 @@ gtk_menu_leave_notify (GtkWidget *widget, GtkMenuItem *menu_item; GtkWidget *event_widget; + if (event->mode == GDK_CROSSING_GTK_GRAB || + event->mode == GDK_CROSSING_GTK_UNGRAB || + event->mode == GDK_CROSSING_STATE_CHANGED) + return TRUE; + menu = GTK_MENU (widget); menu_shell = GTK_MENU_SHELL (widget); @@ -3692,7 +4263,7 @@ gtk_menu_leave_notify (GtkWidget *widget, return TRUE; menu_item = GTK_MENU_ITEM (event_widget); - + /* Here we check to see if we're leaving an active menu item with a submenu, * in which case we enter submenu navigation mode. */ @@ -3722,11 +4293,13 @@ gtk_menu_leave_notify (GtkWidget *widget, static void gtk_menu_stop_navigating_submenu (GtkMenu *menu) { - if (menu->navigation_region) - { - gdk_region_destroy (menu->navigation_region); - menu->navigation_region = NULL; - } + GtkMenuPrivate *priv = gtk_menu_get_private (menu); + + priv->navigation_x = 0; + priv->navigation_y = 0; + priv->navigation_width = 0; + priv->navigation_height = 0; + if (menu->navigation_timeout) { g_source_remove (menu->navigation_timeout); @@ -3740,14 +4313,17 @@ gtk_menu_stop_navigating_submenu (GtkMenu *menu) static gboolean gtk_menu_stop_navigating_submenu_cb (gpointer user_data) { - GtkMenu *menu = user_data; + GtkMenuPopdownData *popdown_data = user_data; + GtkMenu *menu = popdown_data->menu; GdkWindow *child_window; gtk_menu_stop_navigating_submenu (menu); - if (GTK_WIDGET_REALIZED (menu)) + if (gtk_widget_get_realized (GTK_WIDGET (menu))) { - child_window = gdk_window_get_pointer (menu->bin_window, NULL, NULL, NULL); + child_window = gdk_window_get_device_position (menu->bin_window, + popdown_data->device, + NULL, NULL, NULL); if (child_window) { @@ -3756,6 +4332,7 @@ gtk_menu_stop_navigating_submenu_cb (gpointer user_data) send_event->crossing.window = g_object_ref (child_window); send_event->crossing.time = GDK_CURRENT_TIME; /* Bogus */ send_event->crossing.send_event = TRUE; + gdk_event_set_device (send_event, popdown_data->device); GTK_WIDGET_CLASS (gtk_menu_parent_class)->enter_notify_event (GTK_WIDGET (menu), (GdkEventCrossing *)send_event); @@ -3771,82 +4348,47 @@ gtk_menu_navigating_submenu (GtkMenu *menu, gint event_x, gint event_y) { - if (menu->navigation_region) - { - if (gdk_region_point_in (menu->navigation_region, event_x, event_y)) - return TRUE; - else - { - gtk_menu_stop_navigating_submenu (menu); - return FALSE; - } - } - return FALSE; -} - -#undef DRAW_STAY_UP_TRIANGLE + GtkMenuPrivate *priv; + int width, height; -#ifdef DRAW_STAY_UP_TRIANGLE + if (!gtk_menu_has_navigation_triangle (menu)) + return FALSE; -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 (ugly_gc); -} -#endif + priv = gtk_menu_get_private (menu); + width = priv->navigation_width; + height = priv->navigation_height; -static GdkRegion * -flip_region (GdkRegion *region, - gboolean flip_x, - gboolean flip_y) -{ - gint n_rectangles; - GdkRectangle *rectangles; - GdkRectangle clipbox; - GdkRegion *new_region; - gint i; + /* check if x/y are in the triangle spanned by the navigation parameters */ - new_region = gdk_region_new (); - - gdk_region_get_rectangles (region, &rectangles, &n_rectangles); - gdk_region_get_clipbox (region, &clipbox); + /* 1) Move the coordinates so the triangle starts at 0,0 */ + event_x -= priv->navigation_x; + event_y -= priv->navigation_y; - for (i = 0; i < n_rectangles; ++i) + /* 2) Ensure both legs move along the positive axis */ + if (width < 0) { - 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); + event_x = -event_x; + width = -width; + } + if (height < 0) + { + event_y = -event_y; + height = -height; } - g_free (rectangles); - - return new_region; + /* 3) Check that the given coordinate is inside the triangle. The formula + * is a transformed form of this formula: x/w + y/h <= 1 + */ + if (event_x >= 0 && event_y >= 0 && + event_x * height + event_y * width <= width * height) + { + return TRUE; + } + else + { + gtk_menu_stop_navigating_submenu (menu); + return FALSE; + } } static void @@ -3860,90 +4402,83 @@ gtk_menu_set_submenu_navigation_region (GtkMenu *menu, gint submenu_bottom = 0; gint width = 0; gint height = 0; - GdkPoint point[3]; GtkWidget *event_widget; + GtkMenuPopdownData *popdown_data; + GtkMenuPrivate *priv; + GdkWindow *window; g_return_if_fail (menu_item->submenu != NULL); g_return_if_fail (event != NULL); + priv = gtk_menu_get_private (menu); + event_widget = gtk_get_event_widget ((GdkEvent*) event); - - gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top); - gdk_drawable_get_size (menu_item->submenu->window, &width, &height); - - submenu_right = submenu_left + width; - submenu_bottom = submenu_top + height; - - gdk_drawable_get_size (event_widget->window, &width, &height); - + + window = gtk_widget_get_window (menu_item->submenu); + gdk_window_get_origin (window, &submenu_left, &submenu_top); + + submenu_right = submenu_left + gdk_window_get_width (window); + submenu_bottom = submenu_top + gdk_window_get_height (window); + + width = gdk_window_get_width (gtk_widget_get_window (event_widget)); + height = gdk_window_get_height (gtk_widget_get_window (event_widget)); + if (event->x >= 0 && event->x < width) { gint popdown_delay; - gboolean flip_y = FALSE; - gboolean flip_x = FALSE; gtk_menu_stop_navigating_submenu (menu); + /* The navigation region is the triangle closest to the x/y + * location of the rectangle. This is why the width or height + * can be negative. + */ + if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT) { /* right */ - point[0].x = event->x_root; - point[1].x = submenu_left; + priv->navigation_x = submenu_left; + priv->navigation_width = event->x_root - submenu_left; } else { /* left */ - point[0].x = event->x_root + 1; - point[1].x = 2 * (event->x_root + 1) - submenu_right; - - flip_x = TRUE; + priv->navigation_x = submenu_right; + priv->navigation_width = event->x_root - submenu_right; } if (event->y < 0) { /* top */ - point[0].y = event->y_root + 1; - point[1].y = 2 * (event->y_root + 1) - submenu_top + NAVIGATION_REGION_OVERSHOOT; + priv->navigation_y = event->y_root; + priv->navigation_height = submenu_top - event->y_root - NAVIGATION_REGION_OVERSHOOT; - if (point[0].y >= point[1].y - NAVIGATION_REGION_OVERSHOOT) + if (priv->navigation_height >= 0) return; - - flip_y = TRUE; } else { /* bottom */ - point[0].y = event->y_root; - point[1].y = submenu_bottom + NAVIGATION_REGION_OVERSHOOT; + priv->navigation_y = event->y_root; + priv->navigation_height = submenu_bottom - event->y_root + NAVIGATION_REGION_OVERSHOOT; - if (point[0].y >= submenu_bottom) + if (priv->navigation_height <= 0) return; } - point[2].x = point[1].x; - point[2].y = point[0].y; - - menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE); - - 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 (gtk_widget_get_settings (GTK_WIDGET (menu)), "gtk-menu-popdown-delay", &popdown_delay, NULL); - menu->navigation_timeout = gdk_threads_add_timeout (popdown_delay, - gtk_menu_stop_navigating_submenu_cb, - menu); + popdown_data = g_new (GtkMenuPopdownData, 1); + popdown_data->menu = menu; + popdown_data->device = gdk_event_get_device ((GdkEvent *) event); -#ifdef DRAW_STAY_UP_TRIANGLE - draw_stay_up_triangle (gdk_get_default_root_window(), - menu->navigation_region); -#endif + menu->navigation_timeout = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, + popdown_delay, + gtk_menu_stop_navigating_submenu_cb, + popdown_data, + (GDestroyNotify) g_free); } } @@ -3975,21 +4510,21 @@ gtk_menu_position (GtkMenu *menu) GdkScreen *screen; GdkScreen *pointer_screen; GdkRectangle monitor; - + GdkDevice *pointer; + g_return_if_fail (GTK_IS_MENU (menu)); widget = GTK_WIDGET (menu); screen = gtk_widget_get_screen (widget); - gdk_display_get_pointer (gdk_screen_get_display (screen), - &pointer_screen, &x, &y, NULL); - - /* We need the requisition to figure out the right place to - * popup the menu. In fact, we always need to ask here, since - * if a size_request was queued while we weren't popped up, - * the requisition won't have been recomputed yet. + pointer = _gtk_menu_shell_get_grab_device (GTK_MENU_SHELL (menu)); + gdk_display_get_device_state (gdk_screen_get_display (screen), + pointer, &pointer_screen, &x, &y, NULL); + + /* Get the minimum height for minimum width to figure out + * the right place to popup the menu. */ - gtk_widget_size_request (widget, &requisition); + gtk_widget_get_preferred_size (widget, &requisition, NULL); if (pointer_screen != screen) { @@ -4008,13 +4543,14 @@ gtk_menu_position (GtkMenu *menu) private->initially_pushed_in = FALSE; /* Set the type hint here to allow custom position functions to set a different hint */ - if (!GTK_WIDGET_VISIBLE (menu->toplevel)) + if (!gtk_widget_get_visible (menu->toplevel)) gtk_window_set_type_hint (GTK_WINDOW (menu->toplevel), GDK_WINDOW_TYPE_HINT_POPUP_MENU); if (menu->position_func) { (* menu->position_func) (menu, &x, &y, &private->initially_pushed_in, menu->position_func_data); + if (private->monitor_num < 0) private->monitor_num = gdk_screen_get_monitor_at_point (screen, x, y); @@ -4022,11 +4558,12 @@ gtk_menu_position (GtkMenu *menu) } else { + GtkStyle *style = gtk_widget_get_style (widget); gint space_left, space_right, space_above, space_below; gint needed_width; gint needed_height; - gint xthickness = widget->style->xthickness; - gint ythickness = widget->style->ythickness; + gint xthickness = style->xthickness; + gint ythickness = style->ythickness; gboolean rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); /* The placement of popup menus horizontally works like this (with @@ -4142,7 +4679,7 @@ gtk_menu_position (GtkMenu *menu) if (private->initially_pushed_in) { - menu_height = GTK_WIDGET (menu)->requisition.height; + menu_height = requisition.height; if (y + menu_height > monitor.y + monitor.height) { @@ -4193,7 +4730,7 @@ gtk_menu_position (GtkMenu *menu) gtk_window_resize (GTK_WINDOW (menu->tearoff_window), requisition.width, requisition.height); } - + menu->scroll_offset = scroll_offset; } @@ -4229,6 +4766,10 @@ static void gtk_menu_scroll_to (GtkMenu *menu, gint offset) { + GtkMenuPrivate *priv; + GtkAllocation allocation; + GtkBorder arrow_border; + GtkStyle *style; GtkWidget *widget; gint x, y; gint view_width, view_height; @@ -4237,9 +4778,9 @@ gtk_menu_scroll_to (GtkMenu *menu, guint vertical_padding; guint horizontal_padding; gboolean double_arrows; - GtkBorder arrow_border; - + widget = GTK_WIDGET (menu); + priv = gtk_menu_get_private (menu); if (menu->tearoff_active && menu->tearoff_adjustment && @@ -4252,8 +4793,11 @@ gtk_menu_scroll_to (GtkMenu *menu, } /* Move/resize the viewport according to arrows: */ - view_width = widget->allocation.width; - view_height = widget->allocation.height; + gtk_widget_get_allocation (widget, &allocation); + view_width = allocation.width; + view_height = allocation.height; + + style = gtk_widget_get_style (widget); gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, @@ -4262,14 +4806,14 @@ gtk_menu_scroll_to (GtkMenu *menu, double_arrows = get_double_arrows (menu); - border_width = GTK_CONTAINER (menu)->border_width; - view_width -= (border_width + widget->style->xthickness + horizontal_padding) * 2; - view_height -= (border_width + widget->style->ythickness + vertical_padding) * 2; - menu_height = widget->requisition.height - - (border_width + widget->style->ythickness + vertical_padding) * 2; + border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); - x = border_width + widget->style->xthickness + horizontal_padding; - y = border_width + widget->style->ythickness + vertical_padding; + view_width -= (border_width + style->xthickness + horizontal_padding) * 2; + view_height -= (border_width + style->ythickness + vertical_padding) * 2; + menu_height = priv->requested_height - (border_width + style->ythickness + vertical_padding) * 2; + + x = border_width + style->xthickness + horizontal_padding; + y = border_width + style->ythickness + vertical_padding; if (double_arrows && !menu->tearoff_active) { @@ -4388,10 +4932,10 @@ gtk_menu_scroll_to (GtkMenu *menu, } /* Scroll the menu: */ - if (GTK_WIDGET_REALIZED (menu)) + if (gtk_widget_get_realized (widget)) gdk_window_move (menu->bin_window, 0, -offset); - if (GTK_WIDGET_REALIZED (menu)) + if (gtk_widget_get_realized (widget)) gdk_window_move_resize (menu->view_window, x, y, @@ -4440,17 +4984,16 @@ compute_child_offset (GtkMenu *menu, } static void -gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell, - GtkWidget *menu_item) +gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell, + GtkWidget *menu_item) { - GtkMenu *menu; + GtkMenu *menu = GTK_MENU (menu_shell); + GtkWidget *widget = GTK_WIDGET (menu_shell); gint child_offset, child_height; gint width, height; gint y; gint arrow_height; gboolean last_child = 0; - - menu = GTK_MENU (menu_shell); /* We need to check if the selected item fully visible. * If not we need to scroll the menu so that it becomes fully @@ -4464,16 +5007,18 @@ gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell, gboolean double_arrows; y = menu->scroll_offset; - gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); + width = gdk_window_get_width (gtk_widget_get_window (widget)); + height = gdk_window_get_height (gtk_widget_get_window (widget)); - gtk_widget_style_get (GTK_WIDGET (menu), + gtk_widget_style_get (widget, "vertical-padding", &vertical_padding, NULL); double_arrows = get_double_arrows (menu); - height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness + 2*vertical_padding; - + height -= 2 * gtk_container_get_border_width (GTK_CONTAINER (menu)) + + 2 * gtk_widget_get_style (widget)->ythickness + + 2 * vertical_padding; if (child_offset < y) { /* Ignore the enter event we might get if the pointer is on the menu @@ -4515,12 +5060,12 @@ gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell, } static void -gtk_menu_select_item (GtkMenuShell *menu_shell, - GtkWidget *menu_item) +gtk_menu_select_item (GtkMenuShell *menu_shell, + GtkWidget *menu_item) { GtkMenu *menu = GTK_MENU (menu_shell); - if (GTK_WIDGET_REALIZED (GTK_WIDGET (menu))) + if (gtk_widget_get_realized (GTK_WIDGET (menu))) gtk_menu_scroll_item_visible (menu_shell, menu_item); GTK_MENU_SHELL_CLASS (gtk_menu_parent_class)->select_item (menu_shell, menu_item); @@ -4558,11 +5103,11 @@ gtk_menu_select_item (GtkMenuShell *menu_shell, * (the image of the menu) is left in place. */ static void -gtk_menu_reparent (GtkMenu *menu, - GtkWidget *new_parent, - gboolean unrealize) +gtk_menu_reparent (GtkMenu *menu, + GtkWidget *new_parent, + gboolean unrealize) { - GtkObject *object = GTK_OBJECT (menu); + GObject *object = G_OBJECT (menu); GtkWidget *widget = GTK_WIDGET (menu); gboolean was_floating = g_object_is_floating (object); @@ -4571,15 +5116,15 @@ gtk_menu_reparent (GtkMenu *menu, if (unrealize) { g_object_ref (object); - gtk_container_remove (GTK_CONTAINER (widget->parent), widget); + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)), widget); gtk_container_add (GTK_CONTAINER (new_parent), widget); g_object_unref (object); } else - gtk_widget_reparent (GTK_WIDGET (menu), new_parent); - + gtk_widget_reparent (widget, new_parent); + if (was_floating) - g_object_force_floating (G_OBJECT (object)); + g_object_force_floating (object); else g_object_unref (object); } @@ -4602,11 +5147,11 @@ gtk_menu_hide_all (GtkWidget *widget) /** * gtk_menu_set_screen: * @menu: a #GtkMenu. - * @screen: a #GdkScreen, or %NULL if the screen should be + * @screen: (allow-none): a #GdkScreen, or %NULL if the screen should be * determined by the widget the menu is attached to. * * Sets the #GdkScreen on which the menu will be displayed. - * + * * Since: 2.2 **/ void @@ -4658,17 +5203,18 @@ gtk_menu_attach (GtkMenu *menu, guint bottom_attach) { GtkMenuShell *menu_shell; - + GtkWidget *parent; + g_return_if_fail (GTK_IS_MENU (menu)); g_return_if_fail (GTK_IS_MENU_ITEM (child)); - g_return_if_fail (child->parent == NULL || - child->parent == GTK_WIDGET (menu)); + parent = gtk_widget_get_parent (child); + g_return_if_fail (parent == NULL || parent == GTK_WIDGET (menu)); g_return_if_fail (left_attach < right_attach); g_return_if_fail (top_attach < bottom_attach); menu_shell = GTK_MENU_SHELL (menu); - - if (!child->parent) + + if (!parent) { AttachInfo *ai = get_attach_info (child); @@ -4685,7 +5231,7 @@ gtk_menu_attach (GtkMenu *menu, } else { - gtk_container_child_set (GTK_CONTAINER (child->parent), child, + gtk_container_child_set (GTK_CONTAINER (parent), child, "left-attach", left_attach, "right-attach", right_attach, "top-attach", top_attach, @@ -4737,8 +5283,8 @@ find_child_containing (GtkMenuShell *menu_shell, } static void -gtk_menu_move_current (GtkMenuShell *menu_shell, - GtkMenuDirectionType direction) +gtk_menu_move_current (GtkMenuShell *menu_shell, + GtkMenuDirectionType direction) { GtkMenu *menu = GTK_MENU (menu_shell); gint i; @@ -4851,12 +5397,15 @@ gtk_menu_move_current (GtkMenuShell *menu_shell, static gint get_visible_size (GtkMenu *menu) { + GtkAllocation allocation; GtkWidget *widget = GTK_WIDGET (menu); GtkContainer *container = GTK_CONTAINER (menu); - - gint menu_height = (widget->allocation.height - - 2 * (container->border_width - + widget->style->ythickness)); + gint menu_height; + + gtk_widget_get_allocation (widget, &allocation); + menu_height = (allocation.height + - 2 * (gtk_container_get_border_width (container) + + gtk_widget_get_style (widget)->ythickness)); if (!menu->tearoff_active) { @@ -4890,11 +5439,12 @@ child_at (GtkMenu *menu, for (children = menu_shell->children; children; children = children->next) { - if (GTK_WIDGET_VISIBLE (children->data)) + if (gtk_widget_get_visible (children->data)) { GtkRequisition child_requisition; - gtk_widget_size_request (children->data, &child_requisition); + gtk_widget_get_preferred_size (children->data, + &child_requisition, NULL); if (_gtk_menu_item_is_selectable (children->data) && child_offset >= lower && @@ -4917,11 +5467,14 @@ child_at (GtkMenu *menu, static gint get_menu_height (GtkMenu *menu) { - gint height; + GtkAllocation allocation; GtkWidget *widget = GTK_WIDGET (menu); + gint height; - height = widget->requisition.height; - height -= (GTK_CONTAINER (widget)->border_width + widget->style->ythickness) * 2; + gtk_widget_get_allocation (widget, &allocation); + + height = allocation.height; + height -= (gtk_container_get_border_width (GTK_CONTAINER (widget)) + gtk_widget_get_style (widget)->ythickness) * 2; if (!menu->tearoff_active) { @@ -5052,7 +5605,7 @@ gtk_menu_set_monitor (GtkMenu *menu, * Retrieves the number of the monitor on which to show the menu. * * Returns: the number of the monitor on which the menu should - * be popped up or -1 + * be popped up or -1, if no monitor has been set * * Since: 2.14 **/ @@ -5060,7 +5613,7 @@ gint gtk_menu_get_monitor (GtkMenu *menu) { GtkMenuPrivate *priv; - g_return_if_fail (GTK_IS_MENU (menu)); + g_return_val_if_fail (GTK_IS_MENU (menu), -1); priv = gtk_menu_get_private (menu); @@ -5074,7 +5627,7 @@ gtk_menu_get_monitor (GtkMenu *menu) * Returns a list of the menus which are attached to this widget. * This list is owned by GTK+ and must not be modified. * - * Return value: the list of menus attached to his widget. + * Return value: (element-type GtkWidget) (transfer none): the list of menus attached to his widget. * * Since: 2.6 **/ @@ -5096,17 +5649,68 @@ gtk_menu_grab_notify (GtkWidget *widget, GtkWidget *toplevel; GtkWindowGroup *group; GtkWidget *grab; + GdkDevice *pointer; + + pointer = _gtk_menu_shell_get_grab_device (GTK_MENU_SHELL (widget)); + + if (!pointer || + !gtk_widget_device_is_shadowed (widget, pointer)) + return; toplevel = gtk_widget_get_toplevel (widget); + + if (!GTK_IS_WINDOW (toplevel)) + return; + group = gtk_window_get_group (GTK_WINDOW (toplevel)); - grab = _gtk_window_group_get_current_grab (group); + grab = gtk_window_group_get_current_device_grab (group, pointer); + + if (GTK_MENU_SHELL (widget)->active && !GTK_IS_MENU_SHELL (grab)) + gtk_menu_shell_cancel (GTK_MENU_SHELL (widget)); +} + +/** + * gtk_menu_set_reserve_toggle_size: + * @menu: a #GtkMenu + * @reserve_toggle_size: whether to reserve size for toggles + * + * Sets whether the menu should reserve space for drawing toggles + * or icons, regardless of their actual presence. + * + * Since: 2.18 + */ +void +gtk_menu_set_reserve_toggle_size (GtkMenu *menu, + gboolean reserve_toggle_size) +{ + GtkMenuPrivate *priv = gtk_menu_get_private (menu); + gboolean no_toggle_size; + + no_toggle_size = !reserve_toggle_size; - if (!was_grabbed) + if (priv->no_toggle_size != no_toggle_size) { - if (GTK_MENU_SHELL (widget)->active && !GTK_IS_MENU_SHELL (grab)) - gtk_menu_shell_cancel (GTK_MENU_SHELL (widget)); + priv->no_toggle_size = no_toggle_size; + + g_object_notify (G_OBJECT (menu), "reserve-toggle-size"); } } -#define __GTK_MENU_C__ -#include "gtkaliasdef.c" +/** + * gtk_menu_get_reserve_toggle_size: + * @menu: a #GtkMenu + * + * Returns whether the menu reserves space for toggles and + * icons, regardless of their actual presence. + * + * Returns: Whether the menu reserves toggle space + * + * Since: 2.18 + */ +gboolean +gtk_menu_get_reserve_toggle_size (GtkMenu *menu) +{ + GtkMenuPrivate *priv = gtk_menu_get_private (menu); + + return !priv->no_toggle_size; +}