/*
 * 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-scroll-view
 * @short_description: Provides a widget that allows data to be scrolled
 *
 * #CtkScrollView can hold multiple widgets and will allow them to be scrolled using a scrollbar.
 * It can also be customised in order to provide new scrollbar interfaces
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include "ctk-scroll-view.h"

#include <math.h>

#include "ctk-enum-types.h"
#include "ctk-private.h"
#include "ctk-types.h"

G_DEFINE_TYPE (CtkScrollView,
               ctk_scroll_view,
               CTK_TYPE_BIN);

#define CTK_SCROLL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_SCROLL_VIEW, \
  CtkScrollViewPrivate))

struct _CtkScrollViewPrivate
{
  CtkScrollbarType  bar_type;
  ClutterActor     *trough;
  ClutterActor     *slider;

  gfloat value;
  gfloat offset;
  gfloat slider_height;
  gfloat child_natural_height;
  gfloat self_height;

  gfloat last_y;
  gfloat last_offset;
};

enum
{
  PROP_0,

  PROP_BAR_TYPE,
  PROP_VALUE
};

/* Globals */

/* Forwards */
static gboolean on_scroll_event                (ClutterActor       *actor,
    ClutterScrollEvent *event);

static void     ctk_scroll_view_set_value_real (CtkScrollView *view,
    gfloat         value,
    gboolean       animated);

static void on_actor_added   (ClutterContainer *self, ClutterActor *actor);
static void on_actor_removed (ClutterContainer *self, ClutterActor *actor);

/* GObject stuff */
static void
ctk_scroll_view_finalize (GObject *object)
{
  CtkScrollViewPrivate *priv = CTK_SCROLL_VIEW (object)->priv;

  if (priv->trough)
    {
      clutter_actor_unparent (priv->trough);
      priv->trough = NULL;
    }
  if (priv->slider)
    {
      clutter_actor_unparent (priv->slider);
      priv->slider = NULL;
    }
  G_OBJECT_CLASS (ctk_scroll_view_parent_class)->finalize (object);
}

static void
ctk_scroll_view_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  CtkScrollView *view = CTK_SCROLL_VIEW (object);

  switch (prop_id)
    {
    case PROP_BAR_TYPE:
      ctk_scroll_view_set_bar_type (view, g_value_get_enum (value));
      break;

    case PROP_VALUE:
      ctk_scroll_view_set_value_real (view, g_value_get_float (value), FALSE);
      break;

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

static void
ctk_scroll_view_get_property (GObject    *object,
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  CtkScrollView *view = CTK_SCROLL_VIEW (object);

  switch (prop_id)
    {
    case PROP_BAR_TYPE:
      g_value_set_enum (value, ctk_scroll_view_get_bar_type (view));
      break;

    case PROP_VALUE:
      g_value_set_float (value, ctk_scroll_view_get_value (view));
      break;

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

static void
ctk_scroll_view_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_scroll_view_paint, last_effect);
        }

      ctk_actor_set_effects_painting (ctk_actor, FALSE);
    }
  else
    {
      CtkScrollViewPrivate *priv = CTK_SCROLL_VIEW (actor)->priv;

      CLUTTER_ACTOR_CLASS (ctk_scroll_view_parent_class)->paint (actor);

      if (CLUTTER_ACTOR_IS_VISIBLE (priv->trough))
        clutter_actor_paint (priv->trough);

      if (CLUTTER_ACTOR_IS_VISIBLE (priv->slider))
        clutter_actor_paint (priv->slider);
    }
  ctk_actor_set_damaged (ctk_actor, FALSE);
}

static void
ctk_scroll_view_preffered_width (ClutterActor *actor,
                                 gfloat   for_height,
                                 gfloat  *minimum_width,
                                 gfloat  *natural_width)
{
  ClutterActor *child = ctk_bin_get_child (CTK_BIN (actor));

  if (CLUTTER_IS_ACTOR (child))
    clutter_actor_get_preferred_width (child, for_height,
                                       minimum_width, natural_width);

  if (minimum_width)
    *minimum_width += 12.0;

  if (natural_width)
    *natural_width += 12.0;
}

