gnome-shell/src/st/st-scroll-bar.c
Jasper St. Pierre ea19790828 st-widget: Implement a proper allocate
Since an StWidget now has children, it needs to allocate those children
properly. Defer to the currently installed layout manager, like Clutter
does.

Now that we have something that allocates children in St, to prevent
double allocations, we use clutter_actor_set_allocation rather than
chaining up to StWidget::allocate.

https://bugzilla.gnome.org/show_bug.cgi?id=670034
2012-02-22 17:00:44 -05:00

972 lines
28 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-scroll-bar.c: Scroll bar actor
*
* Copyright 2008 OpenedHand
* Copyright 2008, 2009 Intel Corporation.
* Copyright 2009, 2010 Red Hat, Inc.
* Copyright 2010 Maxim Ermilov
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:st-scroll-bar
* @short_description: a user interface element to control scrollable areas.
*
* The #StScrollBar allows users to scroll scrollable actors, either by
* the step or page amount, or by manually dragging the handle.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <clutter/clutter.h>
#include "st-scroll-bar.h"
#include "st-bin.h"
#include "st-enum-types.h"
#include "st-private.h"
#include "st-button.h"
G_DEFINE_TYPE (StScrollBar, st_scroll_bar, ST_TYPE_WIDGET)
#define ST_SCROLL_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_SCROLL_BAR, StScrollBarPrivate))
#define PAGING_INITIAL_REPEAT_TIMEOUT 500
#define PAGING_SUBSEQUENT_REPEAT_TIMEOUT 200
struct _StScrollBarPrivate
{
StAdjustment *adjustment;
gulong capture_handler;
gfloat x_origin;
gfloat y_origin;
ClutterActor *trough;
ClutterActor *handle;
gfloat move_x;
gfloat move_y;
/* Trough-click handling. */
enum { NONE, UP, DOWN } paging_direction;
guint paging_source_id;
guint paging_event_no;
ClutterAnimation *paging_animation;
guint vertical : 1;
};
enum
{
PROP_0,
PROP_ADJUSTMENT,
PROP_VERTICAL
};
enum
{
SCROLL_START,
SCROLL_STOP,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0, };
extern gfloat st_slow_down_factor;
static gboolean
handle_button_press_event_cb (ClutterActor *actor,
ClutterButtonEvent *event,
StScrollBar *bar);
static void stop_scrolling (StScrollBar *bar);
static void
st_scroll_bar_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
StScrollBarPrivate *priv = ST_SCROLL_BAR (gobject)->priv;
switch (prop_id)
{
case PROP_ADJUSTMENT:
g_value_set_object (value, priv->adjustment);
break;
case PROP_VERTICAL:
g_value_set_boolean (value, priv->vertical);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
st_scroll_bar_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
StScrollBar *bar = ST_SCROLL_BAR (gobject);
switch (prop_id)
{
case PROP_ADJUSTMENT:
st_scroll_bar_set_adjustment (bar, g_value_get_object (value));
break;
case PROP_VERTICAL:
bar->priv->vertical = g_value_get_boolean (value);
if (bar->priv->vertical)
clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->handle),
"vhandle");
else
clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->handle),
"hhandle");
clutter_actor_queue_relayout ((ClutterActor*) gobject);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
st_scroll_bar_dispose (GObject *gobject)
{
StScrollBar *bar = ST_SCROLL_BAR (gobject);
StScrollBarPrivate *priv = bar->priv;
if (priv->adjustment)
st_scroll_bar_set_adjustment (bar, NULL);
if (priv->handle)
{
clutter_actor_destroy (priv->handle);
priv->handle = NULL;
}
if (priv->trough)
{
clutter_actor_destroy (priv->trough);
priv->trough = NULL;
}
G_OBJECT_CLASS (st_scroll_bar_parent_class)->dispose (gobject);
}
static void
st_scroll_bar_unmap (ClutterActor *actor)
{
CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->unmap (actor);
stop_scrolling (ST_SCROLL_BAR (actor));
}
static void
scroll_bar_allocate_children (StScrollBar *bar,
const ClutterActorBox *box,
ClutterAllocationFlags flags)
{
StScrollBarPrivate *priv = bar->priv;
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (bar));
ClutterActorBox content_box, trough_box;
st_theme_node_get_content_box (theme_node, box, &content_box);
if (priv->vertical)
{
trough_box.x1 = content_box.x1;
trough_box.y1 = content_box.y1;
trough_box.x2 = content_box.x2;
trough_box.y2 = content_box.y2;
clutter_actor_allocate (priv->trough, &trough_box, flags);
}
else
{
trough_box.x1 = content_box.x1;
trough_box.y1 = content_box.y1;
trough_box.x2 = content_box.x2;
trough_box.y2 = content_box.y2;
clutter_actor_allocate (priv->trough, &trough_box, flags);
}
if (priv->adjustment)
{
float handle_size, position, avail_size;
gdouble value, lower, upper, page_size, increment, min_size, max_size;
ClutterActorBox handle_box = { 0, };
st_adjustment_get_values (priv->adjustment,
&value,
&lower,
&upper,
NULL,
NULL,
&page_size);
if ((upper == lower)
|| (page_size >= (upper - lower)))
increment = 1.0;
else
increment = page_size / (upper - lower);
min_size = 32.;
st_theme_node_lookup_length (theme_node, "min-size", FALSE, &min_size);
max_size = G_MAXINT16;
st_theme_node_lookup_length (theme_node, "max-size", FALSE, &max_size);
if (upper - lower - page_size <= 0)
position = 0;
else
position = (value - lower) / (upper - lower - page_size);
if (priv->vertical)
{
avail_size = content_box.y2 - content_box.y1;
handle_size = increment * avail_size;
handle_size = CLAMP (handle_size, min_size, max_size);
handle_box.x1 = content_box.x1;
handle_box.y1 = content_box.y1 + position * (avail_size - handle_size);
handle_box.x2 = content_box.x2;
handle_box.y2 = handle_box.y1 + handle_size;
}
else
{
avail_size = content_box.x2 - content_box.x1;
handle_size = increment * avail_size;
handle_size = CLAMP (handle_size, min_size, max_size);
handle_box.x1 = content_box.x1 + position * (avail_size - handle_size);
handle_box.y1 = content_box.y1;
handle_box.x2 = handle_box.x1 + handle_size;
handle_box.y2 = content_box.y2;
}
/* snap to pixel */
handle_box.x1 = (int) handle_box.x1;
handle_box.y1 = (int) handle_box.y1;
handle_box.x2 = (int) handle_box.x2;
handle_box.y2 = (int) handle_box.y2;
clutter_actor_allocate (priv->handle,
&handle_box,
flags);
}
}
static void
st_scroll_bar_get_preferred_width (ClutterActor *self,
gfloat for_height,
gfloat *min_width_p,
gfloat *natural_width_p)
{
StScrollBar *bar = ST_SCROLL_BAR (self);
StScrollBarPrivate *priv = bar->priv;
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
gfloat trough_min_width, trough_natural_width;
gfloat handle_min_width, handle_natural_width;
st_theme_node_adjust_for_height (theme_node, &for_height);
_st_actor_get_preferred_width (priv->trough, for_height, TRUE,
&trough_min_width, &trough_natural_width);
_st_actor_get_preferred_width (priv->handle, for_height, TRUE,
&handle_min_width, &handle_natural_width);
if (priv->vertical)
{
if (min_width_p)
*min_width_p = MAX (trough_min_width, handle_min_width);
if (natural_width_p)
*natural_width_p = MAX (trough_natural_width, handle_natural_width);
}
else
{
if (min_width_p)
*min_width_p = trough_min_width + handle_min_width;
if (natural_width_p)
*natural_width_p = trough_natural_width + handle_natural_width;
}
st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
}
static void
st_scroll_bar_get_preferred_height (ClutterActor *self,
gfloat for_width,
gfloat *min_height_p,
gfloat *natural_height_p)
{
StScrollBar *bar = ST_SCROLL_BAR (self);
StScrollBarPrivate *priv = bar->priv;
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
gfloat trough_min_height, trough_natural_height;
gfloat handle_min_height, handle_natural_height;
st_theme_node_adjust_for_width (theme_node, &for_width);
_st_actor_get_preferred_height (priv->trough, for_width, TRUE,
&trough_min_height, &trough_natural_height);
_st_actor_get_preferred_height (priv->handle, for_width, TRUE,
&handle_min_height, &handle_natural_height);
if (priv->vertical)
{
if (min_height_p)
*min_height_p = trough_min_height + handle_min_height;
if (natural_height_p)
*natural_height_p = trough_natural_height + handle_natural_height;
}
else
{
if (min_height_p)
*min_height_p = MAX (trough_min_height, handle_min_height);
if (natural_height_p)
*natural_height_p = MAX (trough_natural_height, handle_natural_height);
}
st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
}
static void
st_scroll_bar_allocate (ClutterActor *actor,
const ClutterActorBox *box,
ClutterAllocationFlags flags)
{
StScrollBar *bar = ST_SCROLL_BAR (actor);
clutter_actor_set_allocation (actor, box, flags);
scroll_bar_allocate_children (bar, box, flags);
}
static void
scroll_bar_update_positions (StScrollBar *bar)
{
ClutterActorBox box;
/* Due to a change in the adjustments, we need to reposition our
* children; since adjustments changes can come from allocation
* changes in the scrolled area, we can't just queue a new relayout -
* we may already be in a relayout cycle. On the other hand, if
* a relayout is already queued, we can't just go ahead and allocate
* our children, since we don't have a valid allocation, and calling
* clutter_actor_get_allocation_box() will trigger an immediate
* stage relayout. So what we do is go ahead and immediately
* allocate our children if we already have a valid allocation, and
* otherwise just wait for the queued relayout.
*/
if (!clutter_actor_has_allocation (CLUTTER_ACTOR (bar)))
return;
clutter_actor_get_allocation_box (CLUTTER_ACTOR (bar), &box);
scroll_bar_allocate_children (bar, &box, CLUTTER_ALLOCATION_NONE);
}
static void
st_scroll_bar_style_changed (StWidget *widget)
{
StScrollBarPrivate *priv = ST_SCROLL_BAR (widget)->priv;
st_widget_style_changed (ST_WIDGET (priv->trough));
st_widget_style_changed (ST_WIDGET (priv->handle));
ST_WIDGET_CLASS (st_scroll_bar_parent_class)->style_changed (widget);
}
static void
bar_reactive_notify_cb (GObject *gobject,
GParamSpec *arg1,
gpointer user_data)
{
StScrollBar *bar = ST_SCROLL_BAR (gobject);
clutter_actor_set_reactive (bar->priv->handle,
clutter_actor_get_reactive (CLUTTER_ACTOR (bar)));
}
static GObject*
st_scroll_bar_constructor (GType type,
guint n_properties,
GObjectConstructParam *properties)
{
GObjectClass *gobject_class;
GObject *obj;
StScrollBar *bar;
gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class);
obj = gobject_class->constructor (type, n_properties, properties);
bar = ST_SCROLL_BAR (obj);
g_signal_connect (bar, "notify::reactive",
G_CALLBACK (bar_reactive_notify_cb), NULL);
return obj;
}
static gboolean
st_scroll_bar_scroll_event (ClutterActor *actor,
ClutterScrollEvent *event)
{
StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv;
gdouble lower, step, upper, value;
if (priv->adjustment)
{
g_object_get (priv->adjustment,
"lower", &lower,
"step-increment", &step,
"upper", &upper,
"value", &value,
NULL);
}
else
{
return FALSE;
}
switch (event->direction)
{
case CLUTTER_SCROLL_UP:
case CLUTTER_SCROLL_LEFT:
if (value == lower)
return FALSE;
else
st_adjustment_set_value (priv->adjustment, value - step);
break;
case CLUTTER_SCROLL_DOWN:
case CLUTTER_SCROLL_RIGHT:
if (value == upper)
return FALSE;
else
st_adjustment_set_value (priv->adjustment, value + step);
break;
}
return TRUE;
}
static void
st_scroll_bar_class_init (StScrollBarClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
GParamSpec *pspec;
g_type_class_add_private (klass, sizeof (StScrollBarPrivate));
object_class->get_property = st_scroll_bar_get_property;
object_class->set_property = st_scroll_bar_set_property;
object_class->dispose = st_scroll_bar_dispose;
object_class->constructor = st_scroll_bar_constructor;
actor_class->get_preferred_width = st_scroll_bar_get_preferred_width;
actor_class->get_preferred_height = st_scroll_bar_get_preferred_height;
actor_class->allocate = st_scroll_bar_allocate;
actor_class->scroll_event = st_scroll_bar_scroll_event;
actor_class->unmap = st_scroll_bar_unmap;
widget_class->style_changed = st_scroll_bar_style_changed;
g_object_class_install_property
(object_class,
PROP_ADJUSTMENT,
g_param_spec_object ("adjustment",
"Adjustment",
"The adjustment",
ST_TYPE_ADJUSTMENT,
ST_PARAM_READWRITE));
pspec = g_param_spec_boolean ("vertical",
"Vertical Orientation",
"Vertical Orientation",
FALSE,
ST_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_VERTICAL, pspec);
signals[SCROLL_START] =
g_signal_new ("scroll-start",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StScrollBarClass, scroll_start),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[SCROLL_STOP] =
g_signal_new ("scroll-stop",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StScrollBarClass, scroll_stop),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
move_slider (StScrollBar *bar,
gfloat x,
gfloat y)
{
StScrollBarPrivate *priv = bar->priv;
gdouble position, lower, upper, page_size;
gfloat ux, uy, pos, size;
if (!priv->adjustment)
return;
if (!clutter_actor_transform_stage_point (priv->trough, x, y, &ux, &uy))
return;
if (priv->vertical)
size = clutter_actor_get_height (priv->trough)
- clutter_actor_get_height (priv->handle);
else
size = clutter_actor_get_width (priv->trough)
- clutter_actor_get_width (priv->handle);
if (size == 0)
return;
if (priv->vertical)
pos = uy - priv->y_origin;
else
pos = ux - priv->x_origin;
pos = CLAMP (pos, 0, size);
st_adjustment_get_values (priv->adjustment,
NULL,
&lower,
&upper,
NULL,
NULL,
&page_size);
position = ((pos / size)
* (upper - lower - page_size))
+ lower;
st_adjustment_set_value (priv->adjustment, position);
}
static void
stop_scrolling (StScrollBar *bar)
{
ClutterStage *stage;
if (!bar->priv->capture_handler)
return;
stage = CLUTTER_STAGE (clutter_actor_get_stage (bar->priv->trough));
g_signal_handler_disconnect (stage, bar->priv->capture_handler);
bar->priv->capture_handler = 0;
clutter_stage_set_motion_events_enabled (stage, TRUE);
g_signal_emit (bar, signals[SCROLL_STOP], 0);
}
static gboolean
handle_capture_event_cb (ClutterActor *trough,
ClutterEvent *event,
StScrollBar *bar)
{
if (clutter_event_type (event) == CLUTTER_MOTION)
{
move_slider (bar,
((ClutterMotionEvent*) event)->x,
((ClutterMotionEvent*) event)->y);
}
else if (clutter_event_type (event) == CLUTTER_BUTTON_RELEASE
&& ((ClutterButtonEvent*) event)->button == 1)
{
ClutterActor *stage, *target;
stop_scrolling (bar);
/* check if the mouse pointer has left the handle during the drag and
* remove the hover state if it has */
stage = clutter_actor_get_stage (bar->priv->trough);
target = clutter_stage_get_actor_at_pos ((ClutterStage*) stage,
CLUTTER_PICK_REACTIVE,
((ClutterButtonEvent*) event)->x,
((ClutterButtonEvent*) event)->y);
if (target != bar->priv->handle)
{
st_widget_remove_style_pseudo_class ((StWidget*) bar->priv->handle, "hover");
}
}
return TRUE;
}
static gboolean
handle_button_press_event_cb (ClutterActor *actor,
ClutterButtonEvent *event,
StScrollBar *bar)
{
ClutterStage *stage;
StScrollBarPrivate *priv = bar->priv;
if (event->button != 1)
return FALSE;
if (!clutter_actor_transform_stage_point (priv->handle,
event->x,
event->y,
&priv->x_origin,
&priv->y_origin))
return FALSE;
/* Account for the scrollbar-trough-handle nesting. */
priv->x_origin += clutter_actor_get_x (priv->trough);
priv->y_origin += clutter_actor_get_y (priv->trough);
stage = CLUTTER_STAGE (clutter_actor_get_stage (bar->priv->trough));
/* Turn off picking for motion events */
clutter_stage_set_motion_events_enabled (stage, FALSE);
priv->capture_handler = g_signal_connect_after (
clutter_actor_get_stage (priv->trough),
"captured-event",
G_CALLBACK (handle_capture_event_cb),
bar);
g_signal_emit (bar, signals[SCROLL_START], 0);
return TRUE;
}
static void
animation_completed_cb (ClutterAnimation *animation,
StScrollBarPrivate *priv)
{
g_object_unref (priv->paging_animation);
priv->paging_animation = NULL;
}
static gboolean
trough_paging_cb (StScrollBar *self)
{
gfloat handle_pos, event_pos, tx, ty;
gdouble value;
gdouble page_increment;
gboolean ret;
gulong mode;
ClutterAnimation *a;
GValue v = { 0, };
ClutterTimeline *t;
if (self->priv->paging_event_no == 0)
{
/* Scroll on after initial timeout. */
mode = CLUTTER_EASE_OUT_CUBIC;
ret = FALSE;
self->priv->paging_event_no = 1;
self->priv->paging_source_id = g_timeout_add (
PAGING_INITIAL_REPEAT_TIMEOUT,
(GSourceFunc) trough_paging_cb,
self);
}
else if (self->priv->paging_event_no == 1)
{
/* Scroll on after subsequent timeout. */
ret = FALSE;
mode = CLUTTER_EASE_IN_CUBIC;
self->priv->paging_event_no = 2;
self->priv->paging_source_id = g_timeout_add (
PAGING_SUBSEQUENT_REPEAT_TIMEOUT,
(GSourceFunc) trough_paging_cb,
self);
}
else
{
/* Keep scrolling. */
ret = TRUE;
mode = CLUTTER_LINEAR;
self->priv->paging_event_no++;
}
/* Do the scrolling */
st_adjustment_get_values (self->priv->adjustment,
&value, NULL, NULL,
NULL, &page_increment, NULL);
if (self->priv->vertical)
handle_pos = clutter_actor_get_y (self->priv->handle);
else
handle_pos = clutter_actor_get_x (self->priv->handle);
clutter_actor_transform_stage_point (CLUTTER_ACTOR (self->priv->trough),
self->priv->move_x,
self->priv->move_y,
&tx, &ty);
if (self->priv->vertical)
event_pos = ty;
else
event_pos = tx;
if (event_pos > handle_pos)
{
if (self->priv->paging_direction == NONE)
{
/* Remember direction. */
self->priv->paging_direction = DOWN;
}
if (self->priv->paging_direction == UP)
{
/* Scrolled far enough. */
return FALSE;
}
value += page_increment;
}
else
{
if (self->priv->paging_direction == NONE)
{
/* Remember direction. */
self->priv->paging_direction = UP;
}
if (self->priv->paging_direction == DOWN)
{
/* Scrolled far enough. */
return FALSE;
}
value -= page_increment;
}
if (self->priv->paging_animation)
{
clutter_animation_completed (self->priv->paging_animation);
}
/* FIXME: Creating a new animation for each scroll is probably not the best
* idea, but it's a lot less involved than extenind the current animation */
a = self->priv->paging_animation = g_object_new (CLUTTER_TYPE_ANIMATION,
"object", self->priv->adjustment,
"duration", (guint)(PAGING_SUBSEQUENT_REPEAT_TIMEOUT * st_slow_down_factor),
"mode", mode,
NULL);
g_value_init (&v, G_TYPE_DOUBLE);
g_value_set_double (&v, value);
clutter_animation_bind (self->priv->paging_animation, "value", &v);
t = clutter_animation_get_timeline (self->priv->paging_animation);
g_signal_connect (a, "completed", G_CALLBACK (animation_completed_cb),
self->priv);
clutter_timeline_start (t);
return ret;
}
static gboolean
trough_button_press_event_cb (ClutterActor *actor,
ClutterButtonEvent *event,
StScrollBar *self)
{
g_return_val_if_fail (self, FALSE);
if (event->button != 1)
return FALSE;
if (self->priv->adjustment == NULL)
return FALSE;
self->priv->move_x = event->x;
self->priv->move_y = event->y;
self->priv->paging_direction = NONE;
self->priv->paging_event_no = 0;
trough_paging_cb (self);
return TRUE;
}
static gboolean
trough_button_release_event_cb (ClutterActor *actor,
ClutterButtonEvent *event,
StScrollBar *self)
{
if (event->button != 1)
return FALSE;
if (self->priv->paging_source_id)
{
g_source_remove (self->priv->paging_source_id);
self->priv->paging_source_id = 0;
}
return TRUE;
}
static gboolean
trough_leave_event_cb (ClutterActor *actor,
ClutterEvent *event,
StScrollBar *self)
{
if (self->priv->paging_source_id)
{
g_source_remove (self->priv->paging_source_id);
self->priv->paging_source_id = 0;
return TRUE;
}
return FALSE;
}
static void
st_scroll_bar_notify_reactive (StScrollBar *self)
{
StScrollBarPrivate *priv = self->priv;
gboolean reactive = CLUTTER_ACTOR_IS_REACTIVE (self);
clutter_actor_set_reactive (CLUTTER_ACTOR (priv->trough), reactive);
clutter_actor_set_reactive (CLUTTER_ACTOR (priv->handle), reactive);
}
static void
st_scroll_bar_init (StScrollBar *self)
{
self->priv = ST_SCROLL_BAR_GET_PRIVATE (self);
self->priv->trough = (ClutterActor *) st_bin_new ();
clutter_actor_set_reactive ((ClutterActor *) self->priv->trough, TRUE);
clutter_actor_set_name (CLUTTER_ACTOR (self->priv->trough), "trough");
clutter_actor_add_child (CLUTTER_ACTOR (self),
CLUTTER_ACTOR (self->priv->trough));
g_signal_connect (self->priv->trough, "button-press-event",
G_CALLBACK (trough_button_press_event_cb), self);
g_signal_connect (self->priv->trough, "button-release-event",
G_CALLBACK (trough_button_release_event_cb), self);
g_signal_connect (self->priv->trough, "leave-event",
G_CALLBACK (trough_leave_event_cb), self);
self->priv->handle = (ClutterActor *) st_button_new ();
clutter_actor_set_name (CLUTTER_ACTOR (self->priv->handle), "hhandle");
clutter_actor_add_child (CLUTTER_ACTOR (self),
CLUTTER_ACTOR (self->priv->handle));
g_signal_connect (self->priv->handle, "button-press-event",
G_CALLBACK (handle_button_press_event_cb), self);
clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
g_signal_connect (self, "notify::reactive",
G_CALLBACK (st_scroll_bar_notify_reactive), NULL);
}
StWidget *
st_scroll_bar_new (StAdjustment *adjustment)
{
return g_object_new (ST_TYPE_SCROLL_BAR,
"adjustment", adjustment,
NULL);
}
static void
on_notify_value (GObject *object,
GParamSpec *pspec,
StScrollBar *bar)
{
scroll_bar_update_positions (bar);
}
static void
on_changed (StAdjustment *adjustment,
StScrollBar *bar)
{
scroll_bar_update_positions (bar);
}
void
st_scroll_bar_set_adjustment (StScrollBar *bar,
StAdjustment *adjustment)
{
StScrollBarPrivate *priv;
g_return_if_fail (ST_IS_SCROLL_BAR (bar));
priv = bar->priv;
if (adjustment == priv->adjustment)
return;
if (priv->adjustment)
{
g_signal_handlers_disconnect_by_func (priv->adjustment,
on_notify_value,
bar);
g_signal_handlers_disconnect_by_func (priv->adjustment,
on_changed,
bar);
g_object_unref (priv->adjustment);
priv->adjustment = NULL;
}
if (adjustment)
{
priv->adjustment = g_object_ref (adjustment);
g_signal_connect (priv->adjustment, "notify::value",
G_CALLBACK (on_notify_value),
bar);
g_signal_connect (priv->adjustment, "changed",
G_CALLBACK (on_changed),
bar);
clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
}
g_object_notify (G_OBJECT (bar), "adjustment");
}
/**
* st_scroll_bar_get_adjustment:
* @bar: a #StScrollbar
*
* Gets the adjustment object that stores the current position
* of the scrollbar.
*
* Return value: (transfer none): the adjustment
*/
StAdjustment *
st_scroll_bar_get_adjustment (StScrollBar *bar)
{
g_return_val_if_fail (ST_IS_SCROLL_BAR (bar), NULL);
return bar->priv->adjustment;
}