/*
 * Copyright 2009 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */
/** 
 * SECTION:ctk-box
 * @short_description: Base class for box containers
 * @see_also: #CtkVBox #CtkHBox
 * @include ctk-box.h
 *
 * #CtkBox is a base class for our box containers, It handles all the logic for
 * #CtkVbox and #CtkHbox 
 */
#if HAVE_CONFIG_H
#include <config.h>
#endif

#include "ctk-box.h"
#include <clutk/ctk-focusable.h>
#include <math.h>

static void clutter_container_iface_init (ClutterContainerIface *iface);

G_DEFINE_ABSTRACT_TYPE_WITH_CODE (CtkBox,
                                  ctk_box,
                                  CTK_TYPE_ACTOR,
                                  G_IMPLEMENT_INTERFACE
                                  (CLUTTER_TYPE_CONTAINER,
                                   clutter_container_iface_init));

#define CTK_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_BOX, \
  CtkBoxPrivate))

struct _CtkBoxPrivate
{
  GList  *children;
  gint16  spacing;
  guint   homogeneous;

  CtkOrientation orientation;
  gboolean            default_expand;
  gboolean            spacing_set;

  ClutterActor *child;
  
  GCompareFunc compare_func;
};

enum
{
  PROP_0,

  PROP_SPACING,
  PROP_HOMOGENEOUS,
  PROP_ORIENTATION
};

struct _CtkBoxChild
{
  ClutterActor *actor;
  gfloat width;
  gfloat height;
  gboolean expand;
  gboolean fill;
  ClutterActorBox box;
};


/* Globals */

/* Forwards */
static void ctk_box_add              (ClutterContainer *container,
                                      ClutterActor     *actor);
static void ctk_box_remove           (ClutterContainer *container,
                                      ClutterActor *actor);
static void ctk_box_foreach          (ClutterContainer *container,
                                      ClutterCallback   callback,
                                      gpointer          user_data);
static void ctk_box_foreach_with_internals (ClutterContainer *container,
    ClutterCallback   callback,
    gpointer          user_data);
static void ctk_box_raise            (ClutterContainer *container,
                                      ClutterActor     *actor,
                                      ClutterActor     *sibling);
static void ctk_box_lower            (ClutterContainer *container,
                                      ClutterActor     *actor,
                                      ClutterActor     *sibling);
static void ctk_box_sort_depth_order (ClutterContainer *container);


static void     ctk_box_focus_in_event  (ClutterActor    *actor);
static gboolean ctk_box_key_press_event (ClutterActor    *actor,
    ClutterKeyEvent *event);

/* GObject stuff */
static void
ctk_box_finalize (GObject *object)
{
  CtkBoxPrivate *priv = CTK_BOX (object)->priv;
  GList *c;

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *box_child = c->data;

      if (box_child)
        {
          if (CLUTTER_IS_ACTOR (box_child->actor))
            clutter_actor_unparent (box_child->actor);
          g_slice_free (CtkBoxChild, box_child);
        }
    }

  g_list_free (priv->children);
  priv->children = NULL;

  G_OBJECT_CLASS (ctk_box_parent_class)->finalize (object);

}

static void
clutter_container_iface_init (ClutterContainerIface *iface)
{
  iface->add              = ctk_box_add;
  iface->remove           = ctk_box_remove;
  iface->foreach          = ctk_box_foreach;
  iface->raise            = ctk_box_raise;
  iface->lower            = ctk_box_lower;
  iface->sort_depth_order = ctk_box_sort_depth_order;
  iface->foreach_with_internals = ctk_box_foreach_with_internals;
}

