/*
 * 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-actor
 * @short_description: The base widget class for Ctk style objects
 * @include ctk-actor.h
 *
 * A #CtkActor is the base widget class for building Ctk style objects, it contains additional logic on top of #ClutterActor for userinterface building
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <gtk/gtk.h>
#include "ctk-actor.h"

#include <math.h>

#include "ctk-enum-types.h"
#include "ctk-focusable.h"
#include "ctk-private.h"
#include "ctk-tooltip.h"
#include "ctk-marshal.h"

static void ctk_focusable_iface_init (CtkFocusableIface *iface);

G_DEFINE_ABSTRACT_TYPE_WITH_CODE (CtkActor, ctk_actor, CLUTTER_TYPE_ACTOR,
                                  G_IMPLEMENT_INTERFACE (
                                                CTK_TYPE_FOCUSABLE,
                                                ctk_focusable_iface_init));

#define CTK_ACTOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_ACTOR, \
  CtkActorPrivate))

/* this could really go somewhere else */
gboolean
_ctk_boolean_handled_accumulator (GSignalInvocationHint *ihint,
                                  GValue                *return_accu,
                                  const GValue          *handler_return,
                                  gpointer               dummy)
{
  gboolean continue_emission;
  gboolean signal_handled;

  signal_handled = g_value_get_boolean (handler_return);
  g_value_set_boolean (return_accu, signal_handled);
  continue_emission = !signal_handled;

  return continue_emission;
}


struct _CtkActorPrivate
{
  CtkPadding     padding;
  gboolean       focused;

  CtkActorState  state;
  ClutterActor  *state_bg[CTK_N_STATES];
  ClutterActor  *bg;
  ClutterActor  *old_bg;

  gchar         *tooltip_text;
  ClutterActor  *tooltip;

  GSList   *effects_list;
  gboolean  effects_painting;
  gboolean  damaged;

  ClutterActorBox stored_allocation;
};

enum
{
  PROP_0,

  PROP_PADDING,
  PROP_FOCUS,
  PROP_STATE,
  PROP_BG,
  PROP_TOOLTIP_TEXT,
  PROP_EFFECTS_PAINTING
};

enum
{
  CHILD_FOCUS_CHANGED,
  DRAG_BEGIN,
  DRAG_DATA_DELETE,
  DRAG_DATA_GET,
  DRAG_DATA_RECEIVED,
  DRAG_DROP,
  DRAG_END,
  DRAG_FAILED,
  DRAG_LEAVE,
  DRAG_MOTION,

  LAST_SIGNAL
};

/* Globals */
static GtkTextDirection default_direction = GTK_TEXT_DIR_LTR;

static guint32 _actor_signals[LAST_SIGNAL] = { 0 };

void
ctk_actor_on_queue_redraw (ClutterActor *actor, ClutterActor *origin);

/* Forwards */
static void     ctk_actor_set_focused (CtkFocusable *focusable,
                                       gboolean      is_focused);
static gboolean ctk_actor_get_focused (CtkFocusable *focusable);

static gboolean ctk_actor_can_focus   (CtkFocusable *focusable);

static void     ctk_actor_activate    (CtkFocusable *focusable);

static void     ctk_actor_paint           (ClutterActor          *actor);

static void     ctk_actor_allocate        (ClutterActor          *actor,
    const ClutterActorBox *box,
    ClutterAllocationFlags flags);

static gboolean ctk_actor_key_press_event (ClutterActor          *actor,
    ClutterKeyEvent       *event);

static void     ctk_actor_focus_in_event  (ClutterActor          *actor);

static void     ctk_actor_focus_out_event (ClutterActor          *actor);

static void     ctk_actor_map             (ClutterActor          *actor);

static void     ctk_actor_unmap           (ClutterActor          *actor);

static gboolean ctk_actor_enter_event     (ClutterActor          *actor,
    ClutterCrossingEvent  *event);

static gboolean ctk_actor_leave_event     (ClutterActor          *actor,
    ClutterCrossingEvent  *event);

