/* dzl-three-grid.c
 *
 * Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define G_LOG_DOMAIN "dzl-three-grid"

#include "config.h"

#include <math.h>

#include "util/dzl-macros.h"
#include "widgets/dzl-three-grid.h"

typedef struct
{
  GtkWidget          *widget;
  DzlThreeGridColumn  column;
  gint                row;
  gint                min_height;
  gint                nat_height;
  gint                min_baseline;
  gint                nat_baseline;
} DzlThreeGridChild;

typedef struct
{
  GPtrArray *children;
  GHashTable *row_infos;
  guint column_spacing;
  guint row_spacing;
} DzlThreeGridPrivate;

typedef struct
{
  gint row;
  gint min_above_baseline;
  gint min_below_baseline;
  gint nat_above_baseline;
  gint nat_below_baseline;
} DzlThreeGridRowInfo;

G_DEFINE_TYPE_WITH_PRIVATE (DzlThreeGrid, dzl_three_grid, GTK_TYPE_CONTAINER)

enum {
  PROP_0,
  PROP_COLUMN_SPACING,
  PROP_ROW_SPACING,
  N_PROPS
};

enum {
  CHILD_PROP_0,
  CHILD_PROP_ROW,
  CHILD_PROP_COLUMN,
  N_CHILD_PROPS
};

static GParamSpec *properties [N_PROPS];
static GParamSpec *child_properties [N_CHILD_PROPS];
static DzlThreeGridChild dummy;

static DzlThreeGridChild *
dzl_three_grid_child_new (void)
{
  return g_slice_new0 (DzlThreeGridChild);
}

static void
dzl_three_grid_child_free (gpointer data)
{
  DzlThreeGridChild *child = data;

  g_clear_object (&child->widget);
  g_slice_free (DzlThreeGridChild, child);
}

static DzlThreeGridChild *
dzl_three_grid_find_child (DzlThreeGrid *self,
                           GtkWidget    *widget)
{
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (GTK_IS_WIDGET (widget));

  for (guint i = 0; i < priv->children->len; i++)
    {
      DzlThreeGridChild *child = g_ptr_array_index (priv->children, i);

      if (child->widget == widget)
        return child;
    }

  return &dummy;
}

static void
dzl_three_grid_add (GtkContainer *container,
                    GtkWidget    *widget)
{
  DzlThreeGrid *self = (DzlThreeGrid *)container;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);
  DzlThreeGridChild *child;

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (GTK_IS_WIDGET (widget));

  child = dzl_three_grid_child_new ();
  child->widget = g_object_ref_sink (widget);
  g_ptr_array_add (priv->children, child);

  gtk_widget_set_parent (widget, GTK_WIDGET (self));
}

static void
dzl_three_grid_remove (GtkContainer *container,
                       GtkWidget    *widget)
{
  DzlThreeGrid *self = (DzlThreeGrid *)container;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (GTK_IS_WIDGET (widget));

  for (guint i = 0; i < priv->children->len; i++)
    {
      DzlThreeGridChild *child = g_ptr_array_index (priv->children, i);

      if (child->widget == widget)
        {
          gtk_widget_unparent (child->widget);
          g_ptr_array_remove_index (priv->children, i);
          gtk_widget_queue_resize (GTK_WIDGET (self));
          return;
        }
    }
}

static GtkSizeRequestMode
dzl_three_grid_get_request_mode (GtkWidget *widget)
{
  return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

static void
dzl_three_grid_get_column_width (DzlThreeGrid       *self,
                                 DzlThreeGridColumn  column,
                                 gint               *min_width,
                                 gint               *nat_width)
{
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);
  gint real_min_width = 0;
  gint real_nat_width = 0;

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (column >= DZL_THREE_GRID_COLUMN_LEFT);
  g_assert (column <= DZL_THREE_GRID_COLUMN_RIGHT);
  g_assert (min_width != NULL);
  g_assert (nat_width != NULL);

  for (guint i = 0; i < priv->children->len; i++)
    {
      DzlThreeGridChild *child = g_ptr_array_index (priv->children, i);

      if (child->column == column)
        {
          gint child_min_width;
          gint child_nat_width;

          gtk_widget_get_preferred_width (child->widget, &child_min_width, &child_nat_width);

          real_min_width = MAX (real_min_width, child_min_width);
          real_nat_width = MAX (real_nat_width, child_nat_width);
        }
    }

  *min_width = real_min_width;
  *nat_width = real_nat_width;
}

static void
dzl_three_grid_get_preferred_width (GtkWidget *widget,
                                    gint      *min_width,
                                    gint      *nat_width)
{
  DzlThreeGrid *self = (DzlThreeGrid *)widget;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);
  GtkStyleContext *style_context;
  GtkBorder margin;
  gint min_widths[3];
  gint nat_widths[3];
  gint border_width;
  GtkStateFlags state;

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (min_width != NULL);
  g_assert (nat_width != NULL);

  style_context = gtk_widget_get_style_context (widget);
  state = gtk_style_context_get_state (style_context);
  gtk_style_context_get_margin (style_context, state, &margin);

  for (guint i = 0; i < 3; i++)
    dzl_three_grid_get_column_width (self, i, &min_widths[i], &nat_widths[i]);

  border_width = gtk_container_get_border_width (GTK_CONTAINER (self));

  *min_width = MAX (min_widths[0], min_widths[2]) * 2 + min_widths[1] + margin.left + margin.right + (border_width * 2) + (priv->column_spacing * 2);
  *nat_width = MAX (nat_widths[0], nat_widths[2]) * 2 + nat_widths[1] + margin.left + margin.right + (border_width * 2) + (priv->column_spacing * 2);
}

static void
row_info_merge (DzlThreeGridRowInfo       *row_info,
                const DzlThreeGridRowInfo *other)
{
  g_assert (row_info);
  g_assert (other);

  row_info->min_above_baseline = MAX (row_info->min_above_baseline, other->min_above_baseline);
  row_info->min_below_baseline = MAX (row_info->min_below_baseline, other->min_below_baseline);
  row_info->nat_above_baseline = MAX (row_info->nat_above_baseline, other->nat_above_baseline);
  row_info->nat_below_baseline = MAX (row_info->nat_below_baseline, other->nat_below_baseline);
}

static void
update_row_info (GHashTable        *hashtable,
                 DzlThreeGridChild *child)
{
  GtkBaselinePosition baseline_position = GTK_BASELINE_POSITION_CENTER;
  DzlThreeGridRowInfo *row_info;
  DzlThreeGridRowInfo current = { 0 };

  g_assert (hashtable);
  g_assert (child);

  row_info = g_hash_table_lookup (hashtable, GINT_TO_POINTER (child->row));

  if (row_info == NULL)
    {
      row_info = g_new0 (DzlThreeGridRowInfo, 1);
      row_info->row = child->row;
      g_hash_table_insert (hashtable, GINT_TO_POINTER (child->row), row_info);
    }

  /*
   * TODO:
   *
   * Allow setting baseline position per row. Right now we only support center
   * because that is the easiest thing to start with.
   */

  if (child->min_baseline == -1)
    {
      if (baseline_position == GTK_BASELINE_POSITION_CENTER)
        {
          current.min_above_baseline = current.min_below_baseline = ceil (child->min_height / 2.0);
          current.nat_above_baseline = current.nat_below_baseline = ceil (child->min_height / 2.0);
        }
      else if (baseline_position == GTK_BASELINE_POSITION_TOP)
        {
          g_assert_not_reached ();
        }
      else if (baseline_position == GTK_BASELINE_POSITION_BOTTOM)
        {
          g_assert_not_reached ();
        }
    }
  else
    {
      current.min_above_baseline = child->min_baseline;
      current.min_below_baseline = child->min_height - child->min_baseline;
      current.nat_above_baseline = child->nat_baseline;
      current.nat_below_baseline = child->nat_height - child->nat_baseline;
    }

  row_info_merge (row_info, &current);
}

