summaryrefslogtreecommitdiff
path: root/src/st/st-table.c
diff options
context:
space:
mode:
authorOwen W. Taylor <otaylor@fishsoup.net>2009-09-29 23:13:29 -0400
committerOwen W. Taylor <otaylor@fishsoup.net>2009-09-30 00:12:55 -0400
commit083eed140c601d059c41943384da1a0613d97e7b (patch)
tree46e0b6c7a3ff968e39182ddb0607d6d8c5719a1d /src/st/st-table.c
parent1283f0b16069bba7cb732a0723acc8927d311820 (diff)
downloadgnome-shell-083eed140c601d059c41943384da1a0613d97e7b.tar.gz
Import MxTable as StTable
Import table code from Mx library https://bugzilla.gnome.org/show_bug.cgi?id=596811
Diffstat (limited to 'src/st/st-table.c')
-rw-r--r--src/st/st-table.c1475
1 files changed, 1475 insertions, 0 deletions
diff --git a/src/st/st-table.c b/src/st/st-table.c
new file mode 100644
index 000000000..9a0a18bd2
--- /dev/null
+++ b/src/st/st-table.c
@@ -0,0 +1,1475 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-table.c: Table layout widget
+ *
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ * Written by: Thomas Wood <thomas@linux.intel.com>
+ *
+ */
+
+/**
+ * SECTION:st-table
+ * @short_description: A multi-child layout container based on rows
+ * and columns
+ *
+ * #StTable is a mult-child layout container based on a table arrangement
+ * with rows and columns. #StTable adds several child properties to it's
+ * children that control their position and size in the table.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "st-table.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <clutter/clutter.h>
+
+#include "st-enum-types.h"
+#include "st-marshal.h"
+#include "st-private.h"
+#include "st-table-child.h"
+#include "st-table-private.h"
+#include "st-stylable.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_PADDING,
+
+ PROP_COL_SPACING,
+ PROP_ROW_SPACING,
+
+ PROP_HOMOGENEOUS,
+
+ PROP_ROW_COUNT,
+ PROP_COL_COUNT,
+};
+
+#define ST_TABLE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TABLE, StTablePrivate))
+
+struct _StTablePrivate
+{
+ GSList *children;
+
+ gint col_spacing;
+ gint row_spacing;
+
+ gint n_rows;
+ gint n_cols;
+
+ gint active_row;
+ gint active_col;
+
+ GArray *min_widths;
+ GArray *pref_widths;
+ GArray *min_heights;
+ GArray *pref_heights;
+
+ GArray *is_expand_col;
+ GArray *is_expand_row;
+
+ GArray *col_widths;
+ GArray *row_heights;
+
+ guint homogeneous : 1;
+};
+
+static void st_container_iface_init (ClutterContainerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StTable, st_table, ST_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+ st_container_iface_init));
+
+
+
+/*
+ * ClutterContainer Implementation
+ */
+static void
+st_container_add_actor (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StTablePrivate *priv = ST_TABLE (container)->priv;
+
+ clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
+
+
+ priv->children = g_slist_append (priv->children, actor);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
+
+ g_signal_emit_by_name (container, "actor-added", actor);
+}
+
+static void
+st_container_remove_actor (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StTablePrivate *priv = ST_TABLE (container)->priv;
+
+ GSList *item = NULL;
+
+ item = g_slist_find (priv->children, actor);
+
+ if (item == NULL)
+ {
+ g_warning ("Widget of type '%s' is not a child of container of type '%s'",
+ g_type_name (G_OBJECT_TYPE (actor)),
+ g_type_name (G_OBJECT_TYPE (container)));
+ return;
+ }
+
+ g_object_ref (actor);
+
+ priv->children = g_slist_delete_link (priv->children, item);
+ clutter_actor_unparent (actor);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
+
+ g_signal_emit_by_name (container, "actor-removed", actor);
+
+ g_object_unref (actor);
+}
+
+static void
+st_container_foreach (ClutterContainer *container,
+ ClutterCallback callback,
+ gpointer callback_data)
+{
+ StTablePrivate *priv = ST_TABLE (container)->priv;
+
+ g_slist_foreach (priv->children, (GFunc) callback, callback_data);
+}
+
+static void
+st_container_lower (ClutterContainer *container,
+ ClutterActor *actor,
+ ClutterActor *sibling)
+{
+ /* XXX: not yet implemented */
+ g_warning ("%s() not yet implemented", __FUNCTION__);
+}
+
+static void
+st_container_raise (ClutterContainer *container,
+ ClutterActor *actor,
+ ClutterActor *sibling)
+{
+ /* XXX: not yet implemented */
+ g_warning ("%s() not yet implemented", __FUNCTION__);
+}
+
+static void
+st_container_sort_depth_order (ClutterContainer *container)
+{
+ /* XXX: not yet implemented */
+ g_warning ("%s() not yet implemented", __FUNCTION__);
+}
+
+static void
+st_container_iface_init (ClutterContainerIface *iface)
+{
+ iface->add = st_container_add_actor;
+ iface->remove = st_container_remove_actor;
+ iface->foreach = st_container_foreach;
+ iface->lower = st_container_lower;
+ iface->raise = st_container_raise;
+ iface->sort_depth_order = st_container_sort_depth_order;
+ iface->child_meta_type = ST_TYPE_TABLE_CHILD;
+}
+
+/* StTable Class Implementation */
+
+static void
+st_table_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StTable *table = ST_TABLE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_COL_SPACING:
+ st_table_set_col_spacing (table, g_value_get_int (value));
+ break;
+
+ case PROP_ROW_SPACING:
+ st_table_set_row_spacing (table, g_value_get_int (value));
+ break;
+
+ case PROP_HOMOGENEOUS:
+ if (table->priv->homogeneous != g_value_get_boolean (value))
+ {
+ table->priv->homogeneous = g_value_get_boolean (value);
+ clutter_actor_queue_relayout ((ClutterActor *) gobject);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_table_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StTablePrivate *priv = ST_TABLE (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_COL_SPACING:
+ g_value_set_int (value, priv->col_spacing);
+ break;
+
+ case PROP_ROW_SPACING:
+ g_value_set_int (value, priv->row_spacing);
+ break;
+
+ case PROP_HOMOGENEOUS:
+ g_value_set_boolean (value, priv->homogeneous);
+ break;
+
+ case PROP_COL_COUNT:
+ g_value_set_int (value, priv->n_cols);
+ break;
+
+ case PROP_ROW_COUNT:
+ g_value_set_int (value, priv->n_rows);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_table_finalize (GObject *gobject)
+{
+ StTablePrivate *priv = ST_TABLE (gobject)->priv;
+
+ g_array_free (priv->min_widths, TRUE);
+ g_array_free (priv->pref_widths, TRUE);
+
+ g_array_free (priv->min_heights, TRUE);
+ g_array_free (priv->pref_heights, TRUE);
+
+ g_array_free (priv->is_expand_col, TRUE);
+ g_array_free (priv->is_expand_row, TRUE);
+
+ g_array_free (priv->col_widths, TRUE);
+ g_array_free (priv->row_heights, TRUE);
+
+ G_OBJECT_CLASS (st_table_parent_class)->finalize (gobject);
+}
+
+static void
+st_table_dispose (GObject *gobject)
+{
+ StTablePrivate *priv = ST_TABLE (gobject)->priv;
+ GSList *l, *next;
+
+ for (l = priv->children; l;)
+ {
+ next = l->next;
+ clutter_container_remove_actor ((ClutterContainer *) gobject,
+ CLUTTER_ACTOR (l->data));
+ l = next;
+ }
+
+ G_OBJECT_CLASS (st_table_parent_class)->dispose (gobject);
+}
+
+#define CLAMP_TO_PIXEL(x) ((float)((int)(x)))
+
+/* Utility function to modify a child allocation box with respect to the
+ * x/y-fill child properties. Expects childbox to contain the available
+ * allocation space.
+ */
+static void
+st_table_allocate_fill (ClutterActor *child,
+ ClutterActorBox *childbox,
+ gdouble x_align,
+ gdouble y_align,
+ gboolean x_fill,
+ gboolean y_fill)
+{
+ gfloat natural_width, natural_height;
+ gfloat min_width, min_height;
+ gfloat child_width, child_height;
+ gfloat available_width, available_height;
+ ClutterRequestMode request;
+ ClutterActorBox allocation = { 0, };
+
+ available_width = childbox->x2 - childbox->x1;
+ available_height = childbox->y2 - childbox->y1;
+
+ if (available_width < 0)
+ available_width = 0;
+
+ if (available_height < 0)
+ available_height = 0;
+
+ if (x_fill)
+ {
+ allocation.x1 = childbox->x1;
+ allocation.x2 = (int)(allocation.x1 + available_width);
+ }
+
+ if (y_fill)
+ {
+ allocation.y1 = childbox->y1;
+ allocation.y2 = (int)(allocation.y1 + available_height);
+ }
+
+ /* if we are filling horizontally and vertically then we're done */
+ if (x_fill && y_fill)
+ {
+ *childbox = allocation;
+ return;
+ }
+
+ request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH;
+ g_object_get (G_OBJECT (child), "request-mode", &request, NULL);
+
+ if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ clutter_actor_get_preferred_width (child, available_height,
+ &min_width,
+ &natural_width);
+
+ child_width = CLAMP (natural_width, min_width, available_width);
+
+ clutter_actor_get_preferred_height (child, child_width,
+ &min_height,
+ &natural_height);
+
+ child_height = CLAMP (natural_height, min_height, available_height);
+ }
+ else
+ {
+ clutter_actor_get_preferred_height (child, available_width,
+ &min_height,
+ &natural_height);
+
+ child_height = CLAMP (natural_height, min_height, available_height);
+
+ clutter_actor_get_preferred_width (child, child_height,
+ &min_width,
+ &natural_width);
+
+ child_width = CLAMP (natural_width, min_width, available_width);
+ }
+
+ if (!x_fill)
+ {
+ allocation.x1 = childbox->x1 + (int)((available_width - child_width) * x_align);
+ allocation.x2 = allocation.x1 + (int) child_width;
+ }
+
+ if (!y_fill)
+ {
+ allocation.y1 = childbox->y1 + (int)((available_height - child_height) * y_align);
+ allocation.y2 = allocation.y1 + (int) child_height;
+ }
+
+ *childbox = allocation;
+
+}
+
+static void
+st_table_homogeneous_allocate (ClutterActor *self,
+ const ClutterActorBox *box,
+ gboolean flags)
+{
+ GSList *list;
+ gfloat col_width, row_height;
+ gint row_spacing, col_spacing;
+ StTablePrivate *priv = ST_TABLE (self)->priv;
+ StPadding padding;
+
+ st_widget_get_padding (ST_WIDGET (self), &padding);
+
+ col_spacing = priv->col_spacing;
+ row_spacing = priv->row_spacing;
+
+ col_width = (box->x2 - box->x1
+ - padding.left - padding.right
+ - (col_spacing * (priv->n_cols - 1)))
+ / priv->n_cols;
+ row_height = (box->y2 - box->y1
+ - padding.top - padding.bottom
+ - (row_spacing * (priv->n_rows - 1)))
+ / priv->n_rows;
+
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ gint row, col, row_span, col_span;
+ StTableChild *meta;
+ ClutterActor *child;
+ ClutterActorBox childbox;
+ gdouble x_align, y_align;
+ gboolean x_fill, y_fill;
+
+ child = CLUTTER_ACTOR (list->data);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
+
+ if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ /* get child properties */
+ col = meta->col;
+ row = meta->row;
+ row_span = meta->row_span;
+ col_span = meta->col_span;
+ x_align = meta->x_align;
+ y_align = meta->y_align;
+ x_fill = meta->x_fill;
+ y_fill = meta->y_fill;
+
+ childbox.x1 = padding.left + (col_width + col_spacing) * col;
+ childbox.x2 = childbox.x1 + (col_width * col_span) + (col_spacing * (col_span - 1));
+
+ childbox.y1 = padding.top + (row_height + row_spacing) * row;
+ childbox.y2 = childbox.y1 + (row_height * row_span) + (row_spacing * (row_span - 1));
+
+ st_table_allocate_fill (child, &childbox, x_align, y_align, x_fill, y_fill);
+
+ clutter_actor_allocate (child, &childbox, flags);
+ }
+
+}
+
+
+static gint *
+st_table_calculate_col_widths (StTable *table,
+ gint for_width)
+{
+ gint total_min_width, i;
+ StTablePrivate *priv = table->priv;
+ gboolean *is_expand_col;
+ gint extra_col_width, n_expanded_cols = 0, expanded_cols = 0;
+ gint *pref_widths, *min_widths;
+ GSList *list;
+ StPadding padding;
+
+ g_array_set_size (priv->is_expand_col, 0);
+ g_array_set_size (priv->is_expand_col, priv->n_cols);
+ is_expand_col = (gboolean *) priv->is_expand_col->data;
+
+ g_array_set_size (priv->pref_widths, 0);
+ g_array_set_size (priv->pref_widths, priv->n_cols);
+ pref_widths = (gint *) priv->pref_widths->data;
+
+ g_array_set_size (priv->min_widths, 0);
+ g_array_set_size (priv->min_widths, priv->n_cols);
+ min_widths = (gint *) priv->min_widths->data;
+
+
+ /* take off the padding values to calculate the allocatable width */
+ st_widget_get_padding (ST_WIDGET (table), &padding);
+
+ for_width -= (int)(padding.left + padding.right);
+
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ gint row, col;
+ gfloat w_min, w_pref;
+ gboolean x_expand;
+ StTableChild *meta;
+ ClutterActor *child;
+ gint col_span, row_span;
+
+ child = CLUTTER_ACTOR (list->data);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child);
+
+ if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ /* get child properties */
+ col = meta->col;
+ row = meta->row;
+ x_expand = meta->x_expand;
+ col_span = meta->col_span;
+ row_span = meta->row_span;
+
+ if (x_expand)
+ is_expand_col[col] = TRUE;
+
+ clutter_actor_get_preferred_width (child, -1, &w_min, &w_pref);
+ if (col_span == 1 && w_pref > pref_widths[col])
+ {
+ pref_widths[col] = w_pref;
+ }
+ if (col_span == 1 && w_min > min_widths[col])
+ {
+ min_widths[col] = w_min;
+ }
+
+ }
+
+ total_min_width = priv->col_spacing * (priv->n_cols - 1);
+ for (i = 0; i < priv->n_cols; i++)
+ total_min_width += pref_widths[i];
+
+ /* calculate the remaining space and distribute it evenly onto all rows/cols
+ * with the x/y expand property set. */
+ for (i = 0; i < priv->n_cols; i++)
+ if (is_expand_col[i])
+ {
+ expanded_cols += pref_widths[i];
+ n_expanded_cols++;
+ }
+
+ /* for_width - total_min_width */
+ extra_col_width = for_width - total_min_width;
+ if (extra_col_width)
+ for (i = 0; i < priv->n_cols; i++)
+ if (is_expand_col[i])
+ {
+ if (extra_col_width < 0)
+ {
+ pref_widths[i] =
+ MAX (min_widths[i],
+ pref_widths[i]
+ + (extra_col_width * (pref_widths[i] / (float) expanded_cols)));
+
+ /* if we reached the minimum width for this column, we need to
+ * stop counting it as expanded */
+ if (pref_widths[i] == min_widths[i])
+ {
+ /* restart calculations :-( */
+ expanded_cols -= pref_widths[i];
+ is_expand_col[i] = 0;
+ n_expanded_cols--;
+ i = -1;
+ }
+ }
+ else
+ pref_widths[i] += extra_col_width / n_expanded_cols;
+ }
+
+ return pref_widths;
+}
+
+static gint *
+st_table_calculate_row_heights (StTable *table,
+ gint for_height,
+ gint * col_widths)
+{
+ StTablePrivate *priv = ST_TABLE (table)->priv;
+ GSList *list;
+ gint *is_expand_row, *min_heights, *pref_heights, *row_heights, extra_row_height;
+ gint i, total_min_height;
+ gint expanded_rows = 0;
+ gint n_expanded_rows = 0;
+ StPadding padding;
+
+ st_widget_get_padding (ST_WIDGET (table), &padding);
+
+ /* take padding off available height */
+ for_height -= (int)(padding.top + padding.bottom);
+
+ g_array_set_size (priv->row_heights, 0);
+ g_array_set_size (priv->row_heights, priv->n_rows);
+ row_heights = (gboolean *) priv->row_heights->data;
+
+ g_array_set_size (priv->is_expand_row, 0);
+ g_array_set_size (priv->is_expand_row, priv->n_rows);
+ is_expand_row = (gboolean *) priv->is_expand_row->data;
+
+ g_array_set_size (priv->min_heights, 0);
+ g_array_set_size (priv->min_heights, priv->n_rows);
+ min_heights = (gboolean *) priv->min_heights->data;
+
+ g_array_set_size (priv->pref_heights, 0);
+ g_array_set_size (priv->pref_heights, priv->n_rows);
+ pref_heights = (gboolean *) priv->pref_heights->data;
+
+ /* calculate minimum row widths and column heights */
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ gint row, col, cell_width;
+ gfloat h_min, h_pref;
+ gboolean x_expand, y_expand;
+ StTableChild *meta;
+ ClutterActor *child;
+ gint col_span, row_span;
+
+ child = CLUTTER_ACTOR (list->data);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child);
+
+ if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ /* get child properties */
+ col = meta->col;
+ row = meta->row;
+ x_expand = meta->x_expand;
+ y_expand = meta->y_expand;
+ col_span = meta->col_span;
+ row_span = meta->row_span;
+
+ if (y_expand)
+ is_expand_row[row] = TRUE;
+
+ /* calculate the cell width by including any spanned columns */
+ cell_width = 0;
+ for (i = 0; i < col_span && col + i < priv->n_cols; i++)
+ cell_width += (float)(col_widths[col + i]);
+
+ if (!meta->x_fill)
+ {
+ gfloat width;
+ clutter_actor_get_preferred_width (child, -1, NULL, &width);
+ cell_width = MIN (cell_width, width);
+ }
+
+ clutter_actor_get_preferred_height (child, cell_width, &h_min, &h_pref);
+
+ if (row_span == 1 && h_pref > pref_heights[row])
+ {
+ pref_heights[row] = (int)(h_pref);
+ }
+ if (row_span == 1 && h_min > min_heights[row])
+ {
+ min_heights[row] = (int)(h_min);
+ }
+ }
+
+ total_min_height = 0; // priv->row_spacing * (priv->n_rows - 1);
+ for (i = 0; i < priv->n_rows; i++)
+ total_min_height += pref_heights[i];
+
+ /* calculate the remaining space and distribute it evenly onto all rows/cols
+ * with the x/y expand property set. */
+ for (i = 0; i < priv->n_rows; i++)
+ if (is_expand_row[i])
+ {
+ expanded_rows += pref_heights[i];
+ n_expanded_rows++;
+ }
+
+ /* extra row height = for height - row spacings - total_min_height */
+ for_height -= (priv->row_spacing * (priv->n_rows - 1));
+ extra_row_height = for_height - total_min_height;
+
+
+ if (extra_row_height < 0)
+ {
+ gint *skip = g_slice_alloc0 (sizeof (gint) * priv->n_rows);
+ gint total_shrink_height;
+
+ /* If we need to shrink rows, we need to do multiple passes.
+ *
+ * We start by assuming all rows can shrink. All rows are sized
+ * proportional to their height in the total table size. If a row would be
+ * sized smaller than its minimum size, we mark it as non-shrinkable, and
+ * reduce extra_row_height by the amount it has been shrunk. The amount
+ * it has been shrunk by is the difference between the preferred and
+ * minimum height, since all rows start at their preferred height. We
+ * also then reduce the total table size (stored in total_shrink_height) by the height
+ * of the row we are going to be skipping.
+ *
+ */
+
+ /* We start by assuming all rows can shrink */
+ total_shrink_height = total_min_height;
+ for (i = 0; i < priv->n_rows; i++)
+ {
+ if (!skip[i])
+ {
+ gint tmp;
+
+ /* Calculate the height of the row by starting with the preferred
+ * height and taking away the extra row height proportional to
+ * the preferred row height over the rows that are being shrunk
+ */
+ tmp = pref_heights[i]
+ + (extra_row_height * (pref_heights[i] / (float) total_shrink_height));
+
+ if (tmp < min_heights[i])
+ {
+ /* This was a row we *were* set to shrink, but we now find it would have
+ * been shrunk too much. We remove it from the list of rows to shrink and
+ * adjust extra_row_height and total_shrink_height appropriately */
+ skip[i] = TRUE;
+ row_heights[i] = min_heights[i];
+
+ /* Reduce extra_row_height by the amount we have reduced this
+ * actor by */
+ extra_row_height += (pref_heights[i] - min_heights[i]);
+ /* now take off the row from the total shrink height */
+ total_shrink_height -= pref_heights[i];
+
+ /* restart the loop */
+ i = -1;
+ }
+ else
+ {
+ skip[i] = FALSE;
+ row_heights[i] = tmp;
+ }
+ }
+
+ }
+
+ g_slice_free1 (sizeof (gint) * priv->n_rows, skip);
+ }
+ else
+ {
+ for (i = 0; i < priv->n_rows; i++)
+ {
+ if (is_expand_row[i])
+ row_heights[i] = pref_heights[i] + (extra_row_height / n_expanded_rows);
+ else
+ row_heights[i] = pref_heights[i];
+ }
+ }
+
+
+ return row_heights;
+}
+
+static void
+st_table_preferred_allocate (ClutterActor *self,
+ const ClutterActorBox *box,
+ gboolean flags)
+{
+ GSList *list;
+ gint row_spacing, col_spacing;
+ gint i, table_width, table_height;
+ gint *col_widths, *row_heights;
+ StTable *table;
+ StTablePrivate *priv;
+ StPadding padding;
+
+ table = ST_TABLE (self);
+ priv = ST_TABLE (self)->priv;
+
+ st_widget_get_padding (ST_WIDGET (self), &padding);
+
+ col_spacing = (priv->col_spacing);
+ row_spacing = (priv->row_spacing);
+
+
+ table_height = (int)(box->y2 - box->y1
+ - padding.top
+ - padding.bottom);
+ table_width = (int)(box->x2 - box->x1
+ - padding.right
+ - padding.left);
+
+ col_widths =
+ st_table_calculate_col_widths (table,
+ (int)(box->x2 - box->x1));
+
+ row_heights =
+ st_table_calculate_row_heights (table,
+ (int)(box->y2 - box->y1),
+ col_widths);
+
+
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ gint row, col, row_span, col_span;
+ gint col_width, row_height;
+ StTableChild *meta;
+ ClutterActor *child;
+ ClutterActorBox childbox;
+ gint child_x, child_y;
+ gdouble x_align, y_align;
+ gboolean x_fill, y_fill;
+
+ child = CLUTTER_ACTOR (list->data);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
+
+ if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ /* get child properties */
+ col = meta->col;
+ row = meta->row;
+ row_span = meta->row_span;
+ col_span = meta->col_span;
+ x_align = meta->x_align;
+ y_align = meta->y_align;
+ x_fill = meta->x_fill;
+ y_fill = meta->y_fill;
+
+
+ /* initialise the width and height */
+ col_width = col_widths[col];
+ row_height = row_heights[row];
+
+ /* Add the widths of the spanned columns:
+ *
+ * First check that we have a non-zero span. Then we loop over each of
+ * the columns that we're spanning but we stop short if we go past the
+ * number of columns in the table. This is necessary to avoid accessing
+ * uninitialised memory. We add the spacing in here too since we only
+ * want to add as much spacing as times we successfully span.
+ */
+ if (col + col_span > priv->n_cols)
+ g_warning ("StTable: col-span exceeds number of columns");
+ if (row + row_span > priv->n_rows)
+ g_warning ("StTable: row-span exceeds number of rows");
+
+ if (col_span > 1)
+ {
+ for (i = col + 1; i < col + col_span && i < priv->n_cols; i++)
+ {
+ col_width += col_widths[i];
+ col_width += col_spacing;
+ }
+ }
+
+ /* add the height of the spanned rows */
+ if (row_span > 1)
+ {
+ for (i = row + 1; i < row + row_span && i < priv->n_rows; i++)
+ {
+ row_height += row_heights[i];
+ row_height += row_spacing;
+ }
+ }
+
+ /* calculate child x */
+ child_x = (int) padding.left
+ + col_spacing * col;
+ for (i = 0; i < col; i++)
+ child_x += col_widths[i];
+
+ /* calculate child y */
+ child_y = (int) padding.top
+ + row_spacing * row;
+ for (i = 0; i < row; i++)
+ child_y += row_heights[i];
+
+ /* set up childbox */
+ childbox.x1 = (float) child_x;
+ childbox.x2 = (float) MAX (0, child_x + col_width);
+
+ childbox.y1 = (float) child_y;
+ childbox.y2 = (float) MAX (0, child_y + row_height);
+
+
+ st_table_allocate_fill (child, &childbox, x_align, y_align, x_fill, y_fill);
+
+ clutter_actor_allocate (child, &childbox, flags);
+ }
+}
+
+static void
+st_table_allocate (ClutterActor *self,
+ const ClutterActorBox *box,
+ ClutterAllocationFlags flags)
+{
+ StTablePrivate *priv = ST_TABLE (self)->priv;
+
+ CLUTTER_ACTOR_CLASS (st_table_parent_class)->allocate (self, box, flags);
+
+ if (priv->n_cols < 1 || priv->n_rows < 1)
+ {
+ return;
+ };
+
+ if (priv->homogeneous)
+ st_table_homogeneous_allocate (self, box, flags);
+ else
+ st_table_preferred_allocate (self, box, flags);
+}
+
+static void
+st_table_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ gint *min_widths, *pref_widths;
+ gfloat total_min_width, total_pref_width;
+ StTablePrivate *priv = ST_TABLE (self)->priv;
+ GSList *list;
+ gint i;
+ StPadding padding;
+
+ st_widget_get_padding (ST_WIDGET (self), &padding);
+
+ if (priv->n_cols < 1)
+ {
+ *min_width_p = 0;
+ *natural_width_p = 0;
+ return;
+ }
+
+ /* Setting size to zero and then what we want it to be causes a clear if
+ * clear flag is set (which it should be.)
+ */
+ g_array_set_size (priv->min_widths, 0);
+ g_array_set_size (priv->pref_widths, 0);
+ g_array_set_size (priv->min_widths, priv->n_cols);
+ g_array_set_size (priv->pref_widths, priv->n_cols);
+
+ min_widths = (gint *) priv->min_widths->data;
+ pref_widths = (gint *) priv->pref_widths->data;
+
+ /* calculate minimum row widths */
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ gint col, col_span;
+ gfloat w_min, w_pref;
+ StTableChild *meta;
+ ClutterActor *child;
+
+ child = CLUTTER_ACTOR (list->data);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
+
+ if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ /* get child properties */
+ col = meta->col;
+ col_span = meta->col_span;
+
+ clutter_actor_get_preferred_width (child, -1, &w_min, &w_pref);
+
+ if (col_span == 1 && w_min > min_widths[col])
+ min_widths[col] = w_min;
+ if (col_span == 1 && w_pref > pref_widths[col])
+ pref_widths[col] = w_pref;
+ }
+
+ total_min_width = padding.left
+ + padding.right
+ + (priv->n_cols - 1)
+ * (float) priv->col_spacing;
+ total_pref_width = total_min_width;
+
+ for (i = 0; i < priv->n_cols; i++)
+ {
+ total_min_width += min_widths[i];
+ total_pref_width += pref_widths[i];
+ }
+
+ if (min_width_p)
+ *min_width_p = total_min_width;
+ if (natural_width_p)
+ *natural_width_p = total_pref_width;
+}
+
+static void
+st_table_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ gint *min_heights, *pref_heights;
+ gfloat total_min_height, total_pref_height;
+ StTablePrivate *priv = ST_TABLE (self)->priv;
+ GSList *list;
+ gint i;
+ gint *min_widths;
+ StPadding padding;
+
+ if (priv->n_rows < 1)
+ {
+ *min_height_p = 0;
+ *natural_height_p = 0;
+ return;
+ }
+
+ /* Setting size to zero and then what we want it to be causes a clear if
+ * clear flag is set (which it should be.)
+ */
+ g_array_set_size (priv->min_heights, 0);
+ g_array_set_size (priv->pref_heights, 0);
+ g_array_set_size (priv->min_heights, priv->n_rows);
+ g_array_set_size (priv->pref_heights, priv->n_rows);
+
+ /* use min_widths to help allocation of height-for-width widgets */
+ min_widths = st_table_calculate_col_widths (ST_TABLE (self), for_width);
+
+ min_heights = (gint *) priv->min_heights->data;
+ pref_heights = (gint *) priv->pref_heights->data;
+
+ /* calculate minimum row heights */
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ gint row, col, col_span, cell_width, row_span;
+ gfloat min, pref;
+ StTableChild *meta;
+ ClutterActor *child;
+
+ child = CLUTTER_ACTOR (list->data);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
+
+ if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ /* get child properties */
+ row = meta->row;
+ col = meta->col;
+ col_span = meta->col_span;
+ row_span = meta->row_span;
+
+ cell_width = 0;
+ for (i = 0; i < col_span && col + i < priv->n_cols; i++)
+ cell_width += min_widths[col + i];
+
+ clutter_actor_get_preferred_height (child,
+ (float) cell_width, &min, &pref);
+
+ if (row_span == 1 && min > min_heights[row])
+ min_heights[row] = min;
+ if (row_span == 1 && pref > pref_heights[row])
+ pref_heights[row] = pref;
+ }
+
+ st_widget_get_padding (ST_WIDGET (self), &padding);
+
+ /* start off with padding plus row spacing */
+ total_min_height = padding.top + padding.bottom + (priv->n_rows - 1) *
+ (float)(priv->row_spacing);
+
+ total_pref_height = total_min_height;
+
+ for (i = 0; i < priv->n_rows; i++)
+ {
+ total_min_height += min_heights[i];
+ total_pref_height += pref_heights[i];
+ }
+
+ if (min_height_p)
+ *min_height_p = total_min_height;
+ if (natural_height_p)
+ *natural_height_p = total_pref_height;
+}
+
+static void
+st_table_paint (ClutterActor *self)
+{
+ StTablePrivate *priv = ST_TABLE (self)->priv;
+ GSList *list;
+
+ /* make sure the background gets painted first */
+ CLUTTER_ACTOR_CLASS (st_table_parent_class)->paint (self);
+
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ ClutterActor *child = CLUTTER_ACTOR (list->data);
+ if (CLUTTER_ACTOR_IS_VISIBLE (child))
+ clutter_actor_paint (child);
+ }
+}
+
+static void
+st_table_pick (ClutterActor *self,
+ const ClutterColor *color)
+{
+ StTablePrivate *priv = ST_TABLE (self)->priv;
+ GSList *list;
+
+ /* Chain up so we get a bounding box painted (if we are reactive) */
+ CLUTTER_ACTOR_CLASS (st_table_parent_class)->pick (self, color);
+
+ for (list = priv->children; list; list = g_slist_next (list))
+ {
+ if (CLUTTER_ACTOR_IS_VISIBLE (list->data))
+ clutter_actor_paint (CLUTTER_ACTOR (list->data));
+ }
+}
+
+static void
+st_table_show_all (ClutterActor *table)
+{
+ StTablePrivate *priv = ST_TABLE (table)->priv;
+ GSList *l;
+
+ for (l = priv->children; l; l = l->next)
+ clutter_actor_show_all (CLUTTER_ACTOR (l->data));
+
+ clutter_actor_show (table);
+}
+
+static void
+st_table_hide_all (ClutterActor *table)
+{
+ StTablePrivate *priv = ST_TABLE (table)->priv;
+ GSList *l;
+
+ clutter_actor_hide (table);
+
+ for (l = priv->children; l; l = l->next)
+ clutter_actor_hide_all (CLUTTER_ACTOR (l->data));
+}
+
+static void
+st_table_class_init (StTableClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ /* StWidgetClass *st_widget_class = ST_WIDGET_CLASS (klass); */
+
+ g_type_class_add_private (klass, sizeof (StTablePrivate));
+
+ gobject_class->set_property = st_table_set_property;
+ gobject_class->get_property = st_table_get_property;
+ gobject_class->dispose = st_table_dispose;
+ gobject_class->finalize = st_table_finalize;
+
+ actor_class->paint = st_table_paint;
+ actor_class->pick = st_table_pick;
+ actor_class->allocate = st_table_allocate;
+ actor_class->get_preferred_width = st_table_get_preferred_width;
+ actor_class->get_preferred_height = st_table_get_preferred_height;
+ actor_class->show_all = st_table_show_all;
+ actor_class->hide_all = st_table_hide_all;
+
+ pspec = g_param_spec_boolean ("homogeneous",
+ "Homogeneous",
+ "Homogeneous rows and columns",
+ TRUE,
+ ST_PARAM_READWRITE);
+ g_object_class_install_property (gobject_class,
+ PROP_HOMOGENEOUS,
+ pspec);
+
+ pspec = g_param_spec_int ("col-spacing",
+ "Column Spacing",
+ "Spacing between columns",
+ 0, G_MAXINT, 0,
+ ST_PARAM_READWRITE);
+ g_object_class_install_property (gobject_class,
+ PROP_COL_SPACING,
+ pspec);
+
+ pspec = g_param_spec_int ("row-spacing",
+ "Row Spacing",
+ "Spacing between row",
+ 0, G_MAXINT, 0,
+ ST_PARAM_READWRITE);
+ g_object_class_install_property (gobject_class,
+ PROP_ROW_SPACING,
+ pspec);
+
+ pspec = g_param_spec_int ("row-count",
+ "Row Count",
+ "The number of rows in the table",
+ 0, G_MAXINT, 0,
+ ST_PARAM_READABLE);
+ g_object_class_install_property (gobject_class,
+ PROP_ROW_COUNT,
+ pspec);
+
+ pspec = g_param_spec_int ("column-count",
+ "Column Count",
+ "The number of columns in the table",
+ 0, G_MAXINT, 0,
+ ST_PARAM_READABLE);
+ g_object_class_install_property (gobject_class,
+ PROP_COL_COUNT,
+ pspec);
+}
+
+static void
+st_table_init (StTable *table)
+{
+ table->priv = ST_TABLE_GET_PRIVATE (table);
+
+ table->priv->n_cols = 0;
+ table->priv->n_rows = 0;
+
+ table->priv->min_widths = g_array_new (FALSE,
+ TRUE,
+ sizeof (gint));
+ table->priv->pref_widths = g_array_new (FALSE,
+ TRUE,
+ sizeof (gint));
+ table->priv->min_heights = g_array_new (FALSE,
+ TRUE,
+ sizeof (gint));
+ table->priv->pref_heights = g_array_new (FALSE,
+ TRUE,
+ sizeof (gint));
+
+ table->priv->is_expand_col = g_array_new (FALSE,
+ TRUE,
+ sizeof (gboolean));
+ table->priv->is_expand_row = g_array_new (FALSE,
+ TRUE,
+ sizeof (gboolean));
+
+ table->priv->col_widths = g_array_new (FALSE,
+ TRUE,
+ sizeof (gint));
+ table->priv->row_heights = g_array_new (FALSE,
+ TRUE,
+ sizeof (gint));
+}
+
+/* used by StTableChild to update row/column count */
+void _st_table_update_row_col (StTable *table,
+ gint row,
+ gint col)
+{
+ if (col > -1)
+ table->priv->n_cols = MAX (table->priv->n_cols, col + 1);
+
+ if (row > -1)
+ table->priv->n_rows = MAX (table->priv->n_rows, row + 1);
+
+}
+
+/*** Public Functions ***/
+
+/**
+ * st_table_new:
+ *
+ * Create a new #StTable
+ *
+ * Returns: a new #StTable
+ */
+StWidget*
+st_table_new (void)
+{
+ return g_object_new (ST_TYPE_TABLE, NULL);
+}
+
+/**
+ * st_table_set_col_spacing
+ * @table: a #StTable
+ * @spacing: spacing in pixels
+ *
+ * Sets the amount of spacing between columns.
+ */
+void
+st_table_set_col_spacing (StTable *table,
+ gint spacing)
+{
+ StTablePrivate *priv;
+
+ g_return_if_fail (ST_IS_TABLE (table));
+ g_return_if_fail (spacing >= 0);
+
+ priv = ST_TABLE (table)->priv;
+
+ priv->col_spacing = spacing;
+}
+
+/**
+ * st_table_set_row_spacing
+ * @table: a #StTable
+ * @spacing: spacing in pixels
+ *
+ * Sets the amount of spacing between rows.
+ */
+void
+st_table_set_row_spacing (StTable *table,
+ gint spacing)
+{
+ StTablePrivate *priv;
+
+ g_return_if_fail (ST_IS_TABLE (table));
+ g_return_if_fail (spacing >= 0);
+
+ priv = ST_TABLE (table)->priv;
+
+ priv->row_spacing = spacing;
+}
+
+/**
+ * st_table_get_row_spacing
+ * @table: a #StTable
+ *
+ * Gets the amount of spacing between rows.
+ *
+ * Returns: the spacing between rows in device units
+ */
+gint
+st_table_get_row_spacing (StTable *table)
+{
+ StTablePrivate *priv;
+
+ g_return_val_if_fail (ST_IS_TABLE (table), -1);
+ priv = ST_TABLE (table)->priv;
+
+ return priv->row_spacing;
+}
+
+/**
+ * st_table_get_col_spacing
+ * @table: a #StTable
+ *
+ * Gets the amount of spacing between columns.
+ *
+ * Returns: the spacing between columns in device units
+ */
+gint
+st_table_get_col_spacing (StTable *table)
+{
+ StTablePrivate *priv;
+
+ g_return_val_if_fail (ST_IS_TABLE (table), -1);
+ priv = ST_TABLE (table)->priv;
+
+ return priv->col_spacing;
+}
+
+/**
+ * st_table_add_actor:
+ * @table: a #StTable
+ * @actor: the child to insert
+ * @row: the row to place the child into
+ * @column: the column to place the child into
+ *
+ * Add an actor at the specified row and column
+ *
+ * Note, column and rows numbers start from zero
+ */
+void
+st_table_add_actor (StTable *table,
+ ClutterActor *actor,
+ gint row,
+ gint column)
+{
+ StTableChild *meta;
+ ClutterContainer *container;
+
+ g_return_if_fail (ST_IS_TABLE (table));
+ g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+ g_return_if_fail (row >= -1);
+ g_return_if_fail (column >= -1);
+
+ if (row < 0)
+ row = table->priv->n_rows + 1;
+
+ if (column < 0)
+ column = table->priv->n_cols + 1;
+
+ container = CLUTTER_CONTAINER (table);
+ clutter_container_add_actor (container, actor);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (container, actor);
+ meta->row = row;
+ meta->col = column;
+ _st_table_update_row_col (table, row, column);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (table));
+}
+
+/**
+ * st_table_add_actor_with_properties
+ * @table: a #StTable
+ * @actor: the child #ClutterActor
+ * @row: the row to place the child into
+ * @column: the column to place the child into
+ * @first_property_name: name of the first property to set
+ * @...: value for the first property, followed optionally by more name/value pairs terminated with NULL.
+ *
+ * Add an actor into at the specified row and column, with additional child
+ * properties to set.
+ */
+void
+st_table_add_actor_with_properties (StTable *table,
+ ClutterActor *actor,
+ gint row,
+ gint column,
+ const gchar *first_property_name,
+ ...)
+{
+ va_list args;
+ StTableChild *meta;
+ ClutterContainer *container;
+
+ g_return_if_fail (ST_IS_TABLE (table));
+ g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+ g_return_if_fail (row >= -1);
+ g_return_if_fail (column >= -1);
+ g_return_if_fail (first_property_name != NULL);
+
+ if (row < 0)
+ row = table->priv->n_rows + 1;
+
+ if (column < 0)
+ column = table->priv->n_cols + 1;
+
+ container = (ClutterContainer *) table;
+ clutter_container_add_actor (container, actor);
+
+ meta = (StTableChild *) clutter_container_get_child_meta (container, actor);
+ meta->row = row;
+ meta->col = column;
+ _st_table_update_row_col (table, row, column);
+
+ va_start (args, first_property_name);
+ g_object_set_valist ((GObject*) meta, first_property_name, args);
+ va_end (args);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (table));
+}
+
+/**
+ * st_table_get_row_count:
+ * @table: A #StTable
+ *
+ * Retrieve the current number rows in the @table
+ *
+ * Returns: the number of rows
+ */
+gint
+st_table_get_row_count (StTable *table)
+{
+ g_return_val_if_fail (ST_IS_TABLE (table), -1);
+
+ return ST_TABLE (table)->priv->n_rows;
+}
+
+/**
+ * st_table_get_column_count:
+ * @table: A #StTable
+ *
+ * Retrieve the current number of columns in @table
+ *
+ * Returns: the number of columns
+ */
+gint
+st_table_get_column_count (StTable *table)
+{
+ g_return_val_if_fail (ST_IS_TABLE (table), -1);
+
+ return ST_TABLE (table)->priv->n_cols;
+}