/* GObject stuff */
static void
ctk_actor_finalize (GObject *object)
{
  CtkActorPrivate *priv = CTK_ACTOR (object)->priv;
  gint i;

  for (i =0; i < CTK_N_STATES; i++)
    {
      if (CLUTTER_IS_ACTOR (priv->state_bg[i]))
        {
          clutter_actor_unparent (priv->state_bg[i]);
          priv->state_bg[i] = NULL;
        }
    }
  if (priv->bg)
    {
      clutter_actor_unparent (priv->bg);
      priv->bg = NULL;
    }

  if (priv->effects_list)
    {
      g_slist_foreach (priv->effects_list, (GFunc)g_object_unref, NULL);
      g_slist_free (priv->effects_list);
      priv->effects_list = NULL;
    }

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

static void
ctk_actor_set_property (GObject      *object,
                        guint         prop_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  CtkActor *actor = CTK_ACTOR (object);

  switch (prop_id)
    {
    case PROP_PADDING:
      ctk_actor_set_padding (actor, g_value_get_boxed (value));
      break;

    case PROP_FOCUS:
      break;
      if (g_value_get_boolean (value))
        ctk_actor_focus_in_event (CLUTTER_ACTOR (actor));
      else
        ctk_actor_focus_out_event (CLUTTER_ACTOR (actor));
      break;

    case PROP_STATE:
      ctk_actor_set_state (actor, g_value_get_enum (value));
      break;

    case PROP_BG:
      ctk_actor_set_background (actor, g_value_get_pointer (value));
      break;

    case PROP_TOOLTIP_TEXT:
      ctk_actor_set_tooltip_text (actor, g_value_get_string (value));
      break;

    case PROP_EFFECTS_PAINTING:
      ctk_actor_set_effects_painting (actor, g_value_get_boolean (value));
      break;

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

static void
ctk_actor_get_property (GObject    *object,
                        guint       prop_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  CtkActor *actor = CTK_ACTOR (object);

  switch (prop_id)
    {
    case PROP_PADDING:
    {
      CtkPadding padding = { 0 };
      ctk_actor_get_padding (actor, &padding);
      g_value_set_boxed (value, &padding);
    }
    break;

    case PROP_FOCUS:
      g_value_set_boolean (value, actor->priv->focused);
      break;

    case PROP_STATE:
      g_value_set_enum (value, actor->priv->state);
      break;

    case PROP_BG:
      g_value_set_pointer (value, actor->priv->bg);
      break;

    case PROP_TOOLTIP_TEXT:
      g_value_set_string (value, actor->priv->tooltip_text);
      break;

    case PROP_EFFECTS_PAINTING:
      g_value_set_boolean (value, actor->priv->effects_painting);
      break;

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

static void
ctk_focusable_iface_init (CtkFocusableIface *iface)
{
  iface->set_focused = ctk_actor_set_focused;
  iface->get_focused = ctk_actor_get_focused;
  iface->can_focus   = ctk_actor_can_focus;
  iface->activate    = ctk_actor_activate;
}

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

  /* Overrides */
  obj_class->finalize        = ctk_actor_finalize;
  obj_class->set_property    = ctk_actor_set_property;
  obj_class->get_property    = ctk_actor_get_property;

  act_class->paint           = ctk_actor_paint;
  act_class->allocate        = ctk_actor_allocate;
  act_class->key_press_event = ctk_actor_key_press_event;
  act_class->key_focus_in    = ctk_actor_focus_in_event;
  act_class->key_focus_out   = ctk_actor_focus_out_event;
  act_class->map             = ctk_actor_map;
  act_class->unmap           = ctk_actor_unmap;
  act_class->enter_event     = ctk_actor_enter_event;
  act_class->leave_event     = ctk_actor_leave_event;

  /* Install Properties */
  pspec = g_param_spec_boxed ("padding", "padding",
                              "Padding around an actor",
                              CTK_TYPE_PADDING, CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_PADDING, pspec);

  pspec = g_param_spec_boolean("focused", "focused",
                               "Whether the actor is currently in focus",
                               FALSE, CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_FOCUS, pspec);

  pspec = g_param_spec_enum ("state", "state",
                             "The current state of the actor",
                             CTK_TYPE_ACTOR_STATE, CTK_STATE_NORMAL,
                             CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_STATE, pspec);

  pspec = g_param_spec_pointer ("background", "background", "Background",
                                CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_BG, pspec);

  pspec = g_param_spec_string ("tooltip-text", "Toolip Text",
                               "The contents of the tooltip for this widget",
                               NULL, CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_TOOLTIP_TEXT, pspec);

  pspec = g_param_spec_boolean("effects-painting", "effects-painting",
                               "Whether effects are controlling painting",
                               FALSE, CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_EFFECTS_PAINTING, pspec);

  /* Signals */
  _actor_signals[CHILD_FOCUS_CHANGED] =
    g_signal_new ("child-focus-changed",
                  G_OBJECT_CLASS_TYPE (obj_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (CtkActorClass, child_focus_changed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__ENUM,
                  G_TYPE_NONE, 1, CLUTTER_TYPE_ACTOR_BOX);

  /* Drag and Drop signals */
  _actor_signals[DRAG_LEAVE] =
    g_signal_new ("drag-leave",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_leave),
		              NULL, NULL,
		              ctk_marshal_VOID__OBJECT_UINT,
		              G_TYPE_NONE, 2,
		              GDK_TYPE_DRAG_CONTEXT,
		              G_TYPE_UINT);

  _actor_signals[DRAG_BEGIN] =
    g_signal_new ("drag-begin",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_begin),
		              NULL, NULL,
		              ctk_marshal_VOID__OBJECT,
		              G_TYPE_NONE, 1,
		              GDK_TYPE_DRAG_CONTEXT);

  _actor_signals[DRAG_END] =
    g_signal_new ("drag-end",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_end),
		              NULL, NULL,
		              ctk_marshal_VOID__OBJECT,
		              G_TYPE_NONE, 1,
		              GDK_TYPE_DRAG_CONTEXT);

  _actor_signals[DRAG_DATA_DELETE] =
    g_signal_new ("drag-data-delete",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_data_delete),
		              NULL, NULL,
		              ctk_marshal_VOID__OBJECT,
		              G_TYPE_NONE, 1,
		              GDK_TYPE_DRAG_CONTEXT);

  _actor_signals[DRAG_FAILED] =
    g_signal_new ("drag-failed",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_failed),
		              NULL, NULL,
		              ctk_marshal_BOOLEAN__OBJECT_ENUM,
		              G_TYPE_BOOLEAN, 2,
		              GDK_TYPE_DRAG_CONTEXT,
		              GTK_TYPE_DRAG_RESULT);

  _actor_signals[DRAG_MOTION] =
    g_signal_new ("drag-motion",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_motion),
		              _ctk_boolean_handled_accumulator, NULL,
		              ctk_marshal_BOOLEAN__OBJECT_INT_INT_UINT,
		              G_TYPE_BOOLEAN, 4,
		              GDK_TYPE_DRAG_CONTEXT,
		              G_TYPE_INT,
		              G_TYPE_INT,
		              G_TYPE_UINT);

  _actor_signals[DRAG_DROP] =
    g_signal_new ("drag-drop",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_drop),
		              _ctk_boolean_handled_accumulator, NULL,
		              ctk_marshal_BOOLEAN__OBJECT_INT_INT_UINT,
		              G_TYPE_BOOLEAN, 4,
		              GDK_TYPE_DRAG_CONTEXT,
		              G_TYPE_INT,
		              G_TYPE_INT,
		              G_TYPE_UINT);

  _actor_signals[DRAG_DATA_GET] =
    g_signal_new ("drag-data-get",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_data_get),
		              NULL, NULL,
		              ctk_marshal_VOID__OBJECT_BOXED_UINT_UINT,
		              G_TYPE_NONE, 4,
		              GDK_TYPE_DRAG_CONTEXT,
		              GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
		              G_TYPE_UINT,
		              G_TYPE_UINT);

  _actor_signals[DRAG_DATA_RECEIVED] =
    g_signal_new ("drag-data-received",
		              G_TYPE_FROM_CLASS (obj_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (CtkActorClass, drag_data_received),
		              NULL, NULL,
		              ctk_marshal_VOID__OBJECT_INT_INT_BOXED_UINT_UINT,
		              G_TYPE_NONE, 6,
		              GDK_TYPE_DRAG_CONTEXT,
		              G_TYPE_INT,
		              G_TYPE_INT,
		              GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
		              G_TYPE_UINT,
		              G_TYPE_UINT);

  /* Add Private struct */
  g_type_class_add_private (obj_class, sizeof (CtkActorPrivate));
}