static void
ctk_scroll_view_preffered_height (ClutterActor *actor,
                                  gfloat   for_width,
                                  gfloat  *minimum_height,
                                  gfloat  *natural_height)
{
  ClutterActor *child = ctk_bin_get_child (CTK_BIN (actor));

  if (CLUTTER_IS_ACTOR (child))
    clutter_actor_get_preferred_height (child, for_width,
                                        minimum_height, natural_height);
}
static void
ctk_scroll_view_allocate (ClutterActor          *actor,
                          const ClutterActorBox *box,
                          ClutterAllocationFlags flags)
{
  CtkScrollViewPrivate *priv = CTK_SCROLL_VIEW (actor)->priv;
  ClutterActor    *child;
  CtkPadding       padding = { 0 };
  ClutterActorBox  child_box;
  gfloat           natural_height = 0;
  gboolean         visible_scollbar = FALSE;
  gfloat           width;
  gfloat           height;
  GtkTextDirection direction;

  /* Chain up early */
  CLUTTER_ACTOR_CLASS (ctk_scroll_view_parent_class)->allocate (actor,
      box, flags);

  direction = ctk_actor_get_default_direction ();
  ctk_actor_get_padding (CTK_ACTOR (actor), &padding);
  child = ctk_bin_get_child (CTK_BIN (actor));

  if (!CLUTTER_IS_ACTOR (child))
    {
      return;
    }

  width = box->x2 - box->x1 - padding.left - padding.right;
  height = box->y2 - box->y1 - padding.top - padding.bottom;

  /* Determine if the scrollbars should be visible */
  clutter_actor_get_preferred_height (child, width, NULL, &natural_height);

  if (natural_height > height)
    visible_scollbar = TRUE;

  /* Allocate the child */
  child_box.x1 = floor (padding.left);
  child_box.x2 = floor (child_box.x1 + width);
  child_box.y1 = floor (padding.top);
  child_box.y2 = floor (child_box.y1 + natural_height);

  if (visible_scollbar)
    {
      gfloat slider_size = (height/natural_height)*height;
      gfloat extra = natural_height - height;
      gfloat trough_width = priv->bar_type == CTK_SCROLLBAR_INSET ?
                            clutter_actor_get_width (priv->trough) : 0;

      if (direction == GTK_TEXT_DIR_LTR)
        {
          child_box.x2 -= floor (trough_width);
        }
      else
        {
          child_box.x1 += floor (trough_width);
          child_box.x2 = floor (child_box.x1 + width - trough_width);
        }

      child_box.y1 -= floor ((priv->offset/(height - slider_size)) * extra);
      child_box.y2 = floor (child_box.y1 + natural_height);
    }

  clutter_actor_allocate (child, &child_box, flags);
  clutter_actor_set_clip (child,
                          0,
                          floor (padding.top + (child_box.y1 * -1)),
                          floor (child_box.x2 - child_box.x1),
                          height);

  /* Allocate the scollbar */
  if (visible_scollbar && (priv->bar_type == CTK_SCROLLBAR_INSET))
    {
      if (direction == GTK_TEXT_DIR_LTR)
        {
          child_box.x1 = floor (padding.left+width-clutter_actor_get_width (priv->trough));
          child_box.x2 = floor (padding.left + width);
        }
      else
        {
          child_box.x1 = floor (padding.left);
          child_box.x2 = floor (padding.left + clutter_actor_get_width (priv->trough));
        }
      child_box.y1 = floor (padding.top);
      child_box.y2 = floor (padding.top + height);

      clutter_actor_allocate (priv->trough, &child_box, flags);
      clutter_actor_show (priv->trough);

      /* Slider */
      if (direction == GTK_TEXT_DIR_LTR)
        {
          child_box.x1 = floor (padding.left+width-clutter_actor_get_width (priv->slider));
          child_box.x2 = floor (padding.left+width);
        }
      else
        {
          child_box.x1 = floor (padding.left);
          child_box.x2 = floor (padding.left + clutter_actor_get_width (priv->slider));
        }
      child_box.y1 = floor (padding.top + priv->offset);
      child_box.y2 = floor (child_box.y1 + ((height/natural_height) *height));

      clutter_actor_allocate (priv->slider, &child_box, flags);
      clutter_actor_show (priv->slider);
    }
  else
    {
      clutter_actor_hide (priv->slider);
      clutter_actor_hide (priv->trough);
    }

  priv->slider_height = floor ((height/natural_height) *height);
  priv->child_natural_height = floor (natural_height);
  priv->self_height = floor (height);
}

