Add ClutterTableLayout, a layout showing children in rows and columns
authorJosé Dapena Paz <jdapena@igalia.com>
Thu, 18 Mar 2010 16:02:31 +0000 (17:02 +0100)
committerEmmanuele Bassi <ebassi@linux.intel.com>
Tue, 10 Aug 2010 22:20:06 +0000 (23:20 +0100)
A TableLayout is a layout manager that allocates its children in rows
and columns. Each child is assigned to a cell (or more if a cell span
is set).

The supported child properties are:

  • x-expand and y-expand: if this cell with try to allocate the
    available extra space for the table.
  • x-fill and y-fill: if the child will get all the space available in
    the cell.
  • x-align and y-align: if the child does not fill the cell, then
    where the child will be aligned inside the cell.
  • row-span and col-span: number of cells the child will allocate for
    itself.

Also, the TableLayout has row-spacing and col-spacing for specifying
the space in pixels between rows and between columns.

We also include a simple test of the layout manager, and the
documentation updates.

The TableLayout was implemented starting from MxTable and
ClutterBoxLayout.

http://bugzilla.clutter-project.org/show_bug.cgi?id=2038

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
clutter/Makefile.am
clutter/clutter-table-layout.c [new file with mode: 0644]
clutter/clutter-table-layout.h [new file with mode: 0644]
clutter/clutter.h
doc/reference/clutter/clutter-docs.xml.in
doc/reference/clutter/clutter-sections.txt
doc/reference/clutter/clutter.types
tests/interactive/.gitignore
tests/interactive/Makefile.am
tests/interactive/test-table-layout.c [new file with mode: 0644]

index a1ef46e..37d1f39 100644 (file)
@@ -133,6 +133,7 @@ source_h =                                  \
        $(srcdir)/clutter-stage-manager.h       \
        $(srcdir)/clutter-stage-window.h        \
        $(srcdir)/clutter-state.h               \
+       $(srcdir)/clutter-table-layout.h        \
        $(srcdir)/clutter-texture.h             \
         $(srcdir)/clutter-text.h                \
        $(srcdir)/clutter-timeline.h            \
@@ -227,6 +228,7 @@ source_c = \
        $(srcdir)/clutter-stage-manager.c       \
        $(srcdir)/clutter-stage-window.c        \
        $(srcdir)/clutter-state.c               \
+       $(srcdir)/clutter-table-layout.c        \
        $(srcdir)/clutter-texture.c             \
        $(srcdir)/clutter-text.c                \
        $(srcdir)/clutter-timeline.c            \