static void
ctk_actor_init (CtkActor *actor)
{
  CtkActorPrivate *priv;

  priv = actor->priv = CTK_ACTOR_GET_PRIVATE (actor);

  priv->padding.top = priv->padding.bottom = 0;
  priv->padding.left = priv->padding.right = 0;

  priv->focused = FALSE;
  priv->state = CTK_STATE_NORMAL;
  priv->tooltip = NULL;
  priv->effects_list = NULL;
  priv->effects_painting = FALSE;
  priv->damaged = TRUE;

  g_signal_connect (actor, "queue-redraw", G_CALLBACK (ctk_actor_on_queue_redraw), NULL);
}

/*
 * Private methods
 */
static void
ctk_actor_paint (ClutterActor *actor)
{
  CtkActorPrivate *priv;
  gint             i;

  priv = CTK_ACTOR (actor)->priv;

  if (CLUTTER_IS_ACTOR (priv->bg))
    clutter_actor_paint (priv->bg);

  if (CLUTTER_IS_ACTOR (priv->old_bg))
    clutter_actor_paint (priv->old_bg);

  for (i =0; i < CTK_N_STATES; i++)
    {
      if (CLUTTER_IS_ACTOR (priv->state_bg[i]))
        {
          clutter_actor_paint (priv->state_bg[i]);
        }
    }
}

