Wrapping Box Container



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]