static void
ctk_scroll_view_pick (ClutterActor       *actor,
                      const ClutterColor *color)
{
  /* Chain up so we get a bounding box painted (if we are reactive) */
  CLUTTER_ACTOR_CLASS (ctk_scroll_view_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_scroll_view_paint (actor);
      ctk_actor_set_effects_painting (CTK_ACTOR (actor), FALSE);
    }
}

static void
ctk_scroll_view_map (ClutterActor *actor)
{
  CtkScrollViewPrivate *priv = CTK_SCROLL_VIEW (actor)->priv;

  CLUTTER_ACTOR_CLASS (ctk_scroll_view_parent_class)->map (actor);

  if (priv->trough)
    clutter_actor_map (priv->trough);
  if (priv->slider)
    clutter_actor_map (priv->slider);
}

static void
ctk_scroll_view_unmap (ClutterActor *actor)
{
  CtkScrollViewPrivate *priv = CTK_SCROLL_VIEW (actor)->priv;

  CLUTTER_ACTOR_CLASS (ctk_scroll_view_parent_class)->unmap (actor);

  if (priv->trough)
    clutter_actor_unmap (priv->trough);
  if (priv->slider)
    clutter_actor_unmap (priv->slider);
}


static gboolean
ctk_scroll_view_key_press_event (ClutterActor    *actor,
                                 ClutterKeyEvent *event)
{
  CtkScrollViewPrivate *priv;
  gint keyval;

  priv = CTK_SCROLL_VIEW (actor)->priv;

  keyval = clutter_event_get_key_symbol ((ClutterEvent *)event);

  switch (keyval)
    {
    case CLUTTER_Page_Up:
    case CLUTTER_KP_Page_Up:
      priv->offset -= priv->self_height;
      priv->offset = CLAMP (priv->offset,
                            0,
                            priv->self_height - priv->slider_height);
      break;

    case CLUTTER_Page_Down:
    case CLUTTER_KP_Page_Down:
      priv->offset += priv->self_height;
      priv->offset = CLAMP (priv->offset,
                            0,
                            priv->self_height - priv->slider_height);
      break;

    default:
      return CLUTTER_ACTOR_CLASS
             (ctk_scroll_view_parent_class)->key_press_event (actor, event);
    }

  ctk_scroll_view_set_value (CTK_SCROLL_VIEW (actor),
                             priv->offset / (priv->self_height-priv->slider_height));
  return TRUE;
}

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

  obj_class->finalize     = ctk_scroll_view_finalize;
  obj_class->set_property = ctk_scroll_view_set_property;
  obj_class->get_property = ctk_scroll_view_get_property;

  act_class->paint                = ctk_scroll_view_paint;
  act_class->allocate             = ctk_scroll_view_allocate;
  act_class->pick                 = ctk_scroll_view_pick;
  act_class->scroll_event         = on_scroll_event;
  act_class->get_preferred_width  = ctk_scroll_view_preffered_width;
  act_class->get_preferred_height = ctk_scroll_view_preffered_height;
  act_class->key_press_event      = ctk_scroll_view_key_press_event;
  act_class->map                  = ctk_scroll_view_map;
  act_class->unmap                = ctk_scroll_view_unmap;

  /*
   * Install class properties
   */
  pspec = g_param_spec_enum ("scrollbar-type", "Scrollbar Type",
                             "How to show the scroll bar",
                             CTK_TYPE_SCROLLBAR_TYPE, CTK_SCROLLBAR_INSET,
                             CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_BAR_TYPE, pspec);

  pspec = g_param_spec_float ("value", "Value",
                              "How far the view has scrolled", 0.0, 1.0, 0.0,
                              CTK_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_VALUE, pspec);

  g_type_class_add_private (obj_class, sizeof (CtkScrollViewPrivate));
}