static void
ctk_box_paint (ClutterActor *actor)
{
  CtkActor *ctk_actor = CTK_ACTOR (actor);
  GSList   *effects   = NULL;

  if (!ctk_actor_get_effects_painting (ctk_actor)
      && (effects = ctk_actor_get_effects (ctk_actor)))
    {
      GSList *e;

      ctk_actor_set_effects_painting (ctk_actor, TRUE);

      for (e = effects; e; e = e->next)
        {
          gboolean last_effect = e->next ? FALSE : TRUE;
          ctk_effect_paint (e->data, ctk_box_paint, last_effect);
        }

      ctk_actor_set_effects_painting (ctk_actor, FALSE);
    }
  else
    {
      GList *c;

      CLUTTER_ACTOR_CLASS (ctk_box_parent_class)->paint (actor);

      for (c = CTK_BOX (actor)->priv->children; c; c = c->next)
        {
          CtkBoxChild *child_info = c->data;

          if (CLUTTER_ACTOR_IS_VISIBLE (child_info->actor))
            clutter_actor_paint (child_info->actor);
        }
    }
  ctk_actor_set_damaged (ctk_actor, FALSE);
}

static void
ctk_box_set_property (GObject      *object,
                      guint         prop_id,
                      const GValue *value,
                      GParamSpec   *pspec)
{
  CtkBox *box = CTK_BOX (object);

  switch (prop_id)
    {
    case PROP_ORIENTATION:
      ctk_box_set_orientation (box, g_value_get_int (value));
      break;

    case PROP_SPACING:
      ctk_box_set_spacing (box, g_value_get_int (value));
      break;

    case PROP_HOMOGENEOUS:
      ctk_box_set_homogeneous (box, g_value_get_boolean (value));
      break;

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

static void
ctk_box_get_property (GObject    *object,
                      guint       prop_id,
                      GValue     *value,
                      GParamSpec *pspec)
{
  CtkBox *box = CTK_BOX (object);

  switch (prop_id)
    {
    case PROP_ORIENTATION:
      g_value_set_int (value, ctk_box_get_orientation (box));
      break;

    case PROP_SPACING:
      g_value_set_int (value, ctk_box_get_spacing (box));
      break;

    case PROP_HOMOGENEOUS:
      g_value_set_boolean (value, ctk_box_get_homogeneous (box));
      break;

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

static void
ctk_box_get_preferred_width (ClutterActor *actor,
                             gfloat   for_height,
                             gfloat  *minimum_width,
                             gfloat  *natural_width)
{
  CtkBox        *box = CTK_BOX (actor);
  CtkBoxPrivate *priv = box->priv;
  CtkBoxChild   *child;
  GList              *children;
  gint                nvis_children;
  gfloat         mwidth;
  gfloat         nwidth;
  CtkPadding     padding;

  ctk_actor_get_padding (CTK_ACTOR (actor), &padding);

  if (minimum_width)
    *minimum_width = 0;
  if (natural_width)
    *natural_width = 0;
  nvis_children = 0;

  /* If the orientation is vertical, we only need to figure out the width
   * of the widest child
   */
  if (priv->orientation == CTK_ORIENTATION_VERTICAL)
    {
      gfloat pmin_width = 0;
      gfloat pnat_width = 0;

      children = priv->children;

      while (children)
        {
          gfloat cmin_width = 0;
          gfloat cnat_width = 0;

          child = children->data;
          children = children->next;

          clutter_actor_get_preferred_width (child->actor, for_height,
                                             &cmin_width, &cnat_width);

          pmin_width = MAX (pmin_width, cmin_width);
          pnat_width = MAX (pnat_width, cnat_width);
        }
      pmin_width += padding.left + padding.right;
      pnat_width += padding.left + padding.right;

      if (minimum_width)
        *minimum_width = pmin_width;
      if (natural_width)
        *natural_width = pnat_width;
      return;
    }

  children = priv->children;
  while (children)
    {
      child = children->data;
      children = children->next;

      if (CLUTTER_ACTOR_IS_VISIBLE (child->actor))
        {
          gfloat min_width = 0;
          gfloat nat_width = 0;

          clutter_actor_get_preferred_width (child->actor,
                                             for_height,
                                             &min_width,
                                             &nat_width);

          if (priv->homogeneous)
            {
              mwidth = min_width;
              nwidth = nat_width;

              if (minimum_width)
                *minimum_width = MAX (*minimum_width, mwidth);
              if (natural_width)
                *natural_width = MAX (*natural_width, nwidth);
            }
          else
            {
              if (minimum_width)
                *minimum_width += min_width;
              if (natural_width)
                *natural_width += nat_width;
            }

          if (minimum_width)
            *minimum_width = MAX (*minimum_width, min_width);
          if (natural_width)
            *natural_width = MAX (*natural_width, nat_width);

          nvis_children += 1;
        }
    }

  if (nvis_children > 0)
    {
      if (priv->homogeneous)
        {
          if (minimum_width)
            *minimum_width *= nvis_children;
          if (natural_width)
            *natural_width *= nvis_children;
        }

      if (priv->orientation == CTK_ORIENTATION_HORIZONTAL)
        {
          gfloat spacing = (gfloat) (priv->spacing);

          if (minimum_width)
            *minimum_width += (nvis_children -1) * spacing;
          if (natural_width)
            *natural_width += (nvis_children -1) * spacing;
        }
    }

  if (minimum_width)
    *minimum_width += padding.left + padding.right;

  if (natural_width)
    *natural_width += padding.left + padding.right;
}

static void
ctk_box_get_preferred_height (ClutterActor *actor,
                              gfloat   for_width,
                              gfloat  *minimum_height,
                              gfloat  *natural_height)
{
  CtkBox        *box = CTK_BOX (actor);
  CtkBoxPrivate *priv = box->priv;
  CtkBoxChild   *child;
  GList         *children, *c;
  gint           nvis_children;
  gfloat    mheight;
  gfloat    nheight;
  gfloat    spacing;
  CtkPadding     padding;

  ctk_actor_get_padding (CTK_ACTOR (actor), &padding);
  spacing = (gfloat) (priv->spacing);

  if (minimum_height)
    *minimum_height = 0;
  if (natural_height)
    *natural_height = 0;
  nvis_children = 0;

  children = priv->children;

  if (priv->orientation == CTK_ORIENTATION_VERTICAL)
    {
      gfloat all_child_height = 0;
      gfloat max_child_height = 0;

      for (c = children; c; c = c->next)
        {
          child = c->data;
          gfloat height;

          clutter_actor_get_preferred_height (child->actor,
                                              for_width, NULL, &height);

          all_child_height += height + spacing;
          max_child_height = MAX (max_child_height, height);
        }

      if (priv->homogeneous)
        {
          if (minimum_height)
            *minimum_height = max_child_height * g_list_length (children);
          if (natural_height)
            *natural_height = max_child_height * g_list_length (children);
        }
      else
        {
          if (minimum_height)
            *minimum_height = all_child_height;
          if (natural_height)
            *natural_height = all_child_height;
        }

      if (minimum_height)
        *minimum_height += padding.top + padding.bottom;
      if (natural_height)
        *natural_height += padding.top + padding.bottom;
      return;
    }

  while (children)
    {
      child = children->data;
      children = children->next;

      if (CLUTTER_ACTOR_IS_VISIBLE (child->actor))
        {
          gfloat min_height = 0;
          gfloat nat_height = 0;

          clutter_actor_get_preferred_height (child->actor,
                                              for_width,
                                              &min_height,
                                              &nat_height);
          if (priv->homogeneous)
            {
              mheight = min_height;
              nheight = nat_height;

              if (minimum_height)
                *minimum_height = MAX (*minimum_height, mheight);
              if (natural_height)
                *natural_height = MAX (*natural_height, nheight);
            }
          else
            {
              if (minimum_height)
                *minimum_height += min_height;
              if (natural_height)
                *natural_height += nat_height;
            }

          if (minimum_height)
            *minimum_height = MAX (*minimum_height, min_height);
          if (natural_height)
            *natural_height = MAX (*natural_height, nat_height);

          nvis_children += 1;
        }
    }

  if (nvis_children > 0)
    {
      if (priv->homogeneous)
        {
          if (minimum_height)
            *minimum_height *= nvis_children;
          if (natural_height)
            *natural_height *= nvis_children;
        }

      if (priv->orientation == CTK_ORIENTATION_VERTICAL)
        {
          gfloat space = (gfloat) (priv->spacing);

          if (minimum_height)
            *minimum_height += (nvis_children -1) * space;
          if (natural_height)
            *natural_height += (nvis_children -1) * space;
        }
    }

  if (minimum_height)
    *minimum_height += padding.top + padding.bottom;

  if (natural_height)
    *natural_height += padding.top + padding.bottom;
}

static void
ctk_box_allocate (ClutterActor          *actor,
                  const ClutterActorBox *box,
                  ClutterAllocationFlags flags)
{
  CtkBoxPrivate    *priv = CTK_BOX (actor)->priv;
  CtkPadding        padding;
  GList            *c;
  gint              n_children = 0;
  gint              n_expanding = 0;
  GtkTextDirection  direction = ctk_actor_get_default_direction ();
  gfloat       width;
  gfloat       height;
  gfloat       spacing;
  gfloat       width_for_expanders;
  gfloat       height_for_expanders;

  CLUTTER_ACTOR_CLASS (ctk_box_parent_class)->allocate (actor, box, flags);

  ctk_actor_get_padding (CTK_ACTOR (actor), &padding);

  width = (box->x2 - box->x1) - padding.left - padding.right;
  height = (box->y2 - box->y1) - padding.top - padding.bottom;
  spacing = (gfloat) (priv->spacing);

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *child = c->data;
      n_children++;

      if (child->expand || priv->homogeneous)
        n_expanding++;
    }

  /* Work out the size of the children first */
  width_for_expanders = width - (spacing * (n_children-1));;
  height_for_expanders = height - (spacing * (n_children-1));
  if (!priv->homogeneous)
    {
      for (c = priv->children; c; c = c->next)
        {
          CtkBoxChild *child = c->data;
          gfloat min_width;
          gfloat min_height;

          if (child->expand)
            continue;

          clutter_actor_get_preferred_width (child->actor, box->y2 - box->y1,
                                             &min_width, NULL);
          clutter_actor_get_preferred_height (child->actor, box->x2 - box->x1,
                                              &min_height, NULL);

          if (priv->orientation == CTK_ORIENTATION_HORIZONTAL)
            {
              child->width = min_width;
              child->height = height;

              width_for_expanders -= min_width;
            }
          else
            {
              child->width = width;
              child->height = min_height;

              height_for_expanders -= min_height;
            }
        }
    }
  if (height_for_expanders < 1)
    height_for_expanders = 1;
  if (width_for_expanders < 1)
    width_for_expanders = 1;

  if (n_expanding > 0)
    {
      width_for_expanders /= n_expanding;
      height_for_expanders /= n_expanding;

      for (c = priv->children; c; c = c->next)
        {
          CtkBoxChild *child = c->data;
          gfloat       min_width;
          gfloat       min_height;

          if (!child->expand)
            continue;

          clutter_actor_get_preferred_size (child->actor,
                                            &min_height, &min_width,
                                            NULL, NULL);
          if (priv->orientation == CTK_ORIENTATION_HORIZONTAL)
            {
              child->width = child->fill ? width_for_expanders : min_width;
              child->height = height;
            }
          else
            {
              child->width = width;
              child->height = child->fill ? height_for_expanders : min_height;
            }
        }
    }

  /* Now we do the positioning */
  gfloat x;
  gfloat y;

  if (direction == GTK_TEXT_DIR_LTR)
    {
      x = padding.left;
      y = padding.top;
    }
  else
    {
      x = padding.right;
      y = padding.top;
    }
  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *child = c->data;
      ClutterActorBox   child_box;
      gfloat       child_width;
      gfloat       child_height;

      clutter_actor_get_allocation_box (child->actor, &child_box);

      if (child->expand || priv->homogeneous)
        {
          if (priv->orientation == CTK_ORIENTATION_HORIZONTAL)
            {
              child_width = width_for_expanders;
              child_height = height;
            }
          else
            {
              child_width = width;
              child_height = height_for_expanders;
            }
        }
      else
        {
          child_width = child->width;
          child_height = child->height;
        }

      if (priv->orientation == CTK_ORIENTATION_HORIZONTAL)
        {
          child_box.x1 = floor (x);
          child_box.x2 = floor (x + child->width);
          child_box.y1 = floor (y);
          child_box.y2 = floor (y + child->height);

          x += child_width + spacing;
        }
      else
        {
          child_box.x1 = floor (x);
          child_box.x2 = floor (x + child->width);
          child_box.y1 = floor (y);
          child_box.y2 = floor (y + child->height);

          y += child_height + spacing;
        }
      if (direction == GTK_TEXT_DIR_RTL
          && priv->orientation == CTK_ORIENTATION_HORIZONTAL)
        {
          child_box.x1 = floor ((box->x2 - box->x1) - padding.left
                                - child_box.x1 - child_width);
          child_box.x2 = floor (child_box.x1 + child->width);
        }
      child->box = child_box;

      clutter_actor_allocate (child->actor, &child_box, flags);
    }
}

static void
ctk_box_pick (ClutterActor       *actor,
              const ClutterColor *color)
{
  /* Chain up so we get a bounding box pained (if we are reactive) */
  CLUTTER_ACTOR_CLASS (ctk_box_parent_class)->pick (actor, color);

  /* Just forward to the paint call which in turn will trigger
   * the child actors also getting 'picked'.
   */
  if (CLUTTER_ACTOR_IS_VISIBLE (actor))
    {
      ctk_actor_set_effects_painting (CTK_ACTOR (actor), TRUE);        
      ctk_box_paint (actor);
      ctk_actor_set_effects_painting (CTK_ACTOR (actor), FALSE);
    }
}

static void
ctk_box_class_init (CtkBoxClass *klass)
{
  GObjectClass      *obj_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *act_class = CLUTTER_ACTOR_CLASS (klass);
  GParamSpec        *pspec;

  obj_class->finalize     = ctk_box_finalize;
  obj_class->set_property = ctk_box_set_property;
  obj_class->get_property = ctk_box_get_property;

  act_class->paint                = ctk_box_paint;
  act_class->get_preferred_width  = ctk_box_get_preferred_width;
  act_class->get_preferred_height = ctk_box_get_preferred_height;
  act_class->allocate             = ctk_box_allocate;
  act_class->pick                 = ctk_box_pick;
  act_class->key_press_event      = ctk_box_key_press_event;
  act_class->key_focus_in         = ctk_box_focus_in_event;

  /* Install properties */
  pspec = g_param_spec_int ("spacing", "spacing",
                            "The amount of space between the children",
                            0, G_MAXINT, 0, G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_SPACING, pspec);

  pspec = g_param_spec_int ("orientation", "orientation",
                            "The orientation of the box",
                            CTK_ORIENTATION_HORIZONTAL,
                            CTK_ORIENTATION_VERTICAL,
                            CTK_ORIENTATION_VERTICAL, G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_ORIENTATION, pspec);

  pspec = g_param_spec_boolean ("homogeneous", "homogeneous",
                                "Whether the children should all be the same size",
                                FALSE, G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_HOMOGENEOUS, pspec);

  /* Install private struct */
  g_type_class_add_private (obj_class, sizeof (CtkBoxPrivate));
}

static void
ctk_box_init (CtkBox *box)
{
  CtkBoxPrivate *priv;

  priv = box->priv = CTK_BOX_GET_PRIVATE (box);

  priv->children = NULL;
  priv->spacing = 0;
  priv->homogeneous = FALSE;
  priv->orientation = CTK_ORIENTATION_VERTICAL;
  priv->default_expand = FALSE;
  priv->spacing_set = TRUE;
}

/*
 * Private methods
 */

static void
ctk_box_add (ClutterContainer *container,
             ClutterActor     *actor)
{
  ctk_box_pack (CTK_BOX (container), actor, FALSE, FALSE);
}

static void
ctk_box_remove (ClutterContainer *container,
                ClutterActor     *actor)
{
  CtkBoxPrivate *priv;
  GList *c;

  g_return_if_fail (CTK_IS_BOX (container));
  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
  priv = CTK_BOX (container)->priv;

  g_object_ref (actor);

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *child_info = c->data;

      if (child_info->actor == actor)
        {
          clutter_actor_unparent (actor);

          priv->children = g_list_remove (priv->children, child_info);
          g_slice_free (CtkBoxChild, child_info);
          break;
        }
    }

  /* queue a relayout, to get the correct positioning inside
   * the ::actor-removed signal handlers
   */
  clutter_actor_queue_relayout (CLUTTER_ACTOR (container));

  g_signal_emit_by_name (container, "actor-removed", actor);

  if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (container)))
    clutter_actor_queue_redraw (CLUTTER_ACTOR (container));

  g_object_unref (actor);
}

