Wrapping Box Container
- From: Tristan Van Berkom <tristanvb openismus com>
- To: gtk-devel-list gnome org
- Subject: Wrapping Box Container
- Date: Wed, 25 Aug 2010 02:42:07 +0900
Hi developers,
Last week I was busy writing up a new container
widget for GTK+ and would like to propose it for
inclusion in 3.0.
The way the container widget works is you give it
an orientation (horizontal/vertical) and it will
line up children in the orientation of choice
and then "line wrap" the children as needed.
It's quite similar to a ClutterFlowLayout except
that I've built it up with some different modes/parameters.
The container at this time has 3 main allocation
modes (the "allocation-mode" property)
Free Unaligned Policy
=====================
With an unaligned wrapping policy we place as
many items on one line as possible for the given
size and then wrap and repeat the process allocating
the children individual sizes for each line.
Aligned Policy
==============
With an aligned policy all items should be aligned
with their siblings on the following and preceding
lines so that they can be viewed as rows/columns
(when wrapping horizontally all items/columns
have the same width except rows may have variable
heights).
Homogeneous Policy
==================
With a homogeneous policy all items get their
minimum/natural sizes calculated and rounded
up into a common 'max_min_size' and
'max_natural_size', items are then all allocated
the same size.
Controlling item sizes
======================
With GTK+ I thought it best not to use the
"min/max-item-width" properties approach that
ClutterFlowLayout uses to normalize item sizes
(as this can risk allocating children smaller sizes
than their requests) instead it's important to control
of the requests of the added children (by using the
GtkLabel:max-width-chars property for instance).
In the same way that "width-chars" and "max-width-chars"
control the minimum and natural width of a GtkLabel;
I've added the properties "minimum-length" and
"natural-length" to this container to control the
minimum and natural number of children on a
given line before wrapping.
Wrapping algorithm
==================
The container will allocate the full natural size for
as many children as possible before wrapping unless
the container was allocated less space. Excepting for
the final line, every line will receive at least "minimum-length"
children allocated at their minimum size.
Distributing extra space
========================
There is also a special "spreading" property that controls if
children will receive expand space, if expand space will be split
up and added between children as spacing or if the expand
pixels should just be prepended or appended to the allocated
children.
Expand/Fill
=========
As many container widgets do, the container children can be
set to "fill" the allocated space or to "expand" into extra space.
Note that the "expand" option can only usefully be consulted
when allocated with the free unaligned policy.
Padding/Spacing
==============
The container provides properties to control both the general
spacing between children vertically and horizontally and also
to control the vertical/horizontal padding of any child independently.
Is this a kind of widget that we are interested in adding to GTK+ ?
Would it be better to let it live in libegg for some time ?
Please try compiling and running the test app (requires GTK+ master)
and take a look at the sources (I'll try attaching them so they show
up inline as well),
eager to hear your thoughts...
Cheers,
-Tristan
PS: I've been trying to send this mail all day, at this point from
several email addresses... I'm dropping the .tgz attachment and will
follow up with another mail after uploading it somewhere if this email
does indeed hit the mailing list.
/*
* Copyright (C) 2010 Openismus GmbH
*
* Authors:
* Tristan Van Berkom <tristan van berkom gmail com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __EGG_WRAP_BOX_H__
#define __EGG_WRAP_BOX_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define EGG_TYPE_WRAP_BOX (egg_wrap_box_get_type ())
#define EGG_WRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_WRAP_BOX, EggWrapBox))
#define EGG_WRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_WRAP_BOX, EggWrapBoxClass))
#define EGG_IS_WRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_WRAP_BOX))
#define EGG_IS_WRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_WRAP_BOX))
#define EGG_WRAP_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_WRAP_BOX, EggWrapBoxClass))
typedef struct _EggWrapBox EggWrapBox;
typedef struct _EggWrapBoxPriv EggWrapBoxPriv;
typedef struct _EggWrapBoxClass EggWrapBoxClass;
typedef enum _EggWrapAllocationMode EggWrapAllocationMode;
typedef enum _EggWrapBoxSpreading EggWrapBoxSpreading;
/**
* EggWrapAllocationMode:
* @EGG_WRAP_ALLOCATE_FREE: Items wrap freely in the box's orientation
* @EGG_WRAP_ALLOCATE_ALIGNED: Items are aligned into rows and columns
* @EGG_WRAP_ALLOCATE_HOMOGENEOUS: Items are all allocated the same size
*
* Describes how an #EggWrapBox positions its children.
*/
enum _EggWrapAllocationMode {
EGG_WRAP_ALLOCATE_FREE = 0,
EGG_WRAP_ALLOCATE_ALIGNED,
EGG_WRAP_ALLOCATE_HOMOGENEOUS
};
/**
* EggWrapBoxSpreading:
* @EGG_WRAP_BOX_SPREAD_BEGING: Items are allocated no more than their natural size
* in the layout's orientation and any extra space is left trailing at
* the end of each line.
* @EGG_WRAP_BOX_SPREAD_END: Items are allocated no more than their natural size
* in the layout's orientation and any extra space skipped at the beginning
* of each line.
* @EGG_WRAP_BOX_SPREAD_EVEN: Items are allocated no more than their natural size
* in the layout's orientation and any extra space is evenly distributed
* between children.
* @EGG_WRAP_BOX_SPREAD_EXPAND: Items share the extra space evenly (or among children that 'expand' when
* in %EGG_WRAP_ALLOCATE_FREE mode.
*
* Describes how an #EggWrapBox deals with extra space when allocating children.
*
* The box always tries to fit as many children at their natural size
* in the given orentation as possible with the exception of fitting "minimum-length"
* items into the available size. When the available size is larger than
* the size needed to fit a given number of children at their natural size
* then extra space is available to distribute among children. The
* #EggWrapBoxSpreading option describes what to do with this space.
*
*/
enum _EggWrapBoxSpreading {
EGG_WRAP_BOX_SPREAD_BEGIN = 0,
EGG_WRAP_BOX_SPREAD_END,
EGG_WRAP_BOX_SPREAD_EVEN,
EGG_WRAP_BOX_SPREAD_EXPAND
};
struct _EggWrapBox
{
GtkContainer container;
/*< private >*/
EggWrapBoxPriv *priv;
};
struct _EggWrapBoxClass
{
GtkContainerClass parent_class;
};
GType egg_wrap_box_get_type (void) G_GNUC_CONST;
GtkWidget *egg_wrap_box_new (EggWrapAllocationMode mode,
EggWrapBoxSpreading spreading,
guint horizontal_spacing,
guint vertical_spacing);
void egg_wrap_box_set_allocation_mode (EggWrapBox *layout,
EggWrapAllocationMode mode);
EggWrapAllocationMode egg_wrap_box_get_allocation_mode (EggWrapBox *layout);
void egg_wrap_box_set_spreading (EggWrapBox *layout,
EggWrapBoxSpreading spreading);
EggWrapBoxSpreading egg_wrap_box_get_spreading (EggWrapBox *layout);
void egg_wrap_box_set_vertical_spacing (EggWrapBox *layout,
guint spacing);
guint egg_wrap_box_get_vertical_spacing (EggWrapBox *layout);
void egg_wrap_box_set_horizontal_spacing (EggWrapBox *layout,
guint spacing);
guint egg_wrap_box_get_horizontal_spacing (EggWrapBox *layout);
void egg_wrap_box_set_minimum_length (EggWrapBox *layout,
guint length);
guint egg_wrap_box_get_minimum_length (EggWrapBox *layout);
void egg_wrap_box_set_natural_length (EggWrapBox *layout,
guint length);
guint egg_wrap_box_get_natural_length (EggWrapBox *layout);
void egg_wrap_box_insert_child (EggWrapBox *layout,
GtkWidget *widget,
gint index,
guint xpad,
guint ypad,
gboolean xexpand,
gboolean yexpand,
gboolean xfill,
gboolean yfill);
void egg_wrap_box_reorder_child (EggWrapBox *layout,
GtkWidget *widget,
guint index);
G_END_DECLS
#endif /* __EGG_WRAP_BOX_H__ */
--------------------------------------------------------------------------------
/* eggwrapbox.c
* Copyright (C) 2007-2010 Openismus GmbH
*
* Authors:
* Tristan Van Berkom <tristan van berkom gmail com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:eggwrapbox
* @Short_Description: A container that wraps its children;
* @Title: EggWrapBox
*
* #EggWrapBox allocates space for an ordered list of children
* by wrapping them over in the box's orentation.
*
*/
#include <glib/gi18n.h>
#include "eggwrapbox.h"
typedef struct _EggWrapBoxChild EggWrapBoxChild;
enum {
PROP_0,
PROP_ORIENTATION,
PROP_ALLOCATION_MODE,
PROP_SPREADING,
PROP_HORIZONTAL_SPACING,
PROP_VERTICAL_SPACING,
PROP_MINIMUM_LENGTH,
PROP_NATURAL_LENGTH
};
enum {
CHILD_PROP_0,
CHILD_PROP_X_EXPAND,
CHILD_PROP_X_FILL,
CHILD_PROP_Y_EXPAND,
CHILD_PROP_Y_FILL,
CHILD_PROP_X_PADDING,
CHILD_PROP_Y_PADDING
};
struct _EggWrapBoxPriv {
GtkOrientation orientation;
EggWrapAllocationMode mode;
EggWrapBoxSpreading spreading;
guint16 vertical_spacing;
guint16 horizontal_spacing;
guint16 minimum_length;
guint16 natural_length;
GList *children;
};
struct _EggWrapBoxChild {
GtkWidget *widget;
guint16 xpadding;
guint16 ypadding;
guint16 xexpand : 1;
guint16 xfill : 1;
guint16 yexpand : 1;
guint16 yfill : 1;
};
/* GObjectClass */
static void egg_wrap_box_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void egg_wrap_box_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
/* GtkWidgetClass */
static void egg_wrap_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
/* GtkContainerClass */
static void egg_wrap_box_add (GtkContainer *container,
GtkWidget *widget);
static void egg_wrap_box_remove (GtkContainer *container,
GtkWidget *widget);
static void egg_wrap_box_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data);
static void egg_wrap_box_set_child_property (GtkContainer *container,
GtkWidget *child,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void egg_wrap_box_get_child_property (GtkContainer *container,
GtkWidget *child,
guint property_id,
GValue *value,
GParamSpec *pspec);
static GType egg_wrap_box_child_type (GtkContainer *container);
/* GtkSizeRequest */
static void egg_wrap_box_size_request_init (GtkSizeRequestIface *iface);
static GtkSizeRequestMode egg_wrap_box_get_request_mode (GtkSizeRequest *widget);
static void egg_wrap_box_get_width (GtkSizeRequest *widget,
gint *minimum_size,
gint *natural_size);
static void egg_wrap_box_get_height (GtkSizeRequest *widget,
gint *minimum_size,
gint *natural_size);
static void egg_wrap_box_get_height_for_width (GtkSizeRequest *box,
gint width,
gint *minimum_height,
gint *natural_height);
static void egg_wrap_box_get_width_for_height (GtkSizeRequest *box,
gint width,
gint *minimum_height,
gint *natural_height);
G_DEFINE_TYPE_WITH_CODE (EggWrapBox, egg_wrap_box, GTK_TYPE_CONTAINER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_SIZE_REQUEST,
egg_wrap_box_size_request_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
static void
egg_wrap_box_class_init (EggWrapBoxClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
gobject_class->get_property = egg_wrap_box_get_property;
gobject_class->set_property = egg_wrap_box_set_property;
widget_class->size_allocate = egg_wrap_box_size_allocate;
container_class->add = egg_wrap_box_add;
container_class->remove = egg_wrap_box_remove;
container_class->forall = egg_wrap_box_forall;
container_class->child_type = egg_wrap_box_child_type;
container_class->set_child_property = egg_wrap_box_set_child_property;
container_class->get_child_property = egg_wrap_box_get_child_property;
/* GObjectClass properties */
g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
/**
* EggWrapBox:allocation-mode:
*
* The #EggWrapAllocationMode to use.
*/
/* XXX TODO: When integrating to a proper library or to GTK+, add EggWrapAllocationMode
* enumeration as a proper GType and make this an enum property.
*/
g_object_class_install_property (gobject_class,
PROP_ALLOCATION_MODE,
g_param_spec_int ("allocation-mode",
_("Allocation Mode"),
_("The allocation mode to use"),
0, 2, EGG_WRAP_ALLOCATE_FREE,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:spreading:
*
* The #EggWrapBoxSpreading to used to define what is done with extra
* space.
*/
/* XXX TODO: When integrating to a proper library or to GTK+, add EggWrapBoxSpreading
* enumeration as a proper GType and make this an enum property.
*/
g_object_class_install_property (gobject_class,
PROP_SPREADING,
g_param_spec_int ("spreading",
_("Spreading"),
_("The spreading mode to use"),
0, 2, EGG_WRAP_BOX_SPREAD_BEGIN,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:minimum-length:
*
* The minimum number of children to allocate consecutively in the given orientation.
*
* <note><para>Setting the minimum wrap length ensures
* that a reasonably small height will be requested
* for the overall minimum width of the box.</para></note>
*
*/
g_object_class_install_property (gobject_class,
PROP_MINIMUM_LENGTH,
g_param_spec_uint ("minimum-length",
_("Minimum Length"),
_("The minimum number of children to allocate "
"consecutively in the given orientation."),
0,
65535,
0,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:natural-length:
*
* The maximum amount of children to request space for consecutively in the given orientation.
*
*/
g_object_class_install_property (gobject_class,
PROP_NATURAL_LENGTH,
g_param_spec_uint ("natural-length",
_("Natural Length"),
_("The maximum amount of children to request space for "
"consecutively in the given orientation."),
0,
65535,
0,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:vertical-spacing:
*
* The amount of vertical space between two children.
*
*/
g_object_class_install_property (gobject_class,
PROP_VERTICAL_SPACING,
g_param_spec_uint ("vertical-spacing",
_("Vertical spacing"),
_("The amount of vertical space between two children"),
0,
65535,
0,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:horizontal-spacing:
*
* The amount of horizontal space between two children.
*
*/
g_object_class_install_property (gobject_class,
PROP_HORIZONTAL_SPACING,
g_param_spec_uint ("horizontal-spacing",
_("Horizontal spacing"),
_("The amount of horizontal space between two children"),
0,
65535,
0,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/* GtkContainerClass child properties */
/**
* EggWrapBox:x-expand:
*
* Whether the child expands horizontally.
*
*/
gtk_container_class_install_child_property (container_class,
CHILD_PROP_X_EXPAND,
g_param_spec_boolean
("x-expand",
_("Horizontally expand"),
_("Whether the child expands horizontally."),
FALSE,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:x-fill:
*
* Whether the child fills its allocated horizontal space.
*
*/
gtk_container_class_install_child_property (container_class,
CHILD_PROP_X_FILL,
g_param_spec_boolean
("x-fill",
_("Horizontally fills"),
_("Whether the child fills its allocated horizontal space."),
FALSE,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:y-expand:
*
* Whether the child expands vertically.
*
*/
gtk_container_class_install_child_property (container_class,
CHILD_PROP_Y_EXPAND,
g_param_spec_boolean
("y-expand",
_("Vertically expand"),
_("Whether the child expands vertically."),
FALSE,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:y-fill:
*
* Whether the child fills its allocated vertical space.
*
*/
gtk_container_class_install_child_property (container_class,
CHILD_PROP_Y_FILL,
g_param_spec_boolean
("y-fill",
_("Vertically fills"),
_("Whether the child fills its allocated vertical space."),
FALSE,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:x-padding:
*
* Extra space to put between the child and its left and right neighbors, in pixels.
*
*/
gtk_container_class_install_child_property (container_class,
CHILD_PROP_X_PADDING,
g_param_spec_uint
("x-padding",
_("Horizontal padding"),
_("Extra space to put between the child and "
"its left and right neighbors, in pixels"),
0, 65535, 0,
G_PARAM_READABLE | G_PARAM_WRITABLE));
/**
* EggWrapBox:y-padding:
*
* Extra space to put between the child and its upper and lower neighbors, in pixels.
*
*/
gtk_container_class_install_child_property (container_class,
CHILD_PROP_Y_PADDING,
g_param_spec_uint
("y-padding",
_("Vertical padding"),
_("Extra space to put between the child and "
"its upper and lower neighbors, in pixels"),
0, 65535, 0,
G_PARAM_READABLE | G_PARAM_WRITABLE));
g_type_class_add_private (class, sizeof (EggWrapBoxPriv));
}
static void
egg_wrap_box_init (EggWrapBox *box)
{
EggWrapBoxPriv *priv;
box->priv = priv =
G_TYPE_INSTANCE_GET_PRIVATE (box, EGG_TYPE_WRAP_BOX, EggWrapBoxPriv);
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
priv->mode = EGG_WRAP_ALLOCATE_FREE;
priv->spreading = EGG_WRAP_BOX_SPREAD_BEGIN;
priv->vertical_spacing = 0;
priv->horizontal_spacing = 0;
gtk_widget_set_has_window (GTK_WIDGET (box), FALSE);
}
/*****************************************************
* GObectClass *
*****************************************************/
static void
egg_wrap_box_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EggWrapBox *box = EGG_WRAP_BOX (object);
EggWrapBoxPriv *priv = box->priv;
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_boolean (value, priv->orientation);
break;
case PROP_ALLOCATION_MODE:
g_value_set_int (value, priv->mode);
break;
case PROP_SPREADING:
g_value_set_int (value, priv->spreading);
break;
case PROP_VERTICAL_SPACING:
g_value_set_uint (value, priv->vertical_spacing);
break;
case PROP_HORIZONTAL_SPACING:
g_value_set_uint (value, priv->horizontal_spacing);
break;
case PROP_MINIMUM_LENGTH:
g_value_set_uint (value, priv->minimum_length);
break;
case PROP_NATURAL_LENGTH:
g_value_set_uint (value, priv->natural_length);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
egg_wrap_box_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EggWrapBox *box = EGG_WRAP_BOX (object);
EggWrapBoxPriv *priv = box->priv;
switch (prop_id)
{
case PROP_ORIENTATION:
priv->orientation = g_value_get_enum (value);
/* Re-box the children in the new orientation */
gtk_widget_queue_resize (GTK_WIDGET (box));
break;
case PROP_ALLOCATION_MODE:
egg_wrap_box_set_allocation_mode (box, g_value_get_int (value));
break;
case PROP_SPREADING:
egg_wrap_box_set_spreading (box, g_value_get_int (value));
break;
case PROP_VERTICAL_SPACING:
egg_wrap_box_set_vertical_spacing (box, g_value_get_uint (value));
break;
case PROP_HORIZONTAL_SPACING:
egg_wrap_box_set_horizontal_spacing (box, g_value_get_uint (value));
break;
case PROP_MINIMUM_LENGTH:
egg_wrap_box_set_minimum_length (box, g_value_get_uint (value));
break;
case PROP_NATURAL_LENGTH:
egg_wrap_box_set_natural_length (box, g_value_get_uint (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************
* GtkWidgetClass *
*****************************************************/
static gint
get_visible_children (EggWrapBox *box)
{
EggWrapBoxPriv *priv = box->priv;
GList *list;
gint i = 0;
for (list = priv->children; list; list = list->next)
{
EggWrapBoxChild *child = list->data;
if (!gtk_widget_get_visible (child->widget))
continue;
i++;
}
return i;
}
static gint
get_visible_expand_children (EggWrapBox *box,
GtkOrientation orientation,
GList *cursor,
gint n_visible)
{
GList *list;
gint i, expand_children = 0;
for (i = 0, list = cursor; (n_visible > 0 ? i < n_visible : TRUE) && list; list = list->next)
{
EggWrapBoxChild *child = list->data;
if (!gtk_widget_get_visible (child->widget))
continue;
if ((orientation == GTK_ORIENTATION_HORIZONTAL && child->xexpand) ||
(orientation == GTK_ORIENTATION_VERTICAL && child->yexpand))
expand_children++;
i++;
}
return expand_children;
}
/* Used in columned modes where all items share at least their
* equal widths or heights
*/
static void
get_average_item_size (EggWrapBox *box,
GtkOrientation orientation,
gint *min_size,
gint *nat_size)
{
EggWrapBoxPriv *priv = box->priv;
GList *list;
gint max_min_size = 0;
gint max_nat_size = 0;
for (list = priv->children; list; list = list->next)
{
EggWrapBoxChild *child = list->data;
gint child_min, child_nat;
if (!gtk_widget_get_visible (child->widget))
continue;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_width (GTK_SIZE_REQUEST (child->widget),
&child_min, &child_nat);
child_min += child->xpadding * 2;
child_nat += child->xpadding * 2;
}
else
{
gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget),
&child_min, &child_nat);
child_min += child->ypadding * 2;
child_nat += child->ypadding * 2;
}
max_min_size = MAX (max_min_size, child_min);
max_nat_size = MAX (max_nat_size, child_nat);
}
if (min_size)
*min_size = max_min_size;
if (nat_size)
*nat_size = max_nat_size;
}
/* Gets the largest minimum/natural size for a given size
* (used to get the largest item heights for a fixed item width and the opposite) */
static void
get_largest_size_for_opposing_orientation (EggWrapBox *box,
GtkOrientation orientation,
gint item_size,
gint *min_item_size,
gint *nat_item_size)
{
EggWrapBoxPriv *priv = box->priv;
GList *list;
gint max_min_size = 0;
gint max_nat_size = 0;
for (list = priv->children; list; list = list->next)
{
EggWrapBoxChild *child = list->data;
gint child_min, child_nat;
if (!gtk_widget_get_visible (child->widget))
continue;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (child->widget),
item_size - child->xpadding * 2,
&child_min, &child_nat);
child_min += child->ypadding * 2;
child_nat += child->ypadding * 2;
}
else
{
gtk_size_request_get_width_for_height (GTK_SIZE_REQUEST (child->widget),
item_size - child->ypadding * 2,
&child_min, &child_nat);
child_min += child->xpadding * 2;
child_nat += child->xpadding * 2;
}
max_min_size = MAX (max_min_size, child_min);
max_nat_size = MAX (max_nat_size, child_nat);
}
if (min_item_size)
*min_item_size = max_min_size;
if (nat_item_size)
*nat_item_size = max_nat_size;
}
/* Gets the largest minimum/natural size on a single line for a given size
* (used to get the largest line heights for a fixed item width and the opposite
* while itterating over a list of children, note the new index is returned) */
GList *
get_largest_size_for_line_in_opposing_orientation (EggWrapBox *box,
GtkOrientation orientation,
GList *cursor,
gint line_length,
gint item_size,
gint extra_pixels,
gint *min_item_size,
gint *nat_item_size)
{
EggWrapBoxPriv *priv = box->priv;
GList *list;
gint max_min_size = 0;
gint max_nat_size = 0;
gint i;
for (list = cursor, i = 0; list && i < line_length; list = list->next)
{
EggWrapBoxChild *child = list->data;
gint child_min, child_nat, this_item_size;
if (!gtk_widget_get_visible (child->widget))
continue;
/* Distribute the extra pixels to the first children in the line
* (could be fancier and spread them out more evenly) */
this_item_size = item_size;
if (extra_pixels > 0 &&
priv->spreading == EGG_WRAP_BOX_SPREAD_EXPAND)
{
this_item_size++;
extra_pixels--;
}
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (child->widget),
this_item_size - child->xpadding * 2,
&child_min, &child_nat);
child_min += child->ypadding * 2;
child_nat += child->ypadding * 2;
}
else
{
gtk_size_request_get_width_for_height (GTK_SIZE_REQUEST (child->widget),
this_item_size - child->ypadding * 2,
&child_min, &child_nat);
child_min += child->xpadding * 2;
child_nat += child->xpadding * 2;
}
max_min_size = MAX (max_min_size, child_min);
max_nat_size = MAX (max_nat_size, child_nat);
i++;
}
if (min_item_size)
*min_item_size = max_min_size;
if (nat_item_size)
*nat_item_size = max_nat_size;
/* Return next item in the list */
return list;
}
/* Gets the largest minimum/natural size on a single line for a given allocated line size
* (used to get the largest line heights for a width in pixels and the opposite
* while itterating over a list of children, note the new index is returned) */
GList *
get_largest_size_for_free_line_in_opposing_orientation (EggWrapBox *box,
GtkOrientation orientation,
GList *cursor,
gint min_items,
gint avail_size,
gint *min_item_size,
gint *nat_item_size,
gint *extra_pixels,
GArray **ret_array)
{
EggWrapBoxPriv *priv = box->priv;
GtkRequestedSize *sizes;
GList *list;
GArray *array;
gint max_min_size = 0;
gint max_nat_size = 0;
gint i, size = avail_size;
gint line_length, spacing;
gint expand_children = 0;
gint expand_per_child;
gint expand_remainder;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
spacing = priv->horizontal_spacing;
else
spacing = priv->vertical_spacing;
/* First determine the length of this line in items (how many items fit) */
for (i = 0, list = cursor; size > 0 && list; list = list->next)
{
EggWrapBoxChild *child = list->data;
gint child_size;
if (!gtk_widget_get_visible (child->widget))
continue;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_width (GTK_SIZE_REQUEST (child->widget),
NULL, &child_size);
child_size += child->xpadding * 2;
}
else
{
gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget),
NULL, &child_size);
child_size += child->ypadding * 2;
}
if (i > 0)
child_size += spacing;
if (size - child_size >= 0)
size -= child_size;
else
break;
i++;
}
line_length = MAX (min_items, i);
size = avail_size;
/* Collect the sizes of the items on this line */
array = g_array_new (0, TRUE, sizeof (GtkRequestedSize));
for (i = 0, list = cursor; i < line_length && list; list = list->next)
{
EggWrapBoxChild *child = list->data;
GtkRequestedSize requested;
if (!gtk_widget_get_visible (child->widget))
continue;
requested.data = child;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_width (GTK_SIZE_REQUEST (child->widget),
&requested.minimum_size,
&requested.natural_size);
size -= child->xpadding * 2;
}
else
{
gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget),
&requested.minimum_size,
&requested.natural_size);
size -= child->ypadding * 2;
}
if (i > 0)
size -= spacing;
size -= requested.minimum_size;
g_array_append_val (array, requested);
i++;
}
sizes = (GtkRequestedSize *)array->data;
size = gtk_distribute_natural_allocation (size, array->len, sizes);
if (extra_pixels)
*extra_pixels = size;
/* Cut out any expand space if we're not distributing any */
if (priv->spreading != EGG_WRAP_BOX_SPREAD_EXPAND)
size = 0;
/* Count how many children are going to expand... */
expand_children = get_visible_expand_children (box, orientation,
cursor, line_length);
/* If no child prefers to expand, they all get some expand space */
if (expand_children == 0)
{
expand_per_child = size / line_length;
expand_remainder = size % line_length;
}
else
{
expand_per_child = size / expand_children;
expand_remainder = size % expand_children;
}
/* Now add the remaining expand space and get the collective size of this line
* in the opposing orientation */
for (i = 0, list = cursor; i < line_length && list; list = list->next)
{
EggWrapBoxChild *child = list->data;
gint child_min, child_nat;
if (!gtk_widget_get_visible (child->widget))
continue;
g_assert (child == sizes[i].data);
if ((orientation == GTK_ORIENTATION_HORIZONTAL && child->xexpand) ||
(orientation == GTK_ORIENTATION_VERTICAL && child->yexpand) ||
expand_children == 0)
{
sizes[i].minimum_size += expand_per_child;
if (expand_remainder)
{
sizes[i].minimum_size++;
expand_remainder--;
}
}
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (child->widget),
sizes[i].minimum_size,
&child_min, &child_nat);
child_min += child->ypadding * 2;
child_nat += child->ypadding * 2;
}
else
{
gtk_size_request_get_width_for_height (GTK_SIZE_REQUEST (child->widget),
sizes[i].minimum_size,
&child_min, &child_nat);
child_min += child->xpadding * 2;
child_nat += child->xpadding * 2;
}
max_min_size = MAX (max_min_size, child_min);
max_nat_size = MAX (max_nat_size, child_nat);
i++;
}
if (ret_array)
*ret_array = array;
else
g_array_free (array, TRUE);
if (min_item_size)
*min_item_size = max_min_size;
if (nat_item_size)
*nat_item_size = max_nat_size;
/* Return the next item */
return list;
}
static void
allocate_child (EggWrapBox *box,
EggWrapBoxChild *child,
gint item_offset,
gint line_offset,
gint item_size,
gint line_size)
{
EggWrapBoxPriv *priv = box->priv;
GtkAllocation widget_allocation;
GtkAllocation child_allocation;
GtkSizeRequestMode request_mode;
gtk_widget_get_allocation (GTK_WIDGET (box), &widget_allocation);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.x = widget_allocation.x + item_offset + child->xpadding;
child_allocation.y = widget_allocation.y + line_offset + child->ypadding;
child_allocation.width = item_size - child->xpadding * 2;
child_allocation.height = line_size - child->ypadding * 2;
}
else /* GTK_ORIENTATION_VERTICAL */
{
child_allocation.x = widget_allocation.x + line_offset + child->xpadding;
child_allocation.y = widget_allocation.y + item_offset + child->ypadding;
child_allocation.width = line_size - child->xpadding * 2;
child_allocation.height = item_size - child->ypadding * 2;
}
request_mode = gtk_size_request_get_request_mode (GTK_SIZE_REQUEST (child->widget));
if (!child->xfill)
{
gint width, height;
if (!child->yfill && request_mode == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
{
gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget), NULL, &height);
height = MIN (child_allocation.height, height);
}
else
height = child_allocation.height;
if (request_mode == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
gtk_size_request_get_width_for_height (GTK_SIZE_REQUEST (child->widget),
height, NULL, &width);
else
gtk_size_request_get_width (GTK_SIZE_REQUEST (child->widget), NULL, &width);
width = MIN (child_allocation.width, width);
child_allocation.x = child_allocation.x + (child_allocation.width - width) / 2;
child_allocation.width = width;
}
if (!child->yfill)
{
gint height;
/* Note here child_allocation.width is already changed if (!child->xfill) */
if (request_mode == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
gtk_size_request_get_height_for_width (GTK_SIZE_REQUEST (child->widget),
child_allocation.width, NULL, &height);
else
gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget), NULL, &height);
height = MIN (child_allocation.height, height);
child_allocation.y = child_allocation.y + (child_allocation.height - height) / 2;
child_allocation.height = height;
}
gtk_widget_size_allocate (child->widget, &child_allocation);
}
typedef struct {
GArray *requested;
gint extra_pixels;
} AllocatedLine;
static void
egg_wrap_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
EggWrapBox *box = EGG_WRAP_BOX (widget);
EggWrapBoxPriv *priv = box->priv;
GtkRequestedSize *sizes;
GArray *array;
guint border_width;
gint avail_size, avail_other_size, min_items, item_spacing, line_spacing;
gtk_widget_set_allocation (widget, allocation);
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
min_items = MAX (1, priv->minimum_length);
/* Collect the line sizes for EGG_WRAP_ALLOCATE_FREE and
* EGG_WRAP_ALLOCATE_ALIGNED modes */
array = g_array_new (0, TRUE, sizeof (GtkRequestedSize));
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
avail_size = allocation->width - border_width * 2;
avail_other_size = allocation->height - border_width * 2;
item_spacing = priv->horizontal_spacing;
line_spacing = priv->vertical_spacing;
}
else /* GTK_ORIENTATION_VERTICAL */
{
avail_size = allocation->height - border_width * 2;
avail_other_size = allocation->width - border_width * 2;
item_spacing = priv->vertical_spacing;
line_spacing = priv->horizontal_spacing;
}
if (priv->mode == EGG_WRAP_ALLOCATE_ALIGNED ||
priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
{
GList *list;
gint min_item_size, nat_item_size;
gint line_length;
gint item_size;
gint line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0;
gint line_offset, n_children, n_lines, line_count;
gint extra_pixels, extra_per_item, extra_extra;
gint i;
get_average_item_size (box, priv->orientation, &min_item_size, &nat_item_size);
/* By default wrap at the natural item width */
line_length = avail_size / (nat_item_size + item_spacing);
/* After the above aproximation, check if we cant fit one more on the line */
if (line_length * item_spacing + (line_length + 1) * nat_item_size <= avail_size)
line_length++;
/* Its possible we were allocated just less than the natural width of the
* minimum item wrap length */
line_length = MAX (min_items, line_length);
/* Now we need the real item allocation size */
item_size = (avail_size - (line_length - 1) * item_spacing) / line_length;
/* Cut out the expand space if we're not distributing any */
if (priv->spreading != EGG_WRAP_BOX_SPREAD_EXPAND)
item_size = MIN (item_size, nat_item_size);
/* Get the real extra pixels incase of EGG_WRAP_BOX_SPREAD_BEGIN lines */
extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length;
extra_per_item = extra_pixels / MAX (line_length -1, 1);
extra_extra = extra_pixels % MAX (line_length -1, 1);
/* Get how many lines that wraps to */
n_children = get_visible_children (box);
n_lines = n_children / line_length;
if ((n_children % line_length) > 0)
n_lines++;
n_lines = MAX (n_lines, 1);
/* Here we just use the largest height-for-width and use that for the height
* of all lines */
if (priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
{
get_largest_size_for_opposing_orientation (box, priv->orientation, item_size,
&min_fixed_line_size,
&nat_fixed_line_size);
/* resolve a fixed 'line_size' */
line_size = (avail_other_size - (n_lines - 1) * line_spacing) / n_lines;
line_size = MIN (line_size, nat_fixed_line_size);
}
else /* EGG_WRAP_ALLOCATE_ALIGNED */
{
/* spread out the available size in the opposing orientation into an array of
* lines (and then allocate those lines naturally) */
GList *list;
gboolean first_line = TRUE;
/* In ALIGNED mode, all items have the same size in the box's orientation except
* individual lines may have a different size */
for (i = 0, list = priv->children; list != NULL; i++)
{
GtkRequestedSize requested;
list =
get_largest_size_for_line_in_opposing_orientation (box, priv->orientation,
list, line_length,
item_size, extra_pixels,
&requested.minimum_size,
&requested.natural_size);
/* Its possible a line is made of completely invisible children */
if (requested.natural_size > 0)
{
if (first_line)
first_line = FALSE;
else
avail_other_size -= line_spacing;
avail_other_size -= requested.minimum_size;
requested.data = GINT_TO_POINTER (i);
g_array_append_val (array, requested);
}
}
/* Distribute space among lines naturally */
sizes = (GtkRequestedSize *)array->data;
avail_other_size = gtk_distribute_natural_allocation (avail_other_size, array->len, sizes);
}
line_offset = border_width;
for (i = 0, line_count = 0, list = priv->children; list; list = list->next)
{
EggWrapBoxChild *child = list->data;
gint position, this_line_size, item_offset;
gint this_item_size;
if (!gtk_widget_get_visible (child->widget))
continue;
/* Get item position */
position = i % line_length;
/* adjust the line_offset/count at the beginning of each new line */
if (i > 0 && position == 0)
{
if (priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
line_offset += line_size + line_spacing;
else /* aligned mode */
line_offset += sizes[line_count].minimum_size + line_spacing;
line_count++;
}
/* We could be smarter here and distribute the extra pixels more
* evenly across the children */
if (position < extra_pixels && priv->spreading == EGG_WRAP_BOX_SPREAD_EXPAND)
this_item_size = item_size + 1;
else
this_item_size = item_size;
/* Push the index along for the last line when spreading to the end */
if (priv->spreading == EGG_WRAP_BOX_SPREAD_END &&
line_count == n_lines -1)
{
gint extra_items = n_children % line_length;
position += line_length - extra_items;
}
item_offset = border_width + (position * item_size) + (position * item_spacing);
if (priv->spreading == EGG_WRAP_BOX_SPREAD_EVEN)
{
item_offset += position * extra_per_item;
item_offset += MIN (position, extra_extra);
}
else if (priv->spreading == EGG_WRAP_BOX_SPREAD_END)
item_offset += extra_pixels;
else if (priv->spreading == EGG_WRAP_BOX_SPREAD_EXPAND)
item_offset += MIN (position, extra_pixels);
/* Get the allocation size for this line */
if (priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
this_line_size = line_size;
else
this_line_size = sizes[line_count].minimum_size;
/* Do the actual allocation */
allocate_child (box, child, item_offset, line_offset, this_item_size, this_line_size);
i++;
}
}
else /* EGG_WRAP_ALLOCATE_FREE */
{
/* Here we just fit as many children as we can allocate their natural size to
* on each line and add the heights for each of them on each line */
GtkRequestedSize requested;
GList *list = priv->children;
gboolean first_line = TRUE;
gint i, line_count = 0;
gint line_offset, item_offset;
gint extra_pixels;
while (list != NULL)
{
GArray *line_array;
AllocatedLine *line;
list =
get_largest_size_for_free_line_in_opposing_orientation (box, priv->orientation,
list, min_items, avail_size,
&requested.minimum_size,
&requested.natural_size,
&extra_pixels,
&line_array);
/* Its possible a line is made of completely invisible children */
if (requested.natural_size > 0)
{
if (first_line)
first_line = FALSE;
else
avail_other_size -= line_spacing;
avail_other_size -= requested.minimum_size;
line = g_slice_new0 (AllocatedLine);
line->requested = line_array;
line->extra_pixels = extra_pixels;
requested.data = line;
g_array_append_val (array, requested);
}
}
/* Distribute space among lines naturally, dont give lines expand space just let them
* unwrap/wrap in and out of the allocated extra space */
sizes = (GtkRequestedSize *)array->data;
avail_other_size = gtk_distribute_natural_allocation (avail_other_size, array->len, sizes);
for (line_offset = border_width, line_count = 0; line_count < array->len; line_count++)
{
AllocatedLine *line = (AllocatedLine *)sizes[line_count].data;
GArray *line_array = line->requested;
GtkRequestedSize *line_sizes = (GtkRequestedSize *)line_array->data;
gint line_size = sizes[line_count].minimum_size;
gint extra_per_item;
gint extra_extra;
/* Set line start offset */
item_offset = border_width;
if (priv->spreading == EGG_WRAP_BOX_SPREAD_END)
item_offset += line->extra_pixels;
else if (priv->spreading == EGG_WRAP_BOX_SPREAD_EVEN)
{
extra_per_item = line->extra_pixels / MAX (line_array->len -1, 1);
extra_extra = line->extra_pixels % MAX (line_array->len -1, 1);
}
for (i = 0; i < line_array->len; i++)
{
EggWrapBoxChild *child = line_sizes[i].data;
gint item_size = line_sizes[i].minimum_size;
item_size += (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ?
child->xpadding * 2 : child->ypadding * 2;
/* Do the actual allocation */
allocate_child (box, child, item_offset, line_offset, item_size, line_size);
/* Add extra space evenly between children */
if (priv->spreading == EGG_WRAP_BOX_SPREAD_EVEN)
{
item_offset += extra_per_item;
if (i < extra_extra)
item_offset++;
}
/* Move item cursor along for the next allocation */
item_offset += item_spacing;
item_offset += item_size;
}
/* New line, increment offset and reset item cursor */
line_offset += line_spacing;
line_offset += line_size;
/* Free the array for this line now its not needed anymore */
g_array_free (line_array, TRUE);
g_slice_free (AllocatedLine, line);
}
}
g_array_free (array, TRUE);
}
/*****************************************************
* GtkContainerClass *
*****************************************************/
static void
egg_wrap_box_add (GtkContainer *container,
GtkWidget *widget)
{
egg_wrap_box_insert_child (EGG_WRAP_BOX (container), widget, -1,
0, 0, TRUE, TRUE, TRUE, TRUE);
}
static gint
find_child_in_list (EggWrapBoxChild *child_in_list,
GtkWidget *search)
{
return (child_in_list->widget == search) ? 0 : -1;
}
static void
egg_wrap_box_remove (GtkContainer *container,
GtkWidget *widget)
{
EggWrapBox *box = EGG_WRAP_BOX (container);
EggWrapBoxPriv *priv = box->priv;
GList *list;
list = g_list_find_custom (priv->children, widget,
(GCompareFunc)find_child_in_list);
if (list)
{
EggWrapBoxChild *child = list->data;
gboolean was_visible = gtk_widget_get_visible (widget);
gtk_widget_unparent (widget);
g_slice_free (EggWrapBoxChild, child);
priv->children = g_list_delete_link (priv->children, list);
if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container)))
gtk_widget_queue_resize (GTK_WIDGET (container));
}
}
static void
egg_wrap_box_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data)
{
EggWrapBox *box = EGG_WRAP_BOX (container);
EggWrapBoxPriv *priv = box->priv;
EggWrapBoxChild *child;
GList *list;
for (list = priv->children; list; list = list->next)
{
child = list->data;
(* callback) (child->widget, callback_data);
}
}
static GType
egg_wrap_box_child_type (GtkContainer *container)
{
return GTK_TYPE_WIDGET;
}
static void
egg_wrap_box_set_child_property (GtkContainer *container,
GtkWidget *widget,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
EggWrapBox *box = EGG_WRAP_BOX (container);
EggWrapBoxPriv *priv = box->priv;
EggWrapBoxChild *child;
GList *list;
list = g_list_find_custom (priv->children, widget,
(GCompareFunc)find_child_in_list);
g_return_if_fail (list != NULL);
child = list->data;
switch (property_id)
{
case CHILD_PROP_X_EXPAND:
child->xexpand = g_value_get_boolean (value);
break;
case CHILD_PROP_X_FILL:
child->xfill = g_value_get_boolean (value);
break;
case CHILD_PROP_Y_EXPAND:
child->yexpand = g_value_get_boolean (value);
break;
case CHILD_PROP_Y_FILL:
child->yfill = g_value_get_boolean (value);
break;
case CHILD_PROP_X_PADDING:
child->xpadding = g_value_get_uint (value);
break;
case CHILD_PROP_Y_PADDING:
child->ypadding = g_value_get_uint (value);
break;
default:
GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
break;
}
if (gtk_widget_get_visible (widget) &&
gtk_widget_get_visible (GTK_WIDGET (box)))
gtk_widget_queue_resize (widget);
}
static void
egg_wrap_box_get_child_property (GtkContainer *container,
GtkWidget *widget,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
EggWrapBox *box = EGG_WRAP_BOX (container);
EggWrapBoxPriv *priv = box->priv;
EggWrapBoxChild *child;
GList *list;
list = g_list_find_custom (priv->children, widget,
(GCompareFunc)find_child_in_list);
g_return_if_fail (list != NULL);
child = list->data;
switch (property_id)
{
case CHILD_PROP_X_EXPAND:
g_value_set_boolean (value, child->xexpand);
break;
case CHILD_PROP_X_FILL:
g_value_set_boolean (value, child->xfill);
break;
case CHILD_PROP_Y_EXPAND:
g_value_set_boolean (value, child->yexpand);
break;
case CHILD_PROP_Y_FILL:
g_value_set_boolean (value, child->yfill);
break;
case CHILD_PROP_X_PADDING:
g_value_set_uint (value, child->xpadding);
break;
case CHILD_PROP_Y_PADDING:
g_value_set_uint (value, child->ypadding);
break;
default:
GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
break;
}
}
/*****************************************************
* GtkSizeRequestIface *
*****************************************************/
static void
egg_wrap_box_size_request_init (GtkSizeRequestIface *iface)
{
iface->get_request_mode = egg_wrap_box_get_request_mode;
iface->get_width = egg_wrap_box_get_width;
iface->get_height = egg_wrap_box_get_height;
iface->get_height_for_width = egg_wrap_box_get_height_for_width;
iface->get_width_for_height = egg_wrap_box_get_width_for_height;
}
static GtkSizeRequestMode
egg_wrap_box_get_request_mode (GtkSizeRequest *widget)
{
EggWrapBox *box = EGG_WRAP_BOX (widget);
EggWrapBoxPriv *priv = box->priv;
return (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ?
GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
}
/* Gets the largest minimum and natural length of
* 'line_length' consecutive items */
static void
get_largest_line_length (EggWrapBox *box,
GtkOrientation orientation,
gint line_length,
gint *min_size,
gint *nat_size)
{
EggWrapBoxPriv *priv = box->priv;
GList *list;
gint max_min_size = 0;
gint max_nat_size = 0;
gint spacing;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
spacing = priv->horizontal_spacing;
else
spacing = priv->vertical_spacing;
/* Get the largest size of 'line_length' consecutive items in the list.
*/
for (list = priv->children; list; list = list->next)
{
GList *l;
gint line_min = 0;
gint line_nat = 0;
gint i;
for (l = list, i = 0; l && i < line_length; l = l->next)
{
EggWrapBoxChild *child = list->data;
gint child_min, child_nat;
if (!gtk_widget_get_visible (child->widget))
continue;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_size_request_get_width (GTK_SIZE_REQUEST (child->widget),
&child_min, &child_nat);
child_min += child->xpadding * 2;
child_nat += child->xpadding * 2;
}
else /* GTK_ORIENTATION_VERTICAL */
{
gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget),
&child_min, &child_nat);
child_min += child->ypadding * 2;
child_nat += child->ypadding * 2;
}
if (i > 0)
{
line_min += spacing;
line_nat += spacing;
}
line_min += child_min;
line_nat += child_nat;
i++;
}
max_min_size = MAX (max_min_size, line_min);
max_nat_size = MAX (max_nat_size, line_nat);
}
if (min_size)
*min_size = max_min_size;
if (nat_size)
*nat_size = max_nat_size;
}
static void
egg_wrap_box_get_width (GtkSizeRequest *widget,
gint *minimum_size,
gint *natural_size)
{
EggWrapBox *box = EGG_WRAP_BOX (widget);
EggWrapBoxPriv *priv = box->priv;
guint border_width;
gint min_item_width, nat_item_width;
gint min_items, nat_items;
gint min_width, nat_width;
min_items = MAX (1, priv->minimum_length);
nat_items = MAX (min_items, priv->natural_length);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
min_width = nat_width = border_width * 2;
if (priv->mode == EGG_WRAP_ALLOCATE_FREE)
{
if (priv->minimum_length <= 1)
{
get_average_item_size (box, GTK_ORIENTATION_HORIZONTAL,
&min_item_width, &nat_item_width);
min_width += min_item_width;
nat_width += nat_item_width;
}
else
{
gint min_line_length, nat_line_length;
get_largest_line_length (box, GTK_ORIENTATION_HORIZONTAL, min_items,
&min_line_length, &nat_line_length);
if (nat_items > min_items)
get_largest_line_length (box, GTK_ORIENTATION_HORIZONTAL, nat_items,
NULL, &nat_line_length);
min_width += min_line_length;
nat_width += nat_line_length;
}
}
else /* In ALIGNED or HOMOGENEOUS modes; horizontally oriented boxs
* give the same width to all children */
{
get_average_item_size (box, GTK_ORIENTATION_HORIZONTAL,
&min_item_width, &nat_item_width);
min_width += min_item_width * min_items;
min_width += (min_items -1) * priv->horizontal_spacing;
nat_width += nat_item_width * nat_items;
nat_width += (nat_items -1) * priv->horizontal_spacing;
}
}
else /* GTK_ORIENTATION_VERTICAL */
{
/* Return the width for the minimum height */
gint min_height;
gtk_size_request_get_height (widget, &min_height, NULL);
gtk_size_request_get_width_for_height (widget, min_height, &min_width, &nat_width);
}
if (minimum_size)
*minimum_size = min_width;
if (natural_size)
*natural_size = nat_width;
}
static void
egg_wrap_box_get_height (GtkSizeRequest *widget,
gint *minimum_size,
gint *natural_size)
{
EggWrapBox *box = EGG_WRAP_BOX (widget);
EggWrapBoxPriv *priv = box->priv;
guint border_width;
gint min_item_height, nat_item_height;
gint min_items, nat_items;
gint min_height, nat_height;
min_items = MAX (1, priv->minimum_length);
nat_items = MAX (min_items, priv->natural_length);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
/* Return the height for the minimum width */
gint min_width;
gtk_size_request_get_width (widget, &min_width, NULL);
gtk_size_request_get_height_for_width (widget, min_width, &min_height, &nat_height);
}
else /* GTK_ORIENTATION_VERTICAL */
{
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
min_height = nat_height = border_width * 2;
if (priv->mode == EGG_WRAP_ALLOCATE_FREE)
{
if (priv->minimum_length <= 1)
{
get_average_item_size (box, GTK_ORIENTATION_VERTICAL,
&min_item_height, &nat_item_height);
min_height += min_item_height;
nat_height += nat_item_height;
}
else
{
gint min_line_length, nat_line_length;
get_largest_line_length (box, GTK_ORIENTATION_VERTICAL, min_items,
&min_line_length, &nat_line_length);
if (nat_items > min_items)
get_largest_line_length (box, GTK_ORIENTATION_VERTICAL, nat_items,
NULL, &nat_line_length);
min_height += min_line_length;
nat_height += nat_line_length;
}
}
else /* In ALIGNED or HOMOGENEOUS modes; horizontally oriented boxs
* give the same width to all children */
{
get_average_item_size (box, GTK_ORIENTATION_VERTICAL,
&min_item_height, &nat_item_height);
min_height += min_item_height * min_items;
min_height += (min_items -1) * priv->vertical_spacing;
nat_height += nat_item_height * nat_items;
nat_height += (nat_items -1) * priv->vertical_spacing;
}
}
if (minimum_size)
*minimum_size = min_height;
if (natural_size)
*natural_size = nat_height;
}
static void
egg_wrap_box_get_height_for_width (GtkSizeRequest *widget,
gint width,
gint *minimum_height,
gint *natural_height)
{
EggWrapBox *box = EGG_WRAP_BOX (widget);
EggWrapBoxPriv *priv = box->priv;
guint border_width;
gint min_item_width, nat_item_width;
gint min_items;
gint min_height, nat_height;
gint avail_size;
min_items = MAX (1, priv->minimum_length);
min_height = 0;
nat_height = 0;
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
gint min_width;
/* Make sure its no smaller than the minimum */
gtk_size_request_get_width (widget, &min_width, NULL);
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
avail_size = MAX (width, min_width);
avail_size -= border_width * 2;
if (priv->mode == EGG_WRAP_ALLOCATE_ALIGNED ||
priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
{
gint line_length;
gint item_size, extra_pixels;
get_average_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width);
/* By default wrap at the natural item width */
line_length = avail_size / (nat_item_width + priv->horizontal_spacing);
/* After the above aproximation, check if we cant fit one more on the line */
if (line_length * priv->horizontal_spacing + (line_length + 1) * nat_item_width <= avail_size)
line_length++;
/* Its possible we were allocated just less than the natural width of the
* minimum item wrap length */
line_length = MAX (min_items, line_length);
/* Now we need the real item allocation size */
item_size = (avail_size - (line_length - 1) * priv->horizontal_spacing) / line_length;
/* Cut out the expand space if we're not distributing any */
if (priv->spreading != EGG_WRAP_BOX_SPREAD_EXPAND)
{
item_size = MIN (item_size, nat_item_width);
extra_pixels = 0;
}
else
/* Collect the extra pixels for expand children */
extra_pixels = (avail_size - (line_length - 1) * priv->horizontal_spacing) % line_length;
if (priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
{
gint min_item_height, nat_item_height;
gint lines, n_children;
/* Here we just use the largest height-for-width and
* add up the size accordingly */
get_largest_size_for_opposing_orientation (box, GTK_ORIENTATION_HORIZONTAL, item_size,
&min_item_height, &nat_item_height);
/* Round up how many lines we need to allocate for */
n_children = get_visible_children (box);
lines = n_children / line_length;
if ((n_children % line_length) > 0)
lines++;
min_height = min_item_height * lines;
nat_height = nat_item_height * lines;
min_height += (lines - 1) * priv->vertical_spacing;
nat_height += (lines - 1) * priv->vertical_spacing;
}
else /* EGG_WRAP_ALLOCATE_ALIGNED */
{
GList *list = priv->children;
gint min_line_height, nat_line_height;
gboolean first_line = TRUE;
/* In ALIGNED mode, all items have the same size in the box's orientation except
* individual rows may have a different size */
while (list != NULL)
{
list =
get_largest_size_for_line_in_opposing_orientation (box, GTK_ORIENTATION_HORIZONTAL,
list, line_length,
item_size, extra_pixels,
&min_line_height, &nat_line_height);
/* Its possible the line only had invisible widgets */
if (nat_line_height > 0)
{
if (first_line)
first_line = FALSE;
else
{
min_height += priv->vertical_spacing;
nat_height += priv->vertical_spacing;
}
min_height += min_line_height;
nat_height += nat_line_height;
}
}
}
}
else /* EGG_WRAP_ALLOCATE_FREE */
{
/* Here we just fit as many children as we can allocate their natural size to
* on each line and add the heights for each of them on each line */
GList *list = priv->children;
gint min_line_height = 0, nat_line_height = 0;
gboolean first_line = TRUE;
while (list != NULL)
{
list =
get_largest_size_for_free_line_in_opposing_orientation (box, GTK_ORIENTATION_HORIZONTAL,
list, min_items, avail_size,
&min_line_height, &nat_line_height,
NULL, NULL);
/* Its possible the last line only had invisible widgets */
if (nat_line_height > 0)
{
if (first_line)
first_line = FALSE;
else
{
min_height += priv->vertical_spacing;
nat_height += priv->vertical_spacing;
}
min_height += min_line_height;
nat_height += nat_line_height;
}
}
}
min_height += border_width * 2;
nat_height += border_width * 2;
}
else /* GTK_ORIENTATION_VERTICAL */
{
/* Return the minimum height */
gtk_size_request_get_height (widget, &min_height, &nat_height);
}
if (minimum_height)
*minimum_height = min_height;
if (natural_height)
*natural_height = nat_height;
}
static void
egg_wrap_box_get_width_for_height (GtkSizeRequest *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
EggWrapBox *box = EGG_WRAP_BOX (widget);
EggWrapBoxPriv *priv = box->priv;
guint border_width;
gint min_item_height, nat_item_height;
gint min_items;
gint min_width, nat_width;
gint avail_size;
min_items = MAX (1, priv->minimum_length);
min_width = 0;
nat_width = 0;
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
/* Return the minimum width */
gtk_size_request_get_width (widget, &min_width, &nat_width);
}
else /* GTK_ORIENTATION_VERTICAL */
{
gint min_height;
/* Make sure its no smaller than the minimum */
gtk_size_request_get_height (widget, &min_height, NULL);
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
avail_size = MAX (height, min_height);
avail_size -= border_width * 2;
if (priv->mode == EGG_WRAP_ALLOCATE_ALIGNED ||
priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
{
gint line_length;
gint item_size, extra_pixels;
get_average_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height);
/* By default wrap at the natural item width */
line_length = avail_size / (nat_item_height + priv->vertical_spacing);
/* After the above aproximation, check if we cant fit one more on the line */
if (line_length * priv->vertical_spacing + (line_length + 1) * nat_item_height <= avail_size)
line_length++;
/* Its possible we were allocated just less than the natural width of the
* minimum item wrap length */
line_length = MAX (min_items, line_length);
/* Now we need the real item allocation size */
item_size = (avail_size - (line_length - 1) * priv->vertical_spacing) / line_length;
/* Cut out the expand space if we're not distributing any */
if (priv->spreading != EGG_WRAP_BOX_SPREAD_EXPAND)
{
item_size = MIN (item_size, nat_item_height);
extra_pixels = 0;
}
else
/* Collect the extra pixels for expand children */
extra_pixels = (avail_size - (line_length - 1) * priv->vertical_spacing) % line_length;
if (priv->mode == EGG_WRAP_ALLOCATE_HOMOGENEOUS)
{
gint min_item_width, nat_item_width;
gint lines, n_children;
/* Here we just use the largest height-for-width and
* add up the size accordingly */
get_largest_size_for_opposing_orientation (box, GTK_ORIENTATION_VERTICAL, item_size,
&min_item_width, &nat_item_width);
/* Round up how many lines we need to allocate for */
n_children = get_visible_children (box);
lines = n_children / line_length;
if ((n_children % line_length) > 0)
lines++;
min_width = min_item_width * lines;
nat_width = nat_item_width * lines;
min_width += (lines - 1) * priv->horizontal_spacing;
nat_width += (lines - 1) * priv->horizontal_spacing;
}
else /* EGG_WRAP_ALLOCATE_ALIGNED */
{
GList *list = priv->children;
gint min_line_width, nat_line_width;
gboolean first_line = TRUE;
/* In ALIGNED mode, all items have the same size in the box's orientation except
* individual rows may have a different size */
while (list != NULL)
{
list =
get_largest_size_for_line_in_opposing_orientation (box, GTK_ORIENTATION_VERTICAL,
list, line_length,
item_size, extra_pixels,
&min_line_width, &nat_line_width);
/* Its possible the last line only had invisible widgets */
if (nat_line_width > 0)
{
if (first_line)
first_line = FALSE;
else
{
min_width += priv->horizontal_spacing;
nat_width += priv->horizontal_spacing;
}
min_width += min_line_width;
nat_width += nat_line_width;
}
}
}
}
else /* EGG_WRAP_ALLOCATE_FREE */
{
/* Here we just fit as many children as we can allocate their natural size to
* on each line and add the heights for each of them on each line */
GList *list = priv->children;
gint min_line_width = 0, nat_line_width = 0;
gboolean first_line = TRUE;
while (list != NULL)
{
list =
get_largest_size_for_free_line_in_opposing_orientation (box, GTK_ORIENTATION_VERTICAL,
list, min_items, avail_size,
&min_line_width, &nat_line_width,
NULL, NULL);
/* Its possible the last line only had invisible widgets */
if (nat_line_width > 0)
{
if (first_line)
first_line = FALSE;
else
{
min_width += priv->horizontal_spacing;
nat_width += priv->horizontal_spacing;
}
min_width += min_line_width;
nat_width += nat_line_width;
}
}
}
min_width += border_width * 2;
nat_width += border_width * 2;
}
if (minimum_width)
*minimum_width = min_width;
if (natural_width)
*natural_width = nat_width;
}
/*****************************************************
* API *
*****************************************************/
/**
* egg_wrap_box_new:
* @allocation_mode: The #EggWrapAllocationMode to use
* @spreading: The #EggWrapBoxSpreading policy to use
* @horizontal_spacing: The horizontal spacing to add between children
* @vertical_spacing: The vertical spacing to add between children
*
* Creates an #EggWrapBox.
*
* Returns: A new #EggWrapBox container
*/
GtkWidget *
egg_wrap_box_new (EggWrapAllocationMode mode,
EggWrapBoxSpreading spreading,
guint horizontal_spacing,
guint vertical_spacing)
{
return (GtkWidget *)g_object_new (EGG_TYPE_WRAP_BOX,
"allocation-mode", mode,
"spreading", spreading,
"vertical-spacing", vertical_spacing,
"horizontal-spacing", horizontal_spacing,
NULL);
}
/**
* egg_wrap_box_set_allocation_mode:
* @box: An #EggWrapBox
* @mode: The #EggWrapAllocationMode to use.
*
* Sets the allocation mode for @box's children.
*/
void
egg_wrap_box_set_allocation_mode (EggWrapBox *box,
EggWrapAllocationMode mode)
{
EggWrapBoxPriv *priv;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
priv = box->priv;
if (priv->mode != mode)
{
priv->mode = mode;
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (G_OBJECT (box), "allocation-mode");
}
}
/**
* egg_wrap_box_get_allocation_mode:
* @box: An #EggWrapBox
*
* Gets the allocation mode.
*
* Returns: The #EggWrapAllocationMode for @box.
*/
EggWrapAllocationMode
egg_wrap_box_get_allocation_mode (EggWrapBox *box)
{
g_return_val_if_fail (EGG_IS_WRAP_BOX (box), FALSE);
return box->priv->mode;
}
/**
* egg_wrap_box_set_spreading:
* @box: An #EggWrapBox
* @spreading: The #EggWrapBoxSpreading to use.
*
* Sets the spreading mode for @box's children.
*/
void
egg_wrap_box_set_spreading (EggWrapBox *box,
EggWrapBoxSpreading spreading)
{
EggWrapBoxPriv *priv;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
priv = box->priv;
if (priv->spreading != spreading)
{
priv->spreading = spreading;
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (G_OBJECT (box), "spreading");
}
}
/**
* egg_wrap_box_get_spreading:
* @box: An #EggWrapBox
*
* Gets the spreading mode.
*
* Returns: The #EggWrapBoxSpreading for @box.
*/
EggWrapBoxSpreading
egg_wrap_box_get_spreading (EggWrapBox *box)
{
g_return_val_if_fail (EGG_IS_WRAP_BOX (box), FALSE);
return box->priv->spreading;
}
/**
* egg_wrap_box_set_vertical_spacing:
* @box: An #EggWrapBox
* @spacing: The spacing to use.
*
* Sets the vertical space to add between children.
*/
void
egg_wrap_box_set_vertical_spacing (EggWrapBox *box,
guint spacing)
{
EggWrapBoxPriv *priv;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
priv = box->priv;
if (priv->vertical_spacing != spacing)
{
priv->vertical_spacing = spacing;
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (G_OBJECT (box), "vertical-spacing");
}
}
/**
* egg_wrap_box_get_vertical_spacing:
* @box: An #EggWrapBox
*
* Gets the vertical spacing.
*
* Returns: The vertical spacing.
*/
guint
egg_wrap_box_get_vertical_spacing (EggWrapBox *box)
{
g_return_val_if_fail (EGG_IS_WRAP_BOX (box), FALSE);
return box->priv->vertical_spacing;
}
/**
* egg_wrap_box_set_horizontal_spacing:
* @box: An #EggWrapBox
* @spacing: The spacing to use.
*
* Sets the horizontal space to add between children.
*/
void
egg_wrap_box_set_horizontal_spacing (EggWrapBox *box,
guint spacing)
{
EggWrapBoxPriv *priv;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
priv = box->priv;
if (priv->horizontal_spacing != spacing)
{
priv->horizontal_spacing = spacing;
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (G_OBJECT (box), "horizontal-spacing");
}
}
/**
* egg_wrap_box_get_horizontal_spacing:
* @box: An #EggWrapBox
*
* Gets the horizontal spacing.
*
* Returns: The horizontal spacing.
*/
guint
egg_wrap_box_get_horizontal_spacing (EggWrapBox *box)
{
g_return_val_if_fail (EGG_IS_WRAP_BOX (box), FALSE);
return box->priv->horizontal_spacing;
}
/**
* egg_wrap_box_set_minimum_length:
* @box: An #EggWrapBox
* @length: The length to set.
*
* Sets the minimum length of items to request and
* allocate space for in @box's orientation.
*/
void
egg_wrap_box_set_minimum_length (EggWrapBox *box,
guint length)
{
EggWrapBoxPriv *priv;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
priv = box->priv;
if (priv->minimum_length != length)
{
priv->minimum_length = length;
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (G_OBJECT (box), "minimum-length");
}
}
/**
* egg_wrap_box_get_minimum_length:
* @box: An #EggWrapBox
*
* Gets the minimum length of items to allocate in
* @box's orientation.
*
* Returns: The minimum length of items to allocate for.
*/
guint
egg_wrap_box_get_minimum_length (EggWrapBox *box)
{
g_return_val_if_fail (EGG_IS_WRAP_BOX (box), FALSE);
return box->priv->minimum_length;
}
/**
* egg_wrap_box_set_natural_length:
* @box: An #EggWrapBox
* @length: The length to set.
*
* Sets the natural length of items to request and
* allocate space for in @box's orientation.
*
* Setting the natural length limits the overall
* natural request to be no more than @length items
* long in the given orientation.
*/
void
egg_wrap_box_set_natural_length (EggWrapBox *box,
guint length)
{
EggWrapBoxPriv *priv;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
priv = box->priv;
if (priv->natural_length != length)
{
priv->natural_length = length;
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (G_OBJECT (box), "natural-length");
}
}
/**
* egg_wrap_box_get_natural_length:
* @box: An #EggWrapBox
*
* Gets the natural length of items to allocate in
* @box's orientation.
*
* Returns: The natural length of items to allocate for.
*/
guint
egg_wrap_box_get_natural_length (EggWrapBox *box)
{
g_return_val_if_fail (EGG_IS_WRAP_BOX (box), FALSE);
return box->priv->natural_length;
}
/**
* egg_wrap_box_insert_child:
* @box: And #EggWrapBox
* @widget: the child #GtkWidget to add
* @index: the position in the child list to insert, specify -1 to append to the list.
* @xpad: horizontal spacing for this child
* @ypad: vertical spacing for this child
* @xexpand: whether this child expands horizontally
* @yexpand: whether this child expands vertically
* @xfill: whether this child fills its horizontal allocation
* @yfill: whether this child fills its vertical allocation
*
* Adds a child to an #EggWrapBox with its packing options set
*
*/
void
egg_wrap_box_insert_child (EggWrapBox *box,
GtkWidget *widget,
gint index,
guint xpad,
guint ypad,
gboolean xexpand,
gboolean yexpand,
gboolean xfill,
gboolean yfill)
{
EggWrapBoxPriv *priv;
EggWrapBoxChild *child;
GList *list;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
g_return_if_fail (GTK_IS_WIDGET (widget));
priv = box->priv;
list = g_list_find_custom (priv->children, widget,
(GCompareFunc)find_child_in_list);
g_return_if_fail (list == NULL);
child = g_slice_new0 (EggWrapBoxChild);
child->widget = widget;
child->xpadding = xpad;
child->ypadding = ypad;
child->xexpand = xexpand;
child->yexpand = yexpand;
child->xfill = xfill;
child->yfill = yfill;
priv->children = g_list_insert (priv->children, child, index);
gtk_widget_set_parent (widget, GTK_WIDGET (box));
}
/**
* egg_wrap_box_reorder_child:
* @box: An #EggWrapBox
* @widget: The child to reorder
* @index: The new child position
*
* Reorders the child @widget in @box's list of children.
*/
void
egg_wrap_box_reorder_child (EggWrapBox *box,
GtkWidget *widget,
guint index)
{
EggWrapBoxPriv *priv;
EggWrapBoxChild *child;
GList *list;
g_return_if_fail (EGG_IS_WRAP_BOX (box));
g_return_if_fail (GTK_IS_WIDGET (widget));
priv = box->priv;
list = g_list_find_custom (priv->children, widget,
(GCompareFunc)find_child_in_list);
g_return_if_fail (list != NULL);
if (g_list_position (priv->children, list) != index)
{
child = list->data;
priv->children = g_list_delete_link (priv->children, list);
priv->children = g_list_insert (priv->children, child, index);
gtk_widget_queue_resize (GTK_WIDGET (box));
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]