static void
dzl_three_grid_get_preferred_height_for_width (GtkWidget *widget,
                                               gint       width,
                                               gint      *min_height,
                                               gint      *nat_height)
{
  DzlThreeGrid *self = (DzlThreeGrid *)widget;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);
  g_autoptr(GHashTable) row_infos = NULL;
  DzlThreeGridRowInfo *row_info;
  GtkStyleContext *style_context;
  GHashTableIter iter;
  GtkStateFlags state;
  GtkBorder margin;
  gint real_min_height = 0;
  gint real_nat_height = 0;
  gint column_min_widths[3];
  gint column_nat_widths[3];
  gint widths[3];
  gint border_width;
  gint n_rows;

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (min_height != NULL);
  g_assert (nat_height != NULL);

  border_width = gtk_container_get_border_width (GTK_CONTAINER (self));
  width -= border_width * 2;
  width -= priv->column_spacing * 2;

  style_context = gtk_widget_get_style_context (widget);
  state = gtk_style_context_get_state (style_context);
  gtk_style_context_get_margin (style_context, state, &margin);
  width -= margin.left + margin.right;

  dzl_three_grid_get_column_width (self, DZL_THREE_GRID_COLUMN_LEFT, &column_min_widths[0], &column_nat_widths[0]);
  dzl_three_grid_get_column_width (self, DZL_THREE_GRID_COLUMN_CENTER, &column_min_widths[1], &column_nat_widths[1]);
  dzl_three_grid_get_column_width (self, DZL_THREE_GRID_COLUMN_RIGHT, &column_min_widths[2], &column_nat_widths[2]);

  if ((MAX (column_min_widths[0], column_min_widths[2]) * 2 + column_nat_widths[1]) > width)
    {
      widths[0] = column_min_widths[0];
      widths[2] = column_min_widths[2];
      widths[1] = width - widths[0] - widths[2];
    }
  else
    {
      /* Handle #1 and #2 */
      widths[1] = column_nat_widths[1];
      widths[0] = (width - widths[1]) / 2;
      widths[2] = width - widths[1] - widths[0];
    }

  row_infos = g_hash_table_new_full (NULL, NULL, NULL, g_free);

  for (guint i = 0; i < priv->children->len; i++)
    {
      DzlThreeGridChild *child = g_ptr_array_index (priv->children, i);

      if (!gtk_widget_get_visible (child->widget) ||
          !gtk_widget_get_child_visible (child->widget))
        continue;

      gtk_widget_get_preferred_height_and_baseline_for_width (child->widget,
                                                              widths[child->column],
                                                              &child->min_height,
                                                              &child->nat_height,
                                                              &child->min_baseline,
                                                              &child->nat_baseline);
      update_row_info (row_infos, child);
    }

  g_hash_table_iter_init (&iter, row_infos);

  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&row_info))
    {
#if 0
      g_print ("Row %d: MIN  Above %d  Below %d\n",
               row_info->row,
               row_info->min_above_baseline, row_info->min_below_baseline);
      g_print ("Row %d: NAT  Above %d  Below %d\n",
               row_info->row,
               row_info->nat_above_baseline, row_info->nat_below_baseline);
#endif
      real_min_height += row_info->min_above_baseline + row_info->min_below_baseline;
      real_nat_height += row_info->nat_above_baseline + row_info->nat_below_baseline;
    }

  real_min_height += border_width * 2;
  real_nat_height += border_width * 2;

  real_min_height += margin.top + margin.bottom;
  real_nat_height += margin.top + margin.bottom;

  n_rows = g_hash_table_size (row_infos);

  if (n_rows > 1)
    {
      real_min_height += (n_rows - 1) * priv->row_spacing;
      real_nat_height += (n_rows - 1) * priv->row_spacing;
    }

  *min_height = real_min_height;
  *nat_height = real_nat_height;

