/*
 * 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. If has_last_allocation is
   * FALSE then this value is not yet known
   */
  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;
  guint has_last_allocation : 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               col,
                          gint               row)
{
  gboolean row_changed = FALSE, col_changed = FALSE;

  if (self->col != col)
    {
      self->col = col;

      col_changed = TRUE;
    }

  if (self->row != row)
    {
      self->row = row;

      row_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                col_span,
                      gint                row_span)
{
  gboolean row_changed = FALSE, col_changed = FALSE;

  if (self->col_span != col_span)
    {
      self->col_span = col_span;

      col_changed = TRUE;
    }

  if (self->row_span != row_span)
    {
      self->row_span = row_span;

      row_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,
                                g_value_get_int (value),
                                self->row);
      break;

    case PROP_CHILD_ROW:
      table_child_set_position (self,
                                self->col,
                                g_value_get_int (value));
      break;

    case PROP_CHILD_COLUMN_SPAN:
      table_child_set_span (self,
                            g_value_get_int (value),
                            self->row_span);
      break;

    case PROP_CHILD_ROW_SPAN:
      table_child_set_span (self,
                            self->col_span,
                            g_value_get_int (value));
      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_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;

  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->has_last_allocation = FALSE;
}

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;
      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;

      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;
  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);

  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);

          if (!meta->has_last_allocation)
            {
              /* 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 = childbox;
              meta->has_last_allocation = TRUE;

              goto do_allocate;
            }

          start = &meta->last_allocation;
          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 = childbox;
          meta->has_last_allocation = TRUE;
        }

    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",
                             P_("Column Spacing"),
                             P_("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",
                             P_("Row Spacing"),
                             P_("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",
                                P_("Use Animations"),
                                P_("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",
                              P_("Easing Mode"),
                              P_("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",
                             P_("Easing Duration"),
                             P_("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
 * @column: the column the @actor should be put, or -1 to append
 * @row: the row 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                 column,
                           gint                 row)
{
  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), column, row);
}

/**
 * clutter_table_layout_set_span:
 * @layout: a #ClutterTableLayout
 * @actor: a #ClutterActor child of @layout
 * @column_span: Column span for @actor
 * @row_span: Row 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                column_span,
                               gint                row_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), column_span, row_span);
}

/**
 * clutter_table_layout_get_span:
 * @layout: a #ClutterTableLayout
 * @actor: a #ClutterActor child of @layout
 * @column_span: (out): return location for the col span
 * @row_span: (out): return location for the row 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               *column_span,
                               gint               *row_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 (column_span)
    *column_span = CLUTTER_TABLE_CHILD (meta)->col_span;

  if (row_span)
    *row_span = CLUTTER_TABLE_CHILD (meta)->row_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;
}