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()).
This commit is contained in:
Tristan Van Berkom 2010-08-18 19:42:02 -04:00
parent 9c26bd525f
commit 52e5f36dc3
2 changed files with 692 additions and 280 deletions

View File

@ -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)

View File

@ -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)
{