[egg-list-box] Add EggFlowBox
- From: William Jon McCann <mccann src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [egg-list-box] Add EggFlowBox
- Date: Fri, 8 Feb 2013 22:11:03 +0000 (UTC)
commit ee1e2fdbeee18d6d2115cf724fb44ff7a0a074fc
Author: William Jon McCann <jmccann redhat com>
Date: Fri Feb 8 17:07:15 2013 -0500
Add EggFlowBox
Derived from EggWrapBox originally written by
Tristan Van Berkom <tristanvb openismus com>
Makefile.am | 21 +-
egg-flow-box.c | 2248 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
egg-flow-box.h | 90 +++
test-flow-box.c | 514 +++++++++++++
4 files changed, 2871 insertions(+), 2 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 2efaf09..21f7960 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,7 +10,10 @@ AM_VALAFLAGS = \
--vapidir=. \
$(NULL)
-noinst_LTLIBRARIES = libegglistbox.la
+noinst_LTLIBRARIES = \
+ libegglistbox.la \
+ libeggflowbox.la \
+ $(NULL)
libegglistbox_la_SOURCES = \
egg-list-box.c egg-list-box.h \
@@ -18,7 +21,12 @@ libegglistbox_la_SOURCES = \
libegglistbox_la_LIBADD = $(LISTBOX_LIBS)
-noinst_PROGRAMS = test-list test-scrolled test-focus test-sel
+libeggflowbox_la_SOURCES = \
+ egg-flow-box.c egg-flow-box.h
+
+libeggflowbox_la_LIBADD = $(LISTBOX_LIBS)
+
+noinst_PROGRAMS = test-list test-scrolled test-focus test-sel test-flow-box
test_sel_SOURCES = \
test-sel.c \
@@ -66,6 +74,15 @@ test_focus_LDADD = \
libegglistbox.la \
$(NULL)
+test_flow_box_SOURCES = \
+ test-flow-box.c \
+ $(NULL)
+
+test_flow_box_LDADD = \
+ $(LISTBOX_LIBS) \
+ libeggflowbox.la \
+ $(NULL)
+
CLEANFILES = \
$(test_list_SOURCES:.vala=.c) \
$(test_scrolled_SOURCES:.vala=.c) \
diff --git a/egg-flow-box.c b/egg-flow-box.c
new file mode 100644
index 0000000..4832a78
--- /dev/null
+++ b/egg-flow-box.c
@@ -0,0 +1,2248 @@
+/*
+ * Copyright (C) 2007-2010 Openismus GmbH
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * 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:eggflowbox
+ * @Short_Description: A container that allows reflowing its children
+ * @Title: EggFlowBox
+ *
+ * #EggFlowBox positions child widgets in sequence according to its
+ * orientation. For instance, with the horizontal orientation, the widgets
+ * will be arranged from left to right, starting a new row under the
+ * previous row when necessary. Reducing the width in this case will
+ * require more rows, so a larger height will be requested.
+ *
+ * Likewise, with the vertical orientation, the widgets will be arranged
+ * from top to bottom, starting a new column to the right when necessary.
+ * Reducing the height will require more columns, so a larger width will be
+ * requested.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include "egg-flow-box.h"
+
+#define P_(msgid) (msgid)
+
+enum {
+ CHILD_ACTIVATED,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_HOMOGENEOUS,
+ PROP_HALIGN_POLICY,
+ PROP_VALIGN_POLICY,
+ PROP_COLUMN_SPACING,
+ PROP_ROW_SPACING,
+ PROP_MIN_CHILDREN_PER_LINE,
+ PROP_MAX_CHILDREN_PER_LINE,
+ PROP_ACTIVATE_ON_SINGLE_CLICK
+};
+
+typedef struct _EggFlowBoxChildInfo EggFlowBoxChildInfo;
+
+struct _EggFlowBoxPrivate {
+ GtkOrientation orientation;
+ GtkAlign halign_policy;
+ GtkAlign valign_policy;
+ guint homogeneous : 1;
+ guint activate_on_single_click : 1;
+
+ guint row_spacing;
+ guint column_spacing;
+
+ gboolean active_child_active;
+ EggFlowBoxChildInfo *active_child;
+
+ guint16 min_children_per_line;
+ guint16 max_children_per_line;
+
+ GSequence *children;
+ GHashTable *child_hash;
+};
+
+struct _EggFlowBoxChildInfo
+{
+ GSequenceIter *iter;
+ GtkWidget *widget;
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE_WITH_CODE (EggFlowBox, egg_flow_box, GTK_TYPE_CONTAINER,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
+
+
+#define ORIENTATION_ALIGN_POLICY(box) \
+ (((EggFlowBox *)(box))->priv->orientation == GTK_ORIENTATION_HORIZONTAL ? \
+ ((EggFlowBox *)(box))->priv->halign_policy : \
+ ((EggFlowBox *)(box))->priv->valign_policy)
+
+#define OPPOSING_ORIENTATION_ALIGN_POLICY(box) \
+ (((EggFlowBox *)(box))->priv->orientation == GTK_ORIENTATION_HORIZONTAL ? \
+ ((EggFlowBox *)(box))->priv->valign_policy : \
+ ((EggFlowBox *)(box))->priv->halign_policy)
+
+static EggFlowBoxChildInfo*
+egg_flow_box_child_info_new (GtkWidget *widget)
+{
+ EggFlowBoxChildInfo *info;
+
+ info = g_new0 (EggFlowBoxChildInfo, 1);
+ info->widget = g_object_ref (widget);
+ return info;
+}
+
+static void
+egg_flow_box_child_info_free (EggFlowBoxChildInfo *info)
+{
+ g_clear_object (&info->widget);
+ g_free (info);
+}
+
+static EggFlowBoxChildInfo*
+egg_flow_box_lookup_info (EggFlowBox *flow_box, GtkWidget* child)
+{
+ EggFlowBoxPrivate *priv = flow_box->priv;
+
+ return g_hash_table_lookup (priv->child_hash, child);
+}
+
+/**
+ * egg_flow_box_get_homogeneous:
+ * @box: a #EggFlowBox
+ *
+ * Returns whether the box is homogeneous (all children are the
+ * same size). See gtk_box_set_homogeneous().
+ *
+ * Return value: %TRUE if the box is homogeneous.
+ **/
+gboolean
+egg_flow_box_get_homogeneous (EggFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_BOX (box), FALSE);
+
+ return box->priv->homogeneous;
+}
+
+/**
+ * egg_flow_box_set_homogeneous:
+ * @box: a #EggFlowBox
+ * @homogeneous: a boolean value, %TRUE to create equal allotments,
+ * %FALSE for variable allotments
+ *
+ * Sets the #EggFlowBox:homogeneous property of @box, controlling
+ * whether or not all children of @box are given equal space
+ * in the box.
+ */
+void
+egg_flow_box_set_homogeneous (EggFlowBox *box,
+ gboolean homogeneous)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if ((homogeneous ? TRUE : FALSE) != priv->homogeneous)
+ {
+ priv->homogeneous = homogeneous ? TRUE : FALSE;
+ g_object_notify (G_OBJECT (box), "homogeneous");
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ }
+}
+
+static gint
+get_visible_children (EggFlowBox *box)
+{
+ EggFlowBoxPrivate *priv = box->priv;
+ GSequenceIter *iter;
+ gint i = 0;
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ EggFlowBoxChildInfo *child_info;
+ GtkWidget *child;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ i++;
+ }
+
+ return i;
+}
+
+/* Used in columned modes where all items share at least their
+ * equal widths or heights
+ */
+static void
+get_average_item_size (EggFlowBox *box,
+ GtkOrientation orientation,
+ gint *min_size,
+ gint *nat_size)
+{
+ EggFlowBoxPrivate *priv = box->priv;
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ EggFlowBoxChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height (child, &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 (EggFlowBox *box,
+ GtkOrientation orientation,
+ gint item_size,
+ gint *min_item_size,
+ gint *nat_item_size)
+{
+ EggFlowBoxPrivate *priv = box->priv;
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ EggFlowBoxChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_height_for_width (child,
+ item_size,
+ &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_width_for_height (child,
+ 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 GSequenceIter *
+get_largest_size_for_line_in_opposing_orientation (EggFlowBox *box,
+ GtkOrientation orientation,
+ GSequenceIter *cursor,
+ gint line_length,
+ GtkRequestedSize *item_sizes,
+ gint extra_pixels,
+ gint *min_item_size,
+ gint *nat_item_size)
+{
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+ gint i;
+
+ i = 0;
+ for (iter = cursor;
+ !g_sequence_iter_is_end (iter) && i < line_length;
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ EggFlowBoxChildInfo *child_info;
+ gint child_min, child_nat, this_item_size;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ 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_ALIGN_POLICY (box) == GTK_ALIGN_FILL)
+ {
+ this_item_size++;
+ extra_pixels--;
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_height_for_width (child,
+ this_item_size,
+ &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_width_for_height (child,
+ 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 iter;
+}
+
+/* fit_aligned_item_requests() helper */
+static gint
+gather_aligned_item_requests (EggFlowBox *box,
+ GtkOrientation orientation,
+ gint line_length,
+ gint item_spacing,
+ gint n_children,
+ GtkRequestedSize *item_sizes)
+{
+ EggFlowBoxPrivate *priv = box->priv;
+ GSequenceIter *iter;
+ gint i;
+ gint extra_items, natural_line_size = 0;
+
+ extra_items = n_children % line_length;
+
+ i = 0;
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter), i++)
+ {
+ EggFlowBoxChildInfo *child_info;
+ GtkWidget *child;
+ GtkAlign item_align;
+ gint child_min, child_nat;
+ gint position;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_width (child,
+ &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height (child,
+ &child_min, &child_nat);
+
+ /* Get the index and push it over for the last line when spreading to the end */
+ position = i % line_length;
+
+ item_align = ORIENTATION_ALIGN_POLICY (box);
+ if (item_align == GTK_ALIGN_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 (EggFlowBox *box,
+ GtkOrientation orientation,
+ gint avail_size,
+ gint item_spacing,
+ gint *line_length, /* in-out */
+ gint items_per_line,
+ 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
+ && items_per_line >= try_length)
+ {
+ *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 gint
+get_offset_pixels (GtkAlign align,
+ gint pixels)
+{
+ gint offset;
+
+ switch (align) {
+ case GTK_ALIGN_START:
+ case GTK_ALIGN_FILL:
+ offset = 0;
+ break;
+ case GTK_ALIGN_CENTER:
+ offset = pixels / 2;
+ break;
+ case GTK_ALIGN_END:
+ offset = pixels;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return offset;
+}
+
+static void
+egg_flow_box_real_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *priv = box->priv;
+ GtkAllocation child_allocation;
+ gint avail_size, avail_other_size, min_items, item_spacing, line_spacing;
+ GtkAlign item_align;
+ GtkAlign line_align;
+ GdkWindow *window;
+ GtkRequestedSize *line_sizes = NULL;
+ GtkRequestedSize *item_sizes = NULL;
+ 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 = 0, extra_per_item = 0, extra_extra = 0;
+ gint extra_line_pixels = 0, extra_per_line = 0, extra_line_extra = 0;
+ gint i, this_line_size;
+ GSequenceIter *iter;
+ GtkStyleContext *context;
+ gint focus_width;
+ gint focus_pad;
+
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+ child_allocation.width = 0;
+ child_allocation.height = 0;
+
+ gtk_widget_set_allocation (widget, allocation);
+ window = gtk_widget_get_window (widget);
+ if (window != NULL)
+ gdk_window_move_resize (window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (box));
+ gtk_style_context_get_style (context,
+ "focus-line-width", &focus_width,
+ "focus-padding", &focus_pad,
+ NULL);
+ child_allocation.x = 0 + focus_width + focus_pad;
+ child_allocation.y = 0;
+ child_allocation.width = allocation->width - 2 * (focus_width + focus_pad);
+
+ min_items = MAX (1, priv->min_children_per_line);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ avail_size = allocation->width;
+ avail_other_size = allocation->height;
+ item_spacing = priv->column_spacing;
+ line_spacing = priv->row_spacing;
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ avail_size = allocation->height;
+ avail_other_size = allocation->width;
+ item_spacing = priv->row_spacing;
+ line_spacing = priv->column_spacing;
+ }
+
+ item_align = ORIENTATION_ALIGN_POLICY (box);
+ line_align = OPPOSING_ORIENTATION_ALIGN_POLICY (box);
+
+
+ /*
+ * Deal with ALIGNED/HOMOGENEOUS modes first, start with
+ * initial guesses at item/line sizes
+ */
+ get_average_item_size (box, priv->orientation, &min_item_size, &nat_item_size);
+
+ /* By default flow 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 flow length */
+ line_length = MAX (min_items, line_length);
+ line_length = MIN (line_length, priv->max_children_per_line);
+
+ /* Get how many lines we'll be needing to flow */
+ 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->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_align != GTK_ALIGN_FILL)
+ 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_align != GTK_ALIGN_FILL)
+ line_size = MIN (line_size, nat_fixed_line_size);
+
+ /* Get the real extra pixels incase of GTK_ALIGN_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
+ {
+ 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,
+ priv->max_children_per_line,
+ 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 */
+ if (avail_size > 0)
+ 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, iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ i++)
+ {
+
+ iter = get_largest_size_for_line_in_opposing_orientation (box,
+ priv->orientation,
+ iter,
+ 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 */
+ if (avail_other_size > 0)
+ 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_align == GTK_ALIGN_FILL)
+ {
+ extra_per_item = extra_pixels / line_length;
+ extra_extra = extra_pixels % line_length;
+ }
+
+ /* Calculate expand space per line */
+ if (line_align == GTK_ALIGN_FILL)
+ {
+ 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 */
+ item_offset += get_offset_pixels (item_align, extra_pixels);
+ line_offset += get_offset_pixels (line_align, extra_line_pixels);
+
+ /* Get the allocation size for the first line */
+ if (priv->homogeneous)
+ this_line_size = line_size;
+ else
+ {
+ this_line_size = line_sizes[0].minimum_size;
+
+ if (line_align == GTK_ALIGN_FILL)
+ {
+ this_line_size += extra_per_line;
+
+ if (extra_line_extra > 0)
+ this_line_size++;
+ }
+ }
+
+ i = 0;
+ line_count = 0;
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ EggFlowBoxChildInfo *child_info;
+ GtkWidget *child;
+ gint position;
+ gint this_item_size;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ {
+ child_info->x = child_allocation.x;
+ child_info->y = child_allocation.y;
+ child_info->width = 0;
+ child_info->height = 0;
+ 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;
+
+ line_count++;
+
+ /* Get the new line size */
+ if (priv->homogeneous)
+ this_line_size = line_size;
+ else
+ {
+ this_line_size = line_sizes[line_count].minimum_size;
+
+ if (line_align == GTK_ALIGN_FILL)
+ {
+ this_line_size += extra_per_line;
+
+ if (line_count < extra_line_extra)
+ this_line_size++;
+ }
+ }
+
+ item_offset = 0;
+
+ if (item_align == GTK_ALIGN_CENTER)
+ {
+ item_offset += get_offset_pixels (item_align, extra_pixels);
+ }
+ else if (item_align == GTK_ALIGN_END)
+ {
+ item_offset += get_offset_pixels (item_align, 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->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_align == GTK_ALIGN_END && line_count == n_lines -1)
+ {
+ gint extra_items = n_children % line_length;
+
+ position += line_length - extra_items;
+ }
+
+ if (priv->homogeneous)
+ this_item_size = item_size;
+ else
+ this_item_size = item_sizes[position].minimum_size;
+
+ if (item_align == GTK_ALIGN_FILL)
+ {
+ this_item_size += extra_per_item;
+
+ if (position < extra_extra)
+ this_item_size++;
+ }
+
+ /* Do the actual allocation */
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ child_allocation.x = allocation->x + item_offset;
+ child_allocation.y = allocation->y + line_offset;
+ child_allocation.width = this_item_size;
+ child_allocation.height = this_line_size;
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ child_allocation.x = allocation->x + line_offset;
+ child_allocation.y = allocation->y + item_offset;
+ child_allocation.width = this_line_size;
+ child_allocation.height = this_item_size;
+ }
+
+ child_info->x = child_allocation.x;
+ child_info->y = child_allocation.y;
+ child_info->width = child_allocation.width;
+ child_info->height = child_allocation.height;
+ gtk_widget_size_allocate (child, &child_allocation);
+
+ item_offset += this_item_size;
+ item_offset += item_spacing;
+
+ i++;
+ }
+
+ g_free (item_sizes);
+ g_free (line_sizes);
+}
+
+static void
+egg_flow_box_real_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (container);
+ EggFlowBoxPrivate *priv = box->priv;
+ EggFlowBoxChildInfo *info;
+ GSequenceIter *iter = NULL;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ info = egg_flow_box_child_info_new (child);
+ g_hash_table_insert (priv->child_hash, child, info);
+ iter = g_sequence_append (priv->children, info);
+ info->iter = iter;
+
+ gtk_widget_set_parent (child, GTK_WIDGET (box));
+}
+
+static void
+egg_flow_box_real_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (container);
+ EggFlowBoxPrivate *priv = box->priv;
+ gboolean was_visible;
+ EggFlowBoxChildInfo *info;
+
+ g_return_if_fail (child != NULL);
+
+ was_visible = gtk_widget_get_visible (child);
+
+ info = egg_flow_box_lookup_info (box, child);
+ if (info == NULL)
+ {
+ g_warning ("Tried to remove non-child %p\n", child);
+ return;
+ }
+
+ gtk_widget_unparent (child);
+ g_hash_table_remove (priv->child_hash, child);
+ g_sequence_remove (info->iter);
+
+ if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+}
+
+static void
+egg_flow_box_real_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_target)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (container);
+ EggFlowBoxPrivate *priv = box->priv;
+ GSequenceIter *iter;
+ EggFlowBoxChildInfo *child_info;
+
+ iter = g_sequence_get_begin_iter (priv->children);
+ while (!g_sequence_iter_is_end (iter))
+ {
+ child_info = g_sequence_get (iter);
+ iter = g_sequence_iter_next (iter);
+ callback (child_info->widget, callback_target);
+ }
+}
+
+static GType
+egg_flow_box_real_child_type (GtkContainer *container)
+{
+ return GTK_TYPE_WIDGET;
+}
+
+static GtkSizeRequestMode
+egg_flow_box_real_get_request_mode (GtkWidget *widget)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *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 when aligned into rows/columns */
+static void
+get_largest_aligned_line_length (EggFlowBox *box,
+ GtkOrientation orientation,
+ gint line_length,
+ gint *min_size,
+ gint *nat_size)
+{
+ EggFlowBoxPrivate *priv = box->priv;
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+ gint spacing, i;
+ GtkRequestedSize *aligned_item_sizes;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ spacing = priv->column_spacing;
+ else
+ spacing = priv->row_spacing;
+
+ aligned_item_sizes = g_new0 (GtkRequestedSize, line_length);
+
+ /* Get the largest sizes of each index in the line.
+ */
+ i = 0;
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ EggFlowBoxChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+
+ child_info = g_sequence_get (iter);
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_width (child,
+ &child_min, &child_nat);
+ else /* GTK_ORIENTATION_VERTICAL */
+ gtk_widget_get_preferred_height (child,
+ &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
+egg_flow_box_real_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *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->min_children_per_line);
+ nat_items = MAX (min_items, priv->max_children_per_line);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ min_width = nat_width = 0;
+
+ if (! priv->homogeneous)
+ {
+ /* When not homogeneous; 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
+ {
+ 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->column_spacing;
+
+ nat_width += nat_item_width * nat_items;
+ nat_width += (nat_items -1) * priv->column_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
+egg_flow_box_real_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *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->min_children_per_line);
+ nat_items = MAX (min_items, priv->max_children_per_line);
+
+ 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->homogeneous)
+ {
+ /* When not homogeneous; 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
+ {
+ 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->row_spacing;
+
+ nat_height += nat_item_height * nat_items;
+ nat_height += (nat_items -1) * priv->row_spacing;
+ }
+ }
+
+ if (minimum_size)
+ *minimum_size = min_height;
+
+ if (natural_size)
+ *natural_size = nat_height;
+}
+
+static void
+egg_flow_box_real_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *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->min_children_per_line);
+
+ min_height = 0;
+ nat_height = 0;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ gint min_width;
+ gint line_length;
+ gint item_size, extra_pixels;
+
+ 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);
+
+ get_average_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width);
+
+ /* By default flow at the natural item width */
+ line_length = avail_size / (nat_item_width + priv->column_spacing);
+
+ /* After the above aproximation, check if we cant fit one more on the line */
+ if (line_length * priv->column_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 flow length */
+ line_length = MAX (min_items, line_length);
+ line_length = MIN (line_length, priv->max_children_per_line);
+
+ /* Now we need the real item allocation size */
+ item_size = (avail_size - (line_length - 1) * priv->column_spacing) / line_length;
+
+ /* Cut out the expand space if we're not distributing any */
+ if (priv->halign_policy != GTK_ALIGN_FILL)
+ {
+ 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->column_spacing) % line_length;
+
+ if (priv->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->row_spacing;
+ nat_height += (lines - 1) * priv->row_spacing;
+ }
+ else
+ {
+ gint min_line_height, nat_line_height, i;
+ gboolean first_line = TRUE;
+ GtkRequestedSize *item_sizes;
+ GSequenceIter *iter;
+
+ /* First get the size each set of items take to span the line
+ * when aligning the items above and below after flowping.
+ */
+ item_sizes = fit_aligned_item_requests (box,
+ priv->orientation,
+ avail_size,
+ priv->column_spacing,
+ &line_length,
+ priv->max_children_per_line,
+ n_children);
+
+ /* Get the available remaining size */
+ avail_size -= (line_length - 1) * priv->column_spacing;
+ for (i = 0; i < line_length; i++)
+ avail_size -= item_sizes[i].minimum_size;
+
+ if (avail_size > 0)
+ extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ iter = get_largest_size_for_line_in_opposing_orientation (box,
+ GTK_ORIENTATION_HORIZONTAL,
+ iter,
+ 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->row_spacing;
+ nat_height += priv->row_spacing;
+ }
+
+ min_height += min_line_height;
+ nat_height += nat_line_height;
+ }
+ }
+
+ g_free (item_sizes);
+ }
+ }
+ 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
+egg_flow_box_real_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *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->min_children_per_line);
+
+ 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;
+ gint line_length;
+ gint item_size, extra_pixels;
+
+ 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);
+
+ get_average_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height);
+
+ /* By default flow at the natural item width */
+ line_length = avail_size / (nat_item_height + priv->row_spacing);
+
+ /* After the above aproximation, check if we cant fit one more on the line */
+ if (line_length * priv->row_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 flow length */
+ line_length = MAX (min_items, line_length);
+ line_length = MIN (line_length, priv->max_children_per_line);
+
+ /* Now we need the real item allocation size */
+ item_size = (avail_size - (line_length - 1) * priv->row_spacing) / line_length;
+
+ /* Cut out the expand space if we're not distributing any */
+ if (priv->valign_policy != GTK_ALIGN_FILL)
+ {
+ 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->row_spacing) % line_length;
+
+ if (priv->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->column_spacing;
+ nat_width += (lines - 1) * priv->column_spacing;
+ }
+ else
+ {
+ gint min_line_width, nat_line_width, i;
+ gboolean first_line = TRUE;
+ GtkRequestedSize *item_sizes;
+ GSequenceIter *iter;
+
+ /* First get the size each set of items take to span the line
+ * when aligning the items above and below after flowping.
+ */
+ item_sizes = fit_aligned_item_requests (box,
+ priv->orientation,
+ avail_size,
+ priv->row_spacing,
+ &line_length,
+ priv->max_children_per_line,
+ n_children);
+
+ /* Get the available remaining size */
+ avail_size -= (line_length - 1) * priv->column_spacing;
+ for (i = 0; i < line_length; i++)
+ avail_size -= item_sizes[i].minimum_size;
+
+ if (avail_size > 0)
+ extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ iter = get_largest_size_for_line_in_opposing_orientation (box,
+ GTK_ORIENTATION_VERTICAL,
+ iter,
+ 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->column_spacing;
+ nat_width += priv->column_spacing;
+ }
+
+ min_width += min_line_width;
+ nat_width += nat_line_width;
+ }
+ }
+ g_free (item_sizes);
+ }
+ }
+
+ if (minimum_width)
+ *minimum_width = min_width;
+
+ if (natural_width)
+ *natural_width = nat_width;
+}
+
+/**
+ * egg_flow_box_set_halign_policy:
+ * @box: An #EggFlowBox
+ * @align: The #GtkAlign to use.
+ *
+ * Sets the horizontal align policy for @box's children.
+ */
+void
+egg_flow_box_set_halign_policy (EggFlowBox *box,
+ GtkAlign align)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->halign_policy != align)
+ {
+ priv->halign_policy = align;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ g_object_notify (G_OBJECT (box), "halign-policy");
+ }
+}
+
+/**
+ * egg_flow_box_get_halign_policy:
+ * @box: An #EggFlowBox
+ *
+ * Gets the horizontal alignment policy.
+ *
+ * Returns: The horizontal #GtkAlign for @box.
+ */
+GtkAlign
+egg_flow_box_get_halign_policy (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->halign_policy;
+}
+
+
+/**
+ * egg_flow_box_set_valign_policy:
+ * @box: An #EggFlowBox
+ * @align: The #GtkAlign to use.
+ *
+ * Sets the vertical alignment policy for @box's children.
+ */
+void
+egg_flow_box_set_valign_policy (EggFlowBox *box,
+ GtkAlign align)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->valign_policy != align)
+ {
+ priv->valign_policy = align;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ g_object_notify (G_OBJECT (box), "valign-policy");
+ }
+}
+
+/**
+ * egg_flow_box_get_valign_policy:
+ * @box: An #EggFlowBox
+ *
+ * Gets the vertical alignment policy.
+ *
+ * Returns: The vertical #GtkAlign for @box.
+ */
+GtkAlign
+egg_flow_box_get_valign_policy (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->valign_policy;
+}
+
+
+/**
+ * egg_flow_box_set_row_spacing:
+ * @box: An #EggFlowBox
+ * @spacing: The spacing to use.
+ *
+ * Sets the vertical space to add between children.
+ */
+void
+egg_flow_box_set_row_spacing (EggFlowBox *box,
+ guint spacing)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->row_spacing != spacing)
+ {
+ priv->row_spacing = spacing;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ g_object_notify (G_OBJECT (box), "vertical-spacing");
+ }
+}
+
+/**
+ * egg_flow_box_get_row_spacing:
+ * @box: An #EggFlowBox
+ *
+ * Gets the vertical spacing.
+ *
+ * Returns: The vertical spacing.
+ */
+guint
+egg_flow_box_get_row_spacing (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->row_spacing;
+}
+
+/**
+ * egg_flow_box_set_column_spacing:
+ * @box: An #EggFlowBox
+ * @spacing: The spacing to use.
+ *
+ * Sets the horizontal space to add between children.
+ */
+void
+egg_flow_box_set_column_spacing (EggFlowBox *box,
+ guint spacing)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->column_spacing != spacing)
+ {
+ priv->column_spacing = spacing;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ g_object_notify (G_OBJECT (box), "horizontal-spacing");
+ }
+}
+
+/**
+ * egg_flow_box_get_column_spacing:
+ * @box: An #EggFlowBox
+ *
+ * Gets the horizontal spacing.
+ *
+ * Returns: The horizontal spacing.
+ */
+guint
+egg_flow_box_get_column_spacing (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->column_spacing;
+}
+
+/**
+ * egg_flow_box_set_min_children_per_line:
+ * @box: An #EggFlowBox
+ * @n_children: The minimum amount of children per line.
+ *
+ * Sets the minimum amount of children to line up
+ * in @box's orientation before flowping.
+ */
+void
+egg_flow_box_set_min_children_per_line (EggFlowBox *box,
+ guint n_children)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->min_children_per_line != n_children)
+ {
+ priv->min_children_per_line = n_children;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ g_object_notify (G_OBJECT (box), "min-children-per-line");
+ }
+}
+
+/**
+ * egg_flow_box_get_min_children_per_line:
+ * @box: An #EggFlowBox
+ *
+ * Gets the minimum amount of children per line.
+ *
+ * Returns: The minimum amount of children per line.
+ */
+guint
+egg_flow_box_get_min_children_per_line (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->min_children_per_line;
+}
+
+/**
+ * egg_flow_box_set_max_children_per_line:
+ * @box: An #EggFlowBox
+ * @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
+egg_flow_box_set_max_children_per_line (EggFlowBox *box,
+ guint n_children)
+{
+ EggFlowBoxPrivate *priv;
+
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->max_children_per_line != n_children)
+ {
+ priv->max_children_per_line = n_children;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ g_object_notify (G_OBJECT (box), "max-children-per-line");
+ }
+}
+
+/**
+ * egg_flow_box_get_max_children_per_line:
+ * @box: An #EggFlowBox
+ *
+ * Gets the natural amount of children per line.
+ *
+ * Returns: The natural amount of children per line.
+ */
+guint
+egg_flow_box_get_max_children_per_line (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->max_children_per_line;
+}
+
+/**
+ * egg_flow_box_set_activate_on_single_click:
+ * @box: An #EggFlowBox
+ * @single: %TRUE to emit child-activated on a single click
+ *
+ * Causes the #EggFlowBox::child-activated signal to be emitted on
+ * a single click instead of a double click.
+ **/
+void
+egg_flow_box_set_activate_on_single_click (EggFlowBox *box,
+ gboolean single)
+{
+ g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+ single = single != FALSE;
+
+ if (box->priv->activate_on_single_click == single)
+ return;
+
+ box->priv->activate_on_single_click = single;
+ g_object_notify (G_OBJECT (box), "activate-on-single-click");
+}
+
+/**
+ * egg_flow_box_get_activate_on_single_click:
+ * @box: An #EggFlowBox
+ *
+ * Gets the setting set by egg_flow_box_set_activate_on_single_click().
+ *
+ * Return value: %TRUE if child-activated will be emitted on a single click
+ **/
+gboolean
+egg_flow_box_get_activate_on_single_click (EggFlowBox *box)
+{
+ g_return_val_if_fail (EGG_IS_FLOW_BOX (box), FALSE);
+
+ return box->priv->activate_on_single_click;
+}
+
+static void
+egg_flow_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (object);
+ EggFlowBoxPrivate *priv = box->priv;
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, priv->orientation);
+ break;
+ case PROP_HOMOGENEOUS:
+ g_value_set_boolean (value, priv->homogeneous);
+ break;
+ case PROP_HALIGN_POLICY:
+ g_value_set_enum (value, priv->halign_policy);
+ break;
+ case PROP_VALIGN_POLICY:
+ g_value_set_enum (value, priv->valign_policy);
+ break;
+ case PROP_COLUMN_SPACING:
+ g_value_set_uint (value, priv->column_spacing);
+ break;
+ case PROP_ROW_SPACING:
+ g_value_set_uint (value, priv->row_spacing);
+ break;
+ case PROP_MIN_CHILDREN_PER_LINE:
+ g_value_set_uint (value, priv->min_children_per_line);
+ break;
+ case PROP_MAX_CHILDREN_PER_LINE:
+ g_value_set_uint (value, priv->max_children_per_line);
+ break;
+ case PROP_ACTIVATE_ON_SINGLE_CLICK:
+ g_value_set_boolean (value, priv->activate_on_single_click);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+egg_flow_box_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (object);
+ EggFlowBoxPrivate *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_HOMOGENEOUS:
+ egg_flow_box_set_homogeneous (box, g_value_get_boolean (value));
+ break;
+ case PROP_HALIGN_POLICY:
+ egg_flow_box_set_halign_policy (box, g_value_get_enum (value));
+ break;
+ case PROP_VALIGN_POLICY:
+ egg_flow_box_set_valign_policy (box, g_value_get_enum (value));
+ break;
+ case PROP_COLUMN_SPACING:
+ egg_flow_box_set_column_spacing (box, g_value_get_uint (value));
+ break;
+ case PROP_ROW_SPACING:
+ egg_flow_box_set_row_spacing (box, g_value_get_uint (value));
+ break;
+ case PROP_MIN_CHILDREN_PER_LINE:
+ egg_flow_box_set_min_children_per_line (box, g_value_get_uint (value));
+ break;
+ case PROP_MAX_CHILDREN_PER_LINE:
+ egg_flow_box_set_max_children_per_line (box, g_value_get_uint (value));
+ break;
+ case PROP_ACTIVATE_ON_SINGLE_CLICK:
+ egg_flow_box_set_activate_on_single_click (box, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static EggFlowBoxChildInfo *
+egg_flow_box_find_child_at_pos (EggFlowBox *box,
+ gint x,
+ gint y)
+{
+ EggFlowBoxPrivate *priv = box->priv;
+ EggFlowBoxChildInfo *child_info;
+ GSequenceIter *iter;
+ EggFlowBoxChildInfo *info;
+
+ child_info = NULL;
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ info = (EggFlowBoxChildInfo *) g_sequence_get (iter);
+ if (x >= info->x && x < (info->x + info->width)
+ && y >= info->y && y < (info->y + info->height))
+ {
+ child_info = info;
+ break;
+ }
+ }
+
+ return child_info;
+}
+
+static gboolean
+egg_flow_box_real_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *priv = box->priv;
+
+ if (event->button == 1)
+ {
+ EggFlowBoxChildInfo *child;
+ child = egg_flow_box_find_child_at_pos (box, event->x, event->y);
+ if (child != NULL)
+ {
+ priv->active_child = child;
+ priv->active_child_active = TRUE;
+ if (event->type == GDK_2BUTTON_PRESS &&
+ !priv->activate_on_single_click)
+ g_signal_emit (box,
+ signals[CHILD_ACTIVATED], 0,
+ child->widget);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+egg_flow_box_select_and_activate (EggFlowBox *box,
+ EggFlowBoxChildInfo *child)
+{
+ GtkWidget *w = NULL;
+
+ if (child != NULL)
+ w = child->widget;
+
+ if (w != NULL)
+ g_signal_emit (box, signals[CHILD_ACTIVATED], 0, w);
+}
+
+static gboolean
+egg_flow_box_real_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ EggFlowBoxPrivate *priv = box->priv;
+
+ if (event->button == 1)
+ {
+ if (priv->active_child != NULL &&
+ priv->active_child_active)
+ {
+ if (priv->activate_on_single_click)
+ egg_flow_box_select_and_activate (box, priv->active_child);
+ }
+ priv->active_child = NULL;
+ priv->active_child_active = FALSE;
+ gtk_widget_queue_draw (GTK_WIDGET (box));
+ }
+
+ return FALSE;
+}
+
+static void
+egg_flow_box_real_realize (GtkWidget *widget)
+{
+ EggFlowBox *box = EGG_FLOW_BOX (widget);
+ GtkAllocation allocation;
+ GdkWindowAttr attributes = {0};
+ GdkWindow *window;
+
+ gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
+ gtk_widget_set_realized (GTK_WIDGET (box), TRUE);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box)) |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (box)),
+ &attributes, GDK_WA_X | GDK_WA_Y);
+ gtk_style_context_set_background (gtk_widget_get_style_context (GTK_WIDGET (box)), window);
+ gdk_window_set_user_data (window, (GObject*) box);
+ gtk_widget_set_window (GTK_WIDGET (box), window); /* Passes ownership */
+}
+
+static void
+egg_flow_box_finalize (GObject *obj)
+{
+ EggFlowBox *flow_box = EGG_FLOW_BOX (obj);
+ EggFlowBoxPrivate *priv = flow_box->priv;
+
+ g_sequence_free (priv->children);
+ g_hash_table_unref (priv->child_hash);
+
+ G_OBJECT_CLASS (egg_flow_box_parent_class)->finalize (obj);
+}
+
+static void
+egg_flow_box_class_init (EggFlowBoxClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
+
+ object_class->finalize = egg_flow_box_finalize;
+ object_class->get_property = egg_flow_box_get_property;
+ object_class->set_property = egg_flow_box_set_property;
+
+ widget_class->size_allocate = egg_flow_box_real_size_allocate;
+ widget_class->realize = egg_flow_box_real_realize;
+ widget_class->button_press_event = egg_flow_box_real_button_press_event;
+ widget_class->button_release_event = egg_flow_box_real_button_release_event;
+ widget_class->get_request_mode = egg_flow_box_real_get_request_mode;
+ widget_class->get_preferred_width = egg_flow_box_real_get_preferred_width;
+ widget_class->get_preferred_height = egg_flow_box_real_get_preferred_height;
+ widget_class->get_preferred_height_for_width = egg_flow_box_real_get_preferred_height_for_width;
+ widget_class->get_preferred_width_for_height = egg_flow_box_real_get_preferred_width_for_height;
+
+ container_class->add = egg_flow_box_real_add;
+ container_class->remove = egg_flow_box_real_remove;
+ container_class->forall = egg_flow_box_real_forall;
+ container_class->child_type = egg_flow_box_real_child_type;
+ gtk_container_class_handle_border_width (container_class);
+
+ /* GObjectClass properties */
+ g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVATE_ON_SINGLE_CLICK,
+ g_param_spec_boolean ("activate-on-single-click",
+ P_("Activate on Single Click"),
+ P_("Activate row on a single click"),
+ TRUE,
+ G_PARAM_READWRITE));
+
+
+ g_object_class_install_property (object_class,
+ PROP_HOMOGENEOUS,
+ g_param_spec_boolean ("homogeneous",
+ P_("Homogeneous"),
+ P_("Whether the children should all be the same size"),
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * EggFlowBox:halign-policy:
+ *
+ * The #GtkAlign to used to define what is done with extra
+ * space in a given orientation.
+ */
+ g_object_class_install_property (object_class,
+ PROP_HALIGN_POLICY,
+ g_param_spec_enum ("halign-policy",
+ P_("Horizontal align policy"),
+ P_("The align policy horizontally"),
+ GTK_TYPE_ALIGN,
+ GTK_ALIGN_FILL,
+ G_PARAM_READWRITE));
+ /**
+ * EggFlowBox:valign-policy:
+ *
+ * The #GtkAlign to used to define what is done with extra
+ * space in a given orientation.
+ */
+ g_object_class_install_property (object_class,
+ PROP_VALIGN_POLICY,
+ g_param_spec_enum ("valign-policy",
+ P_("Vertical align policy"),
+ P_("The align policy vertically"),
+ GTK_TYPE_ALIGN,
+ GTK_ALIGN_START,
+ G_PARAM_READWRITE));
+
+ /**
+ * EggFlowBox:min-children-per-line:
+ *
+ * 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 (object_class,
+ PROP_MIN_CHILDREN_PER_LINE,
+ g_param_spec_uint ("min-children-per-line",
+ P_("Minimum Children Per Line"),
+ P_("The minimum number of children to allocate "
+ "consecutively in the given orientation."),
+ 0,
+ 65535,
+ 0,
+ G_PARAM_READWRITE));
+
+ /**
+ * EggFlowBox:max-children-per-line:
+ *
+ * The maximum amount of children to request space for consecutively in the given orientation.
+ *
+ */
+ g_object_class_install_property (object_class,
+ PROP_MAX_CHILDREN_PER_LINE,
+ g_param_spec_uint ("max-children-per-line",
+ P_("Maximum Children Per Line"),
+ P_("The maximum amount of children to request space for "
+ "consecutively in the given orientation."),
+ 0,
+ 65535,
+ 0,
+ G_PARAM_READWRITE));
+
+ /**
+ * EggFlowBox:vertical-spacing:
+ *
+ * The amount of vertical space between two children.
+ *
+ */
+ g_object_class_install_property (object_class,
+ PROP_ROW_SPACING,
+ g_param_spec_uint ("vertical-spacing",
+ P_("Vertical spacing"),
+ P_("The amount of vertical space between two children"),
+ 0,
+ 65535,
+ 0,
+ G_PARAM_READWRITE));
+
+ /**
+ * EggFlowBox:horizontal-spacing:
+ *
+ * The amount of horizontal space between two children.
+ *
+ */
+ g_object_class_install_property (object_class,
+ PROP_COLUMN_SPACING,
+ g_param_spec_uint ("horizontal-spacing",
+ P_("Horizontal spacing"),
+ P_("The amount of horizontal space between two children"),
+ 0,
+ 65535,
+ 0,
+ G_PARAM_READWRITE));
+
+ signals[CHILD_ACTIVATED] = g_signal_new ("child-activated",
+ EGG_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggFlowBoxClass, child_activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_WIDGET);
+
+ g_type_class_add_private (class, sizeof (EggFlowBoxPrivate));
+}
+
+static void
+egg_flow_box_init (EggFlowBox *box)
+{
+ EggFlowBoxPrivate *priv;
+
+ box->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (box, EGG_TYPE_FLOW_BOX, EggFlowBoxPrivate);
+
+ priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+ priv->halign_policy = GTK_ALIGN_FILL;
+ priv->valign_policy = GTK_ALIGN_START;
+ priv->column_spacing = 0;
+ priv->row_spacing = 0;
+ priv->children = g_sequence_new ((GDestroyNotify)egg_flow_box_child_info_free);
+ priv->child_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+ priv->activate_on_single_click = TRUE;
+
+ gtk_widget_set_has_window (GTK_WIDGET (box), TRUE);
+}
+
+/**
+ * egg_flow_box_new:
+ *
+ * Creates an #EggFlowBox.
+ *
+ * Returns: A new #EggFlowBox container
+ */
+GtkWidget *
+egg_flow_box_new (void)
+{
+ return (GtkWidget *)g_object_new (EGG_TYPE_FLOW_BOX, NULL);
+}
diff --git a/egg-flow-box.h b/egg-flow-box.h
new file mode 100644
index 0000000..4418415
--- /dev/null
+++ b/egg-flow-box.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 Openismus GmbH
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_FLOW_BOX_H__
+#define __EGG_FLOW_BOX_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+
+#define EGG_TYPE_FLOW_BOX (egg_flow_box_get_type ())
+#define EGG_FLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_FLOW_BOX, EggFlowBox))
+#define EGG_FLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_FLOW_BOX, EggFlowBoxClass))
+#define EGG_IS_FLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_FLOW_BOX))
+#define EGG_IS_FLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_FLOW_BOX))
+#define EGG_FLOW_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_FLOW_BOX, EggFlowBoxClass))
+
+typedef struct _EggFlowBox EggFlowBox;
+typedef struct _EggFlowBoxPrivate EggFlowBoxPrivate;
+typedef struct _EggFlowBoxClass EggFlowBoxClass;
+
+struct _EggFlowBox
+{
+ GtkContainer container;
+
+ /*< private >*/
+ EggFlowBoxPrivate *priv;
+};
+
+struct _EggFlowBoxClass
+{
+ GtkContainerClass parent_class;
+
+ void (* child_activated) (EggFlowBox *self, GtkWidget *child);
+};
+
+GType egg_flow_box_get_type (void) G_GNUC_CONST;
+
+GtkWidget *egg_flow_box_new (void);
+
+void egg_flow_box_set_homogeneous (EggFlowBox *box,
+ gboolean homogeneous);
+gboolean egg_flow_box_get_homogeneous (EggFlowBox *box);
+void egg_flow_box_set_halign_policy (EggFlowBox *box,
+ GtkAlign align);
+GtkAlign egg_flow_box_get_halign_policy (EggFlowBox *box);
+void egg_flow_box_set_valign_policy (EggFlowBox *box,
+ GtkAlign align);
+GtkAlign egg_flow_box_get_valign_policy (EggFlowBox *box);
+void egg_flow_box_set_row_spacing (EggFlowBox *box,
+ guint spacing);
+guint egg_flow_box_get_row_spacing (EggFlowBox *box);
+
+void egg_flow_box_set_column_spacing (EggFlowBox *box,
+ guint spacing);
+guint egg_flow_box_get_column_spacing (EggFlowBox *box);
+
+void egg_flow_box_set_min_children_per_line (EggFlowBox *box,
+ guint n_children);
+guint egg_flow_box_get_min_children_per_line (EggFlowBox *box);
+
+void egg_flow_box_set_max_children_per_line (EggFlowBox *box,
+ guint n_children);
+guint egg_flow_box_get_max_children_per_line (EggFlowBox *box);
+
+gboolean egg_flow_box_get_activate_on_single_click (EggFlowBox *box);
+void egg_flow_box_set_activate_on_single_click (EggFlowBox *box,
+ gboolean single);
+
+G_END_DECLS
+
+
+#endif /* __EGG_FLOW_BOX_H__ */
diff --git a/test-flow-box.c b/test-flow-box.c
new file mode 100644
index 0000000..ff7b16c
--- /dev/null
+++ b/test-flow-box.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2010 Openismus GmbH
+ *
+ * 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.
+ */
+
+#include <gtk/gtk.h>
+#include "egg-flow-box.h"
+
+enum {
+ SIMPLE_ITEMS = 0,
+ WRAPPY_ITEMS,
+ STOCK_ITEMS,
+ IMAGE_ITEMS
+};
+
+#define INITIAL_HALIGN GTK_ALIGN_FILL
+#define INITIAL_VALIGN GTK_ALIGN_START
+#define INITIAL_MINIMUM_LENGTH 3
+#define INITIAL_MAXIMUM_LENGTH 6
+#define INITIAL_CSPACING 2
+#define INITIAL_RSPACING 2
+#define N_ITEMS 4000
+
+static EggFlowBox *the_flowbox = NULL;
+static gint items_type = SIMPLE_ITEMS;
+static GtkOrientation text_orientation = GTK_ORIENTATION_HORIZONTAL;
+
+static void
+populate_flowbox_simple (EggFlowBox *flowbox)
+{
+ GtkWidget *widget, *frame;
+ gint i;
+
+ for (i = 0; i < N_ITEMS; i++)
+ {
+ gchar *text = g_strdup_printf ("Item %02d", i);
+
+ widget = gtk_label_new (text);
+ frame = gtk_frame_new (NULL);
+ gtk_widget_show (widget);
+ gtk_widget_show (frame);
+
+ gtk_container_add (GTK_CONTAINER (frame), widget);
+
+ if (text_orientation == GTK_ORIENTATION_VERTICAL)
+ gtk_label_set_angle (GTK_LABEL (widget), 90);
+ g_object_set_data_full (G_OBJECT (frame), "id", (gpointer)g_strdup (text), g_free);
+ gtk_container_add (GTK_CONTAINER (flowbox), frame);
+
+ g_free (text);
+ }
+}
+
+static void
+populate_flowbox_wrappy (EggFlowBox *flowbox)
+{
+ GtkWidget *widget, *frame;
+ gint i;
+
+ const gchar *strings[] = {
+ "These are", "some wrappy label", "texts", "of various", "lengths.",
+ "They should always be", "shown", "consecutively. Except it's",
+ "hard to say", "where exactly the", "label", "will wrap", "and where exactly",
+ "the actual", "container", "will wrap.", "This label is really really really long !",
+ "Let's add some more", "labels to the",
+ "mix. Just to", "make sure we", "got something to work", "with here."
+ };
+
+ for (i = 0; i < G_N_ELEMENTS (strings); i++)
+ {
+ widget = gtk_label_new (strings[i]);
+ frame = gtk_frame_new (NULL);
+ gtk_widget_show (widget);
+ gtk_widget_show (frame);
+
+ if (text_orientation == GTK_ORIENTATION_VERTICAL)
+ gtk_label_set_angle (GTK_LABEL (widget), 90);
+
+ gtk_container_add (GTK_CONTAINER (frame), widget);
+
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (widget), PANGO_WRAP_WORD);
+ gtk_label_set_width_chars (GTK_LABEL (widget), 10);
+ g_object_set_data_full (G_OBJECT (frame), "id", (gpointer)g_strdup (strings[i]), g_free);
+
+ gtk_container_add (GTK_CONTAINER (flowbox), frame);
+ }
+}
+
+static void
+populate_flowbox_stock (EggFlowBox *flowbox)
+{
+ GtkWidget *widget;
+ static GSList *stock_ids = NULL;
+ GSList *l;
+ gint i;
+
+ if (!stock_ids)
+ stock_ids = gtk_stock_list_ids ();
+
+ for (i = 0, l = stock_ids; i < 30 && l != NULL; i++, l = l->next)
+ {
+ gchar *stock_id = l->data;
+ gchar *text = g_strdup_printf ("Item %02d", i);
+
+ widget = gtk_button_new_from_stock (stock_id);
+ gtk_widget_show (widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "id", (gpointer)g_strdup (text), g_free);
+ gtk_container_add (GTK_CONTAINER (flowbox), widget);
+ }
+}
+
+static void
+populate_flowbox_images (EggFlowBox *flowbox)
+{
+ GtkWidget *widget, *image, *label;
+ gint i;
+
+ for (i = 0; i < N_ITEMS; i++)
+ {
+ gchar *text = g_strdup_printf ("Item %02d", i);
+
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_set_hexpand (widget, TRUE);
+
+ image = gtk_image_new_from_icon_name ("face-sad", GTK_ICON_SIZE_DIALOG);
+ gtk_widget_set_hexpand (image, TRUE);
+ gtk_image_set_pixel_size (GTK_IMAGE (image), 256);
+
+ label = gtk_label_new (text);
+
+ gtk_container_add (GTK_CONTAINER (widget), image);
+ gtk_container_add (GTK_CONTAINER (widget), label);
+ gtk_widget_show_all (widget);
+
+ if (text_orientation == GTK_ORIENTATION_VERTICAL)
+ gtk_label_set_angle (GTK_LABEL (widget), 90);
+
+ g_object_set_data_full (G_OBJECT (widget), "id", (gpointer)g_strdup (text), g_free);
+ gtk_container_add (GTK_CONTAINER (flowbox), widget);
+
+ g_free (text);
+ }
+}
+
+static void
+populate_items (EggFlowBox *flowbox)
+{
+ GList *children, *l;
+
+ /* Remove all children first */
+ children = gtk_container_get_children (GTK_CONTAINER (flowbox));
+ for (l = children; l; l = l->next)
+ {
+ GtkWidget *child = l->data;
+
+ gtk_container_remove (GTK_CONTAINER (flowbox), child);
+ }
+ g_list_free (children);
+
+ if (items_type == SIMPLE_ITEMS)
+ populate_flowbox_simple (flowbox);
+ else if (items_type == WRAPPY_ITEMS)
+ populate_flowbox_wrappy (flowbox);
+ else if (items_type == STOCK_ITEMS)
+ populate_flowbox_stock (flowbox);
+ else if (items_type == IMAGE_ITEMS)
+ populate_flowbox_images (flowbox);
+}
+
+static void
+horizontal_alignment_changed (GtkComboBox *box,
+ EggFlowBox *flowbox)
+{
+ GtkAlign alignment = gtk_combo_box_get_active (box);
+
+ egg_flow_box_set_halign_policy (flowbox, alignment);
+}
+
+static void
+vertical_alignment_changed (GtkComboBox *box,
+ EggFlowBox *flowbox)
+{
+ GtkAlign alignment = gtk_combo_box_get_active (box);
+
+ egg_flow_box_set_valign_policy (flowbox, alignment);
+}
+
+static void
+orientation_changed (GtkComboBox *box,
+ EggFlowBox *flowbox)
+{
+ GtkOrientation orientation = gtk_combo_box_get_active (box);
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (flowbox), orientation);
+}
+
+static void
+line_length_changed (GtkSpinButton *spin,
+ EggFlowBox *flowbox)
+{
+ gint length = gtk_spin_button_get_value_as_int (spin);
+
+ egg_flow_box_set_min_children_per_line (flowbox, length);
+}
+
+static void
+max_line_length_changed (GtkSpinButton *spin,
+ EggFlowBox *flowbox)
+{
+ gint length = gtk_spin_button_get_value_as_int (spin);
+
+ egg_flow_box_set_max_children_per_line (flowbox, length);
+}
+
+static void
+spacing_changed (GtkSpinButton *button,
+ gpointer data)
+{
+ GtkOrientation orientation = GPOINTER_TO_INT (data);
+ gint state = gtk_spin_button_get_value_as_int (button);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ egg_flow_box_set_column_spacing (the_flowbox, state);
+ else
+ egg_flow_box_set_row_spacing (the_flowbox, state);
+}
+
+static void
+items_changed (GtkComboBox *box,
+ EggFlowBox *flowbox)
+{
+ items_type = gtk_combo_box_get_active (box);
+
+ populate_items (flowbox);
+}
+
+static void
+text_orientation_changed (GtkComboBox *box,
+ EggFlowBox *flowbox)
+{
+ text_orientation = gtk_combo_box_get_active (box);
+
+ populate_items (flowbox);
+}
+
+static void
+homogeneous_toggled (GtkToggleButton *button,
+ EggFlowBox *flowbox)
+{
+ gboolean state = gtk_toggle_button_get_active (button);
+
+ egg_flow_box_set_homogeneous (flowbox, state);
+}
+
+static void
+on_child_activated (EggFlowBox *self,
+ GtkWidget *child)
+{
+ const char *id;
+ id = g_object_get_data (G_OBJECT (child), "id");
+ g_message ("Child activated %p: %s", child, id);
+}
+
+static GtkWidget *
+create_window (void)
+{
+ GtkWidget *window, *hbox, *vbox, *flowbox_cntl, *items_cntl;
+ GtkWidget *flowbox, *widget, *expander, *swindow;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+ gtk_container_set_border_width (GTK_CONTAINER (window), 8);
+
+ gtk_widget_show (vbox);
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (window), hbox);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+
+ swindow = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+ gtk_widget_show (swindow);
+ gtk_box_pack_start (GTK_BOX (hbox), swindow, TRUE, TRUE, 0);
+
+ flowbox = egg_flow_box_new ();
+ the_flowbox = (EggFlowBox *)flowbox;
+ egg_flow_box_set_halign_policy (EGG_FLOW_BOX (flowbox), INITIAL_HALIGN);
+ egg_flow_box_set_valign_policy (EGG_FLOW_BOX (flowbox), INITIAL_VALIGN);
+ egg_flow_box_set_column_spacing (EGG_FLOW_BOX (flowbox), INITIAL_CSPACING);
+ egg_flow_box_set_row_spacing (EGG_FLOW_BOX (flowbox), INITIAL_RSPACING);
+ egg_flow_box_set_min_children_per_line (EGG_FLOW_BOX (flowbox), INITIAL_MINIMUM_LENGTH);
+ egg_flow_box_set_max_children_per_line (EGG_FLOW_BOX (flowbox), INITIAL_MAXIMUM_LENGTH);
+ gtk_widget_show (flowbox);
+ gtk_container_add (GTK_CONTAINER (swindow), flowbox);
+
+ g_signal_connect (flowbox, "child-activated", G_CALLBACK (on_child_activated), NULL);
+
+ /* Add Flowbox test control frame */
+ expander = gtk_expander_new ("Flow Box controls");
+ gtk_expander_set_expanded (GTK_EXPANDER (expander), TRUE);
+ flowbox_cntl = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_widget_show (flowbox_cntl);
+ gtk_widget_show (expander);
+ gtk_container_add (GTK_CONTAINER (expander), flowbox_cntl);
+ gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
+
+ widget = gtk_check_button_new_with_label ("Homogeneous");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set whether the items should be displayed at the same size");
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (homogeneous_toggled), flowbox);
+
+ /* Add alignment controls */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Fill");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Start");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "End");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Center");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), INITIAL_HALIGN);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the horizontal alignment policy");
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (horizontal_alignment_changed), flowbox);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Fill");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Start");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "End");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Center");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), INITIAL_VALIGN);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the vertical alignment policy");
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (vertical_alignment_changed), flowbox);
+
+ /* Add Orientation control */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the flowbox orientation");
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (orientation_changed), flowbox);
+
+ /* Add minimum line length in items control */
+ widget = gtk_spin_button_new_with_range (1, 10, 1);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_MINIMUM_LENGTH);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the minimum amount of items per line before wrapping");
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (line_length_changed), flowbox);
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (line_length_changed), flowbox);
+
+ /* Add natural line length in items control */
+ widget = gtk_spin_button_new_with_range (1, 10, 1);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_MAXIMUM_LENGTH);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the natural amount of items per line ");
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (max_line_length_changed), flowbox);
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (max_line_length_changed), flowbox);
+
+ /* Add horizontal/vertical spacing controls */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_widget_show (hbox);
+
+ widget = gtk_label_new ("H Spacing");
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+
+ widget = gtk_spin_button_new_with_range (0, 30, 1);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_CSPACING);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the horizontal spacing between children");
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_HORIZONTAL));
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_HORIZONTAL));
+
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), hbox, FALSE, FALSE, 0);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_widget_show (hbox);
+
+ widget = gtk_label_new ("V Spacing");
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+
+ widget = gtk_spin_button_new_with_range (0, 30, 1);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_RSPACING);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the vertical spacing between children");
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_VERTICAL));
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_VERTICAL));
+
+ gtk_box_pack_start (GTK_BOX (flowbox_cntl), hbox, FALSE, FALSE, 0);
+
+
+ /* Add test items control frame */
+ expander = gtk_expander_new ("Test item controls");
+ gtk_expander_set_expanded (GTK_EXPANDER (expander), TRUE);
+ items_cntl = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_widget_show (items_cntl);
+ gtk_widget_show (expander);
+ gtk_container_add (GTK_CONTAINER (expander), items_cntl);
+ gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
+
+ /* Add Items control */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Simple");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Wrappy");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Stock");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Images");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the item set to use");
+ gtk_box_pack_start (GTK_BOX (items_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (items_changed), flowbox);
+
+
+ /* Add Text Orientation control */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+ gtk_widget_show (widget);
+
+ gtk_widget_set_tooltip_text (widget, "Set the item's text orientation (cant be done for stock buttons)");
+ gtk_box_pack_start (GTK_BOX (items_cntl), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (text_orientation_changed), flowbox);
+
+ populate_items (EGG_FLOW_BOX (flowbox));
+
+ /* This line was added only for the convenience of reproducing
+ * a height-for-width inside GtkScrolledWindow bug (bug 629778).
+ * -Tristan
+ */
+ gtk_window_set_default_size (GTK_WINDOW (window), 390, -1);
+
+ return window;
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+
+ gtk_init (&argc, &argv);
+
+ window = create_window ();
+
+ g_signal_connect (window, "delete-event",
+ G_CALLBACK (gtk_main_quit), window);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]