static void
ctk_scroll_view_init (CtkScrollView *self)
{
  CtkScrollViewPrivate *priv;
  ClutterColor  troughcol = { 0x00, 0x00, 0x00, 0xaa };
  ClutterColor  slidercol = { 0xff, 0xff, 0xff, 0xaa };
  ClutterActor *trough, *slider;

  priv = self->priv = CTK_SCROLL_VIEW_GET_PRIVATE (self);

  priv->offset = 0;
  priv->bar_type = CTK_SCROLLBAR_INSET;
  priv->value = 0.0;

  clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);

  trough = clutter_rectangle_new_with_color (&troughcol);
  clutter_actor_set_size (trough, 12, 12);

  slider = clutter_rectangle_new_with_color (&slidercol);
  clutter_actor_set_size (slider, 12, 12);

  ctk_scroll_view_set_scroll_bar (self, trough, slider);

  g_signal_connect (self, "actor-added", G_CALLBACK (on_actor_added), NULL);
  g_signal_connect (self, "actor-removed", G_CALLBACK (on_actor_removed), NULL);
}

ClutterActor *
ctk_scroll_view_new (void)
{
  return g_object_new (CTK_TYPE_SCROLL_VIEW, NULL);
}

/*
 * Private methods
 */
static gboolean
on_scroll_event (ClutterActor       *actor,
                 ClutterScrollEvent *event)
{
  return ctk_scroll_view_scroll (CTK_SCROLL_VIEW (actor), event);
}

static gboolean
on_trough_clicked (ClutterActor       *trough,
                   ClutterButtonEvent *event,
                   CtkScrollView      *view)
{
  CtkScrollViewPrivate *priv;
  gfloat diff;
  gfloat sy=0;

  g_return_val_if_fail (CTK_IS_SCROLL_VIEW (view), FALSE);
  priv = view->priv;

  diff = (priv->self_height/priv->child_natural_height);

  /* See if we need to scroll up or down, depending on whether the click was
   * above or below the slider
   */
  clutter_actor_get_transformed_position (priv->slider, NULL, &sy);
  if (event->y < sy)
    diff *= -1;

  ctk_scroll_view_set_value (view, priv->value + diff);

  return TRUE;
}

static gboolean
on_slider_pressed (ClutterActor       *actor,
                   ClutterButtonEvent *event,
                   CtkScrollView      *view)
{
  CtkScrollViewPrivate *priv;

  g_return_val_if_fail (CTK_IS_SCROLL_VIEW (view), FALSE);
  priv = view->priv;

  clutter_actor_get_transformed_position (priv->trough, NULL, &priv->last_y);
  priv->last_y = event->y;
  priv->last_offset = priv->offset;

  clutter_grab_pointer (priv->slider);

  return TRUE;
}

static gboolean
on_slider_motion (ClutterActor       *actor,
                  ClutterMotionEvent *event,
                  CtkScrollView      *view)
{
  CtkScrollViewPrivate *priv;
  gfloat diff;
  gfloat value;

  if (!(event->modifier_state & CLUTTER_BUTTON1_MASK)
      && !(event->modifier_state & CLUTTER_BUTTON3_MASK))
    return FALSE;

  g_return_val_if_fail (CTK_IS_SCROLL_VIEW (view), FALSE);
  priv = view->priv;

  diff = event->y - priv->last_y;
  value = diff / (priv->self_height - priv->slider_height);

  priv->last_y = event->y;
  ctk_scroll_view_set_value_real (view, priv->value + value, FALSE);

  return TRUE;
}

static gboolean
on_slider_released (ClutterActor       *actor,
                    ClutterButtonEvent *event,
                    CtkScrollView      *view)
{
  clutter_ungrab_pointer ();
  return TRUE;
}

static void
on_child_focus_changed (ClutterActor    *scroll_child,
                        ClutterActorBox *focus_child_box,
                        CtkScrollView   *self)
{
  CtkScrollViewPrivate *priv;
  gfloat y10, y20;

  g_return_if_fail (CTK_IS_SCROLL_VIEW (self));
  priv = self->priv;

  if (!focus_child_box)
    return;

  y10 = priv->offset;
  y20 = priv->offset + priv->self_height;

  if (focus_child_box->y1 < y10)
    {
      gfloat diff;

      diff = y10;
      diff = diff / priv->child_natural_height;
      ctk_scroll_view_set_value (self, diff);
    }
  else if (focus_child_box->y2 > y20)
    {
      gfloat diff;

      diff = focus_child_box->y2;
      diff = diff / priv->child_natural_height;
      ctk_scroll_view_set_value (self, diff);
    }
}