static void
ctk_box_foreach (ClutterContainer *container,
                 ClutterCallback   callback,
                 gpointer          user_data)
{
  CtkBoxPrivate *priv;
  GList *c;

  g_return_if_fail (CTK_IS_BOX (container));
  priv = CTK_BOX (container)->priv;

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *child_info = c->data;
      (* callback) (child_info->actor, user_data);
    }
}

static void
ctk_box_foreach_with_internals (ClutterContainer *container,
                                ClutterCallback   callback,
                                gpointer          user_data)
{
  CtkBoxPrivate *priv;
  GList *c;

  g_return_if_fail (CTK_IS_BOX (container));
  priv = CTK_BOX (container)->priv;

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *child_info = c->data;
      (* callback) (child_info->actor, user_data);
    }
}

static void
ctk_box_raise (ClutterContainer *container,
               ClutterActor     *actor,
               ClutterActor     *sibling)
{
  /* Null op */
}

static void
ctk_box_lower (ClutterContainer *container,
               ClutterActor     *actor,
               ClutterActor     *sibling)
{
  /* Null op */
}

static void
ctk_box_sort_depth_order (ClutterContainer *container)
{
  /* Null op */
}

static void
ctk_box_focus_in_event  (ClutterActor    *actor)
{
  CtkBoxPrivate *priv;
  GList *c;

  g_return_if_fail (CTK_IS_BOX (actor));
  priv = CTK_BOX (actor)->priv;

  CLUTTER_ACTOR_CLASS (ctk_box_parent_class)->key_focus_in (actor);

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild *child = c->data;

      if (CTK_IS_FOCUSABLE (child->actor))
        {
          ClutterActorBox box;
          clutter_actor_get_allocation_box (child->actor, &box);

          ctk_focusable_set_focused (CTK_FOCUSABLE (child->actor), TRUE);
          g_signal_emit_by_name (actor, "child-focus-changed", &child->box);

          break;
        }
    }
}