void
ctk_actor_on_queue_redraw (ClutterActor *actor, ClutterActor *origin)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));
  ctk_actor_set_damaged (CTK_ACTOR (actor), TRUE);
}

static void
ctk_actor_allocate (ClutterActor          *actor,
                    const ClutterActorBox *box,
                    ClutterAllocationFlags flags)
{

  CtkActorPrivate   *priv;
  ClutterActorClass *klass;
  ClutterActorBox    child_box;
  gint               i;

  klass = CLUTTER_ACTOR_CLASS (ctk_actor_parent_class);
  klass->allocate (actor, box, flags);
  priv = CTK_ACTOR (actor)->priv;

  child_box.x1 = 0;
  child_box.y1 = 0;
  child_box.x2 = floor (box->x2 - box->x1);
  child_box.y2 = floor (box->y2 - box->y1);

  if (CLUTTER_IS_ACTOR (priv->bg))
    clutter_actor_allocate (priv->bg, &child_box, flags);

  for (i =0; i < CTK_N_STATES; i++)
    {
      if (CLUTTER_IS_ACTOR (priv->state_bg[i]))
        {
          clutter_actor_allocate (priv->state_bg[i], &child_box, flags);
        }
    }

  priv->stored_allocation.x1 = child_box.x1;
  priv->stored_allocation.x2 = child_box.x2;
  priv->stored_allocation.y1 = child_box.y1;
  priv->stored_allocation.y2 = child_box.y2;
}

/**
 * ctk_actor_get_stored_allocation
 * @actor: A #CtkActor
 * @box: (out): A #ClutterAllocationBox
 *
 * returns the stored allocation box data, based on the allocation box of the
 * last allocation
 */
void
ctk_actor_get_stored_allocation (CtkActor *actor, ClutterActorBox* box)
{
  CtkActorPrivate *priv;

  priv = actor->priv;
  box->x1 = priv->stored_allocation.x1;
  box->x2 = priv->stored_allocation.x2;
  box->y1 = priv->stored_allocation.y1;
  box->y2 = priv->stored_allocation.y2;
}

/**
 * ctk_actor_recurse_get_stored_allocation_box
 * @actor: A #CtkActor
 * @box: (out): A #ClutterAllocationBox
 *
 * returns the screen allocation box data, based on the allocation of all
 * the parent of this actors.
 */
void
ctk_actor_recurse_get_stored_allocation_box(CtkActor *actor, ClutterActorBox* box)
{
  g_return_if_fail (actor);
  g_return_if_fail (box);
  if(CTK_IS_ACTOR(actor))
    {
      CtkActor *parent = (CtkActor*) clutter_actor_get_parent (CLUTTER_ACTOR(actor));
      ClutterActorBox parentbox;
      gfloat anchor_x = 0;
      gfloat anchor_y = 0;
      ctk_actor_get_stored_allocation (actor, box);
      clutter_actor_get_anchor_point (CLUTTER_ACTOR(actor), &anchor_x, &anchor_y);
      ctk_actor_recurse_get_stored_allocation_box (parent, &parentbox);
      box->x1 += -anchor_x + parentbox.x1;
      box->x2 += -anchor_x + parentbox.x1;
      box->y1 += -anchor_y + parentbox.y1;
      box->y2 += -anchor_y + parentbox.y1;
    }
  else
    {
      box->x1 = box->x2 = box->y1 = box->y2 = 0;
    }
}

static gboolean
ctk_actor_key_press_event (ClutterActor    *actor,
                           ClutterKeyEvent *event)
{
  CtkActorPrivate   *priv;
  CtkFocusEventType  type;
  CtkFocusDirection  direction;

  g_return_val_if_fail (CTK_IS_ACTOR (actor), FALSE);
  g_return_val_if_fail (event, FALSE);
  priv = CTK_ACTOR (actor)->priv;

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

  if (type == CTK_FOCUS_EVENT_ACTIVATE)
    {
      ctk_focusable_activate (CTK_FOCUSABLE (actor));
      return TRUE;
    }
  else
    {
      ClutterActor *parent = clutter_actor_get_parent (actor);
      gboolean handled = FALSE;

      handled = clutter_actor_event (parent, (ClutterEvent *)event, FALSE);
      return handled;
    }

  return FALSE;
}

static void
ctk_actor_focus_in_event  (ClutterActor    *actor)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));

  CTK_ACTOR (actor)->priv->focused = TRUE;

  g_object_notify (G_OBJECT (actor), "focused");
}

