From 52e5f36dc30fd61d65b2a40622bab44e74884e31 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Wed, 18 Aug 2010 19:42:02 -0400 Subject: [PATCH] Implemented height-for-width geometry management for menus Now GtkMenu/GtkMenuItem request/allocate in height-for-width manner... to reduce the height of the menu one must explicitly set the requested minimum width of the menu to a greater value (using gtk_widget_set_size_request()). --- gtk/gtkmenu.c | 418 ++++++++++++++++++++++++---------- gtk/gtkmenuitem.c | 554 +++++++++++++++++++++++++++++++++------------- 2 files changed, 692 insertions(+), 280 deletions(-) diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c index 39c83f2e8b..03aff5509a 100644 --- a/gtk/gtkmenu.c +++ b/gtk/gtkmenu.c @@ -41,6 +41,7 @@ #include "gtkhbox.h" #include "gtkvscrollbar.h" #include "gtksettings.h" +#include "gtksizerequest.h" #include "gtkprivate.h" #include "gtkintl.h" @@ -79,6 +80,7 @@ struct _GtkMenuPrivate /* info used for the table */ guint *heights; gint heights_length; + gint requested_height; gint monitor_num; @@ -86,6 +88,8 @@ struct _GtkMenuPrivate gint n_rows; gint n_columns; + guint accel_size; + gchar *title; /* Arrow states */ @@ -169,8 +173,6 @@ static void gtk_menu_get_child_property(GtkContainer *container, static void gtk_menu_destroy (GtkObject *object); 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, @@ -258,6 +260,19 @@ static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget, static void _gtk_menu_refresh_accel_paths (GtkMenu *menu, gboolean group_changed); +static void gtk_menu_size_request_init (GtkSizeRequestIface *iface); +static void gtk_menu_get_width (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_menu_get_height (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_menu_get_height_for_width (GtkSizeRequest *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 }; @@ -268,7 +283,9 @@ gtk_menu_get_private (GtkMenu *menu) return G_TYPE_INSTANCE_GET_PRIVATE (menu, GTK_TYPE_MENU, GtkMenuPrivate); } -G_DEFINE_TYPE (GtkMenu, gtk_menu, GTK_TYPE_MENU_SHELL) +G_DEFINE_TYPE_WITH_CODE (GtkMenu, gtk_menu, GTK_TYPE_MENU_SHELL, + G_IMPLEMENT_INTERFACE (GTK_TYPE_SIZE_REQUEST, + gtk_menu_size_request_init)) static void menu_queue_resize (GtkMenu *menu) @@ -459,7 +476,6 @@ gtk_menu_class_init (GtkMenuClass *class) 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; @@ -1625,7 +1641,7 @@ gtk_menu_popup_for_device (GtkMenu *menu, GtkRequisition tmp_request; GtkAllocation tmp_allocation = { 0, }; - gtk_widget_size_request (menu->toplevel, &tmp_request); + gtk_size_request_get_size (GTK_SIZE_REQUEST (menu->toplevel), NULL, &tmp_request); tmp_allocation.width = tmp_request.width; tmp_allocation.height = tmp_request.height; @@ -2042,10 +2058,13 @@ gtk_menu_set_tearoff_hints (GtkMenu *menu, gint width) { GdkGeometry geometry_hints; - + GtkMenuPrivate *priv; + if (!menu->tearoff_window) return; + priv = gtk_menu_get_private (menu); + if (gtk_widget_get_visible (menu->tearoff_scrollbar)) { gtk_widget_size_request (menu->tearoff_scrollbar, NULL); @@ -2056,7 +2075,7 @@ gtk_menu_set_tearoff_hints (GtkMenu *menu, 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, @@ -2122,10 +2141,13 @@ void gtk_menu_set_tearoff_state (GtkMenu *menu, gboolean torn_off) { - gint width, height; + gint width, 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; @@ -2170,7 +2192,7 @@ gtk_menu_set_tearoff_state (GtkMenu *menu, menu->tearoff_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, - GTK_WIDGET (menu)->requisition.height, + priv->requested_height, MENU_SCROLL_STEP2, height/2, height)); @@ -2371,6 +2393,7 @@ gtk_menu_realize (GtkWidget *widget) gint attributes_mask; gint border_width; GtkMenu *menu; + GtkMenuPrivate *priv; GtkWidget *child; GList *children; guint vertical_padding; @@ -2380,7 +2403,8 @@ gtk_menu_realize (GtkWidget *widget) g_return_if_fail (GTK_IS_MENU (widget)); menu = GTK_MENU (widget); - + priv = gtk_menu_get_private (menu); + gtk_widget_set_realized (widget, TRUE); attributes.window_type = GDK_WINDOW_CHILD; @@ -2424,7 +2448,7 @@ gtk_menu_realize (GtkWidget *widget) 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); + attributes.height = MAX (1, priv->requested_height - (border_width + widget->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); @@ -2525,118 +2549,82 @@ gtk_menu_unrealize (GtkWidget *widget) GTK_WIDGET_CLASS (gtk_menu_parent_class)->unrealize (widget); } -static void -gtk_menu_size_request (GtkWidget *widget, - GtkRequisition *requisition) -{ - 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; - guint border_width; - GtkRequisition child_requisition; - GtkMenuPrivate *priv; - - 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); - children = menu_shell->children; - while (children) +static gint +calculate_line_heights (GtkMenu *menu, + gint for_width, + guint **ret_min_heights, + guint **ret_nat_heights) +{ + 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); + + 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; + + 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 + widget->style->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_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_size_request_get_height_for_width (GTK_SIZE_REQUEST (child), part, + &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); + 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 = child_requisition.width / (r - l); - requisition->width = MAX (requisition->width, part); + part = MAX (child_nat, toggle_size) / (b - t); + nat_heights[t] = MAX (nat_heights[t], part); - part = MAX (child_requisition.height, toggle_size) / (b - t); - priv->heights[t] = MAX (priv->heights[t], part); } - /* 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; + if (ret_min_heights) + *ret_min_heights = min_heights; + else + g_free (min_heights); - gtk_style_get (widget->style, - GTK_TYPE_CHECK_MENU_ITEM, - "toggle-spacing", &toggle_spacing, - "indicator-size", &indicator_size, - NULL); - - max_toggle_size = indicator_size + toggle_spacing; - } - - for (i = 0; i < gtk_menu_get_n_rows (menu); i++) - requisition->height += priv->heights[i]; - - requisition->width += 2 * 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); - - border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); - requisition->width += (border_width + horizontal_padding + widget->style->xthickness) * 2; - requisition->height += (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 @@ -2647,10 +2635,9 @@ gtk_menu_size_allocate (GtkWidget *widget, GtkMenuShell *menu_shell; GtkWidget *child; GtkAllocation child_allocation; - GtkRequisition child_requisition; GtkMenuPrivate *priv; GList *children; - gint x, y; + gint x, y, i; gint width, height; guint border_width; guint vertical_padding; @@ -2664,23 +2651,31 @@ gtk_menu_size_allocate (GtkWidget *widget, priv = gtk_menu_get_private (menu); widget->allocation = *allocation; - gtk_widget_get_child_requisition (GTK_WIDGET (menu), &child_requisition); 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)); + + 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 + GTK_WIDGET (widget)->style->ythickness) * 2; + for (i = 0; i < priv->heights_length; i++) + priv->requested_height += priv->heights[i]; + x = border_width + widget->style->xthickness + horizontal_padding; y = border_width + widget->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); @@ -2769,7 +2764,7 @@ gtk_menu_size_allocate (GtkWidget *widget, if (menu->tearoff_active) { - if (allocation->height >= widget->requisition.height) + if (allocation->height >= priv->requested_height) { if (gtk_widget_get_visible (menu->tearoff_scrollbar)) { @@ -2781,7 +2776,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 > @@ -3009,6 +3004,178 @@ gtk_menu_show (GtkWidget *widget) GTK_WIDGET_CLASS (gtk_menu_parent_class)->show (widget); } + + +static void +gtk_menu_size_request_init (GtkSizeRequestIface *iface) +{ + iface->get_width = gtk_menu_get_width; + iface->get_height = gtk_menu_get_height; + iface->get_height_for_width = gtk_menu_get_height_for_width; +} + +static void +gtk_menu_get_width (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkMenu *menu; + GtkMenuShell *menu_shell; + GtkMenuPrivate *priv; + 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); + + 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_size_request_get_width (GTK_SIZE_REQUEST (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); + + part = child_min / (r - l); + min_width = MAX (min_width, part); + + part = child_nat / (r - l); + nat_width = MAX (nat_width, part); + } + + /* 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; + + gtk_style_get (GTK_WIDGET (widget)->style, + GTK_TYPE_CHECK_MENU_ITEM, + "toggle-spacing", &toggle_spacing, + "indicator-size", &indicator_size, + NULL); + + 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 + GTK_WIDGET (widget)->style->xthickness) * 2; + nat_width += (border_width + horizontal_padding + GTK_WIDGET (widget)->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 void +gtk_menu_get_height (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size) +{ + gint min_width; + + /* Menus are height-for-width only, just return the height for the minimum width */ + gtk_size_request_get_width (widget, &min_width, NULL); + gtk_size_request_get_height_for_width (widget, min_width, minimum_size, natural_size); +} + +static void +gtk_menu_get_height_for_width (GtkSizeRequest *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkMenu *menu = GTK_MENU (widget); + guint *min_heights, *nat_heights; + guint vertical_padding, border_width; + gint n_heights, i; + gint min_height, nat_height; + + gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, NULL); + border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); + + min_height = nat_height = (border_width + vertical_padding + GTK_WIDGET (widget)->style->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 (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) @@ -3471,9 +3638,11 @@ gtk_menu_scroll_by (GtkMenu *menu, gint view_width, view_height; gboolean double_arrows; GtkBorder arrow_border; + GtkMenuPrivate *priv; widget = GTK_WIDGET (menu); offset = menu->scroll_offset + step; + priv = gtk_menu_get_private (menu); get_arrows_border (menu, &arrow_border); @@ -3495,7 +3664,7 @@ gtk_menu_scroll_by (GtkMenu *menu, gdk_drawable_get_size (widget->window, &view_width, &view_height); 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: */ @@ -3508,9 +3677,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); @@ -4308,12 +4477,10 @@ gtk_menu_position (GtkMenu *menu) gdk_display_get_device_state (gdk_screen_get_display (screen), pointer, &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. + /* Get the minimum height for minimum width to figure out + * the right place to popup the menu. */ - gtk_widget_size_request (widget, &requisition); + gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL); if (pointer_screen != screen) { @@ -4467,7 +4634,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) { @@ -4563,8 +4730,10 @@ gtk_menu_scroll_to (GtkMenu *menu, guint horizontal_padding; gboolean double_arrows; GtkBorder arrow_border; + GtkMenuPrivate *priv; widget = GTK_WIDGET (menu); + priv = gtk_menu_get_private (menu); if (menu->tearoff_active && menu->tearoff_adjustment && @@ -4590,7 +4759,7 @@ gtk_menu_scroll_to (GtkMenu *menu, border_width = gtk_container_get_border_width (GTK_CONTAINER (menu)); 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 - + menu_height = priv->requested_height - (border_width + widget->style->ythickness + vertical_padding) * 2; x = border_width + widget->style->xthickness + horizontal_padding; @@ -5244,8 +5413,11 @@ get_menu_height (GtkMenu *menu) { gint height; GtkWidget *widget = GTK_WIDGET (menu); + GtkAllocation allocation; - height = widget->requisition.height; + gtk_widget_get_allocation (widget, &allocation); + + height = allocation.height; height -= gtk_container_get_border_width (GTK_CONTAINER (widget) + widget->style->ythickness) * 2; if (!menu->tearoff_active) diff --git a/gtk/gtkmenuitem.c b/gtk/gtkmenuitem.c index ded538c26c..10c83bc946 100644 --- a/gtk/gtkmenuitem.c +++ b/gtk/gtkmenuitem.c @@ -38,6 +38,7 @@ #include "gtkprivate.h" #include "gtkbuildable.h" #include "gtkactivatable.h" +#include "gtksizerequest.h" #include "gtkintl.h" @@ -78,8 +79,6 @@ static void gtk_menu_item_get_property (GObject *object, GValue *value, GParamSpec *pspec); static void gtk_menu_item_destroy (GtkObject *object); -static void gtk_menu_item_size_request (GtkWidget *widget, - GtkRequisition *requisition); static void gtk_menu_item_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void gtk_menu_item_realize (GtkWidget *widget); @@ -125,6 +124,17 @@ static void gtk_real_menu_item_set_label (GtkMenuItem *menu_item, const gchar *label); static G_CONST_RETURN gchar * gtk_real_menu_item_get_label (GtkMenuItem *menu_item); +static void gtk_menu_item_size_request_init (GtkSizeRequestIface *iface); +static void gtk_menu_item_get_width (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_menu_item_get_height (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_menu_item_get_height_for_width (GtkSizeRequest *widget, + gint for_size, + gint *minimum_size, + gint *natural_size); static void gtk_menu_item_buildable_interface_init (GtkBuildableIface *iface); static void gtk_menu_item_buildable_add_child (GtkBuildable *buildable, @@ -157,7 +167,9 @@ G_DEFINE_TYPE_WITH_CODE (GtkMenuItem, gtk_menu_item, GTK_TYPE_ITEM, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_menu_item_buildable_interface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE, - gtk_menu_item_activatable_interface_init)) + gtk_menu_item_activatable_interface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_SIZE_REQUEST, + gtk_menu_item_size_request_init)) #define GET_PRIVATE(object) \ (G_TYPE_INSTANCE_GET_PRIVATE ((object), GTK_TYPE_MENU_ITEM, GtkMenuItemPrivate)) @@ -177,7 +189,6 @@ gtk_menu_item_class_init (GtkMenuItemClass *klass) object_class->destroy = gtk_menu_item_destroy; - widget_class->size_request = gtk_menu_item_size_request; widget_class->size_allocate = gtk_menu_item_size_allocate; widget_class->expose_event = gtk_menu_item_expose; widget_class->realize = gtk_menu_item_realize; @@ -557,6 +568,384 @@ gtk_menu_item_detacher (GtkWidget *widget, menu_item->submenu = NULL; } +static void +get_arrow_size (GtkWidget *widget, + GtkWidget *child, + gint *size) +{ + PangoContext *context; + PangoFontMetrics *metrics; + gfloat arrow_scaling; + + g_assert (size); + + gtk_widget_style_get (widget, + "arrow-scaling", &arrow_scaling, + NULL); + + context = gtk_widget_get_pango_context (child); + metrics = pango_context_get_metrics (context, + child->style->font_desc, + pango_context_get_language (context)); + + *size = (PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics))); + + pango_font_metrics_unref (metrics); + + *size = *size * arrow_scaling; +} + + +static void +gtk_menu_item_accel_width_foreach (GtkWidget *widget, + gpointer data) +{ + guint *width = data; + + if (GTK_IS_ACCEL_LABEL (widget)) + { + guint w; + + w = gtk_accel_label_get_accel_width (GTK_ACCEL_LABEL (widget)); + *width = MAX (*width, w); + } + else if (GTK_IS_CONTAINER (widget)) + gtk_container_foreach (GTK_CONTAINER (widget), + gtk_menu_item_accel_width_foreach, + data); +} + +static gint +get_minimum_width (GtkWidget *widget) +{ + PangoContext *context; + PangoFontMetrics *metrics; + gint width; + gint width_chars; + + context = gtk_widget_get_pango_context (widget); + metrics = pango_context_get_metrics (context, + widget->style->font_desc, + pango_context_get_language (context)); + + width = pango_font_metrics_get_approximate_char_width (metrics); + + pango_font_metrics_unref (metrics); + + gtk_widget_style_get (widget, "width-chars", &width_chars, NULL); + + return PANGO_PIXELS (width_chars * width); +} + +static void +gtk_menu_item_size_request_init (GtkSizeRequestIface *iface) +{ + iface->get_width = gtk_menu_item_get_width; + iface->get_height = gtk_menu_item_get_height; + iface->get_height_for_width = gtk_menu_item_get_height_for_width; +} + +static void +gtk_menu_item_get_width (GtkSizeRequest *request, + gint *minimum_size, + gint *natural_size) +{ + GtkMenuItem *menu_item; + GtkBin *bin; + GtkWidget *child, *widget = GTK_WIDGET (request); + guint accel_width; + guint horizontal_padding; + guint border_width; + GtkPackDirection pack_dir; + GtkPackDirection child_pack_dir; + gint min_width, nat_width; + + min_width = nat_width = 0; + + gtk_widget_style_get (widget, + "horizontal-padding", &horizontal_padding, + NULL); + + bin = GTK_BIN (widget); + menu_item = GTK_MENU_ITEM (widget); + + if (GTK_IS_MENU_BAR (widget->parent)) + { + pack_dir = gtk_menu_bar_get_pack_direction (GTK_MENU_BAR (widget->parent)); + child_pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (widget->parent)); + } + else + { + pack_dir = GTK_PACK_DIRECTION_LTR; + child_pack_dir = GTK_PACK_DIRECTION_LTR; + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + min_width = (border_width + widget->style->xthickness) * 2; + + if ((pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL) && + (child_pack_dir == GTK_PACK_DIRECTION_LTR || child_pack_dir == GTK_PACK_DIRECTION_RTL)) + min_width += 2 * horizontal_padding; + + nat_width = min_width; + + child = gtk_bin_get_child (bin); + + if (child != NULL && gtk_widget_get_visible (child)) + { + gint child_min, child_nat; + + gtk_size_request_get_width (GTK_SIZE_REQUEST (child), &child_min, &child_nat); + + if (menu_item->submenu && menu_item->show_submenu_indicator) + { + guint arrow_spacing; + gint arrow_size; + + gtk_widget_style_get (widget, + "arrow-spacing", &arrow_spacing, + NULL); + + get_arrow_size (widget, child, &arrow_size); + + min_width += arrow_size; + min_width += arrow_spacing; + + min_width = MAX (min_width, get_minimum_width (widget)); + + nat_width = min_width; + } + + + min_width += child_min; + nat_width += child_nat; + + + } + + accel_width = 0; + gtk_container_foreach (GTK_CONTAINER (menu_item), + gtk_menu_item_accel_width_foreach, + &accel_width); + menu_item->accelerator_width = accel_width; + + if (minimum_size) + *minimum_size = min_width; + + if (natural_size) + *natural_size = nat_width; +} + +static void +gtk_menu_item_get_height (GtkSizeRequest *request, + gint *minimum_size, + gint *natural_size) +{ + GtkMenuItem *menu_item; + GtkBin *bin; + GtkWidget *child, *widget = GTK_WIDGET (request); + guint accel_width; + guint horizontal_padding; + guint border_width; + GtkPackDirection pack_dir; + GtkPackDirection child_pack_dir; + gint min_height, nat_height; + + min_height = nat_height = 0; + + gtk_widget_style_get (widget, + "horizontal-padding", &horizontal_padding, + NULL); + + bin = GTK_BIN (widget); + menu_item = GTK_MENU_ITEM (widget); + + if (GTK_IS_MENU_BAR (widget->parent)) + { + pack_dir = gtk_menu_bar_get_pack_direction (GTK_MENU_BAR (widget->parent)); + child_pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (widget->parent)); + } + else + { + pack_dir = GTK_PACK_DIRECTION_LTR; + child_pack_dir = GTK_PACK_DIRECTION_LTR; + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + min_height = (border_width + widget->style->ythickness) * 2; + + if ((pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT) && + (child_pack_dir == GTK_PACK_DIRECTION_TTB || child_pack_dir == GTK_PACK_DIRECTION_BTT)) + min_height += 2 * horizontal_padding; + + nat_height = min_height; + + child = gtk_bin_get_child (bin); + + if (child != NULL && gtk_widget_get_visible (child)) + { + gint child_min, child_nat; + + gtk_size_request_get_height (GTK_SIZE_REQUEST (child), &child_min, &child_nat); + + min_height += child_min; + nat_height += child_nat; + + if (menu_item->submenu && menu_item->show_submenu_indicator) + { + gint arrow_size; + + get_arrow_size (widget, child, &arrow_size); + + min_height = MAX (min_height, arrow_size); + nat_height = MAX (nat_height, arrow_size); + } + } + else /* separator item */ + { + gboolean wide_separators; + gint separator_height; + + gtk_widget_style_get (widget, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + NULL); + + if (wide_separators) + min_height += separator_height + widget->style->ythickness; + else + min_height += widget->style->ythickness * 2; + + nat_height = min_height; + } + + accel_width = 0; + gtk_container_foreach (GTK_CONTAINER (menu_item), + gtk_menu_item_accel_width_foreach, + &accel_width); + menu_item->accelerator_width = accel_width; + + if (minimum_size) + *minimum_size = min_height; + + if (natural_size) + *natural_size = nat_height; +} + +static void +gtk_menu_item_get_height_for_width (GtkSizeRequest *request, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkMenuItem *menu_item; + GtkBin *bin; + GtkWidget *child, *widget = GTK_WIDGET (request); + guint horizontal_padding; + guint border_width; + GtkPackDirection pack_dir; + GtkPackDirection child_pack_dir; + gint min_height, nat_height; + gint avail_size; + + min_height = nat_height = 0; + + gtk_widget_style_get (widget, + "horizontal-padding", &horizontal_padding, + NULL); + + bin = GTK_BIN (widget); + menu_item = GTK_MENU_ITEM (widget); + + if (GTK_IS_MENU_BAR (widget->parent)) + { + pack_dir = gtk_menu_bar_get_pack_direction (GTK_MENU_BAR (widget->parent)); + child_pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (widget->parent)); + } + else + { + pack_dir = GTK_PACK_DIRECTION_LTR; + child_pack_dir = GTK_PACK_DIRECTION_LTR; + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + min_height = (border_width + widget->style->ythickness) * 2; + + avail_size = for_size; + avail_size -= (border_width + widget->style->xthickness) * 2; + + if ((pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT) && + (child_pack_dir == GTK_PACK_DIRECTION_TTB || child_pack_dir == GTK_PACK_DIRECTION_BTT)) + min_height += 2 * horizontal_padding; + + if ((pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL) && + (child_pack_dir == GTK_PACK_DIRECTION_LTR || child_pack_dir == GTK_PACK_DIRECTION_RTL)) + avail_size -= 2 * horizontal_padding; + + nat_height = min_height; + + child = gtk_bin_get_child (bin); + + if (child != NULL && gtk_widget_get_visible (child)) + { + gint child_min, child_nat; + gint arrow_size = 0; + + if (menu_item->submenu && menu_item->show_submenu_indicator) + { + + guint arrow_spacing; + + gtk_widget_style_get (widget, + "arrow-spacing", &arrow_spacing, + NULL); + + get_arrow_size (widget, child, &arrow_size); + + avail_size -= arrow_size; + avail_size -= arrow_spacing; + } + + gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (child), avail_size, &child_min, &child_nat); + + min_height += child_min; + nat_height += child_nat; + + if (menu_item->submenu && menu_item->show_submenu_indicator) + { + min_height = MAX (min_height, arrow_size); + nat_height = MAX (nat_height, arrow_size); + } + } + else /* separator item */ + { + gboolean wide_separators; + gint separator_height; + + gtk_widget_style_get (widget, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + NULL); + + if (wide_separators) + min_height += separator_height + widget->style->ythickness; + else + min_height += widget->style->ythickness * 2; + + nat_height = min_height; + } + + if (minimum_size) + *minimum_size = min_height; + + if (natural_size) + *natural_size = nat_height; +} + + + static void gtk_menu_item_buildable_interface_init (GtkBuildableIface *iface) { @@ -886,140 +1275,6 @@ gtk_menu_item_toggle_size_allocate (GtkMenuItem *menu_item, g_signal_emit (menu_item, menu_item_signals[TOGGLE_SIZE_ALLOCATE], 0, allocation); } -static void -gtk_menu_item_accel_width_foreach (GtkWidget *widget, - gpointer data) -{ - guint *width = data; - - if (GTK_IS_ACCEL_LABEL (widget)) - { - guint w; - - w = gtk_accel_label_get_accel_width (GTK_ACCEL_LABEL (widget)); - *width = MAX (*width, w); - } - else if (GTK_IS_CONTAINER (widget)) - gtk_container_foreach (GTK_CONTAINER (widget), - gtk_menu_item_accel_width_foreach, - data); -} - -static gint -get_minimum_width (GtkWidget *widget) -{ - PangoContext *context; - PangoFontMetrics *metrics; - gint width; - gint width_chars; - - context = gtk_widget_get_pango_context (widget); - metrics = pango_context_get_metrics (context, - widget->style->font_desc, - pango_context_get_language (context)); - - width = pango_font_metrics_get_approximate_char_width (metrics); - - pango_font_metrics_unref (metrics); - - gtk_widget_style_get (widget, "width-chars", &width_chars, NULL); - - return PANGO_PIXELS (width_chars * width); -} - -static void -gtk_menu_item_size_request (GtkWidget *widget, - GtkRequisition *requisition) -{ - GtkMenuItem *menu_item; - GtkBin *bin; - GtkWidget *child; - guint accel_width; - guint horizontal_padding; - guint border_width; - GtkPackDirection pack_dir; - GtkPackDirection child_pack_dir; - - g_return_if_fail (GTK_IS_MENU_ITEM (widget)); - g_return_if_fail (requisition != NULL); - - gtk_widget_style_get (widget, - "horizontal-padding", &horizontal_padding, - NULL); - - bin = GTK_BIN (widget); - menu_item = GTK_MENU_ITEM (widget); - - if (GTK_IS_MENU_BAR (widget->parent)) - { - pack_dir = gtk_menu_bar_get_pack_direction (GTK_MENU_BAR (widget->parent)); - child_pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (widget->parent)); - } - else - { - pack_dir = GTK_PACK_DIRECTION_LTR; - child_pack_dir = GTK_PACK_DIRECTION_LTR; - } - - border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); - requisition->width = (border_width + widget->style->xthickness) * 2; - requisition->height = (border_width + widget->style->ythickness) * 2; - - if ((pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL) && - (child_pack_dir == GTK_PACK_DIRECTION_LTR || child_pack_dir == GTK_PACK_DIRECTION_RTL)) - requisition->width += 2 * horizontal_padding; - else if ((pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT) && - (child_pack_dir == GTK_PACK_DIRECTION_TTB || child_pack_dir == GTK_PACK_DIRECTION_BTT)) - requisition->height += 2 * horizontal_padding; - - child = gtk_bin_get_child (bin); - - if (child != NULL && gtk_widget_get_visible (child)) - { - GtkRequisition child_requisition; - - gtk_widget_size_request (child, &child_requisition); - - requisition->width += child_requisition.width; - requisition->height += child_requisition.height; - - if (menu_item->submenu && menu_item->show_submenu_indicator) - { - guint arrow_spacing; - - gtk_widget_style_get (widget, - "arrow-spacing", &arrow_spacing, - NULL); - - requisition->width += child_requisition.height; - requisition->width += arrow_spacing; - - requisition->width = MAX (requisition->width, get_minimum_width (widget)); - } - } - else /* separator item */ - { - gboolean wide_separators; - gint separator_height; - - gtk_widget_style_get (widget, - "wide-separators", &wide_separators, - "separator-height", &separator_height, - NULL); - - if (wide_separators) - requisition->height += separator_height + widget->style->ythickness; - else - requisition->height += widget->style->ythickness * 2; - } - - accel_width = 0; - gtk_container_foreach (GTK_CONTAINER (menu_item), - gtk_menu_item_accel_width_foreach, - &accel_width); - menu_item->accelerator_width = accel_width; -} - static void gtk_menu_item_size_allocate (GtkWidget *widget, GtkAllocation *allocation) @@ -1224,32 +1479,17 @@ gtk_menu_item_paint (GtkWidget *widget, { gint arrow_x, arrow_y; gint arrow_size; - gint arrow_extent; guint horizontal_padding; - gfloat arrow_scaling; GtkTextDirection direction; GtkArrowType arrow_type; - PangoContext *context; - PangoFontMetrics *metrics; direction = gtk_widget_get_direction (widget); gtk_widget_style_get (widget, "horizontal-padding", &horizontal_padding, - "arrow-scaling", &arrow_scaling, NULL); - - context = gtk_widget_get_pango_context (child); - metrics = pango_context_get_metrics (context, - child->style->font_desc, - pango_context_get_language (context)); - arrow_size = (PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + - pango_font_metrics_get_descent (metrics))); - - pango_font_metrics_unref (metrics); - - arrow_extent = arrow_size * arrow_scaling; + get_arrow_size (widget, child, &arrow_size); shadow_type = GTK_SHADOW_OUT; if (state_type == GTK_STATE_PRELIGHT) @@ -1257,7 +1497,7 @@ gtk_menu_item_paint (GtkWidget *widget, if (direction == GTK_TEXT_DIR_LTR) { - arrow_x = x + width - horizontal_padding - arrow_extent; + arrow_x = x + width - horizontal_padding - arrow_size; arrow_type = GTK_ARROW_RIGHT; } else @@ -1266,14 +1506,14 @@ gtk_menu_item_paint (GtkWidget *widget, arrow_type = GTK_ARROW_LEFT; } - arrow_y = y + (height - arrow_extent) / 2; + arrow_y = y + (height - arrow_size) / 2; gtk_paint_arrow (widget->style, widget->window, state_type, shadow_type, area, widget, "menuitem", arrow_type, TRUE, arrow_x, arrow_y, - arrow_extent, arrow_extent); + arrow_size, arrow_size); } else if (!child) {