static gboolean
ctk_box_key_press_event (ClutterActor    *actor,
                         ClutterKeyEvent *event)
{
  CtkBoxPrivate     *priv;
  CtkFocusEventType  type;
  CtkFocusDirection  direction;
  GList             *c;
  ClutterActor      *parent;
  ClutterActor      *focused_child = NULL;
  gint               focused_child_pos = 0;

  g_return_val_if_fail (CTK_IS_BOX (actor), FALSE);
  priv = CTK_BOX (actor)->priv;

  parent = clutter_actor_get_parent (actor);

  direction = CTK_FOCUS_DIR_NONE;
  type = ctk_focusable_get_event_type (event, &direction);

  if (type != CTK_FOCUS_EVENT_DIRECTION)
    return FALSE;

  if (direction == CTK_FOCUS_DIR_NONE)
    return FALSE;

  for (c = priv->children; c; c = c->next)
    {
      CtkBoxChild  *box_child = c->data;
      ClutterActor *child = box_child->actor;

      if (CTK_IS_FOCUSABLE (child)
          && ctk_focusable_get_focused (CTK_FOCUSABLE (child)))
        {
          focused_child = child;
          break;
        }
      focused_child_pos++;
    }

  /* If nothing is focused, just focus the first child if possible, otherwise
   * propagate to parent
   */
  if (focused_child == NULL)
    {
      CtkBoxChild *child = g_list_nth_data (priv->children, 0);

      if (CLUTTER_IS_ACTOR (child->actor))
        {
          ctk_focusable_set_focused (CTK_FOCUSABLE (child->actor), TRUE);
          g_signal_emit_by_name (actor, "child-focus-changed", &child->box);
          return TRUE;
        }
      else
        {
          return clutter_actor_event (parent, (ClutterEvent *)event, FALSE);
        }
    }

  /* If it was a next/prev event, then see if the current focused actor is
   * first or last, and if so, chain up
   */
  if (direction == CTK_FOCUS_DIR_PREV)
    {
      CtkBoxChild *child = priv->children->data;

      if (focused_child == child->actor)
        return clutter_actor_event (parent, (ClutterEvent *)event, FALSE);
    }
  else if (direction == CTK_FOCUS_DIR_NEXT)
    {
      CtkBoxChild *child = g_list_last (priv->children)->data;

      if (focused_child == child->actor)
        return clutter_actor_event (parent, (ClutterEvent *)event, FALSE);
    }

  if (direction == CTK_FOCUS_DIR_NEXT
      || (priv->orientation == CTK_ORIENTATION_HORIZONTAL
          && direction == CTK_FOCUS_DIR_RIGHT)
      || (priv->orientation == CTK_ORIENTATION_VERTICAL
          && direction == CTK_FOCUS_DIR_DOWN))
    {
      ClutterActor *next_focus = NULL;
      ClutterActorBox *box = NULL;

      for (c = g_list_nth (priv->children, focused_child_pos+1); c; c = c->next)
        {
          CtkBoxChild *child = c->data;
          if (CTK_IS_FOCUSABLE (child->actor))
            {
              next_focus = child->actor;
              box = &child->box;
              break;
            }
        }

      if (next_focus)
        {
          ctk_focusable_set_focused (CTK_FOCUSABLE (next_focus), TRUE);
          g_signal_emit_by_name (actor, "child-focus-changed", box);
          return TRUE;
        }

      return clutter_actor_event (parent, (ClutterEvent *)event, FALSE);
    }
  else if (direction == CTK_FOCUS_DIR_PREV
           || (priv->orientation == CTK_ORIENTATION_HORIZONTAL
               && direction == CTK_FOCUS_DIR_LEFT)
           || (priv->orientation == CTK_ORIENTATION_VERTICAL
               && direction == CTK_FOCUS_DIR_UP))
    {
      ClutterActor *next_focus = NULL;
      ClutterActorBox *box = NULL;

      for (c = g_list_nth (priv->children, focused_child_pos-1); c; c = c->prev)
        {
          CtkBoxChild *child = c->data;
          if (CTK_IS_FOCUSABLE (child->actor))
            {
              next_focus = child->actor;
              box = &child->box;
              break;
            }
        }

      if (next_focus)
        {
          ctk_focusable_set_focused (CTK_FOCUSABLE (next_focus), TRUE);
          g_signal_emit_by_name (actor, "child-focus-changed", box);
          return TRUE;
        }

      return clutter_actor_event (parent, (ClutterEvent *)event, FALSE);

    }

  return FALSE;
}