static void
on_actor_added (ClutterContainer *self, ClutterActor *actor)
{
  g_return_if_fail (CTK_IS_SCROLL_VIEW (self));

  g_signal_connect (actor, "child-focus-changed",
                    G_CALLBACK (on_child_focus_changed), self);
}

static void
on_actor_removed (ClutterContainer *self, ClutterActor *actor)
{
  g_return_if_fail (CTK_IS_SCROLL_VIEW (self));

  g_signal_handlers_disconnect_by_func (actor,
                                        on_child_focus_changed,
                                        self);
}

/*
 * Public methods
 */

/**
 * ctk_scroll_view_scroll:
 * @view: a #CtkScrollView object
 * @event: a #ClutterScrollEvent object
 *
 * Scrolls @view in a given direction by some ammount, see #ClutterScrollEvent
 *
 * Returns: A boolean value true if the method completed correctly
 */
gboolean
ctk_scroll_view_scroll (CtkScrollView *view,
                        ClutterScrollEvent   *event)
{
  CtkScrollViewPrivate *priv;
  gfloat diff;

  g_return_val_if_fail (CTK_SCROLL_VIEW (view), FALSE);
  priv = view->priv;

  if (priv->child_natural_height <= priv->self_height)
    return FALSE;

  diff = (priv->child_natural_height - priv->self_height) * 0.050;

  priv->offset += (event->direction == CLUTTER_SCROLL_UP) ? -(diff) : diff;

  priv->offset = CLAMP (priv->offset,
                        0,
                        priv->self_height - priv->slider_height);

  ctk_scroll_view_set_value_real (view,
                                  priv->offset ? priv->offset / (priv->self_height - priv->slider_height) : 0,
                                  FALSE);

  return TRUE;
}

/**
 * ctk_scroll_view_set_bar_type:
 * @view: a #CtkScrollView object
 * @type: a #CtkScrollbarType object
 *
 * sets @view to use the type of scrollbar defined in @type. see: #CtkScrollbarType
 */