#if 0
  g_print ("%d children in %d rows: %dx%d\n",
           priv->children->len,
           g_hash_table_size (row_infos),
           real_min_height, real_nat_height);
#endif

  g_clear_pointer (&priv->row_infos, g_hash_table_unref);
  priv->row_infos = g_steal_pointer (&row_infos);
}

static gint
sort_by_row (gconstpointer a,
             gconstpointer b)
{
  const DzlThreeGridRowInfo *info_a = a;
  const DzlThreeGridRowInfo *info_b = b;

  return info_a->row - info_b->row;
}

static void
dzl_three_grid_size_allocate_children (DzlThreeGrid       *self,
                                       DzlThreeGridColumn  column,
                                       gint                row,
                                       GtkAllocation      *allocation,
                                       gint                baseline)
{
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (allocation != NULL);

  for (guint i = 0; i < priv->children->len; i++)
    {
      DzlThreeGridChild *child = g_ptr_array_index (priv->children, i);

      if (child->row == row && child->column == column)
        {
          GtkAllocation copy = *allocation;
          gtk_widget_size_allocate_with_baseline (child->widget, &copy, baseline);
        }
    }
}

static void
dzl_three_grid_size_allocate (GtkWidget     *widget,
                              GtkAllocation *allocation)
{
  DzlThreeGrid *self = (DzlThreeGrid *)widget;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);
  g_autofree GtkRequestedSize *rows = NULL;
  const GList *iter;
  GtkStyleContext *style_context;
  GtkAllocation area;
  GtkTextDirection dir;
  GList *values;
  GtkStateFlags state;
  GtkBorder margin;
  guint i;
  guint n_rows;
  gint min_height;
  gint nat_height;
  gint border_width;
  gint left_min_width;
  gint left_nat_width;
  gint center_min_width;
  gint center_nat_width;
  gint right_min_width;
  gint right_nat_width;
  gint left;
  gint center;
  gint right;

  g_assert (DZL_IS_THREE_GRID (self));
  g_assert (allocation != NULL);

  area = *allocation;

  style_context = gtk_widget_get_style_context (widget);
  state = gtk_style_context_get_state (style_context);
  gtk_style_context_get_margin (style_context, state, &margin);
  border_width = gtk_container_get_border_width (GTK_CONTAINER (self));

  area.x += border_width;
  area.y += border_width;
  area.width -= border_width * 2;
  area.height -= border_width * 2;

  area.x += margin.left;
  area.width -= margin.right + margin.left;
  area.y += margin.top;
  area.height -= margin.top + margin.bottom;

  gtk_widget_set_allocation (widget, &area);

  dir = gtk_widget_get_direction (widget);

  dzl_three_grid_get_preferred_height_for_width (widget, allocation->width, &min_height, &nat_height);

  if (min_height > allocation->height)
    g_warning ("%s requested a minimum height of %d and got %d",
               G_OBJECT_TYPE_NAME (widget), min_height, allocation->height);

  if (priv->row_infos == NULL)
    return;

  values = g_hash_table_get_values (priv->row_infos);
  values = g_list_sort (values, sort_by_row);

  dzl_three_grid_get_column_width (self, DZL_THREE_GRID_COLUMN_LEFT, &left_min_width, &left_nat_width);
  dzl_three_grid_get_column_width (self, DZL_THREE_GRID_COLUMN_CENTER, &center_min_width, &center_nat_width);
  dzl_three_grid_get_column_width (self, DZL_THREE_GRID_COLUMN_RIGHT, &right_min_width, &right_nat_width);

  /*
   * Determine how much to give to the center widget first. This is because we will
   * just give the rest of the space on the sides to left/right columns and they
   * can deal with alignment by using halign.
   *
   * We can be in one of a couple states:
   *
   * 1) There is enough room for all columns natural size.
   *    (We allocate the same to the left and the right).
   * 2) There is enough for the natural size of the center
   *    but for some amount between natural and min sizing
   *    of the left/right columns.
   * 3) There is only minimum size for columns and some
   *    amount between natural/minimum of the center.
   *
   * We can handle #1 and #2 with the same logic though.
   */

  if ((MAX (left_min_width, right_min_width) * 2 + center_nat_width + 2 * priv->column_spacing) > area.width)
    {
      /* Handle #3 */
      left = right = MAX (left_min_width, right_min_width);
      center = area.width - left - right - 2 * priv->column_spacing;
    }
  else
    {
      /* Handle #1 and #2 */
      center = center_nat_width;
      right = left = (area.width - center) / 2 - priv->column_spacing;
    }

  n_rows = g_list_length (values);
  rows = g_new0 (GtkRequestedSize, n_rows);

  for (iter = values, i = 0; iter != NULL; iter = iter->next, i++)
    {
      DzlThreeGridRowInfo *row_info = iter->data;

      rows[i].data = row_info;
      rows[i].minimum_size = row_info->min_above_baseline + row_info->min_below_baseline;
      rows[i].natural_size = row_info->nat_above_baseline + row_info->nat_below_baseline;
    }

  gtk_distribute_natural_allocation (area.height, n_rows, rows);

  for (i = 0; i < n_rows; i++)
    {
      GtkRequestedSize *size = &rows[i];
      DzlThreeGridRowInfo *row_info = size->data;
      GtkAllocation child_alloc;
      gint baseline;

      if (row_info->nat_above_baseline + row_info->nat_below_baseline < size->minimum_size)
        baseline = row_info->nat_above_baseline;
      else
        baseline = row_info->min_above_baseline;

      child_alloc.x = area.x;
      child_alloc.width = left;
      child_alloc.y = area.y;
      child_alloc.height = size->minimum_size;
      if (dir == GTK_TEXT_DIR_LTR)
        dzl_three_grid_size_allocate_children (self, DZL_THREE_GRID_COLUMN_LEFT, row_info->row, &child_alloc, baseline);
      else
        dzl_three_grid_size_allocate_children (self, DZL_THREE_GRID_COLUMN_RIGHT, row_info->row, &child_alloc, baseline);

      child_alloc.x = area.x + left + priv->column_spacing;
      child_alloc.width = center;
      child_alloc.y = area.y;
      child_alloc.height = size->minimum_size;
      dzl_three_grid_size_allocate_children (self, DZL_THREE_GRID_COLUMN_CENTER, row_info->row, &child_alloc, baseline);

      child_alloc.x = area.x + area.width - right;
      child_alloc.width = right;
      child_alloc.y = area.y;
      child_alloc.height = size->minimum_size;
      if (dir == GTK_TEXT_DIR_LTR)
        dzl_three_grid_size_allocate_children (self, DZL_THREE_GRID_COLUMN_RIGHT, row_info->row, &child_alloc, baseline);
      else
        dzl_three_grid_size_allocate_children (self, DZL_THREE_GRID_COLUMN_LEFT, row_info->row, &child_alloc, baseline);

      area.y += child_alloc.height + priv->row_spacing;
      area.height -= child_alloc.height + priv->row_spacing;
    }

  g_list_free (values);
}