/*
 * Public methods
 */

/** 
 * ctk_box_pack:
 * @box: A CtkBox 
 * @child: The ClutterActor that is to be added to the @box
 * @expand: TRUE if the @actor is to be given extra space allocated to @box
 * @fill: TRUE if space given to @actor by the expand option is actually allocated to @actor, rather than just padding it.
 *
 * Adds an @actor to the given @box, the @actor is added to the end of the box
 */
void
ctk_box_pack (CtkBox      *box,
              ClutterActor     *actor,
              gboolean          expand,
              gboolean          fill)
{
  CtkBoxPrivate *priv;
  CtkBoxChild *child_info = NULL;

  g_return_if_fail (CTK_IS_BOX (box));
  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
  g_return_if_fail (clutter_actor_get_parent (actor) == NULL);
  priv = box->priv;

  g_object_ref (actor);

  child_info = g_slice_new0 (CtkBoxChild);
  child_info->actor = actor;
  child_info->expand = expand;
  child_info->fill = fill;

  priv->children = g_list_append (priv->children, child_info);
  clutter_actor_set_parent (actor, CLUTTER_ACTOR (box));

  /* queue a relayout, to get the correct positioning inside
   * the ::actor-added signal handlers
   */
  clutter_actor_queue_relayout (CLUTTER_ACTOR (box));

  g_signal_emit_by_name (box, "actor-added", actor);

  g_object_unref (actor);

}

