summaryrefslogtreecommitdiff
path: root/gtk/gtkbox.c
diff options
context:
space:
mode:
authorJohannes Schmmid <johannes.schmid@openismus.com>2009-12-02 09:48:42 +0100
committerJohannes Schmid <jhs@gnome.org>2009-12-14 15:32:49 +0100
commite08d04b5613ca267413bc0380a94de81bd65a486 (patch)
treea970f7c146a0d9c1418a3d0ab5950d07b553a440 /gtk/gtkbox.c
parenta8ec02c93029e0ee71922fee493a5c5582d32557 (diff)
downloadgtk+-e08d04b5613ca267413bc0380a94de81bd65a486.tar.gz
native-layout: Introduce GtkExtendedLayout interface.
Diffstat (limited to 'gtk/gtkbox.c')
-rw-r--r--gtk/gtkbox.c603
1 files changed, 333 insertions, 270 deletions
diff --git a/gtk/gtkbox.c b/gtk/gtkbox.c
index 50c588bb61..f8dc28f4db 100644
--- a/gtk/gtkbox.c
+++ b/gtk/gtkbox.c
@@ -28,6 +28,7 @@
#include "gtkbox.h"
#include "gtkorientable.h"
+#include "gtkextendedlayout.h"
#include "gtkprivate.h"
#include "gtkintl.h"
#include "gtkalias.h"
@@ -60,6 +61,27 @@ struct _GtkBoxPrivate
#define GTK_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_BOX, GtkBoxPrivate))
+typedef struct _GtkBoxDesiredSizes GtkBoxDesiredSizes;
+typedef struct _GtkBoxSpreading GtkBoxSpreading;
+
+struct _GtkBoxDesiredSizes
+{
+ gint minimum_size;
+ gint natural_size;
+};
+
+struct _GtkBoxSpreading
+{
+ GtkBoxChild *child;
+ gint index;
+};
+
+static void gtk_box_get_desired_size (GtkExtendedLayout *layout,
+ GtkRequisition *minimum_size,
+ GtkRequisition *natural_size);
+static void gtk_box_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static void gtk_box_layout_interface_init (GtkExtendedLayoutIface *iface);
static void gtk_box_set_property (GObject *object,
guint prop_id,
@@ -70,11 +92,6 @@ static void gtk_box_get_property (GObject *object,
GValue *value,
GParamSpec *pspec);
-static void gtk_box_size_request (GtkWidget *widget,
- GtkRequisition *requisition);
-static void gtk_box_size_allocate (GtkWidget *widget,
- GtkAllocation *allocation);
-
static void gtk_box_add (GtkContainer *container,
GtkWidget *widget);
static void gtk_box_remove (GtkContainer *container,
@@ -98,7 +115,9 @@ static GType gtk_box_child_type (GtkContainer *container);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkBox, gtk_box, GTK_TYPE_CONTAINER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
- NULL));
+ NULL)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_EXTENDED_LAYOUT,
+ gtk_box_layout_interface_init));
static void
gtk_box_class_init (GtkBoxClass *class)
@@ -110,7 +129,6 @@ gtk_box_class_init (GtkBoxClass *class)
object_class->set_property = gtk_box_set_property;
object_class->get_property = gtk_box_get_property;
- widget_class->size_request = gtk_box_size_request;
widget_class->size_allocate = gtk_box_size_allocate;
container_class->add = gtk_box_add;
@@ -182,6 +200,12 @@ gtk_box_class_init (GtkBoxClass *class)
}
static void
+gtk_box_layout_interface_init (GtkExtendedLayoutIface *iface)
+{
+ iface->get_desired_size = gtk_box_get_desired_size;
+}
+
+static void
gtk_box_init (GtkBox *box)
{
GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (box);
@@ -252,78 +276,143 @@ gtk_box_get_property (GObject *object,
}
static void
-gtk_box_size_request (GtkWidget *widget,
- GtkRequisition *requisition)
+gtk_box_get_desired_size (GtkExtendedLayout *layout,
+ GtkRequisition *minimum_size,
+ GtkRequisition *natural_size)
{
- GtkBox *box = GTK_BOX (widget);
- GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (box);
- GtkBoxChild *child;
+ GtkBox *box;
+ GtkBoxPrivate *private;
GList *children;
gint nvis_children;
- gint width;
- gint height;
+ gint border_width;
- requisition->width = 0;
- requisition->height = 0;
- nvis_children = 0;
+ box = GTK_BOX (layout);
+ private = GTK_BOX_GET_PRIVATE (box);
+ border_width = GTK_CONTAINER (box)->border_width;
+
+ minimum_size->width = minimum_size->height = 0;
+ natural_size->width = natural_size->height = 0;
+ nvis_children = 0;
children = box->children;
while (children)
{
+ GtkBoxChild *child;
+
child = children->data;
children = children->next;
if (GTK_WIDGET_VISIBLE (child->widget))
- {
- GtkRequisition child_requisition;
+ {
+ GtkRequisition child_minimum_size;
+ GtkRequisition child_natural_size;
- gtk_widget_size_request (child->widget, &child_requisition);
+ gtk_widget_get_desired_size (child->widget,
+ &child_minimum_size,
+ &child_natural_size);
- if (box->homogeneous)
- {
- width = child_requisition.width + child->padding * 2;
- height = child_requisition.height + child->padding * 2;
+ if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (box->homogeneous)
+ {
+ gint width;
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- requisition->width = MAX (requisition->width, width);
- else
- requisition->height = MAX (requisition->height, height);
- }
- else
- {
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- requisition->width += child_requisition.width + child->padding * 2;
+ width = child_minimum_size.width + child->padding * 2;
+ minimum_size->width = MAX (minimum_size->width, width);
+
+ width = child_natural_size.width + child->padding * 2;
+ natural_size->width = MAX (natural_size->width, width);
+ }
else
- requisition->height += child_requisition.height + child->padding * 2;
- }
+ {
+ minimum_size->width += child_minimum_size.width + child->padding * 2;
+ natural_size->width += child_natural_size.width + child->padding * 2;
+ }
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- requisition->height = MAX (requisition->height, child_requisition.height);
+ minimum_size->height = MAX (minimum_size->height, child_minimum_size.height);
+ natural_size->height = MAX (natural_size->height, child_natural_size.height);
+ }
else
- requisition->width = MAX (requisition->width, child_requisition.width);
+ {
+ if (box->homogeneous)
+ {
+ gint height;
+
+ height = child_minimum_size.height + child->padding * 2;
+ minimum_size->height = MAX (minimum_size->height, height);
+
+ height = child_natural_size.height + child->padding * 2;
+ natural_size->height = MAX (natural_size->height, height);
+ }
+ else
+ {
+ minimum_size->height += child_minimum_size.height + child->padding * 2;
+ natural_size->height += child_natural_size.height + child->padding * 2;
+ }
+
+ minimum_size->width = MAX (minimum_size->width, child_minimum_size.width);
+ natural_size->width = MAX (natural_size->width, child_natural_size.width);
+ }
- nvis_children += 1;
- }
- }
+ nvis_children += 1;
+ }
+ }
if (nvis_children > 0)
{
- if (box->homogeneous)
+ if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
{
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- requisition->width *= nvis_children;
- else
- requisition->height *= nvis_children;
- }
+ if (box->homogeneous)
+ {
+ minimum_size->width *= nvis_children;
+ natural_size->width *= nvis_children;
+ }
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- requisition->width += (nvis_children - 1) * box->spacing;
+ minimum_size->width += (nvis_children - 1) * box->spacing;
+ natural_size->width += (nvis_children - 1) * box->spacing;
+ }
else
- requisition->height += (nvis_children - 1) * box->spacing;
+ {
+ if (box->homogeneous)
+ {
+ minimum_size->height *= nvis_children;
+ natural_size->height *= nvis_children;
+ }
+
+ minimum_size->height += (nvis_children - 1) * box->spacing;
+ natural_size->height += (nvis_children - 1) * box->spacing;
+ }
}
- requisition->width += GTK_CONTAINER (box)->border_width * 2;
- requisition->height += GTK_CONTAINER (box)->border_width * 2;
+ minimum_size->width += border_width * 2;
+ minimum_size->height += border_width * 2;
+
+ natural_size->width += border_width * 2;
+ natural_size->height += border_width * 2;
+}
+
+static gint
+gtk_box_compare_gap (gconstpointer p1,
+ gconstpointer p2,
+ gpointer data)
+{
+ GtkBoxDesiredSizes *sizes = data;
+ const GtkBoxSpreading *c1 = p1;
+ const GtkBoxSpreading *c2 = p2;
+
+ const gint d1 = MAX (sizes[c1->index].natural_size -
+ sizes[c1->index].minimum_size,
+ 0);
+ const gint d2 = MAX (sizes[c2->index].natural_size -
+ sizes[c2->index].minimum_size,
+ 0);
+
+ gint delta = (d2 - d1);
+
+ if (0 == delta)
+ delta = (c2->index - c1->index);
+
+ return delta;
}
static void
@@ -334,21 +423,13 @@ gtk_box_size_allocate (GtkWidget *widget,
GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (box);
GtkBoxChild *child;
GList *children;
- GtkAllocation child_allocation;
- gint nvis_children = 0;
- gint nexpand_children = 0;
- gint child_width = 0;
- gint child_height = 0;
- gint width = 0;
- gint height = 0;
- gint extra = 0;
- gint x = 0;
- gint y = 0;
- GtkTextDirection direction;
+ gint nvis_children;
+ gint nexpand_children;
widget->allocation = *allocation;
- direction = gtk_widget_get_direction (widget);
+ nvis_children = 0;
+ nexpand_children = 0;
for (children = box->children; children; children = children->next)
{
@@ -364,239 +445,221 @@ gtk_box_size_allocate (GtkWidget *widget,
if (nvis_children > 0)
{
- if (box->homogeneous)
- {
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- width = (allocation->width -
- GTK_CONTAINER (box)->border_width * 2 -
- (nvis_children - 1) * box->spacing);
- extra = width / nvis_children;
- }
- else
- {
- height = (allocation->height -
- GTK_CONTAINER (box)->border_width * 2 -
- (nvis_children - 1) * box->spacing);
- extra = height / nvis_children;
- }
- }
- else if (nexpand_children > 0)
- {
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- width = (gint) allocation->width - (gint) widget->requisition.width;
- extra = width / nexpand_children;
- }
- else
- {
- height = (gint) allocation->height - (gint) widget->requisition.height;
- extra = height / nexpand_children;
- }
- }
-
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- x = allocation->x + GTK_CONTAINER (box)->border_width;
- child_allocation.y = allocation->y + GTK_CONTAINER (box)->border_width;
- child_allocation.height = MAX (1, (gint) allocation->height - (gint) GTK_CONTAINER (box)->border_width * 2);
- }
- else
- {
- y = allocation->y + GTK_CONTAINER (box)->border_width;
- child_allocation.x = allocation->x + GTK_CONTAINER (box)->border_width;
- child_allocation.width = MAX (1, (gint) allocation->width - (gint) GTK_CONTAINER (box)->border_width * 2);
- }
+ gint border_width = GTK_CONTAINER (box)->border_width;
+ GtkTextDirection direction = gtk_widget_get_direction (widget);
+ GtkAllocation child_allocation;
- children = box->children;
- while (children)
- {
- child = children->data;
- children = children->next;
-
- if ((child->pack == GTK_PACK_START) && GTK_WIDGET_VISIBLE (child->widget))
- {
- if (box->homogeneous)
- {
- if (nvis_children == 1)
- {
- child_width = width;
- child_height = height;
- }
- else
- {
- child_width = extra;
- child_height = extra;
- }
+ GtkBoxSpreading *spreading = g_newa (GtkBoxSpreading, nvis_children);
+ GtkBoxDesiredSizes *sizes = g_newa (GtkBoxDesiredSizes, nvis_children);
- nvis_children -= 1;
- width -= extra;
- height -= extra;
- }
- else
- {
- GtkRequisition child_requisition;
+ GtkPackType packing;
- gtk_widget_get_child_requisition (child->widget, &child_requisition);
+ gint size;
+ gint extra;
+ gint x, y, i;
+ gint child_size;
- child_width = child_requisition.width + child->padding * 2;
- child_height = child_requisition.height + child->padding * 2;
+ if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
+ size = allocation->width - border_width * 2 - (nvis_children - 1) * box->spacing;
+ else
+ size = allocation->height - border_width * 2 - (nvis_children - 1) * box->spacing;
- if (child->expand)
- {
- if (nexpand_children == 1)
- {
- child_width += width;
- child_height += height;
- }
- else
- {
- child_width += extra;
- child_height += extra;
- }
+ if (box->homogeneous)
+ {
+ extra = size / nvis_children;
+ }
+ else
+ {
+ /* Retrieve desired size for visible children */
- nexpand_children -= 1;
- width -= extra;
- height -= extra;
- }
- }
+ i = 0;
+ children = box->children;
+ while (children)
+ {
+ child = children->data;
+ children = children->next;
- if (child->fill)
- {
+ if (GTK_WIDGET_VISIBLE (child->widget))
+ {
if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- child_allocation.width = MAX (1, (gint) child_width - (gint) child->padding * 2);
- child_allocation.x = x + child->padding;
- }
+ gtk_widget_get_width_for_height (child->widget,
+ allocation->height,
+ &sizes[i].minimum_size,
+ &sizes[i].natural_size);
else
- {
- child_allocation.height = MAX (1, child_height - (gint)child->padding * 2);
- child_allocation.y = y + child->padding;
- }
- }
- else
- {
- GtkRequisition child_requisition;
+ gtk_widget_get_height_for_width (child->widget,
+ allocation->width,
+ &sizes[i].minimum_size,
+ &sizes[i].natural_size);
- gtk_widget_get_child_requisition (child->widget, &child_requisition);
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- child_allocation.width = child_requisition.width;
- child_allocation.x = x + (child_width - child_allocation.width) / 2;
- }
- else
- {
- child_allocation.height = child_requisition.height;
- child_allocation.y = y + (child_height - child_allocation.height) / 2;
- }
- }
+ size -= sizes[i].minimum_size;
- if (direction == GTK_TEXT_DIR_RTL &&
- private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
+ spreading[i].index = i;
+ spreading[i].child = child;
+
+ i += 1;
}
+ }
- gtk_widget_size_allocate (child->widget, &child_allocation);
+ /* Distribute the container's extra space c_gap. We want to assign
+ * this space such that the sum of extra space assigned to children
+ * (c^i_gap) is equal to c_cap. The case that there's not enough
+ * space for all children to take their natural size needs some
+ * attention. The goals we want to achieve are:
+ *
+ * a) Maximize number of children taking their natural size.
+ * b) The allocated size of children should be a continuous
+ * function of c_gap. That is, increasing the container size by
+ * one pixel should never make drastic changes in the distribution.
+ * c) If child i takes its natural size and child j doesn't,
+ * child j should have received at least as much gap as child i.
+ *
+ * The following code distributes the additional space by following
+ * this rules.
+ */
+
+ /* Sort descending by gap and position. */
+
+ g_qsort_with_data (spreading,
+ nvis_children, sizeof (GtkBoxSpreading),
+ gtk_box_compare_gap, sizes);
+
+ /* Distribute available space.
+ * This master piece of a loop was conceived by Behdad Esfahbod.
+ */
+ for (i = nvis_children - 1; i >= 0; --i)
+ {
+ /* Divide remaining space by number of remaining children.
+ * Sort order and reducing remaining space by assigned space
+ * ensures that space is distributed equally.
+ */
+ gint glue = (size + i) / (i + 1);
+ gint gap = sizes[spreading[i].index].natural_size
+ - sizes[spreading[i].index].minimum_size;
+
+ extra = MIN (glue, gap);
+ sizes[spreading[i].index].minimum_size += extra;
+
+ size -= extra;
+ }
- x += child_width + box->spacing;
- y += child_height + box->spacing;
- }
- }
+ /* Calculate space which hasn't distributed yet,
+ * and is available for expanding children.
+ */
+ if (nexpand_children > 0)
+ extra = size / nexpand_children;
+ else
+ extra = 0;
+ }
- x = allocation->x + allocation->width - GTK_CONTAINER (box)->border_width;
- y = allocation->y + allocation->height - GTK_CONTAINER (box)->border_width;
+ /* Allocate child positions. */
- children = box->children;
- while (children)
- {
- child = children->data;
- children = children->next;
+ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing)
+ {
+ if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ child_allocation.y = allocation->y + border_width;
+ child_allocation.height = MAX (1, allocation->height - border_width * 2);
+ if (packing == GTK_PACK_START)
+ x = allocation->x + border_width;
+ else
+ x = allocation->x + allocation->width - border_width;
+ }
+ else
+ {
+ child_allocation.x = allocation->x + border_width;
+ child_allocation.width = MAX (1, allocation->width - border_width * 2);
+ if (packing == GTK_PACK_START)
+ y = allocation->y + border_width;
+ else
+ y = allocation->y + allocation->height - border_width;
+ }
- if ((child->pack == GTK_PACK_END) && GTK_WIDGET_VISIBLE (child->widget))
+ i = 0;
+ children = box->children;
+ while (children)
{
- GtkRequisition child_requisition;
-
- gtk_widget_get_child_requisition (child->widget, &child_requisition);
-
- if (box->homogeneous)
- {
- if (nvis_children == 1)
- {
- child_width = width;
- child_height = height;
- }
- else
- {
- child_width = extra;
- child_height = extra;
- }
-
- nvis_children -= 1;
- width -= extra;
- height -= extra;
- }
- else
- {
- child_width = child_requisition.width + child->padding * 2;
- child_height = child_requisition.height + child->padding * 2;
+ child = children->data;
+ children = children->next;
- if (child->expand)
+ if (GTK_WIDGET_VISIBLE (child->widget))
+ {
+ if (child->pack == packing)
{
- if (nexpand_children == 1)
+ /* Assign the child's size. */
+
+ if (box->homogeneous)
+ {
+ if (nvis_children == 1)
+ child_size = size;
+ else
+ child_size = extra;
+
+ nvis_children -= 1;
+ size -= extra;
+ }
+ else
+ {
+ child_size = sizes[i].minimum_size + child->padding * 2;
+
+ if (child->expand)
+ {
+ if (nexpand_children == 1)
+ child_size += size;
+ else
+ child_size += extra;
+
+ nexpand_children -= 1;
+ size -= extra;
+ }
+ }
+
+ /* Assign the child's position. */
+
+ if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
{
- child_width += width;
- child_height += height;
- }
+ if (child->fill)
+ {
+ child_allocation.width = MAX (1, child_size - child->padding * 2);
+ child_allocation.x = x + child->padding;
+ }
+ else
+ {
+ child_allocation.width = sizes[i].minimum_size;
+ child_allocation.x = x + (child_size - child_allocation.width) / 2;
+ }
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
+
+ if (packing == GTK_PACK_START)
+ x += child_size + box->spacing;
+ else
+ x -= child_size + box->spacing;
+ }
else
{
- child_width += extra;
- child_height += extra;
+ if (child->fill)
+ {
+ child_allocation.height = MAX (1, child_size - child->padding * 2);
+ child_allocation.y = y + child->padding;
+ }
+ else
+ {
+ child_allocation.height = sizes[i].minimum_size;
+ child_allocation.y = y + (child_size - child_allocation.height) / 2;
+ }
+
+ if (packing == GTK_PACK_START)
+ y += child_size + box->spacing;
+ else
+ y -= child_size + box->spacing;
}
- nexpand_children -= 1;
- width -= extra;
- height -= extra;
- }
- }
-
- if (child->fill)
- {
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- child_allocation.width = MAX (1, (gint)child_width - (gint)child->padding * 2);
- child_allocation.x = x + child->padding - child_width;
+ gtk_widget_size_allocate (child->widget, &child_allocation);
}
- else
- {
- child_allocation.height = MAX (1, child_height - (gint)child->padding * 2);
- child_allocation.y = y + child->padding - child_height;
- }
+ i += 1;
}
- else
- {
- if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- child_allocation.width = child_requisition.width;
- child_allocation.x = x + (child_width - child_allocation.width) / 2 - child_width;
- }
- else
- {
- child_allocation.height = child_requisition.height;
- child_allocation.y = y + (child_height - child_allocation.height) / 2 - child_height;
- }
- }
-
- if (direction == GTK_TEXT_DIR_RTL &&
- private->orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
- }
-
- gtk_widget_size_allocate (child->widget, &child_allocation);
-
- x -= (child_width + box->spacing);
- y -= (child_height + box->spacing);
}
}
}