/* gtkwrapbox.c * Copyright (C) 2007-2010 Openismus GmbH * * Authors: * Tristan Van Berkom * * 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 "gtkprivate.h" #include "gtkintl.h" #include "gtktypeutils.h" typedef struct _GtkWrapBoxChild GtkWrapBoxChild; enum { PROP_0, PROP_ORIENTATION, PROP_ALLOCATION_MODE, PROP_HORIZONTAL_SPREADING, PROP_VERTICAL_SPREADING, PROP_HORIZONTAL_SPACING, PROP_VERTICAL_SPACING, PROP_MINIMUM_LINE_CHILDREN, PROP_NATURAL_LINE_CHILDREN }; enum { CHILD_PROP_0, CHILD_PROP_PACKING }; struct _GtkWrapBoxPrivate { GtkOrientation orientation; GtkWrapAllocationMode mode; GtkWrapBoxSpreading horizontal_spreading; GtkWrapBoxSpreading vertical_spreading; guint16 vertical_spacing; guint16 horizontal_spacing; guint16 minimum_line_children; guint16 natural_line_children; GList *children; }; struct _GtkWrapBoxChild { GtkWidget *widget; GtkWrapBoxPacking packing; }; /* 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); /* GtkWidget */ static GtkSizeRequestMode gtk_wrap_box_get_request_mode (GtkWidget *widget); static void gtk_wrap_box_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size); static void gtk_wrap_box_get_preferred_height (GtkWidget *widget, gint *minimum_size, gint *natural_size); static void gtk_wrap_box_get_preferred_height_for_width (GtkWidget *box, gint width, gint *minimum_height, gint *natural_height); static void gtk_wrap_box_get_preferred_width_for_height (GtkWidget *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_ORIENTABLE, NULL)) #define ORIENTATION_SPREADING(box) \ (((GtkWrapBox *)(box))->priv->orientation == GTK_ORIENTATION_HORIZONTAL ? \ ((GtkWrapBox *)(box))->priv->horizontal_spreading : \ ((GtkWrapBox *)(box))->priv->vertical_spreading) #define OPPOSING_ORIENTATION_SPREADING(box) \ (((GtkWrapBox *)(box))->priv->orientation == GTK_ORIENTATION_HORIZONTAL ? \ ((GtkWrapBox *)(box))->priv->vertical_spreading : \ ((GtkWrapBox *)(box))->priv->horizontal_spreading) 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; widget_class->get_request_mode = gtk_wrap_box_get_request_mode; widget_class->get_preferred_width = gtk_wrap_box_get_preferred_width; widget_class->get_preferred_height = gtk_wrap_box_get_preferred_height; widget_class->get_preferred_height_for_width = gtk_wrap_box_get_preferred_height_for_width; widget_class->get_preferred_width_for_height = gtk_wrap_box_get_preferred_width_for_height; 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; gtk_container_class_handle_border_width (container_class); /* 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, GTK_PARAM_READWRITE)); /** * GtkWrapBox:horizontal-spreading: * * The #GtkWrapBoxSpreading to used to define what is done with extra * space in a given orientation. */ g_object_class_install_property (gobject_class, PROP_HORIZONTAL_SPREADING, g_param_spec_enum ("horizontal-spreading", P_("Horizontal Spreading"), P_("The spreading mode to use horizontally"), GTK_TYPE_WRAP_BOX_SPREADING, GTK_WRAP_BOX_SPREAD_START, GTK_PARAM_READWRITE)); /** * GtkWrapBox:vertical-spreading: * * The #GtkWrapBoxSpreading to used to define what is done with extra * space in a given orientation. */ g_object_class_install_property (gobject_class, PROP_VERTICAL_SPREADING, g_param_spec_enum ("vertical-spreading", P_("Vertical Spreading"), P_("The spreading mode to use vertically"), GTK_TYPE_WRAP_BOX_SPREADING, GTK_WRAP_BOX_SPREAD_START, GTK_PARAM_READWRITE)); /** * GtkWrapBox:minimum-line-children: * * The minimum number of children to allocate consecutively in the given orientation. * * Setting the minimum children per line ensures * that a reasonably small height will be requested * for the overall minimum width of the box. * */ 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, GTK_PARAM_READWRITE)); /** * 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, GTK_PARAM_READWRITE)); /** * 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, GTK_PARAM_READWRITE)); /** * 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, GTK_PARAM_READWRITE)); /* GtkContainerClass child properties */ /** * GtkWrapBox:packing: * * The #GtkWrapBoxPacking options to specify how to pack a child into the box. */ gtk_container_class_install_child_property (container_class, CHILD_PROP_PACKING, g_param_spec_flags ("packing", P_("Packing"), P_("The packing options to use for this child"), GTK_TYPE_WRAP_BOX_PACKING, 0, GTK_PARAM_READWRITE)); g_type_class_add_private (class, sizeof (GtkWrapBoxPrivate)); } static void gtk_wrap_box_init (GtkWrapBox *box) { GtkWrapBoxPrivate *priv; box->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (box, GTK_TYPE_WRAP_BOX, GtkWrapBoxPrivate); priv->orientation = GTK_ORIENTATION_HORIZONTAL; priv->mode = GTK_WRAP_ALLOCATE_FREE; priv->horizontal_spreading = GTK_WRAP_BOX_SPREAD_START; priv->vertical_spreading = GTK_WRAP_BOX_SPREAD_START; priv->horizontal_spacing = 0; priv->vertical_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); GtkWrapBoxPrivate *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_HORIZONTAL_SPREADING: g_value_set_enum (value, priv->horizontal_spreading); break; case PROP_VERTICAL_SPREADING: g_value_set_enum (value, priv->vertical_spreading); break; case PROP_HORIZONTAL_SPACING: g_value_set_uint (value, priv->horizontal_spacing); break; case PROP_VERTICAL_SPACING: g_value_set_uint (value, priv->vertical_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); GtkWrapBoxPrivate *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_HORIZONTAL_SPREADING: gtk_wrap_box_set_horizontal_spreading (box, g_value_get_enum (value)); break; case PROP_VERTICAL_SPREADING: gtk_wrap_box_set_vertical_spreading (box, g_value_get_enum (value)); break; case PROP_HORIZONTAL_SPACING: gtk_wrap_box_set_horizontal_spacing (box, g_value_get_uint (value)); break; case PROP_VERTICAL_SPACING: gtk_wrap_box_set_vertical_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) { GtkWrapBoxPrivate *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->packing & GTK_WRAP_BOX_H_EXPAND) != 0) || (orientation == GTK_ORIENTATION_VERTICAL && (child->packing & GTK_WRAP_BOX_V_EXPAND) != 0)) 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) { GtkWrapBoxPrivate *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_widget_get_preferred_width (child->widget, &child_min, &child_nat); else gtk_widget_get_preferred_height (child->widget, &child_min, &child_nat); 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) { GtkWrapBoxPrivate *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_widget_get_preferred_height_for_width (child->widget, item_size, &child_min, &child_nat); else gtk_widget_get_preferred_width_for_height (child->widget, item_size, &child_min, &child_nat); 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) */ static GList * get_largest_size_for_line_in_opposing_orientation (GtkWrapBox *box, GtkOrientation orientation, GList *cursor, gint line_length, GtkRequestedSize *item_sizes, gint extra_pixels, gint *min_item_size, gint *nat_item_size) { 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_sizes[i].minimum_size; if (extra_pixels > 0 && ORIENTATION_SPREADING (box) == GTK_WRAP_BOX_SPREAD_EXPAND) { this_item_size++; extra_pixels--; } if (orientation == GTK_ORIENTATION_HORIZONTAL) gtk_widget_get_preferred_height_for_width (child->widget, this_item_size, &child_min, &child_nat); else gtk_widget_get_preferred_width_for_height (child->widget, this_item_size, &child_min, &child_nat); 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) */ static 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) { GtkWrapBoxPrivate *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_widget_get_preferred_width (child->widget, NULL, &child_size); else gtk_widget_get_preferred_height (child->widget, NULL, &child_size); 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_widget_get_preferred_width (child->widget, &requested.minimum_size, &requested.natural_size); else gtk_widget_get_preferred_height (child->widget, &requested.minimum_size, &requested.natural_size); 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 (ORIENTATION_SPREADING (box) != 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->packing & GTK_WRAP_BOX_H_EXPAND) != 0) || (orientation == GTK_ORIENTATION_VERTICAL && (child->packing & GTK_WRAP_BOX_V_EXPAND) != 0) || 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_widget_get_preferred_height_for_width (child->widget, sizes[i].minimum_size, &child_min, &child_nat); else gtk_widget_get_preferred_width_for_height (child->widget, sizes[i].minimum_size, &child_min, &child_nat); 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) { GtkWrapBoxPrivate *priv = box->priv; GtkAllocation widget_allocation; GtkAllocation child_allocation; gtk_widget_get_allocation (GTK_WIDGET (box), &widget_allocation); if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { child_allocation.x = widget_allocation.x + item_offset; child_allocation.y = widget_allocation.y + line_offset; child_allocation.width = item_size; child_allocation.height = line_size; } else /* GTK_ORIENTATION_VERTICAL */ { child_allocation.x = widget_allocation.x + line_offset; child_allocation.y = widget_allocation.y + item_offset; child_allocation.width = line_size; child_allocation.height = item_size; } gtk_widget_size_allocate (child->widget, &child_allocation); } /* fit_aligned_item_requests() helper */ static gint gather_aligned_item_requests (GtkWrapBox *box, GtkOrientation orientation, gint line_length, gint item_spacing, gint n_children, GtkRequestedSize *item_sizes) { GtkWrapBoxPrivate *priv = box->priv; GList *list; gint i; gint extra_items, natural_line_size = 0; extra_items = n_children % line_length; for (list = priv->children, i = 0; list; list = list->next, i++) { GtkWrapBoxChild *child = list->data; gint child_min, child_nat; gint position; if (!gtk_widget_get_visible (child->widget)) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) gtk_widget_get_preferred_width (child->widget, &child_min, &child_nat); else gtk_widget_get_preferred_height (child->widget, &child_min, &child_nat); /* Get the index and push it over for the last line when spreading to the end */ position = i % line_length; if (ORIENTATION_SPREADING (box) == GTK_WRAP_BOX_SPREAD_END && i >= n_children - extra_items) position += line_length - extra_items; /* Round up the size of every column/row */ item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min); item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat); } for (i = 0; i < line_length; i++) natural_line_size += item_sizes[i].natural_size; natural_line_size += (line_length - 1) * item_spacing; return natural_line_size; } static GtkRequestedSize * fit_aligned_item_requests (GtkWrapBox *box, GtkOrientation orientation, gint avail_size, gint item_spacing, gint *line_length, /* in-out */ gint n_children) { GtkRequestedSize *sizes, *try_sizes; gint try_line_size, try_length; sizes = g_new0 (GtkRequestedSize, *line_length); /* get the sizes for the initial guess */ try_line_size = gather_aligned_item_requests (box, orientation, *line_length, item_spacing, n_children, sizes); /* Try columnizing the whole thing and adding an item to the end of the line; * try to fit as many columns into the available size as possible */ for (try_length = *line_length + 1; try_line_size < avail_size; try_length++) { try_sizes = g_new0 (GtkRequestedSize, try_length); try_line_size = gather_aligned_item_requests (box, orientation, try_length, item_spacing, n_children, try_sizes); if (try_line_size <= avail_size) { *line_length = try_length; g_free (sizes); sizes = try_sizes; } else { /* oops, this one failed; stick to the last size that fit and then return */ g_free (try_sizes); break; } } return sizes; } 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); GtkWrapBoxPrivate *priv = box->priv; gint avail_size, avail_other_size, min_items, item_spacing, line_spacing; GtkWrapBoxSpreading item_spreading; GtkWrapBoxSpreading line_spreading; gtk_widget_set_allocation (widget, allocation); min_items = MAX (1, priv->minimum_line_children); if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { avail_size = allocation->width; avail_other_size = allocation->height; item_spacing = priv->horizontal_spacing; line_spacing = priv->vertical_spacing; } else /* GTK_ORIENTATION_VERTICAL */ { avail_size = allocation->height; avail_other_size = allocation->width; item_spacing = priv->vertical_spacing; line_spacing = priv->horizontal_spacing; } item_spreading = ORIENTATION_SPREADING (box); line_spreading = OPPOSING_ORIENTATION_SPREADING (box); /********************************************************* * Deal with ALIGNED/HOMOGENEOUS modes first, start with * * initial guesses at item/line sizes * *********************************************************/ if (priv->mode == GTK_WRAP_ALLOCATE_ALIGNED || priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS) { GtkRequestedSize *line_sizes = NULL; GtkRequestedSize *item_sizes = NULL; GList *list; gint min_item_size, nat_item_size; gint line_length; gint item_size = 0; gint line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0; gint line_offset, item_offset, n_children, n_lines, line_count; gint extra_pixels, extra_per_item = 0, extra_extra = 0; gint extra_line_pixels, extra_per_line = 0, extra_line_extra = 0; gint i, this_line_size; 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); /* Get how many lines we'll be needing to wrap */ n_children = get_visible_children (box); /* 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) { n_lines = n_children / line_length; if ((n_children % line_length) > 0) n_lines++; n_lines = MAX (n_lines, 1); /* 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 (item_spreading != GTK_WRAP_BOX_SPREAD_EXPAND) item_size = MIN (item_size, nat_item_size); 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; if (line_spreading != GTK_WRAP_BOX_SPREAD_EXPAND) line_size = MIN (line_size, nat_fixed_line_size); /* Get the real extra pixels incase of GTK_WRAP_BOX_SPREAD_START lines */ extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length; extra_line_pixels = avail_other_size - (n_lines - 1) * line_spacing - line_size * n_lines; } else /* GTK_WRAP_ALLOCATE_ALIGNED */ { GList *list; gboolean first_line = TRUE; /* Find the amount of columns that can fit aligned into the available space * and collect their requests. */ item_sizes = fit_aligned_item_requests (box, priv->orientation, avail_size, item_spacing, &line_length, n_children); /* Calculate the number of lines after determining the final line_length */ n_lines = n_children / line_length; if ((n_children % line_length) > 0) n_lines++; n_lines = MAX (n_lines, 1); line_sizes = g_new0 (GtkRequestedSize, n_lines); /* Get the available remaining size */ avail_size -= (line_length - 1) * item_spacing; for (i = 0; i < line_length; i++) avail_size -= item_sizes[i].minimum_size; /* Perform a natural allocation on the columnized items and get the remaining pixels */ extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes); /* Now that we have the size of each column of items find the size of each individual * line based on the aligned item sizes. */ for (i = 0, list = priv->children; list != NULL; i++) { list = get_largest_size_for_line_in_opposing_orientation (box, priv->orientation, list, line_length, item_sizes, extra_pixels, &line_sizes[i].minimum_size, &line_sizes[i].natural_size); /* Its possible a line is made of completely invisible children */ if (line_sizes[i].natural_size > 0) { if (first_line) first_line = FALSE; else avail_other_size -= line_spacing; avail_other_size -= line_sizes[i].minimum_size; line_sizes[i].data = GINT_TO_POINTER (i); } } /* Distribute space among lines naturally */ extra_line_pixels = gtk_distribute_natural_allocation (avail_other_size, n_lines, line_sizes); } /********************************************************* * Initial sizes of items/lines guessed at this point, * * go on to distribute expand space if needed. * *********************************************************/ /* FIXME: This portion needs to consider which columns * and rows asked for expand space and distribute those * accordingly for the case of ALIGNED allocation. * * If at least one child in a column/row asked for expand; * we should make that row/column expand entirely. */ /* Calculate expand space per item */ if (item_spreading == GTK_WRAP_BOX_SPREAD_EVEN) { extra_per_item = extra_pixels / MAX (line_length -1, 1); extra_extra = extra_pixels % MAX (line_length -1, 1); } else if (item_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { extra_per_item = extra_pixels / line_length; extra_extra = extra_pixels % line_length; } /* Calculate expand space per line */ if (line_spreading == GTK_WRAP_BOX_SPREAD_EVEN) { extra_per_line = extra_line_pixels / MAX (n_lines -1, 1); extra_line_extra = extra_line_pixels % MAX (n_lines -1, 1); } else if (line_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { extra_per_line = extra_line_pixels / n_lines; extra_line_extra = extra_line_pixels % n_lines; } /********************************************************* * Prepare item/line initial offsets and jump into the * * real allocation loop. * *********************************************************/ line_offset = item_offset = 0; /* prepend extra space to item_offset/line_offset for SPREAD_END */ if (item_spreading == GTK_WRAP_BOX_SPREAD_END) item_offset += extra_pixels; if (line_spreading == GTK_WRAP_BOX_SPREAD_END) line_offset += extra_line_pixels; /* Get the allocation size for the first line */ if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS) this_line_size = line_size; else { this_line_size = line_sizes[0].minimum_size; if (line_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { this_line_size += extra_per_line; if (extra_line_extra > 0) this_line_size++; } } for (i = 0, line_count = 0, list = priv->children; list; list = list->next) { GtkWrapBoxChild *child = list->data; gint position; 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) { /* Push the line_offset */ line_offset += this_line_size + line_spacing; if (line_spreading == GTK_WRAP_BOX_SPREAD_EVEN) { line_offset += extra_per_line; if (line_count < extra_line_extra) line_offset++; } line_count++; /* Get the new line size */ if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS) this_line_size = line_size; else { this_line_size = line_sizes[line_count].minimum_size; if (line_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { this_line_size += extra_per_line; if (line_count < extra_line_extra) this_line_size++; } } item_offset = 0; if (item_spreading == GTK_WRAP_BOX_SPREAD_END) { item_offset += extra_pixels; /* If we're on the last line, prepend the space for * any leading items */ if (line_count == n_lines -1) { gint extra_items = n_children % line_length; if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS) { item_offset += item_size * (line_length - extra_items); item_offset += item_spacing * (line_length - extra_items); } else { gint j; for (j = 0; j < (line_length - extra_items); j++) { item_offset += item_sizes[j].minimum_size; item_offset += item_spacing; } } } } } /* Push the index along for the last line when spreading to the end */ if (item_spreading == GTK_WRAP_BOX_SPREAD_END && line_count == n_lines -1) { gint extra_items = n_children % line_length; position += line_length - extra_items; } if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS) this_item_size = item_size; else /* aligned mode */ this_item_size = item_sizes[position].minimum_size; if (item_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { this_item_size += extra_per_item; if (position < extra_extra) this_item_size++; } /* Do the actual allocation */ allocate_child (box, child, item_offset, line_offset, this_item_size, this_line_size); item_offset += this_item_size; item_offset += item_spacing; /* deal with extra spacing here */ if (item_spreading == GTK_WRAP_BOX_SPREAD_EVEN) { item_offset += extra_per_item; if (position < extra_extra) item_offset++; } i++; } g_free (item_sizes); g_free (line_sizes); } 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; GtkRequestedSize *sizes = NULL; GList *list = priv->children; gboolean first_line = TRUE; gint i, line_count = 0; gint line_offset, item_offset; gint extra_per_line = 0, extra_line_extra = 0; gint extra_pixels; GArray *array; array = g_array_new (0, TRUE, sizeof (GtkRequestedSize)); 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 */ sizes = (GtkRequestedSize *)array->data; avail_other_size = gtk_distribute_natural_allocation (avail_other_size, array->len, sizes); /* Calculate expand space per line */ if (line_spreading == GTK_WRAP_BOX_SPREAD_EVEN) { extra_per_line = avail_other_size / MAX (array->len -1, 1); extra_line_extra = avail_other_size % MAX (array->len -1, 1); } else if (line_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { extra_per_line = avail_other_size / array->len; extra_line_extra = avail_other_size % array->len; } if (line_spreading == GTK_WRAP_BOX_SPREAD_END) line_offset = avail_other_size; else line_offset = 0; for (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 = 0; if (line_spreading == GTK_WRAP_BOX_SPREAD_EXPAND) { line_size += extra_per_line; if (line_count < extra_line_extra) line_size++; } if (item_spreading == GTK_WRAP_BOX_SPREAD_END) item_offset += line->extra_pixels; else if (item_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; /* Do the actual allocation */ allocate_child (box, child, item_offset, line_offset, item_size, line_size); /* Add extra space evenly between children */ if (item_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; if (line_spreading == GTK_WRAP_BOX_SPREAD_EVEN) { line_offset += extra_per_line; if (line_count < extra_line_extra) line_offset++; } /* 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); } 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); GtkWrapBoxPrivate *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); GtkWrapBoxPrivate *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); GtkWrapBoxPrivate *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_PACKING: child->packing = g_value_get_flags (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); GtkWrapBoxPrivate *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_PACKING: g_value_set_flags (value, child->packing); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } /***************************************************** * size requests * *****************************************************/ static GtkSizeRequestMode gtk_wrap_box_get_request_mode (GtkWidget *widget) { GtkWrapBox *box = GTK_WRAP_BOX (widget); GtkWrapBoxPrivate *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) { GtkWrapBoxPrivate *priv = box->priv; GList *list, *l; 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) { gint line_min = 0; gint line_nat = 0; gint i; for (l = list, i = 0; l && i < line_length; l = l->next) { GtkWrapBoxChild *child = l->data; gint child_min, child_nat; if (!gtk_widget_get_visible (child->widget)) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) gtk_widget_get_preferred_width (child->widget, &child_min, &child_nat); else /* GTK_ORIENTATION_VERTICAL */ gtk_widget_get_preferred_height (child->widget, &child_min, &child_nat); 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); } max_min_size += (line_length - 1) * spacing; max_nat_size += (line_length - 1) * spacing; if (min_size) *min_size = max_min_size; if (nat_size) *nat_size = max_nat_size; } /* Gets the largest minimum and natural length of * 'line_length' consecutive items when aligned into rows/columns */ static void get_largest_aligned_line_length (GtkWrapBox *box, GtkOrientation orientation, gint line_length, gint *min_size, gint *nat_size) { GtkWrapBoxPrivate *priv = box->priv; GList *list; gint max_min_size = 0; gint max_nat_size = 0; gint spacing, i; GtkRequestedSize *aligned_item_sizes; if (orientation == GTK_ORIENTATION_HORIZONTAL) spacing = priv->horizontal_spacing; else spacing = priv->vertical_spacing; aligned_item_sizes = g_new0 (GtkRequestedSize, line_length); /* Get the largest sizes of each index in the line. */ for (list = priv->children, i = 0; 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_widget_get_preferred_width (child->widget, &child_min, &child_nat); else /* GTK_ORIENTATION_VERTICAL */ gtk_widget_get_preferred_height (child->widget, &child_min, &child_nat); aligned_item_sizes[i % line_length].minimum_size = MAX (aligned_item_sizes[i % line_length].minimum_size, child_min); aligned_item_sizes[i % line_length].natural_size = MAX (aligned_item_sizes[i % line_length].natural_size, child_nat); i++; } /* Add up the largest indexes */ for (i = 0; i < line_length; i++) { max_min_size += aligned_item_sizes[i].minimum_size; max_nat_size += aligned_item_sizes[i].natural_size; } g_free (aligned_item_sizes); max_min_size += (line_length - 1) * spacing; max_nat_size += (line_length - 1) * spacing; if (min_size) *min_size = max_min_size; if (nat_size) *nat_size = max_nat_size; } static void gtk_wrap_box_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size) { GtkWrapBox *box = GTK_WRAP_BOX (widget); GtkWrapBoxPrivate *priv = box->priv; 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) { min_width = nat_width = 0; if (priv->mode == GTK_WRAP_ALLOCATE_FREE || priv->mode == GTK_WRAP_ALLOCATE_ALIGNED) { /* In FREE and ALIGNED modes; horizontally oriented boxes * need enough width for the widest row */ if (min_items == 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 if (priv->mode == GTK_WRAP_ALLOCATE_FREE) { 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 /* GTK_WRAP_MODE_ALIGNED */ { gint min_line_length, nat_line_length; get_largest_aligned_line_length (box, GTK_ORIENTATION_HORIZONTAL, min_items, &min_line_length, &nat_line_length); if (nat_items > min_items) get_largest_aligned_line_length (box, GTK_ORIENTATION_HORIZONTAL, nat_items, NULL, &nat_line_length); min_width += min_line_length; nat_width += nat_line_length; } } else /* In HOMOGENEOUS mode; 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_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL); GTK_WIDGET_GET_CLASS (widget)->get_preferred_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_preferred_height (GtkWidget *widget, gint *minimum_size, gint *natural_size) { GtkWrapBox *box = GTK_WRAP_BOX (widget); GtkWrapBoxPrivate *priv = box->priv; 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_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_width, &min_height, &nat_height); } else /* GTK_ORIENTATION_VERTICAL */ { min_height = nat_height = 0; if (priv->mode == GTK_WRAP_ALLOCATE_FREE || priv->mode == GTK_WRAP_ALLOCATE_ALIGNED) { /* In FREE and ALIGNED modes; vertically oriented boxes * need enough height for the tallest column */ if (min_items == 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 if (priv->mode == GTK_WRAP_ALLOCATE_FREE) { 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 /* GTK_WRAP_ALLOCATE_ALIGNED */ { gint min_line_length, nat_line_length; get_largest_aligned_line_length (box, GTK_ORIENTATION_VERTICAL, min_items, &min_line_length, &nat_line_length); if (nat_items > min_items) get_largest_aligned_line_length (box, GTK_ORIENTATION_VERTICAL, nat_items, NULL, &nat_line_length); min_height += min_line_length; nat_height += nat_line_length; } } else /* In HOMOGENEOUS mode; vertically oriented boxs * give the same height 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_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { GtkWrapBox *box = GTK_WRAP_BOX (widget); GtkWrapBoxPrivate *priv = box->priv; gint min_item_width, nat_item_width; gint min_items; gint min_height, nat_height; gint avail_size, n_children; min_items = MAX (1, priv->minimum_line_children); min_height = 0; nat_height = 0; if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { gint min_width; n_children = get_visible_children (box); /* Make sure its no smaller than the minimum */ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); avail_size = MAX (width, min_width); 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->horizontal_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; /* 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 */ 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, i; gboolean first_line = TRUE; GtkRequestedSize *item_sizes; /* First get the size each set of items take to span the line * when aligning the items above and below after wrapping. */ item_sizes = fit_aligned_item_requests (box, priv->orientation, avail_size, priv->horizontal_spacing, &line_length, n_children); /* Get the available remaining size */ avail_size -= (line_length - 1) * priv->horizontal_spacing; for (i = 0; i < line_length; i++) avail_size -= item_sizes[i].minimum_size; extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes); while (list != NULL) { list = get_largest_size_for_line_in_opposing_orientation (box, GTK_ORIENTATION_HORIZONTAL, list, line_length, item_sizes, 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; } } g_free (item_sizes); } } 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; } } } } else /* GTK_ORIENTATION_VERTICAL */ { /* Return the minimum height */ GTK_WIDGET_GET_CLASS (widget)->get_preferred_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_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { GtkWrapBox *box = GTK_WRAP_BOX (widget); GtkWrapBoxPrivate *priv = box->priv; gint min_item_height, nat_item_height; gint min_items; gint min_width, nat_width; gint avail_size, n_children; 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_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, &nat_width); } else /* GTK_ORIENTATION_VERTICAL */ { gint min_height; n_children = get_visible_children (box); /* Make sure its no smaller than the minimum */ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL); avail_size = MAX (height, min_height); 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->vertical_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; /* 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, i; gboolean first_line = TRUE; GtkRequestedSize *item_sizes; /* First get the size each set of items take to span the line * when aligning the items above and below after wrapping. */ item_sizes = fit_aligned_item_requests (box, priv->orientation, avail_size, priv->vertical_spacing, &line_length, n_children); /* Get the available remaining size */ avail_size -= (line_length - 1) * priv->horizontal_spacing; for (i = 0; i < line_length; i++) avail_size -= item_sizes[i].minimum_size; extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes); while (list != NULL) { list = get_largest_size_for_line_in_opposing_orientation (box, GTK_ORIENTATION_VERTICAL, list, line_length, item_sizes, 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; } } g_free (item_sizes); } } 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; } } } } if (minimum_width) *minimum_width = min_width; if (natural_width) *natural_width = nat_width; } /***************************************************** * API * *****************************************************/ /** * gtk_wrap_box_new: * @mode: The #GtkWrapAllocationMode to use * @horizontal_spreading: The horizontal #GtkWrapBoxSpreading policy to use * @vertical_spreading: The vertical #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 horizontal_spreading, GtkWrapBoxSpreading vertical_spreading, guint horizontal_spacing, guint vertical_spacing) { return (GtkWidget *)g_object_new (GTK_TYPE_WRAP_BOX, "allocation-mode", mode, "horizontal-spreading", horizontal_spreading, "vertical-spreading", vertical_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) { GtkWrapBoxPrivate *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_horizontal_spreading: * @box: An #GtkWrapBox * @spreading: The #GtkWrapBoxSpreading to use. * * Sets the horizontal spreading mode for @box's children. */ void gtk_wrap_box_set_horizontal_spreading (GtkWrapBox *box, GtkWrapBoxSpreading spreading) { GtkWrapBoxPrivate *priv; g_return_if_fail (GTK_IS_WRAP_BOX (box)); priv = box->priv; if (priv->horizontal_spreading != spreading) { priv->horizontal_spreading = spreading; gtk_widget_queue_resize (GTK_WIDGET (box)); g_object_notify (G_OBJECT (box), "horizontal-spreading"); } } /** * gtk_wrap_box_get_horizontal_spreading: * @box: An #GtkWrapBox * * Gets the horizontal spreading mode. * * Returns: The horizontal #GtkWrapBoxSpreading for @box. */ GtkWrapBoxSpreading gtk_wrap_box_get_horizontal_spreading (GtkWrapBox *box) { g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); return box->priv->horizontal_spreading; } /** * gtk_wrap_box_set_vertical_spreading: * @box: An #GtkWrapBox * @spreading: The #GtkWrapBoxSpreading to use. * * Sets the vertical spreading mode for @box's children. */ void gtk_wrap_box_set_vertical_spreading (GtkWrapBox *box, GtkWrapBoxSpreading spreading) { GtkWrapBoxPrivate *priv; g_return_if_fail (GTK_IS_WRAP_BOX (box)); priv = box->priv; if (priv->vertical_spreading != spreading) { priv->vertical_spreading = spreading; gtk_widget_queue_resize (GTK_WIDGET (box)); g_object_notify (G_OBJECT (box), "vertical-spreading"); } } /** * gtk_wrap_box_get_vertical_spreading: * @box: An #GtkWrapBox * * Gets the vertical spreading mode. * * Returns: The vertical #GtkWrapBoxSpreading for @box. */ GtkWrapBoxSpreading gtk_wrap_box_get_vertical_spreading (GtkWrapBox *box) { g_return_val_if_fail (GTK_IS_WRAP_BOX (box), FALSE); return box->priv->vertical_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) { GtkWrapBoxPrivate *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) { GtkWrapBoxPrivate *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) { GtkWrapBoxPrivate *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) { GtkWrapBoxPrivate *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. * @packing: The #GtkWrapBoxPacking options to use. * * Adds a child to an #GtkWrapBox with its packing options set * */ void gtk_wrap_box_insert_child (GtkWrapBox *box, GtkWidget *widget, gint index, GtkWrapBoxPacking packing) { GtkWrapBoxPrivate *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->packing = packing; 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) { GtkWrapBoxPrivate *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)); } }