static void
dzl_three_grid_forall (GtkContainer *container,
                       gboolean      include_internals,
                       GtkCallback   callback,
                       gpointer      user_data)
{
  DzlThreeGrid *self = (DzlThreeGrid *)container;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  g_assert (GTK_IS_CONTAINER (self));
  g_assert (callback != NULL);

  for (guint i = priv->children->len; i > 0; i--)
    {
      DzlThreeGridChild *child = g_ptr_array_index (priv->children, i - 1);

      callback (child->widget, user_data);
    }
}

static void
dzl_three_grid_get_child_property (GtkContainer *container,
                                   GtkWidget    *widget,
                                   guint         prop_id,
                                   GValue       *value,
                                   GParamSpec   *pspec)
{
  DzlThreeGrid *self = (DzlThreeGrid *)container;
  DzlThreeGridChild *child = dzl_three_grid_find_child (self, widget);

  switch (prop_id)
    {
    case CHILD_PROP_COLUMN:
      g_value_set_enum (value, child->column);
      break;

    case CHILD_PROP_ROW:
      g_value_set_uint (value, child->row);
      break;

    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
    }
}

static void
dzl_three_grid_set_child_property (GtkContainer *container,
                                   GtkWidget    *widget,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  DzlThreeGrid *self = (DzlThreeGrid *)container;
  DzlThreeGridChild *child = dzl_three_grid_find_child (self, widget);

  switch (prop_id)
    {
    case CHILD_PROP_COLUMN:
      child->column = g_value_get_enum (value);
      break;

    case CHILD_PROP_ROW:
      child->row = g_value_get_uint (value);
      break;

    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
    }

  gtk_widget_queue_resize (GTK_WIDGET (container));
}