static void
ctk_actor_focus_out_event (ClutterActor *actor)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));

  CTK_ACTOR (actor)->priv->focused = FALSE;

  g_object_notify (G_OBJECT (actor), "focused");
}

static gboolean
source (ClutterActor *actor)
{
  clutter_actor_grab_key_focus (CLUTTER_ACTOR (actor));
  return FALSE;
}

static void
ctk_actor_set_focused (CtkFocusable *focusable,
                       gboolean      is_focused)
{
  CtkActorPrivate *priv;

  g_return_if_fail (CTK_IS_ACTOR (focusable));
  priv = CTK_ACTOR (focusable)->priv;

  /* If normal actor, just set priv->focused to TRUE & grab keyboard
   * If container, propagate focus event
   */
  if (is_focused)
    {
      /* FIXME: There's a bug in Clutter-0.9 which stops you from setting a
       * new key_focus actor on the stage while in a key event propagation.
       * Hence the idle until I find out why
       *
       * clutter_actor_grab_key_focus (CLUTTER_ACTOR (focusable));
       */
      g_idle_add ((GSourceFunc)source, focusable);
    }
}

static gboolean
ctk_actor_get_focused (CtkFocusable *focusable)
{
  g_return_val_if_fail (CTK_IS_ACTOR (focusable), FALSE);

  if (CLUTTER_IS_CONTAINER (focusable))
    {
      GList *children, *c;
      gboolean focused = CTK_ACTOR (focusable)->priv->focused;

      children = clutter_container_get_children (CLUTTER_CONTAINER (focusable));
      for (c = children; c; c = c->next)
        {
          ClutterActor *child = c->data;

          if (CTK_IS_FOCUSABLE (child)
              && ctk_focusable_get_focused (CTK_FOCUSABLE (child)))
            {
              focused = TRUE;
              break;
            }
        }
      g_list_free (children);

      return focused;
    }

  return CTK_ACTOR (focusable)->priv->focused;
}

static gboolean
ctk_actor_can_focus (CtkFocusable *focusable)
{
  return CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (focusable));
}

static void
ctk_actor_activate (CtkFocusable *focusable)
{
}

static void
ctk_actor_map (ClutterActor *actor)
{
  CtkActorPrivate *priv = CTK_ACTOR (actor)->priv;
  gint i;

  CLUTTER_ACTOR_CLASS (ctk_actor_parent_class)->map (actor);

  if (CLUTTER_IS_ACTOR (priv->bg))
    clutter_actor_map (priv->bg);

  for (i =0; i < CTK_N_STATES; i++)
    {
      if (CLUTTER_IS_ACTOR (priv->state_bg[i]))
        {
          clutter_actor_map (priv->state_bg[i]);
        }
    }
}

static void
ctk_actor_unmap (ClutterActor *actor)
{
  CtkActorPrivate *priv = CTK_ACTOR (actor)->priv;
  gint i;

  CLUTTER_ACTOR_CLASS (ctk_actor_parent_class)->unmap (actor);

  if (CLUTTER_IS_ACTOR (priv->bg))
    clutter_actor_unmap (priv->bg);

  for (i =0; i < CTK_N_STATES; i++)
    {
      if (CLUTTER_IS_ACTOR (priv->state_bg[i]))
        {
          clutter_actor_unmap (priv->state_bg[i]);
        }
    }
}

static gboolean
ctk_actor_enter_event (ClutterActor          *actor,
                       ClutterCrossingEvent  *event)
{
  CtkActorPrivate *priv = CTK_ACTOR (actor)->priv;

  if (priv->tooltip)
    ctk_tooltip_show (CTK_TOOLTIP (priv->tooltip), event->x, event->y);

  return FALSE;
}

static gboolean
ctk_actor_leave_event (ClutterActor          *actor,
                       ClutterCrossingEvent  *event)
{
  CtkActorPrivate *priv = CTK_ACTOR (actor)->priv;

  if (priv->tooltip)
    ctk_tooltip_hide (CTK_TOOLTIP (priv->tooltip));

  return FALSE;
}

/*
 * Public methods
 */
/**
 * ctk_actor_set_state:
 * @actor: A CtkActor
 * @state: A CtkActorState
 *
 * Sets the state of @actor
 * !FIXME! flesh this out more
 */