/**
 * ctk_box_set_homogeneous:
 * @box: a CtkBox
 * @homogeneous: a boolean value dictating if children in @box are homogeneous
 *
 * sets the "homogeneous" property of the @box, controls whether all the children
 * in @box are given equal space.
 */
void
ctk_box_set_homogeneous (CtkBox  *box,
                         gboolean      homogeneous)
{
  g_return_if_fail (CTK_IS_BOX (box));

  if (homogeneous != box->priv->homogeneous)
    {
      box->priv->homogeneous = homogeneous;

      g_object_notify (G_OBJECT (box), "homogeneous");

      clutter_actor_queue_relayout (CLUTTER_ACTOR (box));
    }
}

/** 
 * ctk_box_get_homogeneous:
 * @box: a CtkBox
 *
 * gets whether the @box is homogeneous, see ctk_box_set_homogeneous()
 * Returns: TRUE or FALSE 
 */
gboolean
ctk_box_get_homogeneous (CtkBox  *box)
{
  g_return_val_if_fail (CTK_IS_BOX (box), FALSE);
  return box->priv->homogeneous;
}

/** 
 * ctk_box_set_spacing:
 * @box: a CtkBox
 * @spacing: a gint refering to the size of the spacing in pixels
 *
 * Sets the size of the spacing for @box, which is the number of pixels between the children of @box
 */