static void
dzl_three_grid_finalize (GObject *object)
{
  DzlThreeGrid *self = (DzlThreeGrid *)object;
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  g_clear_pointer (&priv->row_infos, g_hash_table_unref);
  g_clear_pointer (&priv->children, g_ptr_array_unref);

  G_OBJECT_CLASS (dzl_three_grid_parent_class)->finalize (object);
}

static void
dzl_three_grid_get_property (GObject    *object,
                             guint       prop_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  DzlThreeGrid *self = DZL_THREE_GRID (object);
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  switch (prop_id)
    {
    case PROP_COLUMN_SPACING:
      g_value_set_uint (value, priv->column_spacing);
      break;

    case PROP_ROW_SPACING:
      g_value_set_uint (value, priv->row_spacing);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_three_grid_set_property (GObject      *object,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  DzlThreeGrid *self = DZL_THREE_GRID (object);
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  switch (prop_id)
    {
    case PROP_COLUMN_SPACING:
      priv->column_spacing = g_value_get_uint (value);
      gtk_widget_queue_resize (GTK_WIDGET (self));
      break;

    case PROP_ROW_SPACING:
      priv->row_spacing = g_value_get_uint (value);
      gtk_widget_queue_resize (GTK_WIDGET (self));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_three_grid_class_init (DzlThreeGridClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

  object_class->finalize = dzl_three_grid_finalize;
  object_class->get_property = dzl_three_grid_get_property;
  object_class->set_property = dzl_three_grid_set_property;

  widget_class->get_request_mode = dzl_three_grid_get_request_mode;
  widget_class->get_preferred_height_for_width = dzl_three_grid_get_preferred_height_for_width;
  widget_class->get_preferred_width = dzl_three_grid_get_preferred_width;
  widget_class->size_allocate = dzl_three_grid_size_allocate;

  container_class->add = dzl_three_grid_add;
  container_class->forall = dzl_three_grid_forall;
  container_class->get_child_property = dzl_three_grid_get_child_property;
  container_class->remove = dzl_three_grid_remove;
  container_class->set_child_property = dzl_three_grid_set_child_property;

  properties [PROP_COLUMN_SPACING] =
    g_param_spec_uint ("column-spacing",
                       "Column Spacing",
                       "The amount of spacing to add between columns",
                       0,
                       G_MAXUINT,
                       0,
                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties [PROP_ROW_SPACING] =
    g_param_spec_uint ("row-spacing",
                       "Row Spacing",
                       "The amount of spacing to add between rows",
                       0,
                       G_MAXUINT,
                       0,
                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, N_PROPS, properties);

  child_properties [CHILD_PROP_COLUMN] =
    g_param_spec_enum ("column",
                       "Column",
                       "Column",
                       DZL_TYPE_THREE_GRID_COLUMN,
                       DZL_THREE_GRID_COLUMN_LEFT,
                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  child_properties [CHILD_PROP_ROW] =
    g_param_spec_uint ("row",
                       "Row",
                       "Row",
                       0,
                       G_MAXUINT,
                       0,
                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gtk_container_class_install_child_properties (container_class, N_CHILD_PROPS, child_properties);

  gtk_widget_class_set_css_name (widget_class, "threegrid");
}

static void
dzl_three_grid_init (DzlThreeGrid *self)
{
  DzlThreeGridPrivate *priv = dzl_three_grid_get_instance_private (self);

  priv->children = g_ptr_array_new_with_free_func (dzl_three_grid_child_free);

  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
}

GtkWidget *
dzl_three_grid_new (void)
{
  return g_object_new (DZL_TYPE_THREE_GRID, NULL);
}

GType
dzl_three_grid_column_get_type (void)
{
  static GType type_id;

  if (g_once_init_enter (&type_id))
    {
      GType _type_id;
      static const GEnumValue values[] = {
        { DZL_THREE_GRID_COLUMN_LEFT, "DZL_THREE_GRID_COLUMN_LEFT", "left" },
        { DZL_THREE_GRID_COLUMN_CENTER, "DZL_THREE_GRID_COLUMN_CENTER", "center" },
        { DZL_THREE_GRID_COLUMN_RIGHT, "DZL_THREE_GRID_COLUMN_RIGHT", "right" },
        { 0 }
      };
      _type_id = g_enum_register_static ("DzlThreeGridColumn", values);
      g_once_init_leave (&type_id, _type_id);
    }

  return type_id;
}