void
ctk_actor_set_state (CtkActor     *actor,
                     CtkActorState state)
{
  CtkActorPrivate *priv;
  ClutterActor    *old_state_bg;
  ClutterActor    *new_state_bg;

  g_return_if_fail (CTK_IS_ACTOR (actor));
  g_return_if_fail (state < CTK_N_STATES);
  priv = actor->priv;

  old_state_bg = priv->state_bg[priv->state];
  new_state_bg = priv->state_bg[state];

  priv->state = state;

  if (old_state_bg)
    clutter_actor_animate (old_state_bg, CLUTTER_EASE_OUT_SINE, 160,
                           "opacity", 0, NULL);
  if (new_state_bg)
    {
      clutter_actor_set_opacity (new_state_bg, 0);
      clutter_actor_animate (new_state_bg, CLUTTER_EASE_IN_SINE, 160,
                             "opacity", 255, NULL);
    }

  g_object_notify (G_OBJECT (actor), "state");
}

/**
 * ctk_actor_get_state:
 * @actor: A CtkActor
 *
 * gets the currently set CtkActorState for @actor
 *
 * Returns: a CtkActorState
 */
CtkActorState
ctk_actor_get_state (CtkActor     *actor)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), CTK_STATE_NORMAL);

  return actor->priv->state;
}

/**
 * ctk_actor_set_background_for_state:
 * @actor: A CtkActor
 * @state: A CtkActorState
 * @bg: A #ClutterActor
 *
 * Sets the background of @actor for the given @state
 * !FIXME! - needs fleshing out more
 */
void
ctk_actor_set_background_for_state (CtkActor      *actor,
                                    CtkActorState  state,
                                    ClutterActor  *new_bg)
{
  CtkActorPrivate *priv;
  ClutterActor    *old_bg;

  g_return_if_fail (CTK_IS_ACTOR (actor));
  g_return_if_fail (state < CTK_N_STATES);
  priv = actor->priv;

  old_bg = priv->state_bg[state];
  if (old_bg)
    clutter_actor_unparent (old_bg);

  priv->state_bg[state] = new_bg;

  if (new_bg)
    {
      clutter_actor_set_parent (new_bg, CLUTTER_ACTOR (actor));
      clutter_actor_set_opacity (new_bg, priv->state == state ? 255 : 0);
    }

  if (CLUTTER_ACTOR_IS_VISIBLE (actor))
    clutter_actor_queue_relayout (CLUTTER_ACTOR (actor));
}

/**
 * ctk_actor_get_background_for_state:
 * @actor: A CtkActor
 * @state: A CtkActorState
 *
 * Gets the current background for @actor in the given @state previously set by ctk_actor_set_background_for_state()
 *
 * Returns: a ClutterActor
 */
ClutterActor *
ctk_actor_get_background_for_state (CtkActor *actor, CtkActorState state)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), NULL);
  g_return_val_if_fail (state < CTK_N_STATES, NULL);

  return actor->priv->state_bg[state];
}

static void
on_animation_completed (ClutterAnimation *animation, ClutterActor *actor)
{
  ClutterActor *cur_bg = NULL;
  ClutterActor *obj_bg;

  if (CTK_IS_ACTOR (actor))
    cur_bg = CTK_ACTOR (actor)->priv->bg;

  obj_bg = (ClutterActor *)clutter_animation_get_object (animation);

  if (obj_bg != cur_bg)
    clutter_actor_unparent (obj_bg);
}

/**
 * ctk_actor_set_background:
 * @actor: CtkActor
 * @bg: ClutterActor
 *
 * Sets the background for @actor to @bg
 * Will fade out old background first if one exists
 */
void
ctk_actor_set_background (CtkActor     *actor,
                          ClutterActor *bg)
{
  CtkActorPrivate *priv;

  g_return_if_fail (CTK_IS_ACTOR (actor));
  priv = CTK_ACTOR (actor)->priv;

  if (priv->bg)
    {
      priv->old_bg = priv->bg;
      clutter_actor_animate (priv->old_bg, CLUTTER_EASE_OUT_SINE, 100,
                             "opacity", 0,
                          "signal::completed",on_animation_completed, actor,
                             NULL);
    }

  priv->bg = bg;

  if (priv->bg)
    {
      if (!clutter_actor_get_parent (priv->bg))
        clutter_actor_set_parent (priv->bg, CLUTTER_ACTOR (actor));
      clutter_actor_show (bg);
      clutter_actor_set_opacity (bg, 0);
      clutter_actor_animate (priv->bg, CLUTTER_EASE_IN_SINE, 100,
                             "opacity", 255,
                             NULL);
    }

  g_object_notify (G_OBJECT (actor), "background");

  if (CLUTTER_ACTOR_IS_VISIBLE (actor))
    clutter_actor_queue_relayout (CLUTTER_ACTOR (actor));
}