diff --git a/clutter/clutter-table-layout.c b/clutter/clutter-table-layout.c
new file mode 100644 (file)
index 0000000..5c8595a
--- /dev/null
@@ -0,0 +1,2642 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2009  Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Jose Dapena Paz <jdapena@igalia.com>
+ *
+ * Based on the MX MxTable actor by:
+ *   Thomas Wood <thomas.wood@intel.com>
+ * and ClutterBoxLayout by:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+/**
+ * SECTION:clutter-table-layout
+ * @title: ClutterTableLayout
+ * @short_description: A layout manager arranging children in rows
+ *   and columns
+ *
+ * The #ClutterTableLayout is a #ClutterLayoutManager implementing the
+ * following layout policy:
+ *
+ * <itemizedlist>
+ *   <listitem><para>children are arranged in a table</para></listitem>
+ *   <listitem><para>each child specifies the specific row and column
+ *   cell to appear;</para></listitem>
+ *   <listitem><para>a child can also set a span, and this way, take
+ *   more than one cell both horizontally and vertically;</para></listitem>
+ *   <listitem><para>each child will be allocated to its natural
+ *   size or, if set to expand, the available size;</para></listitem>
+ *   <listitem><para>if a child is set to fill on either (or both)
+ *   axis, its allocation will match all the available size; the
+ *   fill layout property only makes sense if the expand property is
+ *   also set;</para></listitem>
+ *   <listitem><para>if a child is set to expand but not to fill then
+ *   it is possible to control the alignment using the horizontal and
+ *   vertical alignment layout properties.</para></listitem>
+ * </itemizedlist>
+ *
+ * It is possible to control the spacing between children of a
+ * #ClutterTableLayout by using clutter_table_layout_set_row_spacing()
+ * and clutter_table_layout_set_column_spacing().
+ *
+ * In order to set the layout properties when packing an actor inside a
+ * #ClutterTableLayout you should use the clutter_table_layout_pack()
+ * function.
+ *
+ * A #ClutterTableLayout can use animations to transition between different
+ * values of the layout management properties; the easing mode and duration
+ * used for the animations are controlled by the
+ * #ClutterTableLayout:easing-mode and #ClutterTableLayout:easing-duration
+ * properties and their accessor functions.
+ *
+ * #ClutterTableLayout is available since Clutter 1.4
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+
+#include "clutter-table-layout.h"
+
+#include "clutter-debug.h"
+#include "clutter-enum-types.h"
+#include "clutter-layout-meta.h"
+#include "clutter-private.h"
+#include "clutter-types.h"
+
+#define CLUTTER_TYPE_TABLE_CHILD          (clutter_table_child_get_type ())
+#define CLUTTER_TABLE_CHILD(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_TABLE_CHILD, ClutterTableChild))
+#define CLUTTER_IS_TABLE_CHILD(obj)       (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_TABLE_CHILD))
+
+#define CLUTTER_TABLE_LAYOUT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayoutPrivate))
+
+typedef struct _ClutterTableChild         ClutterTableChild;
+typedef struct _ClutterLayoutMetaClass    ClutterTableChildClass;
+
+typedef struct _DimensionData {
+  gfloat min_size;
+  gfloat pref_size;
+  gfloat final_size;
+
+  guint expand  : 1;
+  guint shrink  : 1;
+  guint visible : 1;
+} DimensionData;
+
+struct _ClutterTableLayoutPrivate
+{
+  ClutterContainer *container;
+
+  guint col_spacing;
+  guint row_spacing;
+
+  gint n_rows;
+  gint n_cols;
+  gint active_row;
+  gint active_col;
+  gint visible_rows;
+  gint visible_cols;
+
+  GArray *columns;
+  GArray *rows;
+
+  gulong easing_mode;
+  guint easing_duration;
+
+  guint is_animating   : 1;
+  guint use_animations : 1;
+};
+
+struct _ClutterTableChild
+{
+  ClutterLayoutMeta parent_instance;
+
+  /* the last stable allocation before an animation; it is
+   * used as the initial ActorBox when interpolating
+   */
+  ClutterActorBox *last_allocation;
+
+  gint col;
+  gint row;
+
+  gint col_span;
+  gint row_span;
+
+  ClutterTableAlignment x_align;
+  ClutterTableAlignment y_align;
+
+  guint x_expand : 1;
+  guint y_expand : 1;
+  guint x_fill   : 1;
+  guint y_fill   : 1;
+};
+
+enum
+{
+  PROP_CHILD_0,
+
+  PROP_CHILD_ROW,
+  PROP_CHILD_COLUMN,
+  PROP_CHILD_ROW_SPAN,
+  PROP_CHILD_COLUMN_SPAN,
+  PROP_CHILD_X_ALIGN,
+  PROP_CHILD_Y_ALIGN,
+  PROP_CHILD_X_FILL,
+  PROP_CHILD_Y_FILL,
+  PROP_CHILD_X_EXPAND,
+  PROP_CHILD_Y_EXPAND
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_ROW_SPACING,
+  PROP_COLUMN_SPACING,
+  PROP_USE_ANIMATIONS,
+  PROP_EASING_MODE,
+  PROP_EASING_DURATION
+};
+
+G_DEFINE_TYPE (ClutterTableChild,
+               clutter_table_child,
+               CLUTTER_TYPE_LAYOUT_META);
+
+G_DEFINE_TYPE (ClutterTableLayout,
+               clutter_table_layout,
+               CLUTTER_TYPE_LAYOUT_MANAGER);
+
+/*
+ * ClutterBoxChild
+ */
+
+static void
+table_child_set_position (ClutterTableChild *self,
+                          gint               row,
+                          gint               col)
+{
+  gboolean row_changed = FALSE, col_changed = FALSE;
+
+  if (self->row != row)
+    {
+      self->row = row;
+
+      row_changed = TRUE;
+    }
+
+  if (self->col != col)
+    {
+      self->col = col;
+
+      col_changed = TRUE;
+    }
+
+  if (row_changed || col_changed)
+    {
+      ClutterLayoutManager *layout;
+      ClutterTableLayoutPrivate *priv;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      priv = CLUTTER_TABLE_LAYOUT (layout)->priv;
+
+      g_object_freeze_notify (G_OBJECT (self));
+
+      if (priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (layout,
+                                                  priv->easing_duration,
+                                                  priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (layout);
+
+      if (row_changed)
+        g_object_notify (G_OBJECT (self), "row");
+
+      if (col_changed)
+        g_object_notify (G_OBJECT (self), "column");
+
+      g_object_thaw_notify (G_OBJECT (self));
+    }
+}
+
+static void
+table_child_set_span (ClutterTableChild  *self,
+                      gint                row_span,
+                      gint                col_span)
+{
+  gboolean row_changed = FALSE, col_changed = FALSE;
+
+  if (self->row_span != row_span)
+    {
+      self->row_span = row_span;
+
+      row_changed = TRUE;
+    }
+
+  if (self->col_span != col_span)
+    {
+      self->col_span = col_span;
+
+      col_changed = TRUE;
+    }
+
+  if (row_changed || col_changed)
+    {
+      ClutterLayoutManager *layout;
+      ClutterTableLayout *table;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      table = CLUTTER_TABLE_LAYOUT (layout);
+
+      if (table->priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (layout,
+                                                  table->priv->easing_duration,
+                                                  table->priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (layout);
+
+      if (row_changed)
+        g_object_notify (G_OBJECT (self), "row-span");
+
+      if (col_changed)
+        g_object_notify (G_OBJECT (self), "column-span");
+    }
+}
+
+static void
+table_child_set_align (ClutterTableChild     *self,
+                       ClutterTableAlignment  x_align,
+                       ClutterTableAlignment  y_align)
+{
+  gboolean x_changed = FALSE, y_changed = FALSE;
+
+  if (self->x_align != x_align)
+    {
+      self->x_align = x_align;
+
+      x_changed = TRUE;
+    }
+
+  if (self->y_align != y_align)
+    {
+      self->y_align = y_align;
+
+      y_changed = TRUE;
+    }
+
+  if (x_changed || y_changed)
+    {
+      ClutterLayoutManager *layout;
+      ClutterTableLayout *table;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      table = CLUTTER_TABLE_LAYOUT (layout);
+
+      if (table->priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (layout,
+                                                  table->priv->easing_duration,
+                                                  table->priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (layout);
+
+      if (x_changed)
+        g_object_notify (G_OBJECT (self), "x-align");
+
+      if (y_changed)
+        g_object_notify (G_OBJECT (self), "y-align");
+    }
+}
+
+static void
+table_child_set_fill (ClutterTableChild *self,
+                      gboolean           x_fill,
+                      gboolean           y_fill)
+{
+  gboolean x_changed = FALSE, y_changed = FALSE;
+
+  x_fill = !!x_fill;
+  y_fill = !!y_fill;
+
+  if (self->x_fill != x_fill)
+    {
+      self->x_fill = x_fill;
+
+      x_changed = TRUE;
+    }
+
+  if (self->y_fill != y_fill)
+    {
+      self->y_fill = y_fill;
+
+      y_changed = TRUE;
+    }
+
+  if (x_changed || y_changed)
+    {
+      ClutterLayoutManager *layout;
+      ClutterTableLayoutPrivate *priv;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      priv = CLUTTER_TABLE_LAYOUT (layout)->priv;
+
+      g_object_freeze_notify (G_OBJECT (self));
+
+      if (priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (layout,
+                                                  priv->easing_duration,
+                                                  priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (layout);
+
+      if (x_changed)
+        g_object_notify (G_OBJECT (self), "x-fill");
+
+      if (y_changed)
+        g_object_notify (G_OBJECT (self), "y-fill");
+
+      g_object_thaw_notify (G_OBJECT (self));
+    }
+}
+
+static void
+table_child_set_expand (ClutterTableChild *self,
+                        gboolean           x_expand,
+                        gboolean           y_expand)
+{
+  gboolean x_changed = FALSE, y_changed = FALSE;
+
+  x_expand = !!x_expand;
+  y_expand = !!y_expand;
+
+  if (self->x_expand != x_expand)
+    {
+      self->x_expand = x_expand;
+
+      x_changed = TRUE;
+    }
+
+  if (self->y_expand != y_expand)
+    {
+      self->y_expand = y_expand;
+
+      y_changed = TRUE;
+    }
+
+  if (x_changed || y_changed)
+    {
+      ClutterLayoutManager *layout;
+      ClutterTableLayoutPrivate *priv;
+
+      layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self));
+      priv = CLUTTER_TABLE_LAYOUT (layout)->priv;
+
+      g_object_freeze_notify (G_OBJECT (self));
+
+      if (priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (layout,
+                                                  priv->easing_duration,
+                                                  priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (layout);
+
+      if (x_changed)
+        g_object_notify (G_OBJECT (self), "x-expand");
+
+      if (y_changed)
+        g_object_notify (G_OBJECT (self), "y-expand");
+
+      g_object_thaw_notify (G_OBJECT (self));
+    }
+}
+
+static void
+clutter_table_child_set_property (GObject      *gobject,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  ClutterTableChild *self = CLUTTER_TABLE_CHILD (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD_COLUMN:
+      table_child_set_position (self,
+                                self->row,
+                                g_value_get_int (value));
+      break;
+
+    case PROP_CHILD_ROW:
+      table_child_set_position (self,
+                                g_value_get_int (value),
+                                self->col);
+      break;
+
+    case PROP_CHILD_COLUMN_SPAN:
+      table_child_set_span (self,
+                            self->row_span,
+                            g_value_get_int (value));
+      break;
+
+    case PROP_CHILD_ROW_SPAN:
+      table_child_set_span (self,
+                            g_value_get_int (value),
+                            self->col_span);
+      break;
+
+    case PROP_CHILD_X_ALIGN:
+      table_child_set_align (self,
+                             g_value_get_enum (value),
+                             self->y_align);
+      break;
+
+    case PROP_CHILD_Y_ALIGN:
+      table_child_set_align (self,
+                             self->x_align,
+                             g_value_get_enum (value));
+      break;
+
+    case PROP_CHILD_X_FILL:
+      table_child_set_fill (self,
+                            g_value_get_boolean (value),
+                            self->y_fill);
+      break;
+
+    case PROP_CHILD_Y_FILL:
+      table_child_set_fill (self,
+                            self->x_fill,
+                            g_value_get_boolean (value));
+      break;
+
+    case PROP_CHILD_X_EXPAND:
+      table_child_set_expand (self,
+                            g_value_get_boolean (value),
+                            self->y_expand);
+      break;
+
+    case PROP_CHILD_Y_EXPAND:
+      table_child_set_expand (self,
+                            self->x_expand,
+                            g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_table_child_get_property (GObject    *gobject,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  ClutterTableChild *self = CLUTTER_TABLE_CHILD (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD_ROW:
+      g_value_set_int (value, self->row);
+      break;
+
+    case PROP_CHILD_COLUMN:
+      g_value_set_int (value, self->col);
+      break;
+
+    case PROP_CHILD_ROW_SPAN:
+      g_value_set_int (value, self->row_span);
+      break;
+
+    case PROP_CHILD_COLUMN_SPAN:
+      g_value_set_int (value, self->col_span);
+      break;
+
+    case PROP_CHILD_X_ALIGN:
+      g_value_set_enum (value, self->x_align);
+      break;
+
+    case PROP_CHILD_Y_ALIGN:
+      g_value_set_enum (value, self->y_align);
+      break;
+
+    case PROP_CHILD_X_FILL:
+      g_value_set_boolean (value, self->x_fill);
+      break;
+
+    case PROP_CHILD_Y_FILL:
+      g_value_set_boolean (value, self->y_fill);
+      break;
+
+    case PROP_CHILD_X_EXPAND:
+      g_value_set_boolean (value, self->x_expand);
+      break;
+
+    case PROP_CHILD_Y_EXPAND:
+      g_value_set_boolean (value, self->y_expand);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_table_child_finalize (GObject *gobject)
+{
+  ClutterTableChild *self = CLUTTER_TABLE_CHILD (gobject);
+
+  clutter_actor_box_free (self->last_allocation);
+
+  G_OBJECT_CLASS (clutter_table_child_parent_class)->finalize (gobject);
+}
+
+static void
+clutter_table_child_class_init (ClutterTableChildClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GParamSpec *pspec;
+
+  gobject_class->set_property = clutter_table_child_set_property;
+  gobject_class->get_property = clutter_table_child_get_property;
+  gobject_class->finalize = clutter_table_child_finalize;
+
+  pspec = g_param_spec_int ("column",
+                            P_("Column Number"),
+                            P_("The column the widget resides in"),
+                            0, G_MAXINT,
+                            0,
+                            CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_COLUMN, pspec);
+
+  pspec = g_param_spec_int ("row",
+                            P_("Row Number"),
+                            P_("The row the widget resides in"),
+                            0, G_MAXINT,
+                            0,
+                            CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_ROW, pspec);
+
+  pspec = g_param_spec_int ("column-span",
+                            P_("Column Span"),
+                            P_("The number of columns the widget should span"),
+                            1, G_MAXINT,
+                            1,
+                            CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_COLUMN_SPAN, pspec);
+
+  pspec = g_param_spec_int ("row-span",
+                            P_("Row Span"),
+                            P_("The number of rows the widget should span"),
+                            1, G_MAXINT,
+                            1,
+                            CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_ROW_SPAN, pspec);
+
+  pspec = g_param_spec_boolean ("x-expand",
+                                P_("Horizontal Expand"),
+                                P_("Allocate extra space for the child in horizontal axis"),
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_X_EXPAND, pspec);
+
+  pspec = g_param_spec_boolean ("y-expand",
+                                P_("Vertical Expand"),
+                                P_("Allocate extra space for the child in vertical axis"),
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_Y_EXPAND, pspec);
+
+  pspec = g_param_spec_boolean ("x-fill",
+                                P_("Horizontal Fill"),
+                                P_("Whether the child should receive priority when the container is allocating spare space on the horizontal axis"),
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_X_FILL, pspec);
+
+  pspec = g_param_spec_boolean ("y-fill",
+                                P_("Vertical Fill"),
+                                P_("Whether the child should receive priority when the container is allocating spare space on the vertical axis"),
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_Y_FILL, pspec);
+
+  /**
+   * ClutterTableLayout:x-align:
+   *
+   * The horizontal alignment of the actor within the cell
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_enum ("x-align",
+                             P_("Horizontal Alignment"),
+                             P_("Horizontal alignment of the actor within the cell"),
+                             CLUTTER_TYPE_TABLE_ALIGNMENT,
+                             CLUTTER_TABLE_ALIGNMENT_CENTER,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_X_ALIGN, pspec);
+
+  /**
+   * ClutterTableLayout:y-align:
+   *
+   * The vertical alignment of the actor within the cell
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_enum ("y-align",
+                             P_("Vertical Alignment"),
+                             P_("Vertical alignment of the actor within the cell"),
+                             CLUTTER_TYPE_TABLE_ALIGNMENT,
+                             CLUTTER_TABLE_ALIGNMENT_CENTER,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD_Y_ALIGN, pspec);
+}
+
+static void
+clutter_table_child_init (ClutterTableChild *self)
+{
+  self->col_span = 1;
+  self->row_span = 1;
+
+  self->x_align = CLUTTER_TABLE_ALIGNMENT_CENTER;
+  self->y_align = CLUTTER_TABLE_ALIGNMENT_CENTER;
+
+  self->x_expand = TRUE;
+  self->y_expand = TRUE;
+
+  self->x_fill = TRUE;
+  self->y_fill = TRUE;
+
+  self->last_allocation = NULL;
+}
+
+static GType
+clutter_table_layout_get_child_meta_type (ClutterLayoutManager *manager)
+{
+  return CLUTTER_TYPE_TABLE_CHILD;
+}
+
+static void
+clutter_table_layout_set_container (ClutterLayoutManager *layout,
+                                    ClutterContainer     *container)
+{
+  ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (layout)->priv;
+
+  priv->container = container;
+}
+
+
+static void
+update_row_col (ClutterTableLayout *layout,
+                ClutterContainer *container)
+{
+  ClutterTableLayoutPrivate *priv = layout->priv;
+  ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout);
+  GList *children, *l;
+  gint n_cols, n_rows;
+
+  n_cols = n_rows = 0;
+  children = container ? clutter_container_get_children (container) : NULL;
+
+  for (l = children; l; l = g_list_next (l))
+    {
+      ClutterActor *child = l->data;
+      ClutterTableChild *meta;
+
+      meta = CLUTTER_TABLE_CHILD (clutter_layout_manager_get_child_meta (manager, container, child));
+
+      n_cols = MAX (n_cols, meta->col + meta->col_span);
+      n_rows = MAX (n_rows, meta->row + meta->row_span);
+    }
+
+  g_list_free (children);
+
+  priv->n_cols = n_cols;
+  priv->n_rows = n_rows;
+
+}
+
+static void
+calculate_col_widths (ClutterTableLayout *self,
+                      ClutterContainer   *container,
+                      gint                for_width)
+{
+  ClutterTableLayoutPrivate *priv = self->priv;
+  ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (self);
+  gint i;
+  DimensionData *columns;
+  GList *l, *children;
+
+  update_row_col (self, container);
+  g_array_set_size (priv->columns, 0);
+  g_array_set_size (priv->columns, priv->n_cols);
+  columns = (DimensionData *) priv->columns->data;
+
+  /* reset the visibility of all columns */
+  priv->visible_cols = 0;
+  for (i = 0; i < priv->n_cols; i++)
+    columns[i].visible = FALSE;
+
+  children = clutter_container_get_children (container);
+
+  /* STAGE ONE: calculate column widths for non-spanned children */
+  for (l = children; l; l = g_list_next (l))
+    {
+      ClutterActor *child = l->data;
+      ClutterTableChild *meta;
+      DimensionData *col;
+      gfloat c_min, c_pref;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      meta = (ClutterTableChild *)
+        clutter_layout_manager_get_child_meta (manager, container, child);
+
+      if (meta->col_span > 1)
+        continue;
+
+      col = &columns[meta->col];
+
+      if (!col->visible)
+        {
+          col->visible = TRUE;
+          priv->visible_cols += 1;
+        }
+
+      clutter_actor_get_preferred_width (child, -1, &c_min, &c_pref);
+
+      col->min_size = MAX (col->min_size, c_min);
+      col->pref_size = MAX (col->pref_size, c_pref);
+      col->expand = MAX (col->expand, meta->x_expand);
+    }
+
+  /* STAGE TWO: take spanning children into account */
+  for (l = children; l; l = g_list_next (l))
+    {
+      ClutterActor *child = l->data;
+      ClutterTableChild *meta;
+      DimensionData *col;
+      gfloat c_min, c_pref;
+      gfloat min_width, pref_width;
+      gint start_col, end_col;
+      gint n_expand;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      meta = (ClutterTableChild *)
+        clutter_layout_manager_get_child_meta (manager, container, child);
+
+      if (meta->col_span < 2)
+        continue;
+
+      col = &columns[meta->col];
+      start_col = meta->col;
+      end_col = meta->col + meta->col_span - 1;
+
+      clutter_actor_get_preferred_width (child, -1, &c_min, &c_pref);
+
+      /* check there is enough room for this actor */
+      min_width = 0;
+      pref_width = 0;
+      n_expand = 0;
+      for (i = start_col; i <= end_col; i++)
+        {
+          min_width += columns[i].min_size;
+          pref_width += columns[i].pref_size;
+
+          if (columns[i].expand)
+            n_expand++;
+
+          if (!col->visible)
+            {
+              col->visible = TRUE;
+              priv->visible_cols += 1;
+            }
+        }
+      min_width += priv->col_spacing * (meta->col_span - 1);
+      pref_width += priv->col_spacing * (meta->col_span - 1);
+
+      /* see calculate_row_heights() for comments */
+      /* (1) */
+      if (c_min > min_width)
+        {
+
+          /* (2) */
+          /* we can start from preferred width and decrease */
+          if (pref_width > c_min)
+            {
+              for (i = start_col; i <= end_col; i++)
+                columns[i].final_size = columns[i].pref_size;
+
+              while (pref_width > c_min)
+                {
+                  for (i = start_col; i <= end_col; i++)
+                    {
+                      if (columns[i].final_size > columns[i].min_size)
+                        {
+                          columns[i].final_size--;
+                          pref_width--;
+                        }
+                    }
+                }
+
+              for (i = start_col; i <= end_col; i++)
+                columns[i].min_size = columns[i].final_size;
+            }
+          else
+            {
+              /* (3) */
+              /* we can expand from preferred size */
+              gfloat expand_by;
+
+              expand_by = c_pref - pref_width;
+
+              for (i = start_col; i <= end_col; i++)
+                {
+                  if (n_expand)
+                    {
+                      if (columns[i].expand)
+                        columns[i].min_size = columns[i].pref_size
+                                            + expand_by / n_expand;
+                    }
+                  else
+                    columns[i].min_size = columns[i].pref_size
+                                        + expand_by / meta->col_span;
+                }
+            }
+        }
+
+
+    }
+  g_list_free (children);
+
+  /* calculate final widths */
+  if (for_width >= 0)
+    {
+      gfloat min_width, pref_width;
+      gint n_expand;
+
+      min_width = 0;
+      pref_width = 0;
+      n_expand = 0;
+      for (i = 0; i < self->priv->n_cols; i++)
+        {
+          pref_width += columns[i].pref_size;
+          min_width += columns[i].min_size;
+          if (columns[i].expand)
+            n_expand++;
+        }
+      pref_width += priv->col_spacing * (priv->n_cols - 1);
+      min_width += priv->col_spacing * (priv->n_cols - 1);
+
+      if (for_width <= min_width)
+        {
+          /* erk, we can't shrink this! */
+          for (i = 0; i < priv->n_cols; i++)
+            columns[i].final_size = columns[i].min_size;
+
+          return;
+        }
+
+      if (for_width == pref_width)
+        {
+          /* perfect! */
+          for (i = 0; i < self->priv->n_cols; i++)
+            columns[i].final_size = columns[i].pref_size;
+
+          return;
+        }
+
+      /* for_width is between min_width and pref_width */
+      if (for_width < pref_width && for_width > min_width)
+        {
+          gfloat width;
+
+          /* shrink columns until they reach min_width */
+
+          /* start with all columns at preferred size */
+          for (i = 0; i < self->priv->n_cols; i++)
+            columns[i].final_size = columns[i].pref_size;
+
+          width = pref_width;
+
+          while (width > for_width)
+            {
+              for (i = 0; i < self->priv->n_cols; i++)
+                {
+                  if (columns[i].final_size > columns[i].min_size)
+                    {
+                      columns[i].final_size--;
+                      width--;
+                    }
+                }
+            }
+
+          return;
+        }
+
+      /* expand columns */
+      if (for_width > pref_width)
+        {
+          gfloat extra_width = for_width - pref_width;
+          gint remaining;
+
+          if (n_expand)
+            remaining = (gint) extra_width % n_expand;
+          else
+            remaining = (gint) extra_width % priv->n_cols;
+
+          for (i = 0; i < self->priv->n_cols; i++)
+            {
+              if (columns[i].expand)
+                {
+                  if (n_expand)
+                    columns[i].final_size = columns[i].pref_size
+                                          + (extra_width / n_expand);
+                  else
+                    columns[i].final_size = columns[i].pref_size
+                                          + (extra_width / priv->n_cols);
+                }
+              else
+                columns[i].final_size = columns[i].pref_size;
+            }
+
+          /* distribute the remainder among children */
+          i = 0;
+          while (remaining)
+            {
+              columns[i].final_size++;
+              i++;
+              remaining--;
+            }
+        }
+    }
+
+}
+
+static void
+calculate_row_heights (ClutterTableLayout *self,
+                       ClutterContainer   *container,
+                       gint                for_height)
+{
+  ClutterTableLayoutPrivate *priv = self->priv;
+  ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (self);
+  GList *l, *children;
+  gint i;
+  DimensionData *rows, *columns;
+
+  update_row_col (self, container);
+  g_array_set_size (priv->rows, 0);
+  g_array_set_size (priv->rows, self->priv->n_rows);
+
+  rows = (DimensionData*) priv->rows->data;
+  columns = (DimensionData*) priv->columns->data;
+
+  /* reset the visibility of all rows */
+  priv->visible_rows = 0;
+  for (i = 0; i < priv->n_rows; i++)
+    rows[i].visible = FALSE;
+
+  children = clutter_container_get_children (container);
+  /* STAGE ONE: calculate row heights for non-spanned children */
+  for (l = children; l; l = g_list_next (l))
+    {
+      ClutterActor *child = l->data;
+      ClutterTableChild *meta;
+      DimensionData *row;
+      gfloat c_min, c_pref;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      meta = (ClutterTableChild *)
+        clutter_layout_manager_get_child_meta (manager, container, child);
+
+      if (meta->row_span > 1)
+        continue;
+
+      row = &rows[meta->row];
+
+      if (!row->visible)
+        {
+          row->visible = TRUE;
+          priv->visible_rows += 1;
+        }
+
+      clutter_actor_get_preferred_height (child, columns[meta->col].final_size,
+                                          &c_min, &c_pref);
+
+      row->min_size = MAX (row->min_size, c_min);
+      row->pref_size = MAX (row->pref_size, c_pref);
+      row->expand = MAX (row->expand, meta->y_expand);
+    }
+
+
+
+  /* STAGE TWO: take spanning children into account */
+  for (l = children; l; l = g_list_next (l))
+    {
+      ClutterActor *child = l->data;
+      ClutterTableChild *meta;
+      DimensionData *row;
+      gfloat c_min, c_pref;
+      gfloat min_height, pref_height;
+      gint start_row, end_row;
+      gint n_expand;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      meta = (ClutterTableChild *)
+        clutter_layout_manager_get_child_meta (manager, container, child);
+
+      if (meta->row_span < 2)
+        continue;
+
+      row = &rows[meta->row];
+      start_row = meta->row;
+      end_row = meta->row + meta->row_span - 1;
+
+      clutter_actor_get_preferred_height (child, columns[meta->col].final_size,
+                                         &c_min, &c_pref);
+
+
+      /* check there is enough room for this actor */
+      min_height = 0;
+      pref_height = 0;
+      n_expand = 0;
+      for (i = start_row; i <= end_row; i++)
+        {
+          min_height += rows[i].min_size;
+          pref_height += rows[i].pref_size;
+
+          if (rows[i].expand)
+            n_expand++;
+
+          if (!rows[i].visible)
+            {
+              rows[i].visible = TRUE;
+              priv->visible_rows += 1;
+            }
+        }
+      min_height += priv->row_spacing * (meta->row_span - 1);
+      pref_height += priv->row_spacing * (meta->row_span - 1);
+
+      /* 1) If the minimum height of the rows spanned is less than the
+       *    minimum height of the child that is spanning them, then we
+       *    must increase the minimum height of the rows spanned.
+       *
+       * 2) If the preferred height of the spanned rows is more than
+       *    the minimum height of the spanning child, then we can start
+       *    at this size and decrease each row evenly.
+       *
+       * 3) If the preferred height of the rows is more than the minimum
+       *    height of the spanned child, then we can start at the preferred
+       *    height and expand.
+       */
+
+      /* (1) */
+      if (c_min > min_height)
+        {
+
+          /* (2) */
+          /* we can start from preferred height and decrease */
+          if (pref_height > c_min)
+            {
+              for (i = start_row; i <= end_row; i++)
+                rows[i].final_size = rows[i].pref_size;
+
+              while (pref_height > c_min)
+                {
+                  for (i = start_row; i <= end_row; i++)
+                    {
+                      if (rows[i].final_size > rows[i].min_size)
+                        {
+                          rows[i].final_size--;
+                          pref_height--;
+                        }
+                    }
+                }
+
+              for (i = start_row; i <= end_row; i++)
+                rows[i].min_size = rows[i].final_size;
+            }
+          else
+            {
+              /* (3) */
+              /* we can expand from preferred size */
+              gfloat expand_by = c_pref - pref_height;
+
+              for (i = start_row; i <= end_row; i++)
+                {
+                  if (n_expand)
+                    {
+                      if (rows[i].expand)
+                        rows[i].min_size = rows[i].pref_size
+                                         + expand_by / n_expand;
+                    }
+                  else
+                    rows[i].min_size = rows[i].pref_size
+                                     + expand_by / meta->row_span;
+                }
+            }
+        }
+
+    }
+
+  g_list_free (children);
+
+  /* calculate final heights */
+  if (for_height >= 0)
+    {
+      gfloat min_height, pref_height;
+      gint n_expand;
+
+      min_height = 0;
+      pref_height = 0;
+      n_expand = 0;
+      for (i = 0; i < self->priv->n_rows; i++)
+        {
+          pref_height += rows[i].pref_size;
+          min_height += rows[i].min_size;
+          if (rows[i].expand)
+            n_expand++;
+        }
+      pref_height += priv->row_spacing * (priv->n_rows - 1);
+      min_height += priv->row_spacing * (priv->n_rows - 1);
+
+      if (for_height <= min_height)
+        {
+          /* erk, we can't shrink this! */
+          for (i = 0; i < self->priv->n_rows; i++)
+            rows[i].final_size = rows[i].min_size;
+
+          return;
+        }
+
+      if (for_height == pref_height)
+        {
+          /* perfect! */
+          for (i = 0; i < self->priv->n_rows; i++)
+            rows[i].final_size = rows[i].pref_size;
+
+          return;
+        }
+
+      /* for_height is between min_height and pref_height */
+      if (for_height < pref_height && for_height > min_height)
+        {
+          gfloat height;
+
+          /* shrink rows until they reach min_height */
+
+          /* start with all rows at preferred size */
+          for (i = 0; i < self->priv->n_rows; i++)
+            rows[i].final_size = rows[i].pref_size;
+
+          height = pref_height;
+
+          while (height > for_height)
+            {
+              for (i = 0; i < priv->n_rows; i++)
+                {
+                  if (rows[i].final_size > rows[i].min_size)
+                    {
+                      rows[i].final_size--;
+                      height--;
+                    }
+                }
+            }
+
+          return;
+        }
+
+      /* expand rows */
+      if (for_height > pref_height)
+        {
+          gfloat extra_height = for_height - pref_height;
+          gint remaining;
+
+          if (n_expand)
+            remaining = (gint) extra_height % n_expand;
+          else
+            remaining = (gint) extra_height % self->priv->n_rows;
+
+          for (i = 0; i < self->priv->n_rows; i++)
+            {
+              if (rows[i].expand)
+                {
+                  if (n_expand)
+                    rows[i].final_size = rows[i].pref_size
+                                       + (extra_height / n_expand);
+                  else
+                    rows[i].final_size = rows[i].pref_size
+                                       + (extra_height / priv->n_rows);
+                }
+              else
+                rows[i].final_size = rows[i].pref_size;
+            }
+
+          /* distribute the remainder among children */
+          i = 0;
+          while (remaining)
+            {
+              rows[i].final_size++;
+              i++;
+              remaining--;
+            }
+        }
+    }
+
+}
+
+static void
+calculate_table_dimensions (ClutterTableLayout *self,
+                            ClutterContainer   *container,
+                            gfloat              for_width,
+                            gfloat              for_height)
+{
+  calculate_col_widths (self, container, for_width);
+  calculate_row_heights (self, container, for_height);
+}
+
+static void
+clutter_table_layout_get_preferred_width (ClutterLayoutManager *layout,
+                                          ClutterContainer     *container,
+                                          gfloat                for_height,
+                                          gfloat               *min_width_p,
+                                          gfloat               *natural_width_p)
+{
+  ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (layout);
+  ClutterTableLayoutPrivate *priv = self->priv;
+  gfloat total_min_width, total_pref_width;
+  DimensionData *columns;
+  gint i;
+
+  update_row_col (self, container);
+  if (priv->n_cols < 1)
+    {
+      *min_width_p = 0;
+      *natural_width_p = 0;
+      return;
+    }
+
+  calculate_table_dimensions (self, container, -1, for_height);
+  columns = (DimensionData *) priv->columns->data;
+
+  total_min_width = (priv->visible_cols - 1) * (float) priv->col_spacing;
+  total_pref_width = total_min_width;
+
+  for (i = 0; i < priv->n_cols; i++)
+    {
+      total_min_width += columns[i].min_size;
+      total_pref_width += columns[i].pref_size;
+    }
+
+  if (min_width_p)
+    *min_width_p = total_min_width;
+
+  if (natural_width_p)
+    *natural_width_p = total_pref_width;
+}
+
+static void
+clutter_table_layout_get_preferred_height (ClutterLayoutManager *layout,
+                                           ClutterContainer     *container,
+                                           gfloat                for_width,
+                                           gfloat               *min_height_p,
+                                           gfloat               *natural_height_p)
+{
+  ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (layout);
+  ClutterTableLayoutPrivate *priv = self->priv;
+  gfloat total_min_height, total_pref_height;
+  DimensionData *rows;
+  gint i;
+
+  update_row_col (self, container);
+  if (priv->n_rows < 1)
+    {
+      *min_height_p = 0;
+      *natural_height_p = 0;
+      return;
+    }
+
+  calculate_table_dimensions (self, container, for_width, -1);
+  rows = (DimensionData *) priv->rows->data;
+
+  total_min_height = (priv->visible_rows - 1) * (float) priv->row_spacing;
+  total_pref_height = total_min_height;
+
+  for (i = 0; i < self->priv->n_rows; i++)
+    {
+      total_min_height += rows[i].min_size;
+      total_pref_height += rows[i].pref_size;
+    }
+
+  if (min_height_p)
+    *min_height_p = total_min_height;
+
+  if (natural_height_p)
+    *natural_height_p = total_pref_height;
+}
+
+static gdouble
+get_table_alignment_factor (ClutterTableAlignment alignment)
+{
+  switch (alignment)
+    {
+    case CLUTTER_TABLE_ALIGNMENT_START:
+      return 0.0;
+
+    case CLUTTER_TABLE_ALIGNMENT_CENTER:
+      return 0.5;
+
+    case CLUTTER_TABLE_ALIGNMENT_END:
+      return 1.0;
+    }
+
+  return 0.0;
+}
+
+static void
+clutter_table_layout_allocate (ClutterLayoutManager   *layout,
+                               ClutterContainer       *container,
+                               const ClutterActorBox  *box,
+                               ClutterAllocationFlags  flags)
+{
+  ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (layout);
+  ClutterTableLayoutPrivate *priv = self->priv;
+  GList *list, *children;
+  gint row_spacing, col_spacing;
+  gint i, table_width, table_height;
+  DimensionData *rows, *columns;
+
+  update_row_col (self, container);
+  if (priv->n_cols < 1 || priv->n_rows < 1)
+    return;
+
+  children = clutter_container_get_children (container);
+  if (children == NULL)
+    return;
+
+  col_spacing = (priv->col_spacing);
+  row_spacing = (priv->row_spacing);
+
+
+  table_height = (int)(box->y2 - box->y1);
+  table_width = (int)(box->x2 - box->x1);
+
+  calculate_table_dimensions (self, container, box->x2 - box->x1, box->y2 - box->y1);
+
+  rows = (DimensionData *) priv->rows->data;
+  columns = (DimensionData *) priv->columns->data;
+
+  for (list = children; list; list = g_list_next (list))
+    {
+      ClutterActor *child = list->data;
+      gint row, col, row_span, col_span;
+      gint col_width, row_height;
+      ClutterTableChild *meta;
+      ClutterActorBox childbox;
+      gint child_x, child_y;
+      gdouble x_align, y_align;
+      gboolean x_fill, y_fill;
+
+      if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+        continue;
+
+      meta = (ClutterTableChild *)
+        clutter_layout_manager_get_child_meta (layout, priv->container, child);
+
+      /* get child properties */
+      col = meta->col;
+      row = meta->row;
+      row_span = meta->row_span;
+      col_span = meta->col_span;
+      x_align = get_table_alignment_factor (meta->x_align);
+      y_align = get_table_alignment_factor (meta->y_align);
+      x_fill = meta->x_fill;
+      y_fill = meta->y_fill;
+
+      /* initialise the width and height */
+      col_width = columns[col].final_size;
+      row_height = rows[row].final_size;
+
+      /* 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 (G_STRLOC ": column-span exceeds number of columns");
+      if (row + row_span > priv->n_rows)
+        g_warning (G_STRLOC ": 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 += columns[i].final_size;
+              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 += rows[i].final_size;
+              row_height += row_spacing;
+            }
+        }
+
+      /* calculate child x */
+      child_x = 0.0f;
+      for (i = 0; i < col; i++)
+        {
+          if (columns[i].visible)
+            {
+              child_x += columns[i].final_size;
+              child_x += col_spacing;
+            }
+        }
+
+      /* calculate child y */
+      child_y = 0.0f;
+      for (i = 0; i < row; i++)
+        {
+          if (rows[i].visible)
+            {
+              child_y += rows[i].final_size;
+              child_y += row_spacing;
+            }
+        }
+
+      /* 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);
+
+      clutter_actor_allocate_align_fill (child, &childbox,
+                                         x_align, y_align,
+                                         x_fill, y_fill,
+                                         flags);
+
+      /* since we call this after allocate_align_fill(), this is
+       * just a cheap copy
+       */
+      clutter_actor_get_allocation_box (child, &childbox);
+
+      if (priv->use_animations && priv->is_animating)
+        {
+          ClutterActorBox *start = NULL;
+          ClutterActorBox end = { 0, };
+          gdouble p;
+
+          p = clutter_layout_manager_get_animation_progress (layout);
+
+          start = meta->last_allocation;
+          if (start == NULL)
+            {
+              /* if there is no allocation available then the child has just
+               * been added to the container; we put it in the final state
+               * and store its allocation for later
+               */
+              meta->last_allocation = clutter_actor_box_copy (&childbox);
+
+              goto do_allocate;
+            }
+
+          end = childbox;
+
+          /* interpolate between the initial and final values */
+          clutter_actor_box_interpolate (start, &end, p, &childbox);
+
+          CLUTTER_NOTE (ANIMATION,
+                        "Animate { %.1f, %.1f, %.1f, %.1f }\t"
+                        "%.3f * { %.1f, %.1f, %.1f, %.1f }\t"
+                        "-> { %.1f, %.1f, %.1f, %.1f }",
+                        start->x1, start->y1,
+                        start->x2, start->y2,
+                        p,
+                        childbox.x1, childbox.y1,
+                        childbox.x2, childbox.y2,
+                        end.x1, end.y1,
+                        end.x2, end.y2);
+        }
+      else
+        {
+          /* store the allocation for later animations */
+          meta->last_allocation = clutter_actor_box_copy (&childbox);
+        }
+
+    do_allocate:
+      clutter_actor_allocate (child, &childbox, flags);
+    }
+
+  g_list_free (children);
+}
+
+static ClutterAlpha *
+clutter_table_layout_begin_animation (ClutterLayoutManager *manager,
+                                      guint                 duration,
+                                      gulong                easing)
+{
+  ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (manager)->priv;
+  ClutterLayoutManagerClass *parent_class;
+
+  priv->is_animating = TRUE;
+
+  /* we want the default implementation */
+  parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (clutter_table_layout_parent_class);
+
+  return parent_class->begin_animation (manager, duration, easing);
+}
+
+static void
+clutter_table_layout_end_animation (ClutterLayoutManager *manager)
+{
+  ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (manager)->priv;
+  ClutterLayoutManagerClass *parent_class;
+
+  priv->is_animating = FALSE;
+
+  /* we want the default implementation */
+  parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (clutter_table_layout_parent_class);
+  parent_class->end_animation (manager);
+}
+
+static void
+clutter_table_layout_set_property (GObject      *gobject,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_COLUMN_SPACING:
+      clutter_table_layout_set_column_spacing (self, g_value_get_uint (value));
+      break;
+
+    case PROP_ROW_SPACING:
+      clutter_table_layout_set_row_spacing (self, g_value_get_uint (value));
+      break;
+
+    case PROP_USE_ANIMATIONS:
+      clutter_table_layout_set_use_animations (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_EASING_MODE:
+      clutter_table_layout_set_easing_mode (self, g_value_get_ulong (value));
+      break;
+
+    case PROP_EASING_DURATION:
+      clutter_table_layout_set_easing_duration (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_table_layout_get_property (GObject    *gobject,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_ROW_SPACING:
+      g_value_set_uint (value, priv->row_spacing);
+      break;
+
+    case PROP_COLUMN_SPACING:
+      g_value_set_uint (value, priv->col_spacing);
+      break;
+
+    case PROP_USE_ANIMATIONS:
+      g_value_set_boolean (value, priv->use_animations);
+      break;
+
+    case PROP_EASING_MODE:
+      g_value_set_ulong (value, priv->easing_mode);
+      break;
+
+    case PROP_EASING_DURATION:
+      g_value_set_uint (value, priv->easing_duration);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_table_layout_finalize (GObject *gobject)
+{
+  ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (gobject)->priv;
+
+  g_array_free (priv->columns, TRUE);
+  g_array_free (priv->rows, TRUE);
+
+  G_OBJECT_CLASS (clutter_table_layout_parent_class)->finalize (gobject);
+}
+
+static void
+clutter_table_layout_class_init (ClutterTableLayoutClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterLayoutManagerClass *layout_class;
+  GParamSpec *pspec;
+
+  layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
+
+  gobject_class->set_property = clutter_table_layout_set_property;
+  gobject_class->get_property = clutter_table_layout_get_property;
+  gobject_class->finalize = clutter_table_layout_finalize;
+
+  layout_class->get_preferred_width =
+    clutter_table_layout_get_preferred_width;
+  layout_class->get_preferred_height =
+    clutter_table_layout_get_preferred_height;
+  layout_class->allocate = clutter_table_layout_allocate;
+  layout_class->set_container = clutter_table_layout_set_container;
+  layout_class->get_child_meta_type =
+    clutter_table_layout_get_child_meta_type;
+  layout_class->begin_animation = clutter_table_layout_begin_animation;
+  layout_class->end_animation = clutter_table_layout_end_animation;
+
+  g_type_class_add_private (klass, sizeof (ClutterTableLayoutPrivate));
+
+  /**
+   * ClutterTableLayout:column-spacing:
+   *
+   * The spacing between columns of the #ClutterTableLayout, in pixels
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_uint ("column-spacing",
+                             "Column Spacing",
+                             "Spacing between columns",
+                             0, G_MAXUINT, 0,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_COLUMN_SPACING, pspec);
+
+  /**
+   * ClutterTableLayout:row-spacing:
+   *
+   * The spacing between rows of the #ClutterTableLayout, in pixels
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_uint ("row-spacing",
+                             "Row Spacing",
+                             "Spacing between rows",
+                             0, G_MAXUINT, 0,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_ROW_SPACING, pspec);
+
+  /**
+   * ClutterTableLayout:use-animations:
+   *
+   * Whether the #ClutterTableLayout should animate changes in the
+   * layout properties
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_boolean ("use-animations",
+                                "Use Animations",
+                                "Whether layout changes should be animated",
+                                FALSE,
+                                CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_USE_ANIMATIONS, pspec);
+
+  /**
+   * ClutterTableLayout:easing-mode:
+   *
+   * The easing mode for the animations, in case
+   * #ClutterTableLayout:use-animations is set to %TRUE
+   *
+   * The easing mode has the same semantics of #ClutterAnimation:mode: it can
+   * either be a value from the #ClutterAnimationMode enumeration, like
+   * %CLUTTER_EASE_OUT_CUBIC, or a logical id as returned by
+   * clutter_alpha_register_func()
+   *
+   * The default value is %CLUTTER_EASE_OUT_CUBIC
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_ulong ("easing-mode",
+                              "Easing Mode",
+                              "The easing mode of the animations",
+                              0, G_MAXULONG,
+                              CLUTTER_EASE_OUT_CUBIC,
+                              CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_EASING_MODE, pspec);
+
+  /**
+   * ClutterTableLayout:easing-duration:
+   *
+   * The duration of the animations, in case #ClutterTableLayout:use-animations
+   * is set to %TRUE
+   *
+   * The duration is expressed in milliseconds
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_uint ("easing-duration",
+                             "Easing Duration",
+                             "The duration of the animations",
+                             0, G_MAXUINT,
+                             500,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_EASING_DURATION, pspec);
+}
+
+static void
+clutter_table_layout_init (ClutterTableLayout *layout)
+{
+  ClutterTableLayoutPrivate *priv;
+
+  layout->priv = priv = CLUTTER_TABLE_LAYOUT_GET_PRIVATE (layout);
+
+  priv->row_spacing = 0;
+  priv->col_spacing = 0;
+
+  priv->use_animations = FALSE;
+  priv->easing_mode = CLUTTER_EASE_OUT_CUBIC;
+  priv->easing_duration = 500;
+
+  priv->columns = g_array_new (FALSE, TRUE, sizeof (DimensionData));
+  priv->rows = g_array_new (FALSE, TRUE, sizeof (DimensionData));
+}
+
+/**
+ * clutter_table_layout_new:
+ *
+ * Creates a new #ClutterTableLayout layout manager
+ *
+ * Return value: the newly created #ClutterTableLayout
+ *
+ * Since: 1.4
+ */
+ClutterLayoutManager *
+clutter_table_layout_new (void)
+{
+  return g_object_new (CLUTTER_TYPE_TABLE_LAYOUT, NULL);
+}
+
+/**
+ * clutter_table_layout_set_column_spacing:
+ * @layout: a #ClutterTableLayout
+ * @spacing: the spacing between columns of the layout, in pixels
+ *
+ * Sets the spacing between columns of @layout
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_column_spacing (ClutterTableLayout *layout,
+                                       guint               spacing)
+{
+  ClutterTableLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->col_spacing != spacing)
+    {
+      ClutterLayoutManager *manager;
+
+      priv->col_spacing = spacing;
+
+      manager = CLUTTER_LAYOUT_MANAGER (layout);
+
+      if (priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (manager,
+                                                  priv->easing_duration,
+                                                  priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (manager);
+
+      g_object_notify (G_OBJECT (layout), "column-spacing");
+    }
+}
+
+/**
+ * clutter_table_layout_get_column_spacing:
+ * @layout: a #ClutterTableLayout
+ *
+ * Retrieves the spacing set using clutter_table_layout_set_column_spacing()
+ *
+ * Return value: the spacing between columns of the #ClutterTableLayout
+ *
+ * Since: 1.4
+ */
+guint
+clutter_table_layout_get_column_spacing (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), 0);
+
+  return layout->priv->col_spacing;
+}
+
+/**
+ * clutter_table_layout_set_row_spacing:
+ * @layout: a #ClutterTableLayout
+ * @spacing: the spacing between rows of the layout, in pixels
+ *
+ * Sets the spacing between rows of @layout
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_row_spacing (ClutterTableLayout *layout,
+                                      guint             spacing)
+{
+  ClutterTableLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->row_spacing != spacing)
+    {
+      ClutterLayoutManager *manager;
+
+      priv->row_spacing = spacing;
+
+      manager = CLUTTER_LAYOUT_MANAGER (layout);
+
+      if (priv->use_animations)
+        {
+          clutter_layout_manager_begin_animation (manager,
+                                                  priv->easing_duration,
+                                                  priv->easing_mode);
+        }
+      else
+        clutter_layout_manager_layout_changed (manager);
+
+      g_object_notify (G_OBJECT (layout), "row-spacing");
+    }
+}
+
+/**
+ * clutter_table_layout_get_row_spacing:
+ * @layout: a #ClutterTableLayout
+ *
+ * Retrieves the spacing set using clutter_table_layout_set_row_spacing()
+ *
+ * Return value: the spacing between rows of the #ClutterTableLayout
+ *
+ * Since: 1.4
+ */
+guint
+clutter_table_layout_get_row_spacing (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), 0);
+
+  return layout->priv->row_spacing;
+}
+
+/**
+ * clutter_table_layout_pack:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor
+ * @row: the row the @actor should be put, or -1 to append
+ * @column: the column the @actor should be put, or -1 to append
+ *
+ * Packs @actor inside the #ClutterContainer associated to @layout
+ * at the given row and column.
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_pack (ClutterTableLayout  *layout,
+                           ClutterActor        *actor,
+                           gint                 row,
+                           gint                 column)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before adding children",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  update_row_col (CLUTTER_TABLE_LAYOUT (layout), priv->container);
+
+  clutter_container_add_actor (priv->container, actor);
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  if (row < 0)
+    row = priv->n_rows + 1;
+
+  if (column < 0)
+    column = priv->n_cols + 1;
+
+  table_child_set_position (CLUTTER_TABLE_CHILD (meta), row, column);
+}
+
+/**
+ * clutter_table_layout_set_span:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @row_span: Row span for @actor
+ * @column_span: Column span for @actor
+ *
+ * Sets the row and column span for @actor
+ * inside @layout
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_span (ClutterTableLayout *layout,
+                               ClutterActor       *actor,
+                               gint                row_span,
+                               gint                column_span)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  table_child_set_span (CLUTTER_TABLE_CHILD (meta), row_span, column_span);
+}
+
+/**
+ * clutter_table_layout_get_span:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @row_span: (out): return location for the row span
+ * @column_span: (out): return location for the col span
+ *
+ * Retrieves the row and column span for @actor as set using
+ * clutter_table_layout_pack() or clutter_table_layout_set_span()
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_get_span (ClutterTableLayout *layout,
+                               ClutterActor       *actor,
+                               gint               *row_span,
+                               gint               *column_span)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  if (row_span)
+    *row_span = CLUTTER_TABLE_CHILD (meta)->row_span;
+
+  if (column_span)
+    *column_span = CLUTTER_TABLE_CHILD (meta)->col_span;
+}
+
+/**
+ * clutter_table_layout_set_alignment:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_align: Horizontal alignment policy for @actor
+ * @y_align: Vertical alignment policy for @actor
+ *
+ * Sets the horizontal and vertical alignment policies for @actor
+ * inside @layout
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_alignment (ClutterTableLayout    *layout,
+                                    ClutterActor          *actor,
+                                    ClutterTableAlignment  x_align,
+                                    ClutterTableAlignment  y_align)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  table_child_set_align (CLUTTER_TABLE_CHILD (meta), x_align, y_align);
+}
+
+/**
+ * clutter_table_layout_get_alignment:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_align: (out): return location for the horizontal alignment policy
+ * @y_align: (out): return location for the vertical alignment policy
+ *
+ * Retrieves the horizontal and vertical alignment policies for @actor
+ * as set using clutter_table_layout_pack() or
+ * clutter_table_layout_set_alignment().
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_get_alignment (ClutterTableLayout    *layout,
+                                    ClutterActor          *actor,
+                                    ClutterTableAlignment *x_align,
+                                    ClutterTableAlignment *y_align)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  if (x_align)
+    *x_align = CLUTTER_TABLE_CHILD (meta)->x_align;
+
+  if (y_align)
+    *y_align = CLUTTER_TABLE_CHILD (meta)->y_align;
+}
+
+/**
+ * clutter_table_layout_set_fill:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_fill: whether @actor should fill horizontally the allocated space
+ * @y_fill: whether @actor should fill vertically the allocated space
+ *
+ * Sets the horizontal and vertical fill policies for @actor
+ * inside @layout
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_fill (ClutterTableLayout *layout,
+                               ClutterActor       *actor,
+                               gboolean            x_fill,
+                               gboolean            y_fill)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  table_child_set_fill (CLUTTER_TABLE_CHILD (meta), x_fill, y_fill);
+}
+
+/**
+ * clutter_table_layout_get_fill:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_fill: (out): return location for the horizontal fill policy
+ * @y_fill: (out): return location for the vertical fill policy
+ *
+ * Retrieves the horizontal and vertical fill policies for @actor
+ * as set using clutter_table_layout_pack() or clutter_table_layout_set_fill()
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_get_fill (ClutterTableLayout *layout,
+                               ClutterActor       *actor,
+                               gboolean           *x_fill,
+                               gboolean           *y_fill)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  if (x_fill)
+    *x_fill = CLUTTER_TABLE_CHILD (meta)->x_fill;
+
+  if (y_fill)
+    *y_fill = CLUTTER_TABLE_CHILD (meta)->y_fill;
+}
+
+
+/**
+ * clutter_table_layout_set_expand:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_expand: whether @actor should allocate extra space horizontally
+ * @y_expand: whether @actor should allocate extra space vertically
+ *
+ * Sets the horizontal and vertical expand policies for @actor
+ * inside @layout
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_expand (ClutterTableLayout *layout,
+                                 ClutterActor       *actor,
+                                 gboolean            x_expand,
+                                 gboolean            y_expand)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  table_child_set_expand (CLUTTER_TABLE_CHILD (meta), x_expand, y_expand);
+}
+
+/**
+ * clutter_table_layout_get_expand:
+ * @layout: a #ClutterTableLayout
+ * @actor: a #ClutterActor child of @layout
+ * @x_expand: (out): return location for the horizontal expand policy
+ * @y_expand: (out): return location for the vertical expand policy
+ *
+ * Retrieves the horizontal and vertical expand policies for @actor
+ * as set using clutter_table_layout_pack() or clutter_table_layout_set_expand()
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_get_expand (ClutterTableLayout *layout,
+                                 ClutterActor       *actor,
+                                 gboolean           *x_expand,
+                                 gboolean           *y_expand)
+{
+  ClutterTableLayoutPrivate *priv;
+  ClutterLayoutManager *manager;
+  ClutterLayoutMeta *meta;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
+
+  priv = layout->priv;
+
+  if (priv->container == NULL)
+    {
+      g_warning ("The layout of type '%s' must be associated to "
+                 "a ClutterContainer before querying layout "
+                 "properties",
+                 G_OBJECT_TYPE_NAME (layout));
+      return;
+    }
+
+  manager = CLUTTER_LAYOUT_MANAGER (layout);
+  meta = clutter_layout_manager_get_child_meta (manager,
+                                                priv->container,
+                                                actor);
+  if (meta == NULL)
+    {
+      g_warning ("No layout meta found for the child of type '%s' "
+                 "inside the layout manager of type '%s'",
+                 G_OBJECT_TYPE_NAME (actor),
+                 G_OBJECT_TYPE_NAME (manager));
+      return;
+    }
+
+  g_assert (CLUTTER_IS_TABLE_CHILD (meta));
+
+  if (x_expand)
+    *x_expand = CLUTTER_TABLE_CHILD (meta)->x_expand;
+
+  if (y_expand)
+    *y_expand = CLUTTER_TABLE_CHILD (meta)->y_expand;
+}
+
+/**
+ * clutter_table_layout_set_use_animations:
+ * @layout: a #ClutterTableLayout
+ * @animate: %TRUE if the @layout should use animations
+ *
+ * Sets whether @layout should animate changes in the layout properties
+ *
+ * The duration of the animations is controlled by
+ * clutter_table_layout_set_easing_duration(); the easing mode to be used
+ * by the animations is controlled by clutter_table_layout_set_easing_mode()
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_use_animations (ClutterTableLayout *layout,
+                                         gboolean            animate)
+{
+  ClutterTableLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  animate = !!animate;
+  if (priv->use_animations != animate)
+    {
+      priv->use_animations = animate;
+
+      g_object_notify (G_OBJECT (layout), "use-animations");
+    }
+}
+
+/**
+ * clutter_table_layout_get_use_animations:
+ * @layout: a #ClutterTableLayout
+ *
+ * Retrieves whether @layout should animate changes in the layout properties
+ *
+ * Since clutter_table_layout_set_use_animations()
+ *
+ * Return value: %TRUE if the animations should be used, %FALSE otherwise
+ *
+ * Since: 1.4
+ */
+gboolean
+clutter_table_layout_get_use_animations (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), FALSE);
+
+  return layout->priv->use_animations;
+}
+
+/**
+ * clutter_table_layout_set_easing_mode:
+ * @layout: a #ClutterTableLayout
+ * @mode: an easing mode, either from #ClutterAnimationMode or a logical id
+ *   from clutter_alpha_register_func()
+ *
+ * Sets the easing mode to be used by @layout when animating changes in layout
+ * properties
+ *
+ * Use clutter_table_layout_set_use_animations() to enable and disable the
+ * animations
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_easing_mode (ClutterTableLayout *layout,
+                                      gulong              mode)
+{
+  ClutterTableLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->easing_mode != mode)
+    {
+      priv->easing_mode = mode;
+
+      g_object_notify (G_OBJECT (layout), "easing-mode");
+    }
+}
+
+/**
+ * clutter_table_layout_get_easing_mode:
+ * @layout: a #ClutterTableLayout
+ *
+ * Retrieves the easing mode set using clutter_table_layout_set_easing_mode()
+ *
+ * Return value: an easing mode
+ *
+ * Since: 1.4
+ */
+gulong
+clutter_table_layout_get_easing_mode (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout),
+                        CLUTTER_EASE_OUT_CUBIC);
+
+  return layout->priv->easing_mode;
+}
+
+/**
+ * clutter_table_layout_set_easing_duration:
+ * @layout: a #ClutterTableLayout
+ * @msecs: the duration of the animations, in milliseconds
+ *
+ * Sets the duration of the animations used by @layout when animating changes
+ * in the layout properties
+ *
+ * Use clutter_table_layout_set_use_animations() to enable and disable the
+ * animations
+ *
+ * Since: 1.4
+ */
+void
+clutter_table_layout_set_easing_duration (ClutterTableLayout *layout,
+                                          guint               msecs)
+{
+  ClutterTableLayoutPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout));
+
+  priv = layout->priv;
+
+  if (priv->easing_duration != msecs)
+    {
+      priv->easing_duration = msecs;
+
+      g_object_notify (G_OBJECT (layout), "easing-duration");
+    }
+}
+
+/**
+ * clutter_table_layout_get_easing_duration:
+ * @layout: a #ClutterTableLayout
+ *
+ * Retrieves the duration set using clutter_table_layout_set_easing_duration()
+ *
+ * Return value: the duration of the animations, in milliseconds
+ *
+ * Since: 1.4
+ */
+guint
+clutter_table_layout_get_easing_duration (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), 500);
+
+  return layout->priv->easing_duration;
+}
+
+
+/**
+ * clutter_table_layout_get_row_count:
+ * @layout: A #ClutterTableLayout
+ *
+ * Retrieve the current number rows in the @layout
+ *
+ * Returns: the number of rows
+ *
+ * Since: 1.4
+ */
+gint
+clutter_table_layout_get_row_count (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), -1);
+
+  update_row_col (layout, layout->priv->container);
+  return CLUTTER_TABLE_LAYOUT (layout)->priv->n_rows;
+}
+
+/**
+ * clutter_table_layout_get_column_count:
+ * @layout: A #ClutterTableLayout
+ *
+ * Retrieve the current number of columns in @layout
+ *
+ * Returns: the number of columns
+ *
+ * Since: 1.4
+ */
+gint
+clutter_table_layout_get_column_count (ClutterTableLayout *layout)
+{
+  g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), -1);
+
+  update_row_col (layout, layout->priv->container);
+  return CLUTTER_TABLE_LAYOUT (layout)->priv->n_cols;
+}
diff --git a/clutter/clutter-table-layout.h b/clutter/clutter-table-layout.h
new file mode 100644 (file)
index 0000000..bf7006a
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010  Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Jose Dapena Paz <jdapena@igalia.com>
+ *
+ * Based on the MX MxTable actor by:
+ *   Thomas Wood <thomas.wood@intel.com>
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_TABLE_LAYOUT_H__
+#define __CLUTTER_TABLE_LAYOUT_H__
+
+#include <clutter/clutter-layout-manager.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_TABLE_LAYOUT                 (clutter_table_layout_get_type ())
+#define CLUTTER_TABLE_LAYOUT(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayout))
+#define CLUTTER_IS_TABLE_LAYOUT(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_TABLE_LAYOUT))
+#define CLUTTER_TABLE_LAYOUT_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayoutClass))
+#define CLUTTER_IS_TABLE_LAYOUT_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_TABLE_LAYOUT))
+#define CLUTTER_TABLE_LAYOUT_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayoutClass))
+
+typedef struct _ClutterTableLayout                ClutterTableLayout;
+typedef struct _ClutterTableLayoutPrivate         ClutterTableLayoutPrivate;
+typedef struct _ClutterTableLayoutClass           ClutterTableLayoutClass;
+
+/**
+ * ClutterTableAlignment:
+ * @CLUTTER_TABLE_ALIGNMENT_START: Align the child to the top or to the
+ *   left of a cell in the table, depending on the axis
+ * @CLUTTER_TABLE_ALIGNMENT_CENTER: Align the child to the center of
+ *   a cell in the table
+ * @CLUTTER_TABLE_ALIGNMENT_END: Align the child to the bottom or to the
+ *   right of a cell in the table, depending on the axis
+ *
+ * The alignment policies available on each axis of the #ClutterTableLayout
+ *
+ * Since: 1.4
+ */
+typedef enum {
+  CLUTTER_TABLE_ALIGNMENT_START,
+  CLUTTER_TABLE_ALIGNMENT_CENTER,
+  CLUTTER_TABLE_ALIGNMENT_END
+} ClutterTableAlignment;
+
+/**
+ * ClutterTableLayout:
+ *
+ * The #ClutterTableLayout structure contains only private data
+ * and should be accessed using the provided API
+ *
+ * Since: 1.4
+ */
+struct _ClutterTableLayout
+{
+  /*< private >*/
+  ClutterLayoutManager parent_instance;
+
+  ClutterTableLayoutPrivate *priv;
+};
+
+/**
+ * ClutterTableLayoutClass:
+ *
+ * The #ClutterTableLayoutClass structure contains only private
+ * data and should be accessed using the provided API
+ *
+ * Since: 1.4
+ */
+struct _ClutterTableLayoutClass
+{
+  /*< private >*/
+  ClutterLayoutManagerClass parent_class;
+};
+
+GType clutter_table_layout_get_type (void) G_GNUC_CONST;
+
+ClutterLayoutManager *clutter_table_layout_new                 (void);
+
+void                  clutter_table_layout_pack                (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gint                   row,
+                                                                gint                   column);
+
+void                  clutter_table_layout_set_column_spacing  (ClutterTableLayout    *layout,
+                                                                guint                  spacing);
+void                  clutter_table_layout_set_row_spacing     (ClutterTableLayout    *layout,
+                                                                guint                  spacing);
+guint                 clutter_table_layout_get_column_spacing  (ClutterTableLayout    *layout);
+guint                 clutter_table_layout_get_row_spacing     (ClutterTableLayout    *layout);
+
+void                  clutter_table_layout_set_span            (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gint                   column_span,
+                                                                gint                   row_span);
+void                  clutter_table_layout_get_span            (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gint                  *column_span,
+                                                                gint                  *row_span);
+void                  clutter_table_layout_set_alignment       (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                ClutterTableAlignment  x_align,
+                                                                ClutterTableAlignment  y_align);
+void                  clutter_table_layout_get_alignment       (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                ClutterTableAlignment *x_align,
+                                                                ClutterTableAlignment *y_align);
+void                  clutter_table_layout_set_fill            (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gboolean               x_fill,
+                                                                gboolean               y_fill);
+void                  clutter_table_layout_get_fill            (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gboolean              *x_fill,
+                                                                gboolean              *y_fill);
+void                  clutter_table_layout_set_expand          (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gboolean               x_expand,
+                                                                gboolean               y_expand);
+void                  clutter_table_layout_get_expand          (ClutterTableLayout    *layout,
+                                                                ClutterActor          *actor,
+                                                                gboolean              *x_expand,
+                                                                gboolean              *y_expand);
+
+gint                  clutter_table_layout_get_row_count       (ClutterTableLayout    *layout);
+gint                  clutter_table_layout_get_column_count    (ClutterTableLayout    *layout);
+
+void                  clutter_table_layout_set_use_animations  (ClutterTableLayout    *layout,
+                                                                gboolean               animate);
+gboolean              clutter_table_layout_get_use_animations  (ClutterTableLayout    *layout);
+void                  clutter_table_layout_set_easing_mode     (ClutterTableLayout    *layout,
+                                                                gulong                 mode);
+gulong                clutter_table_layout_get_easing_mode     (ClutterTableLayout    *layout);
+void                  clutter_table_layout_set_easing_duration (ClutterTableLayout    *layout,
+                                                                guint                  msecs);
+guint                 clutter_table_layout_get_easing_duration (ClutterTableLayout    *layout);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_TABLE_LAYOUT_H__ */
index 7fae0cd..1814547 100644 (file)
@@ -95,6 +95,7 @@
 #include "clutter-stage-manager.h"
 #include "clutter-stage-window.h"
 #include "clutter-state.h"
+#include "clutter-table-layout.h"
 #include "clutter-texture.h"
 #include "clutter-text.h"
 #include "clutter-timeline.h"
index 22f9d21..fd6ea0c 100644 (file)
@@ -90,6 +90,7 @@
       <xi:include href="xml/clutter-bin-layout.xml"/>
       <xi:include href="xml/clutter-flow-layout.xml"/>
       <xi:include href="xml/clutter-box-layout.xml"/>
+      <xi:include href="xml/clutter-table-layout.xml"/>
     </chapter>
 
     <chapter>
index d928165..e785901 100644 (file)
@@ -2069,6 +2069,53 @@ clutter_box_layout_get_type
 </SECTION>
 
 <SECTION>
+<FILE>clutter-table-layout</FILE>
+ClutterTableAlignment
+ClutterTableLayout
+ClutterTableLayoutClass
+clutter_table_layout_new
+clutter_table_layout_set_row_spacing
+clutter_table_layout_get_row_spacing
+clutter_table_layout_set_column_spacing
+clutter_table_layout_get_column_spacing
+clutter_table_layout_get_row_count
+clutter_table_layout_get_column_count
+
+<SUBSECTION>
+clutter_table_layout_pack
+
+<SUBSECTION>
+clutter_table_layout_set_alignment
+clutter_table_layout_get_alignment
+clutter_table_layout_set_expand
+clutter_table_layout_get_expand
+clutter_table_layout_set_fill
+clutter_table_layout_get_fill
+clutter_table_layout_get_span
+clutter_table_layout_set_span
+
+<SUBSECTION>
+clutter_table_layout_set_use_animations
+clutter_table_layout_get_use_animations
+clutter_table_layout_set_easing_duration
+clutter_table_layout_get_easing_duration
+clutter_table_layout_set_easing_mode
+clutter_table_layout_get_easing_mode
+
+<SUBSECTION Standard>
+CLUTTER_TYPE_TABLE_LAYOUT
+CLUTTER_TABLE_LAYOUT
+CLUTTER_TABLE_LAYOUT_CLASS
+CLUTTER_IS_TABLE_LAYOUT
+CLUTTER_IS_TABLE_LAYOUT_CLASS
+CLUTTER_TABLE_LAYOUT_GET_CLASS
+
+<SUBSECTION Private>
+ClutterTableLayoutPrivate
+clutter_table_layout_get_type
+</SECTION>
+
+<SECTION>
 <FILE>clutter-animator</FILE>
 <TITLE>ClutterAnimator</TITLE>
 ClutterAnimator
index 0541f96..86b1c69 100644 (file)
@@ -56,6 +56,7 @@ clutter_shader_effect_get_type
 clutter_stage_get_type
 clutter_stage_manager_get_type
 clutter_state_get_type
+clutter_table_layout_get_type
 clutter_text_get_type
 clutter_texture_get_type
 clutter_timeline_get_type
index fa5269a..ea21db2 100644 (file)
@@ -71,3 +71,4 @@
 /test-scrolling
 /test-bind
 /test-cogl-point-sprites
+/test-table-layout
index 190436f..58eeae5 100644 (file)
@@ -56,7 +56,8 @@ UNIT_TESTS = \
        test-constraints.c \
        test-scrolling.c \
        test-bind.c \
-       test-cogl-point-sprites.c
+       test-cogl-point-sprites.c \
+       test-table-layout.c
 
 if X11_TESTS
 UNIT_TESTS += test-pixmap.c
diff --git a/tests/interactive/test-table-layout.c b/tests/interactive/test-table-layout.c
new file mode 100644 (file)
index 0000000..fe39bc3
--- /dev/null
@@ -0,0 +1,275 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gmodule.h>
+
+#include <clutter/clutter.h>
+#include <cogl/cogl.h>
+#include "pango/cogl-pango.h"
+
+#define FONT "Sans 12"
+
+static void
+set_text (ClutterActor *actor, const gchar *text)
+{
+  GList *children, *l;
+
+  children = clutter_container_get_children (CLUTTER_CONTAINER (actor));
+  for (l = children; l; l = g_list_next (l)) {
+    if (CLUTTER_IS_TEXT (l->data)) {
+      clutter_text_set_text (CLUTTER_TEXT (l->data), text);
+      break;
+    }
+  }
+  g_list_free (children);
+}
+
+static void
+toggle_expand (ClutterActor *actor, ClutterEvent *event, ClutterBox *box)
+{
+  gboolean x_expand;
+  gchar *label;
+  ClutterLayoutManager *layout = clutter_box_get_layout_manager (box);
+
+
+  clutter_layout_manager_child_get (layout, CLUTTER_CONTAINER (box), actor,
+                                   "x-expand", &x_expand,
+                                   NULL);
+
+  x_expand = !x_expand;
+
+  clutter_layout_manager_child_set (layout, CLUTTER_CONTAINER (box), actor,
+                                   "x-expand", x_expand,
+                                   "y-expand", x_expand,
+                                   NULL);
+
+  label = g_strdup_printf ("Expand = %d", x_expand);
+  set_text (actor, label);
+
+  g_free (label);
+}
+
+static const gchar *
+get_alignment_name (ClutterTableAlignment alignment)
+{
+  switch (alignment)
+    {
+    case CLUTTER_TABLE_ALIGNMENT_START:
+      return "start";
+
+    case CLUTTER_TABLE_ALIGNMENT_CENTER:
+      return "center";
+
+    case CLUTTER_TABLE_ALIGNMENT_END:
+      return "end";
+    }
+
+  return "undefined";
+}
+
+static void
+randomise_align (ClutterActor *actor, ClutterEvent *event, ClutterBox *box)
+{
+  ClutterTableAlignment x_align, y_align;
+  gchar *label;
+  ClutterLayoutManager *layout;
+
+  layout = clutter_box_get_layout_manager (box);
+
+  x_align = (ClutterTableAlignment) g_random_int_range (0, 3);
+  y_align = (ClutterTableAlignment) g_random_int_range (0, 3);
+
+  clutter_layout_manager_child_set (layout, CLUTTER_CONTAINER (box), actor,
+                                   "x-align", x_align,
+                                   "y-align", y_align,
+                                   NULL);
+
+  label = g_strdup_printf ("Align (%s, %s)",
+                           get_alignment_name (x_align),
+                           get_alignment_name (y_align));
+  set_text (actor, label);
+  g_free (label);
+}
+
+static void
+toggle_visible (ClutterActor *actor, ClutterEvent *event, gpointer userdata)
+{
+  clutter_actor_hide (actor);
+}
+
+gboolean drag = FALSE;
+
+static ClutterActor *
+create_cell (ClutterActor *actor, const gchar *color_str)
+{
+  ClutterActor *result;
+  ClutterActor *rectangle;
+  ClutterColor color;
+
+  result =
+    clutter_box_new (clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL,
+                                             CLUTTER_BIN_ALIGNMENT_FILL));
+
+  rectangle = clutter_rectangle_new ();
+  clutter_color_from_string (&color, color_str);
+  clutter_rectangle_set_color (CLUTTER_RECTANGLE (rectangle), (const ClutterColor *) &color);
+  clutter_color_from_string (&color, "#000f");
+  clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rectangle), (const ClutterColor *) &color);
+  clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), 2);
+
+  clutter_actor_show (rectangle);
+  clutter_actor_set_reactive (result, TRUE);
+  clutter_container_add_actor (CLUTTER_CONTAINER (result), rectangle);
+  clutter_box_pack (CLUTTER_BOX (result), actor,
+                    "x-align", CLUTTER_BIN_ALIGNMENT_CENTER,
+                    "y-align", CLUTTER_BIN_ALIGNMENT_CENTER,
+                    NULL);
+
+  return result;
+}
+
+static ClutterActor *
+create_text (const gchar *label, const gchar *color)
+{
+  ClutterActor *text;
+  ClutterActor *result;
+
+  text = clutter_text_new_with_text (FONT, label);
+  clutter_actor_show (text);
+
+  result = create_cell (text, color);
+  clutter_actor_show (result);
+
+  return result;
+}
+
+static ClutterActor *
+create_image (const gchar *file, const gchar *color)
+{
+  ClutterActor *texture;
+  ClutterActor *result;
+
+  texture = clutter_texture_new_from_file (file, NULL);
+  g_object_set (G_OBJECT (texture), "keep-aspect-ratio", TRUE, NULL);
+  clutter_actor_show (texture);
+
+  result = create_cell (texture, color);
+  clutter_actor_show (result);
+
+  return result;
+}
+
+G_MODULE_EXPORT int
+test_table_layout_main (int argc, char *argv[])
+{
+  ClutterActor *stage;
+  ClutterLayoutManager *layout;
+  ClutterActor *actor1, *actor2, *actor3, *actor4, *actor5, *actor6, *actor7, *actor8, *actor9, *actor10;
+  ClutterActor *box;
+  gchar *file;
+
+  clutter_init (&argc, &argv);
+
+  stage = clutter_stage_get_default ();
+  clutter_stage_set_title (CLUTTER_STAGE (stage), "Table Layout");
+  clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);
+  clutter_actor_set_size (stage, 640, 480);
+
+  layout = clutter_table_layout_new ();
+  clutter_table_layout_set_column_spacing (CLUTTER_TABLE_LAYOUT (layout), 10);
+  clutter_table_layout_set_row_spacing (CLUTTER_TABLE_LAYOUT (layout), 10);
+
+  box = clutter_box_new (layout);
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage), box);
+  clutter_actor_add_constraint (box, clutter_bind_constraint_new (stage, CLUTTER_BIND_WIDTH, -10.0));
+  clutter_actor_add_constraint (box, clutter_bind_constraint_new (stage, CLUTTER_BIND_HEIGHT, -10.0));
+
+  actor1 = create_text ("label 1", "#f66f");
+  file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL);
+  actor2 = create_image (file, "#bbcf");
+  g_free (file);
+  actor3 = create_text ("label 3", "#6f6f");
+  actor4 = create_text ("Expand = 1", "#66ff");
+  actor5 = create_text ("label 5", "#f6ff");
+  actor6 = create_text ("label 6", "#6fff");
+  actor7 = create_text ("Align (center, center)", "#66ff");
+  actor8 = create_text ("label 8", "#ffff");
+  actor9 = create_text ("label 9", "#666f");
+  actor10 = create_text ("label 10", "#aaaf");
+
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor1, 0, 0);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor2, 0, 1);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor3, 1, 1);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor4, 2, 0);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor5, 3, 0);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor6, 3, 1);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor7, 4, 1);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor8, 4, 0);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor9, 5, 0);
+  clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor10, -1, 0);
+  clutter_table_layout_set_span (CLUTTER_TABLE_LAYOUT (layout), actor1, 2, 1);
+  clutter_table_layout_set_span (CLUTTER_TABLE_LAYOUT (layout), actor7, 2, 1);
+  clutter_table_layout_set_span (CLUTTER_TABLE_LAYOUT (layout), actor4, 1, 2);
+
+  clutter_actor_set_size (actor1, 100, 100);
+  clutter_actor_set_width (actor4, 250);
+
+  clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout),
+                                   CLUTTER_CONTAINER (box),
+                                   actor1,
+                                   "x-expand", FALSE, "y-expand", FALSE,
+                                   NULL);
+  clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout),
+                                   CLUTTER_CONTAINER (box),
+                                   actor4,
+                                   "x-expand", TRUE, "y-expand", TRUE,
+                                   "x-fill", TRUE, "y-fill", TRUE,
+                                   NULL);
+  clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout),
+                                   CLUTTER_CONTAINER (box),
+                                   actor7,
+                                   "x-expand", TRUE, "y-expand", TRUE,
+                                   "x-fill", FALSE, "y-fill", FALSE,
+                                   NULL);
+  clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout),
+                                   CLUTTER_CONTAINER (box),
+                                   actor8,
+                                   "x-expand", FALSE, "y-expand", FALSE,
+                                   NULL);
+  clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout),
+                                   CLUTTER_CONTAINER (box),
+                                   actor9,
+                                   "x-expand", FALSE, "y-expand", FALSE,
+                                   NULL);
+
+  clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout),
+                                   CLUTTER_CONTAINER (box),
+                                   actor2,
+                                   "y-fill", FALSE,
+                                   "x-fill", FALSE,
+                                   NULL);
+
+  clutter_actor_set_position (box, 5, 5);
+
+  g_signal_connect (actor4, "button-release-event", G_CALLBACK (toggle_expand), box);
+  g_signal_connect (actor7, "button-release-event", G_CALLBACK (randomise_align), box);
+  g_signal_connect (actor10, "button-release-event", G_CALLBACK (toggle_visible), NULL);
+
+  /* g_signal_connect (stage, "button-press-event", G_CALLBACK (button_press), */
+  /*                   box); */
+  /* g_signal_connect (stage, "motion-event", G_CALLBACK (motion_event), */
+  /*                   box); */
+  /* g_signal_connect (stage, "button-release-event", G_CALLBACK (button_release), */
+  /*                   box); */
+
+  clutter_actor_show (stage);
+
+  g_debug ("table row count = %d",
+           clutter_table_layout_get_row_count (CLUTTER_TABLE_LAYOUT (layout)));
+  g_debug ("table column count = %d",
+           clutter_table_layout_get_column_count (CLUTTER_TABLE_LAYOUT (layout)));
+
+  clutter_main ();
+
+  return EXIT_SUCCESS;
+}