void
ctk_box_set_spacing (CtkBox *box,
                     gint         spacing)
{
  g_return_if_fail (CTK_IS_BOX (box));

  if (spacing != box->priv->spacing)
    {
      box->priv->spacing = spacing;
      box->priv->spacing_set = TRUE;

      g_object_notify (G_OBJECT (box), "spacing");

      clutter_actor_queue_relayout (CLUTTER_ACTOR (box));
    }
}

/** 
 * ctk_box_get_spacing:
 * @box: a CtkBox
 *
 * Returns the spacing of @box, see also ctk_box_set_spacing()
 *
 * Returns: An integer regarding @box spacing 
 */
gint
ctk_box_get_spacing (CtkBox *box)
{
  g_return_val_if_fail (CTK_IS_BOX (box), 0);
  return box->priv->spacing;
}

/** 
 * ctk_box_set_orientation:
 * @box: a CtkBox
 * @orient: a CtkOrientation orientation
 *
 * Sets the orientation of the given box, see #CtkOrientation
 */
void
ctk_box_set_orientation (CtkBox *box,
                         CtkOrientation orient)
{
  g_return_if_fail (CTK_IS_BOX (box));

  box->priv->orientation = orient;
  clutter_actor_queue_relayout (CLUTTER_ACTOR (box));

  g_object_notify (G_OBJECT (box), "orientation");
}

/** 
 * ctk_box_get_orientation:
 * @box: a CtkBox
 *
 * Gets the orientation of @box
 *
 * Returns: a CtkOrientation detailing @box orientation
 */
CtkOrientation
ctk_box_get_orientation (CtkBox *box)
{
  g_return_val_if_fail (CTK_IS_BOX (box), CTK_ORIENTATION_VERTICAL);

  return box->priv->orientation;
}


static gint
sort_func (CtkBoxChild *achild, CtkBoxChild *bchild, CtkBox *box)
{
  CtkBoxPrivate *priv = box->priv;

  return priv->compare_func (achild->actor, bchild->actor);
}

/** 
 * ctk_box_sort_children:
 * @box: a CtkBox
 * @func: a GCompareFunc to be used to sort children in @box
 *
 * Sorts the children in @box using @func as its comparison function
 */
void
ctk_box_sort_children (CtkBox *box, GCompareFunc func)
{
  g_return_if_fail (CTK_IS_BOX (box));
  g_return_if_fail (func);

  box->priv->compare_func = func;

  box->priv->children = g_list_sort_with_data (box->priv->children,
                        (GCompareDataFunc)sort_func,
                        box);

  clutter_actor_queue_relayout (CLUTTER_ACTOR (box));
}