/**
 * ctk_actor_get_background:
 * @actor: A CtkActor
 *
 * Gets the current background for @actor as previously set by ctk_actor_set_background()
 *
 * Returns: A ClutterActor
 */
ClutterActor *
ctk_actor_get_background (CtkActor     *actor)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), NULL);

  return actor->priv->bg;
}

/**
 * ctk_actor_set_padding:
 * @actor: A CtkActor
 * @padding: A CtkPadding struct
 *
 * Sets the padding of @actor to @padding
 */
void
ctk_actor_set_padding (CtkActor  *actor,
                       CtkPadding *padding)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));
  g_return_if_fail (padding != NULL);

  actor->priv->padding = *padding;

  g_object_notify (G_OBJECT (actor), "padding");

  if (CLUTTER_ACTOR_IS_VISIBLE (actor))
    clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
}

/**
 * ctk_actor_get_padding:
 * @actor: A CtkActor
 * @padding: a CtkPadding struct to be filled with the return value
 *
 * Will return via @padding the padding values of @actor set by ctk_actor_set_padding()
 */
void
ctk_actor_get_padding (CtkActor  *actor,
                       CtkPadding *padding)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));
  g_return_if_fail (padding != NULL);

  *padding = actor->priv->padding;
}

/**
 * ctk_actor_get_default_direction:
 *
 * Returns the default direction for all CtkActor widgets
 *
 * Returns: A #GtkTextDirection object
 */
GtkTextDirection
ctk_actor_get_default_direction (void)
{
  return default_direction;
}

/**
 * ctk_actor_set_default_direction:
 * @dir: A #GtkTextDirection object
 *
 * Sets the default direction for all CtkActor objects
 */
void
ctk_actor_set_default_direction (GtkTextDirection  dir)
{
  g_return_if_fail (dir == GTK_TEXT_DIR_LTR || dir == GTK_TEXT_DIR_RTL);

  default_direction = dir;
}

/**
 * ctk_actor_set_tooltip_text:
 * @actor: A CtkActor
 * @self: a text string
 *
 * Sets the tooltip for @actor to the contents of @tooltip_text
 */
void
ctk_actor_set_tooltip_text (CtkActor     *actor,
                            const gchar  *tooltip_text)
{
  CtkActorPrivate *priv;

  g_return_if_fail (CTK_IS_ACTOR (actor));
  priv = actor->priv;

  if (priv->tooltip_text)
    {
      g_free (priv->tooltip_text);
      priv->tooltip_text = NULL;
    }

  if (tooltip_text)
    {
      priv->tooltip_text = g_strdup (tooltip_text);
      if (!priv->tooltip)
        {
          priv->tooltip = ctk_tooltip_new (CLUTTER_ACTOR (actor));
        }
      ctk_tooltip_set_label (CTK_TOOLTIP (priv->tooltip), tooltip_text);
    }
  else if (priv->tooltip)
    {
      ctk_tooltip_set_label (CTK_TOOLTIP (priv->tooltip), tooltip_text);
    }
}

/**
 * ctk_actor_get_tooltip_text:
 * @actor: a CtkActor
 *
 * gets the tooltip text for @actor previously set by ctk_actor_set_tooltip_text()
 *
 * Returns: a string defining the tooltip text
 */
const gchar *
ctk_actor_get_tooltip_text (CtkActor *actor)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), NULL);

  return actor->priv->tooltip_text;
}


/**
 * ctk_actor_add_effect:
 * @actor: a #CtkActor
 * @effect: a #CtkEffect
 *
 * Adds @effect to the list of @actor's effects
 **/
void
ctk_actor_add_effect (CtkActor     *actor,
                      CtkEffect    *effect)
{
  CtkActorPrivate *priv;

  g_return_if_fail (CTK_IS_ACTOR (actor));
  g_return_if_fail (CTK_IS_EFFECT (effect));
  priv = actor->priv;

  /*
   * We don't want to change the list if effects are painting as it would make
   * the list pointer invalid
   */
  if (priv->effects_painting)
    {
      g_warning ("Cannot add an effect during a paint cycle");
      return;
    }

  /* We don't support multiple actors per effect right now, maybe later on */
  if (CLUTTER_IS_ACTOR (ctk_effect_get_actor (effect)))
    {
      g_warning ("Cannot attach one effect to multiple actors");
      return;
    }

  g_object_ref_sink (effect);
  priv->effects_list = g_slist_append (priv->effects_list, effect);
  ctk_effect_set_actor (effect, CLUTTER_ACTOR (actor));
}


/**
 * ctk_actor_remove_effect:
 * @actor: a #CtkActor
 * @effect: a #CtkEffect
 *
 * Remove @effect from the list of effects attached to the @actor
 **/