void
ctk_scroll_view_set_bar_type (CtkScrollView      *view,
                              CtkScrollbarType    type)
{
  CtkScrollViewPrivate *priv;

  g_return_if_fail (CTK_IS_SCROLL_VIEW (view));
  priv = view->priv;

  if (priv->bar_type == type)
    return;

  priv->bar_type = type;

  g_object_notify (G_OBJECT (view), "scrollbar-type");

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

/**
 * ctk_scroll_view_get_bar_type:
 * @view: a #CtkScrollView object
 *
 * Retrives the scrollbar type of @view
 *
 * Returns: a #CtkScrollbarType
 */
CtkScrollbarType
ctk_scroll_view_get_bar_type (CtkScrollView *view)
{
  g_return_val_if_fail (CTK_IS_SCROLL_VIEW (view), CTK_SCROLLBAR_INSET);

  return view->priv->bar_type;
}


static void
ctk_scroll_view_set_value_real (CtkScrollView      *view,
                                gfloat              value,
                                gboolean            animated)
{
  CtkScrollViewPrivate *priv;

  g_return_if_fail (CTK_IS_SCROLL_VIEW (view));
  priv = view->priv;

  value = CLAMP (value, 0.0, 1.0);
  if (value < 0.00001)
    value = 0.0;

  if (animated)
    {
      ClutterAnimation *anim;

      anim = clutter_actor_get_animation (CLUTTER_ACTOR (view));
      if (anim)
        clutter_animation_completed (anim);

      clutter_actor_animate (CLUTTER_ACTOR (view), CLUTTER_EASE_OUT_SINE,
                             120 * (2.0-priv->self_height/priv->child_natural_height),
                             "value", value, NULL);
    }
  else
    {
      priv->value = value;
      priv->offset = value * (priv->self_height - priv->slider_height);

      g_object_notify (G_OBJECT (view), "value");

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


/**
 * ctk_scroll_view_set_value:
 * @view: a #CtkScrollView object
 * @value: a float from 0.0->1.0
 *
 * Sets the scroll position to the given @value
 */
void
ctk_scroll_view_set_value (CtkScrollView *view,
                           gfloat         value)
{
  ctk_scroll_view_set_value_real (view, value, TRUE);
}

/**
 * ctk_scroll_view_get_value:
 * @view: A #CtkScrollView
 *
 * Retrives the current scroll position (from 0.0 -> 1.0) of @view
 *
 * Returns: a float from 0.0 to 1.0
 */
gfloat
ctk_scroll_view_get_value (CtkScrollView *view)
{
  g_return_val_if_fail (CTK_IS_SCROLL_VIEW (view), 0.0);

  if (view->priv->child_natural_height < view->priv->self_height)
    return 0.0;

  return view->priv->value;
}

/**
 * ctk_scroll_view_set_scroll_bar:
 * @view: A #CtkScrollView object
 * @trough: a #ClutterActor object
 * @slider: a #ClutterActor object
 *
 * Sets @view to use @trough and @slider as its actors for its scrollbar
 */
void
ctk_scroll_view_set_scroll_bar (CtkScrollView      *view,
                                ClutterActor       *trough,
                                ClutterActor       *slider)
{
  CtkScrollViewPrivate *priv;

  g_return_if_fail (CTK_IS_SCROLL_VIEW (view));
  g_return_if_fail (CLUTTER_IS_ACTOR (trough));
  g_return_if_fail (CLUTTER_IS_ACTOR (slider));
  priv = view->priv;

  if (priv->trough)
    {
      g_signal_handlers_disconnect_by_func (priv->trough, on_trough_clicked,
                                            view);
      clutter_actor_unparent (priv->trough);
    }
  if (priv->slider)
    {
      g_signal_handlers_disconnect_by_func (priv->slider, on_slider_pressed,
                                            view);
      g_signal_handlers_disconnect_by_func (priv->slider, on_slider_motion, view);
      g_signal_handlers_disconnect_by_func (priv->slider, on_slider_released,
                                            view);
      clutter_actor_unparent (priv->slider);
    }

  /* Do the various setup that's required */
  priv->trough = trough;
  clutter_actor_set_parent (trough, CLUTTER_ACTOR (view));
  clutter_actor_set_reactive (trough, TRUE);
  g_signal_connect (trough, "button-press-event",
                    G_CALLBACK (on_trough_clicked), view);

  priv->slider = slider;
  clutter_actor_set_parent (slider, CLUTTER_ACTOR (view));
  clutter_actor_set_reactive (slider, TRUE);
  g_signal_connect (slider, "button-press-event",
                    G_CALLBACK (on_slider_pressed), view);
  g_signal_connect (slider, "motion-event",
                    G_CALLBACK (on_slider_motion), view);
  g_signal_connect (slider, "button-release-event",
                    G_CALLBACK (on_slider_released), view);

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

/**
 * ctk_scroll_view_get_scroll_bar:
 * @view: a #CtkScrollView object
 * @trough: a Pointer to a #ClutterActor object
 * @slider: a Pointer to a #ClutterActor object
 *
 * Retrives the current #ClutterActor used in @view for its trough and slider graphics
 */
void
ctk_scroll_view_get_scroll_bar (CtkScrollView      *view,
                                ClutterActor      **trough,
                                ClutterActor      **slider)
{
  CtkScrollViewPrivate *priv;

  g_return_if_fail (CTK_IS_SCROLL_VIEW (view));
  priv = view->priv;

  if (trough)
    *trough = priv->trough;
  if (slider)
    *slider = priv->slider;
}

/**
 * ctk_scroll_view_can_scroll:
 * @view: a #CtkScrollView object
 *
 * Retrives wheather @view can be scrolled or not
 *
 * Returns: a boolean value, false if @view is invalid
 */
gboolean
ctk_scroll_view_can_scroll (CtkScrollView      *view)
{
  g_return_val_if_fail (CTK_IS_SCROLL_VIEW (view), FALSE);

  return view->priv->child_natural_height > view->priv->self_height;
}
