diff options
author | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2010-08-28 15:59:23 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2010-08-28 16:01:59 +0900 |
commit | deaa351630d73c04d22007ff8ca7f1f7a46bbe06 (patch) | |
tree | 895d991701e6e6b64c81de5086680449973d29c3 /gtk | |
parent | 2a2f7c0993ed7f9d2150040fb0133b73627ef2c1 (diff) | |
download | gtk+-deaa351630d73c04d22007ff8ca7f1f7a46bbe06.tar.gz |
Added initial revision of GtkWrapBox container widget and test case.
Diffstat (limited to 'gtk')
-rw-r--r-- | gtk/Makefile.am | 4 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtk.symbols | 23 | ||||
-rw-r--r-- | gtk/gtkenums.h | 46 | ||||
-rw-r--r-- | gtk/gtkwrapbox.c | 2403 | ||||
-rw-r--r-- | gtk/gtkwrapbox.h | 101 |
6 files changed, 2577 insertions, 1 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 6a791e05cc..8fdc74757a 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -335,7 +335,8 @@ gtk_public_h_sources = \ gtkvscrollbar.h \ gtkvseparator.h \ gtkwidget.h \ - gtkwindow.h + gtkwindow.h \ + gtkwrapbox.h if OS_UNIX gtk_unix_print_public_h_sources = \ @@ -619,6 +620,7 @@ gtk_base_c_sources = \ gtkwidget.c \ gtkwindow-decorate.c \ gtkwindow.c \ + gtkwrapbox.c \ $(gtk_clipboard_dnd_c_sources) gtk_c_sources = $(gtk_base_c_sources) @@ -218,6 +218,7 @@ #include <gtk/gtkvseparator.h> #include <gtk/gtkwidget.h> #include <gtk/gtkwindow.h> +#include <gtk/gtkwrapbox.h> #undef __GTK_H_INSIDE__ diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 44e0b0abef..01ed52b5c4 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -4629,6 +4629,29 @@ gtk_window_unstick #endif #endif + +#if IN_HEADER(__GTK_WRAP_BOX_H__) +#if IN_FILE(__GTK_WRAP_BOX_C__) +gtk_wrap_box_get_allocation_mode +gtk_wrap_box_get_horizontal_spacing +gtk_wrap_box_get_minimum_line_children +gtk_wrap_box_get_natural_line_children +gtk_wrap_box_get_spreading +gtk_wrap_box_get_type G_GNUC_CONST +gtk_wrap_box_get_vertical_spacing +gtk_wrap_box_insert_child +gtk_wrap_box_new +gtk_wrap_box_reorder_child +gtk_wrap_box_set_allocation_mode +gtk_wrap_box_set_horizontal_spacing +gtk_wrap_box_set_minimum_line_children +gtk_wrap_box_set_natural_line_children +gtk_wrap_box_set_spreading +gtk_wrap_box_set_vertical_spacing +#endif +#endif + + #if IN_HEADER(__GTK_WIN32_EMBED_WIDGET_H__) #if IN_FILE(__GTK_WIN32_EMBED_WIDGET_C__) #ifdef G_OS_WIN32 diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index fd021393a5..f2c931ecaa 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -560,6 +560,52 @@ typedef enum } GtkSizeRequestMode; +/** + * GtkWrapAllocationMode: + * @GTK_WRAP_ALLOCATE_FREE: Items wrap freely in the box's orientation + * @GTK_WRAP_ALLOCATE_ALIGNED: Items are aligned into rows and columns + * @GTK_WRAP_ALLOCATE_HOMOGENEOUS: Items are all allocated the same size + * + * Describes how an #GtkWrapBox positions its children. + */ +typedef enum { + GTK_WRAP_ALLOCATE_FREE = 0, + GTK_WRAP_ALLOCATE_ALIGNED, + GTK_WRAP_ALLOCATE_HOMOGENEOUS +} GtkWrapAllocationMode; + +/** + * GtkWrapBoxSpreading: + * @GTK_WRAP_BOX_SPREAD_BEGIN: 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. + * @GTK_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. + * @GTK_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. + * @GTK_WRAP_BOX_SPREAD_EXPAND: Items share the extra space evenly (or among children that 'expand' when + * in %GTK_WRAP_ALLOCATE_FREE mode. + * + * Describes how an #GtkWrapBox 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-line-children" + * 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 + * #GtkWrapBoxSpreading option describes what to do with this space. + * + */ +typedef enum { + GTK_WRAP_BOX_SPREAD_BEGIN = 0, + GTK_WRAP_BOX_SPREAD_END, + GTK_WRAP_BOX_SPREAD_EVEN, + GTK_WRAP_BOX_SPREAD_EXPAND +} GtkWrapBoxSpreading; + + G_END_DECLS #endif /* __GTK_ENUMS_H__ */ diff --git a/gtk/gtkwrapbox.c b/gtk/gtkwrapbox.c new file mode 100644 index 0000000000..eba306f50b --- /dev/null +++ b/gtk/gtkwrapbox.c @@ -0,0 +1,2403 @@ +/* gtkwrapbox.c + * Copyright (C) 2007-2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.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:gtkwrapbox + * @Short_Description: A container that wraps its children; + * @Title: GtkWrapBox + * + * #GtkWrapBox allocates space for an ordered list of children + * by wrapping them over in the box's orentation. + * + */ + +#include "config.h" +#include "gtksizerequest.h" +#include "gtkorientable.h" +#include "gtkwrapbox.h" +#include "gtkintl.h" + + +typedef struct _GtkWrapBoxChild GtkWrapBoxChild; + +enum { + PROP_0, + PROP_ORIENTATION, + PROP_ALLOCATION_MODE, + PROP_SPREADING, + PROP_HORIZONTAL_SPACING, + PROP_VERTICAL_SPACING, + PROP_MINIMUM_LINE_CHILDREN, + PROP_NATURAL_LINE_CHILDREN +}; + +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 _GtkWrapBoxPriv { + GtkOrientation orientation; + GtkWrapAllocationMode mode; + GtkWrapBoxSpreading spreading; + + guint16 vertical_spacing; + guint16 horizontal_spacing; + + guint16 minimum_line_children; + guint16 natural_line_children; + + GList *children; +}; + +struct _GtkWrapBoxChild { + GtkWidget *widget; + + guint16 xpadding; + guint16 ypadding; + + guint16 xexpand : 1; + guint16 xfill : 1; + guint16 yexpand : 1; + guint16 yfill : 1; +}; + +/* GObjectClass */ +static void gtk_wrap_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_wrap_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +/* GtkWidgetClass */ +static void gtk_wrap_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +/* GtkContainerClass */ +static void gtk_wrap_box_add (GtkContainer *container, + GtkWidget *widget); +static void gtk_wrap_box_remove (GtkContainer *container, + GtkWidget *widget); +static void gtk_wrap_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static void gtk_wrap_box_set_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_wrap_box_get_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + GValue *value, + GParamSpec *pspec); +static GType gtk_wrap_box_child_type (GtkContainer *container); + + +/* GtkSizeRequest */ +static void gtk_wrap_box_size_request_init (GtkSizeRequestIface *iface); +static GtkSizeRequestMode gtk_wrap_box_get_request_mode (GtkSizeRequest *widget); +static void gtk_wrap_box_get_width (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_wrap_box_get_height (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_wrap_box_get_height_for_width (GtkSizeRequest *box, + gint width, + gint *minimum_height, + gint *natural_height); +static void gtk_wrap_box_get_width_for_height (GtkSizeRequest *box, + gint width, + gint *minimum_height, + gint *natural_height); + + +G_DEFINE_TYPE_WITH_CODE (GtkWrapBox, gtk_wrap_box, GTK_TYPE_CONTAINER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_SIZE_REQUEST, + gtk_wrap_box_size_request_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) + + +static void +gtk_wrap_box_class_init (GtkWrapBoxClass *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 = gtk_wrap_box_get_property; + gobject_class->set_property = gtk_wrap_box_set_property; + + widget_class->size_allocate = gtk_wrap_box_size_allocate; + + container_class->add = gtk_wrap_box_add; + container_class->remove = gtk_wrap_box_remove; + container_class->forall = gtk_wrap_box_forall; + container_class->child_type = gtk_wrap_box_child_type; + container_class->set_child_property = gtk_wrap_box_set_child_property; + container_class->get_child_property = gtk_wrap_box_get_child_property; + + /* GObjectClass properties */ + g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); + + /** + * GtkWrapBox:allocation-mode: + * + * The #GtkWrapAllocationMode to use. + */ + g_object_class_install_property (gobject_class, + PROP_ALLOCATION_MODE, + g_param_spec_enum ("allocation-mode", + P_("Allocation Mode"), + P_("The allocation mode to use"), + GTK_TYPE_WRAP_ALLOCATION_MODE, + GTK_WRAP_ALLOCATE_FREE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox:spreading: + * + * The #GtkWrapBoxSpreading to used to define what is done with extra + * space. + */ + g_object_class_install_property (gobject_class, + PROP_SPREADING, + g_param_spec_enum ("spreading", + P_("Spreading"), + P_("The spreading mode to use"), + GTK_TYPE_WRAP_BOX_SPREADING, + GTK_WRAP_BOX_SPREAD_BEGIN, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + /** + * GtkWrapBox:minimum-line-children: + * + * The minimum number of children to allocate consecutively in the given orientation. + * + * <note><para>Setting the minimum children per line 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_LINE_CHILDREN, + g_param_spec_uint ("minimum-line-children", + P_("Minimum Line Children"), + P_("The minimum number of children to allocate " + "consecutively in the given orientation."), + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + /** + * GtkWrapBox:natural-line-children: + * + * The maximum amount of children to request space for consecutively in the given orientation. + * + */ + g_object_class_install_property (gobject_class, + PROP_NATURAL_LINE_CHILDREN, + g_param_spec_uint ("natural-line-children", + P_("Natural Line Children"), + P_("The maximum amount of children to request space for " + "consecutively in the given orientation."), + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox: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", + P_("Vertical spacing"), + P_("The amount of vertical space between two children"), + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox: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", + P_("Horizontal spacing"), + P_("The amount of horizontal space between two children"), + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /* GtkContainerClass child properties */ + + /** + * GtkWrapBox: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", + P_("Horizontally expand"), + P_("Whether the child expands horizontally."), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox: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", + P_("Horizontally fills"), + P_("Whether the child fills its allocated horizontal space."), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox: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", + P_("Vertically expand"), + P_("Whether the child expands vertically."), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox: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", + P_("Vertically fills"), + P_("Whether the child fills its allocated vertical space."), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /** + * GtkWrapBox: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", + P_("Horizontal padding"), + P_("Extra space to put between the child and " + "its left and right neighbors, in pixels"), + 0, 65535, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + /** + * GtkWrapBox: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", + P_("Vertical padding"), + P_("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 (GtkWrapBoxPriv)); +} + +static void +gtk_wrap_box_init (GtkWrapBox *box) +{ + GtkWrapBoxPriv *priv; + + box->priv = priv = + G_TYPE_INSTANCE_GET_PRIVATE (box, GTK_TYPE_WRAP_BOX, GtkWrapBoxPriv); + + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + priv->mode = GTK_WRAP_ALLOCATE_FREE; + priv->spreading = GTK_WRAP_BOX_SPREAD_BEGIN; + priv->vertical_spacing = 0; + priv->horizontal_spacing = 0; + priv->children = NULL; + + gtk_widget_set_has_window (GTK_WIDGET (box), FALSE); +} + +/***************************************************** + * GObectClass * + *****************************************************/ +static void +gtk_wrap_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *box = GTK_WRAP_BOX (object); + GtkWrapBoxPriv *priv = box->priv; + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_boolean (value, priv->orientation); + break; + case PROP_ALLOCATION_MODE: + g_value_set_enum (value, priv->mode); + break; + case PROP_SPREADING: + g_value_set_enum (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_LINE_CHILDREN: + g_value_set_uint (value, priv->minimum_line_children); + break; + case PROP_NATURAL_LINE_CHILDREN: + g_value_set_uint (value, priv->natural_line_children); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_wrap_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *box = GTK_WRAP_BOX (object); + GtkWrapBoxPriv *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: + gtk_wrap_box_set_allocation_mode (box, g_value_get_enum (value)); + break; + case PROP_SPREADING: + gtk_wrap_box_set_spreading (box, g_value_get_enum (value)); + break; + case PROP_VERTICAL_SPACING: + gtk_wrap_box_set_vertical_spacing (box, g_value_get_uint (value)); + break; + case PROP_HORIZONTAL_SPACING: + gtk_wrap_box_set_horizontal_spacing (box, g_value_get_uint (value)); + break; + case PROP_MINIMUM_LINE_CHILDREN: + gtk_wrap_box_set_minimum_line_children (box, g_value_get_uint (value)); + break; + case PROP_NATURAL_LINE_CHILDREN: + gtk_wrap_box_set_natural_line_children (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 (GtkWrapBox *box) +{ + GtkWrapBoxPriv *priv = box->priv; + GList *list; + gint i = 0; + + for (list = priv->children; list; list = list->next) + { + GtkWrapBoxChild *child = list->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + i++; + } + + return i; +} + +static gint +get_visible_expand_children (GtkWrapBox *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) + { + GtkWrapBoxChild *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 (GtkWrapBox *box, + GtkOrientation orientation, + gint *min_size, + gint *nat_size) +{ + GtkWrapBoxPriv *priv = box->priv; + GList *list; + gint max_min_size = 0; + gint max_nat_size = 0; + + for (list = priv->children; list; list = list->next) + { + GtkWrapBoxChild *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 (GtkWrapBox *box, + GtkOrientation orientation, + gint item_size, + gint *min_item_size, + gint *nat_item_size) +{ + GtkWrapBoxPriv *priv = box->priv; + GList *list; + gint max_min_size = 0; + gint max_nat_size = 0; + + for (list = priv->children; list; list = list->next) + { + GtkWrapBoxChild *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 (GtkWrapBox *box, + GtkOrientation orientation, + GList *cursor, + gint line_length, + gint item_size, + gint extra_pixels, + gint *min_item_size, + gint *nat_item_size) +{ + GtkWrapBoxPriv *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) + { + GtkWrapBoxChild *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 == GTK_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 (GtkWrapBox *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) +{ + GtkWrapBoxPriv *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) + { + GtkWrapBoxChild *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) + { + GtkWrapBoxChild *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 != GTK_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) + { + GtkWrapBoxChild *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 (GtkWrapBox *box, + GtkWrapBoxChild *child, + gint item_offset, + gint line_offset, + gint item_size, + gint line_size) +{ + GtkWrapBoxPriv *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 +gtk_wrap_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWrapBox *box = GTK_WRAP_BOX (widget); + GtkWrapBoxPriv *priv = box->priv; + GtkRequestedSize *sizes = NULL; + 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_line_children); + + /* Collect the line sizes for GTK_WRAP_ALLOCATE_FREE and + * GTK_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 == GTK_WRAP_ALLOCATE_ALIGNED || + priv->mode == GTK_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 != GTK_WRAP_BOX_SPREAD_EXPAND) + item_size = MIN (item_size, nat_item_size); + + /* Get the real extra pixels incase of GTK_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 == GTK_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 /* GTK_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) + { + GtkWrapBoxChild *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 == GTK_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 == GTK_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 == GTK_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 == GTK_WRAP_BOX_SPREAD_EVEN) + { + item_offset += position * extra_per_item; + item_offset += MIN (position, extra_extra); + } + else if (priv->spreading == GTK_WRAP_BOX_SPREAD_END) + item_offset += extra_pixels; + else if (priv->spreading == GTK_WRAP_BOX_SPREAD_EXPAND) + item_offset += MIN (position, extra_pixels); + + /* Get the allocation size for this line */ + if (priv->mode == GTK_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 /* GTK_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 = 0; + gint extra_extra = 0; + + /* Set line start offset */ + item_offset = border_width; + + if (priv->spreading == GTK_WRAP_BOX_SPREAD_END) + item_offset += line->extra_pixels; + else if (priv->spreading == GTK_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++) + { + GtkWrapBoxChild *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 == GTK_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 +gtk_wrap_box_add (GtkContainer *container, + GtkWidget *widget) +{ + gtk_wrap_box_insert_child (GTK_WRAP_BOX (container), widget, -1, + 0, 0, FALSE, FALSE, FALSE, FALSE); +} + +static gint +find_child_in_list (GtkWrapBoxChild *child_in_list, + GtkWidget *search) +{ + return (child_in_list->widget == search) ? 0 : -1; +} + +static void +gtk_wrap_box_remove (GtkContainer *container, + GtkWidget *widget) +{ + GtkWrapBox *box = GTK_WRAP_BOX (container); + GtkWrapBoxPriv *priv = box->priv; + GList *list; + + list = g_list_find_custom (priv->children, widget, + (GCompareFunc)find_child_in_list); + + if (list) + { + GtkWrapBoxChild *child = list->data; + gboolean was_visible = gtk_widget_get_visible (widget); + + gtk_widget_unparent (widget); + + g_slice_free (GtkWrapBoxChild, 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 +gtk_wrap_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkWrapBox *box = GTK_WRAP_BOX (container); + GtkWrapBoxPriv *priv = box->priv; + GtkWrapBoxChild *child; + GList *list; + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + (* callback) (child->widget, callback_data); + } +} + +static GType +gtk_wrap_box_child_type (GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +static void +gtk_wrap_box_set_child_property (GtkContainer *container, + GtkWidget *widget, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *box = GTK_WRAP_BOX (container); + GtkWrapBoxPriv *priv = box->priv; + GtkWrapBoxChild *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 +gtk_wrap_box_get_child_property (GtkContainer *container, + GtkWidget *widget, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *box = GTK_WRAP_BOX (container); + GtkWrapBoxPriv *priv = box->priv; + GtkWrapBoxChild *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 +gtk_wrap_box_size_request_init (GtkSizeRequestIface *iface) +{ + iface->get_request_mode = gtk_wrap_box_get_request_mode; + iface->get_width = gtk_wrap_box_get_width; + iface->get_height = gtk_wrap_box_get_height; + iface->get_height_for_width = gtk_wrap_box_get_height_for_width; + iface->get_width_for_height = gtk_wrap_box_get_width_for_height; +} + +static GtkSizeRequestMode +gtk_wrap_box_get_request_mode (GtkSizeRequest *widget) +{ + GtkWrapBox *box = GTK_WRAP_BOX (widget); + GtkWrapBoxPriv *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 (GtkWrapBox *box, + GtkOrientation orientation, + gint line_length, + gint *min_size, + gint *nat_size) +{ + GtkWrapBoxPriv *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) + { + GtkWrapBoxChild *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 +gtk_wrap_box_get_width (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkWrapBox *box = GTK_WRAP_BOX (widget); + GtkWrapBoxPriv *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_line_children); + nat_items = MAX (min_items, priv->natural_line_children); + + 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 == GTK_WRAP_ALLOCATE_FREE) + { + if (priv->minimum_line_children <= 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 +gtk_wrap_box_get_height (GtkSizeRequest *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkWrapBox *box = GTK_WRAP_BOX (widget); + GtkWrapBoxPriv *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_line_children); + nat_items = MAX (min_items, priv->natural_line_children); + + 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 == GTK_WRAP_ALLOCATE_FREE) + { + if (priv->minimum_line_children <= 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 +gtk_wrap_box_get_height_for_width (GtkSizeRequest *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + GtkWrapBox *box = GTK_WRAP_BOX (widget); + GtkWrapBoxPriv *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_line_children); + + 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 == GTK_WRAP_ALLOCATE_ALIGNED || + priv->mode == GTK_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 != GTK_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 == GTK_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 /* GTK_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 /* GTK_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 +gtk_wrap_box_get_width_for_height (GtkSizeRequest *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + GtkWrapBox *box = GTK_WRAP_BOX (widget); + GtkWrapBoxPriv *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_line_children); + + 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 == GTK_WRAP_ALLOCATE_ALIGNED || + priv->mode == GTK_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 != GTK_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 == GTK_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 /* GTK_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 /* GTK_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 * + *****************************************************/ + +/** + * gtk_wrap_box_new: + * @allocation_mode: The #GtkWrapAllocationMode to use + * @spreading: The #GtkWrapBoxSpreading policy to use + * @horizontal_spacing: The horizontal spacing to add between children + * @vertical_spacing: The vertical spacing to add between children + * + * Creates an #GtkWrapBox. + * + * Returns: A new #GtkWrapBox container + */ +GtkWidget * +gtk_wrap_box_new (GtkWrapAllocationMode mode, + GtkWrapBoxSpreading spreading, + guint horizontal_spacing, + guint vertical_spacing) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_WRAP_BOX, + "allocation-mode", mode, + "spreading", spreading, + "vertical-spacing", vertical_spacing, + "horizontal-spacing", horizontal_spacing, + NULL); +} + +/** + * gtk_wrap_box_set_allocation_mode: + * @box: An #GtkWrapBox + * @mode: The #GtkWrapAllocationMode to use. + * + * Sets the allocation mode for @box's children. + */ +void +gtk_wrap_box_set_allocation_mode (GtkWrapBox *box, + GtkWrapAllocationMode mode) +{ + GtkWrapBoxPriv *priv; + + g_return_if_fail (GTK_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"); + } +} + +/** + * gtk_wrap_box_get_allocation_mode: + * @box: An #GtkWrapBox + * + * Gets the allocation mode. + * + * Returns: The #GtkWrapAllocationMode for @box. + */ +GtkWrapAllocationMode +gtk_wrap_box_get_allocation_mode (GtkWrapBox *box) +{ + g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); + + return box->priv->mode; +} + + +/** + * gtk_wrap_box_set_spreading: + * @box: An #GtkWrapBox + * @spreading: The #GtkWrapBoxSpreading to use. + * + * Sets the spreading mode for @box's children. + */ +void +gtk_wrap_box_set_spreading (GtkWrapBox *box, + GtkWrapBoxSpreading spreading) +{ + GtkWrapBoxPriv *priv; + + g_return_if_fail (GTK_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"); + } +} + +/** + * gtk_wrap_box_get_spreading: + * @box: An #GtkWrapBox + * + * Gets the spreading mode. + * + * Returns: The #GtkWrapBoxSpreading for @box. + */ +GtkWrapBoxSpreading +gtk_wrap_box_get_spreading (GtkWrapBox *box) +{ + g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); + + return box->priv->spreading; +} + + +/** + * gtk_wrap_box_set_vertical_spacing: + * @box: An #GtkWrapBox + * @spacing: The spacing to use. + * + * Sets the vertical space to add between children. + */ +void +gtk_wrap_box_set_vertical_spacing (GtkWrapBox *box, + guint spacing) +{ + GtkWrapBoxPriv *priv; + + g_return_if_fail (GTK_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"); + } +} + +/** + * gtk_wrap_box_get_vertical_spacing: + * @box: An #GtkWrapBox + * + * Gets the vertical spacing. + * + * Returns: The vertical spacing. + */ +guint +gtk_wrap_box_get_vertical_spacing (GtkWrapBox *box) +{ + g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); + + return box->priv->vertical_spacing; +} + +/** + * gtk_wrap_box_set_horizontal_spacing: + * @box: An #GtkWrapBox + * @spacing: The spacing to use. + * + * Sets the horizontal space to add between children. + */ +void +gtk_wrap_box_set_horizontal_spacing (GtkWrapBox *box, + guint spacing) +{ + GtkWrapBoxPriv *priv; + + g_return_if_fail (GTK_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"); + } +} + +/** + * gtk_wrap_box_get_horizontal_spacing: + * @box: An #GtkWrapBox + * + * Gets the horizontal spacing. + * + * Returns: The horizontal spacing. + */ +guint +gtk_wrap_box_get_horizontal_spacing (GtkWrapBox *box) +{ + g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); + + return box->priv->horizontal_spacing; +} + +/** + * gtk_wrap_box_set_minimum_line_children: + * @box: An #GtkWrapBox + * @n_children: The minimum amount of children per line. + * + * Sets the minimum amount of children to line up + * in @box's orientation before wrapping. + */ +void +gtk_wrap_box_set_minimum_line_children (GtkWrapBox *box, + guint n_children) +{ + GtkWrapBoxPriv *priv; + + g_return_if_fail (GTK_IS_WRAP_BOX (box)); + + priv = box->priv; + + if (priv->minimum_line_children != n_children) + { + priv->minimum_line_children = n_children; + + gtk_widget_queue_resize (GTK_WIDGET (box)); + + g_object_notify (G_OBJECT (box), "minimum-line-children"); + } +} + +/** + * gtk_wrap_box_get_minimum_line_children: + * @box: An #GtkWrapBox + * + * Gets the minimum amount of children per line. + * + * Returns: The minimum amount of children per line. + */ +guint +gtk_wrap_box_get_minimum_line_children (GtkWrapBox *box) +{ + g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); + + return box->priv->minimum_line_children; +} + +/** + * gtk_wrap_box_set_natural_line_children: + * @box: An #GtkWrapBox + * @n_children: The natural amount of children per line. + * + * Sets the natural length of items to request and + * allocate space for in @box's orientation. + * + * Setting the natural amount of children per line + * limits the overall natural size request to be no more + * than @n_children items long in the given orientation. + */ +void +gtk_wrap_box_set_natural_line_children (GtkWrapBox *box, + guint n_children) +{ + GtkWrapBoxPriv *priv; + + g_return_if_fail (GTK_IS_WRAP_BOX (box)); + + priv = box->priv; + + if (priv->natural_line_children != n_children) + { + priv->natural_line_children = n_children; + + gtk_widget_queue_resize (GTK_WIDGET (box)); + + g_object_notify (G_OBJECT (box), "natural-line-children"); + } +} + +/** + * gtk_wrap_box_get_natural_line_children: + * @box: An #GtkWrapBox + * + * Gets the natural amount of children per line. + * + * Returns: The natural amount of children per line. + */ +guint +gtk_wrap_box_get_natural_line_children (GtkWrapBox *box) +{ + g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); + + return box->priv->natural_line_children; +} + + +/** + * gtk_wrap_box_insert_child: + * @box: And #GtkWrapBox + * @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 #GtkWrapBox with its packing options set + * + */ +void +gtk_wrap_box_insert_child (GtkWrapBox *box, + GtkWidget *widget, + gint index, + guint xpad, + guint ypad, + gboolean xexpand, + gboolean yexpand, + gboolean xfill, + gboolean yfill) +{ + GtkWrapBoxPriv *priv; + GtkWrapBoxChild *child; + GList *list; + + g_return_if_fail (GTK_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 (GtkWrapBoxChild); + 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)); +} + +/** + * gtk_wrap_box_reorder_child: + * @box: An #GtkWrapBox + * @widget: The child to reorder + * @index: The new child position + * + * Reorders the child @widget in @box's list of children. + */ +void +gtk_wrap_box_reorder_child (GtkWrapBox *box, + GtkWidget *widget, + guint index) +{ + GtkWrapBoxPriv *priv; + GtkWrapBoxChild *child; + GList *list; + + g_return_if_fail (GTK_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)); + } +} diff --git a/gtk/gtkwrapbox.h b/gtk/gtkwrapbox.h new file mode 100644 index 0000000000..76c6370771 --- /dev/null +++ b/gtk/gtkwrapbox.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.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 __GTK_WRAP_BOX_H__ +#define __GTK_WRAP_BOX_H__ + +#include <gtk/gtkcontainer.h> + +G_BEGIN_DECLS + + +#define GTK_TYPE_WRAP_BOX (gtk_wrap_box_get_type ()) +#define GTK_WRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WRAP_BOX, GtkWrapBox)) +#define GTK_WRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_WRAP_BOX, GtkWrapBoxClass)) +#define GTK_IS_WRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WRAP_BOX)) +#define GTK_IS_WRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WRAP_BOX)) +#define GTK_WRAP_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_WRAP_BOX, GtkWrapBoxClass)) + +typedef struct _GtkWrapBox GtkWrapBox; +typedef struct _GtkWrapBoxPriv GtkWrapBoxPriv; +typedef struct _GtkWrapBoxClass GtkWrapBoxClass; + +struct _GtkWrapBox +{ + GtkContainer container; + + /*< private >*/ + GtkWrapBoxPriv *priv; +}; + +struct _GtkWrapBoxClass +{ + GtkContainerClass parent_class; +}; + +GType gtk_wrap_box_get_type (void) G_GNUC_CONST; + +GtkWidget *gtk_wrap_box_new (GtkWrapAllocationMode mode, + GtkWrapBoxSpreading spreading, + guint horizontal_spacing, + guint vertical_spacing); +void gtk_wrap_box_set_allocation_mode (GtkWrapBox *layout, + GtkWrapAllocationMode mode); +GtkWrapAllocationMode gtk_wrap_box_get_allocation_mode (GtkWrapBox *layout); + +void gtk_wrap_box_set_spreading (GtkWrapBox *layout, + GtkWrapBoxSpreading spreading); +GtkWrapBoxSpreading gtk_wrap_box_get_spreading (GtkWrapBox *layout); + +void gtk_wrap_box_set_vertical_spacing (GtkWrapBox *layout, + guint spacing); +guint gtk_wrap_box_get_vertical_spacing (GtkWrapBox *layout); + +void gtk_wrap_box_set_horizontal_spacing (GtkWrapBox *layout, + guint spacing); +guint gtk_wrap_box_get_horizontal_spacing (GtkWrapBox *layout); + +void gtk_wrap_box_set_minimum_line_children (GtkWrapBox *layout, + guint n_children); +guint gtk_wrap_box_get_minimum_line_children (GtkWrapBox *layout); + +void gtk_wrap_box_set_natural_line_children (GtkWrapBox *layout, + guint n_children); +guint gtk_wrap_box_get_natural_line_children (GtkWrapBox *layout); + +void gtk_wrap_box_insert_child (GtkWrapBox *layout, + GtkWidget *widget, + gint index, + guint xpad, + guint ypad, + gboolean xexpand, + gboolean yexpand, + gboolean xfill, + gboolean yfill); +void gtk_wrap_box_reorder_child (GtkWrapBox *layout, + GtkWidget *widget, + guint index); + +G_END_DECLS + + +#endif /* __GTK_WRAP_BOX_H__ */ |