void
ctk_actor_remove_effect (CtkActor     *actor,
                         CtkEffect    *effect)
{
  CtkActorPrivate *priv;

  g_return_if_fail (CTK_IS_ACTOR (actor));
  g_return_if_fail (CTK_IS_EFFECT (effect));
  priv = actor->priv;

  /* Same reason as in _add_effect */
  if (priv->effects_painting)
    {
      g_warning ("Cannot remove an effect during a paint cycle");
    }

  if (g_slist_find (priv->effects_list, effect))
    {
      priv->effects_list = g_slist_remove (priv->effects_list, effect);
      g_object_unref (effect);
    }
  else
    {
      g_warning ("This effect does not exist in the effect list");
    }
}

/**
 * ctk_actor_remove_all_effects:
 * @actor: a #CtkActor
 *
 * Remove all effets from the list of effects attached to the @actor
 **/
void
ctk_actor_remove_all_effects (CtkActor     *actor)
{
  CtkActorPrivate *priv;

  g_return_if_fail (CLUTTER_IS_ACTOR(actor));
  priv = actor->priv;

  g_slist_foreach (priv->effects_list, (GFunc)g_object_unref, NULL);
  g_slist_free (priv->effects_list);
  priv->effects_list = NULL;
}

/**
 * ctk_actor_get_effects:
 * @actor: a #CtkActor
 *
 * Returns: a #GSList of #CtkEffects. Do not modify or free the returned list.
 **/
GSList *
ctk_actor_get_effects (CtkActor *actor)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), NULL);

  return actor->priv->effects_list;
}


/**
 * ctk_actor_set_effects_painting:
 * @actor: a #CtkActor
 * @painting: whether the @actor is being painting be a #CtkEffect
 *
 * This function should be used by objects inheriting #CtkActor, to stop parent
 * paint functions from also painting any #CtkEffects attached to @actor.
 *
 * In the same way, you should check for #ctk_actor_get_effects_painting in
 * your paint function before calling #ctk_effect_paint.
 **/
void
ctk_actor_set_effects_painting (CtkActor     *actor,
                                gboolean      painting)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));

  if (actor->priv->effects_painting == painting)
    return;

  actor->priv->effects_painting = painting;

  g_object_notify (G_OBJECT (actor), "effects-painting");
}


/**
 * ctk_actor_get_effects_painting:
 * @actor: a #CtkActor
 *
 * Returns: whether @actor is currently being painting by an #CtkEffect. See
 * #ctk_actor_set_effects_painting.
 **/
gboolean
ctk_actor_get_effects_painting (CtkActor     *actor)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), FALSE);

  return actor->priv->effects_painting;
}

/**
 * ctk_actor_set_damaged:
 * @actor: A #CtkActor
 * @damaged: A Boolean value dicatating the state of damage
 *
 * Sets the damaged value on @actor, if the @damaged is true then the actor
 * will bubble up to its parent #CtkActor, but stop at a non #CtkActor
 */
void
ctk_actor_set_damaged (CtkActor *actor, gboolean damaged)
{
  g_return_if_fail (CTK_IS_ACTOR (actor));
  ClutterActor *parent;

  if (damaged)
    {
      parent = clutter_actor_get_parent (CLUTTER_ACTOR (actor));
      if (CTK_IS_ACTOR (parent))
      	{
      	  ctk_actor_set_damaged (CTK_ACTOR (parent), TRUE);
      	}
    }

  actor->priv->damaged = damaged;
}

/**
 * ctk_actor_get_damaged:
 * @actor: A #CtkActor
 *
 * Returns: the damaged property of this #CtkActor
 */
gboolean ctk_actor_get_damaged (CtkActor *actor)
{
  g_return_val_if_fail (CTK_IS_ACTOR (actor), FALSE);
  return actor->priv->damaged;
}

/*
 * CtkPadding type
 */

static CtkPadding *
ctk_padding_copy (const CtkPadding *padding)
{
  CtkPadding *copy;

  g_return_val_if_fail (padding != NULL, NULL);

  copy = g_slice_new (CtkPadding);
  *copy = *padding;

  return copy;
}

static void
ctk_padding_free (CtkPadding *padding)
{
  if (G_LIKELY (padding))
    g_slice_free (CtkPadding, padding);
}

GType
ctk_padding_get_type (void)
{
  static GType our_type = 0;

  if (G_UNLIKELY (our_type == 0))
    our_type =
      g_boxed_type_register_static ("CtkPadding",
                                    (GBoxedCopyFunc) ctk_padding_copy,
                                    (GBoxedFreeFunc) ctk_padding_free);

  return our_type;
}
