diff --git a/src/Makefile-st.am b/src/Makefile-st.am index 24bc10c46..c069c7199 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -65,10 +65,15 @@ st-enum-types.c: stamp-st-enum-types.h st/st-enum-types.c.in # please, keep this sorted alphabetically st_source_h = \ + st/st-adjustment.h \ st/st-bin.h \ + st/st-button.h \ st/st-private.h \ st/st-stylable.h \ st/st-style.h \ + st/st-scrollable.h \ + st/st-scroll-bar.h \ + st/st-scroll-view.h \ st/st-subtexture.h \ st/st-texture-cache.h \ st/st-texture-frame.h \ @@ -79,8 +84,13 @@ st_source_h = \ # please, keep this sorted alphabetically st_source_c = \ + st/st-adjustment.c \ st/st-bin.c \ + st/st-button.c \ st/st-private.c \ + st/st-scrollable.c \ + st/st-scroll-bar.c \ + st/st-scroll-view.c \ st/st-stylable.c \ st/st-style.c \ st/st-subtexture.c \ diff --git a/src/st/st-adjustment.c b/src/st/st-adjustment.c new file mode 100644 index 000000000..a2c2af14a --- /dev/null +++ b/src/st/st-adjustment.c @@ -0,0 +1,776 @@ +/* + * st-adjustment.c: Adjustment object + * + * Copyright (C) 2008 OpenedHand + * Copyright (c) 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Chris Lord , inspired by GtkAdjustment + * Port to St by: Robert Staudinger + * + */ + +/** + * SECTION:st-adjustment + * @short_description: A GObject representing an adjustable bounded value + * + * The #StAdjustment object represents a range of values bounded between a + * minimum and maximum, together with step and page increments and a page size. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "st-adjustment.h" +#include "st-marshal.h" +#include "st-private.h" + +G_DEFINE_TYPE (StAdjustment, st_adjustment, G_TYPE_OBJECT) + +#define ADJUSTMENT_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_ADJUSTMENT, StAdjustmentPrivate)) + +struct _StAdjustmentPrivate +{ + /* Do not sanity-check values while constructing, + * not all properties may be set yet. */ + gboolean is_constructing : 1; + + gdouble lower; + gdouble upper; + gdouble value; + gdouble step_increment; + gdouble page_increment; + gdouble page_size; + + /* For interpolation */ + ClutterTimeline *interpolation; + gdouble old_position; + gdouble new_position; + + /* For elasticity */ + gboolean elastic; + guint bounce_source; + ClutterAlpha *bounce_alpha; +}; + +enum +{ + PROP_0, + + PROP_LOWER, + PROP_UPPER, + PROP_VALUE, + PROP_STEP_INC, + PROP_PAGE_INC, + PROP_PAGE_SIZE, + + PROP_ELASTIC, +}; + +enum +{ + CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +static gboolean st_adjustment_set_lower (StAdjustment *adjustment, + gdouble lower); +static gboolean st_adjustment_set_upper (StAdjustment *adjustment, + gdouble upper); +static gboolean st_adjustment_set_step_increment (StAdjustment *adjustment, + gdouble step); +static gboolean st_adjustment_set_page_increment (StAdjustment *adjustment, + gdouble page); +static gboolean st_adjustment_set_page_size (StAdjustment *adjustment, + gdouble size); + +static void +st_adjustment_constructed (GObject *object) +{ + GObjectClass *g_class; + StAdjustment *self = ST_ADJUSTMENT (object); + + g_class = G_OBJECT_CLASS (st_adjustment_parent_class); + /* The docs say we're suppose to chain up, but would crash without + * some extra care. */ + if (g_class && g_class->constructed && + g_class->constructed != st_adjustment_constructed) + { + g_class->constructed (object); + } + + ST_ADJUSTMENT (self)->priv->is_constructing = FALSE; + st_adjustment_clamp_page (self, self->priv->lower, self->priv->upper); +} + +static void +st_adjustment_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StAdjustmentPrivate *priv = ST_ADJUSTMENT (gobject)->priv; + + switch (prop_id) + { + case PROP_LOWER: + g_value_set_double (value, priv->lower); + break; + + case PROP_UPPER: + g_value_set_double (value, priv->upper); + break; + + case PROP_VALUE: + g_value_set_double (value, priv->value); + break; + + case PROP_STEP_INC: + g_value_set_double (value, priv->step_increment); + break; + + case PROP_PAGE_INC: + g_value_set_double (value, priv->page_increment); + break; + + case PROP_PAGE_SIZE: + g_value_set_double (value, priv->page_size); + break; + + case PROP_ELASTIC: + g_value_set_boolean (value, priv->elastic); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_adjustment_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StAdjustment *adj = ST_ADJUSTMENT (gobject); + + switch (prop_id) + { + case PROP_LOWER: + st_adjustment_set_lower (adj, g_value_get_double (value)); + break; + + case PROP_UPPER: + st_adjustment_set_upper (adj, g_value_get_double (value)); + break; + + case PROP_VALUE: + st_adjustment_set_value (adj, g_value_get_double (value)); + break; + + case PROP_STEP_INC: + st_adjustment_set_step_increment (adj, g_value_get_double (value)); + break; + + case PROP_PAGE_INC: + st_adjustment_set_page_increment (adj, g_value_get_double (value)); + break; + + case PROP_PAGE_SIZE: + st_adjustment_set_page_size (adj, g_value_get_double (value)); + break; + + case PROP_ELASTIC: + st_adjustment_set_elastic (adj, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +stop_interpolation (StAdjustment *adjustment) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + if (priv->interpolation) + { + clutter_timeline_stop (priv->interpolation); + g_object_unref (priv->interpolation); + priv->interpolation = NULL; + + if (priv->bounce_alpha) + { + g_object_unref (priv->bounce_alpha); + priv->bounce_alpha = NULL; + } + } + + if (priv->bounce_source) + { + g_source_remove (priv->bounce_source); + priv->bounce_source = 0; + } +} + +static void +st_adjustment_dispose (GObject *object) +{ + stop_interpolation (ST_ADJUSTMENT (object)); + + G_OBJECT_CLASS (st_adjustment_parent_class)->dispose (object); +} + +static void +st_adjustment_class_init (StAdjustmentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (StAdjustmentPrivate)); + + object_class->constructed = st_adjustment_constructed; + object_class->get_property = st_adjustment_get_property; + object_class->set_property = st_adjustment_set_property; + object_class->dispose = st_adjustment_dispose; + + g_object_class_install_property (object_class, + PROP_LOWER, + g_param_spec_double ("lower", + "Lower", + "Lower bound", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_UPPER, + g_param_spec_double ("upper", + "Upper", + "Upper bound", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_VALUE, + g_param_spec_double ("value", + "Value", + "Current value", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_STEP_INC, + g_param_spec_double ("step-increment", + "Step Increment", + "Step increment", + 0.0, + G_MAXDOUBLE, + 0.0, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_PAGE_INC, + g_param_spec_double ("page-increment", + "Page Increment", + "Page increment", + 0.0, + G_MAXDOUBLE, + 0.0, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_PAGE_SIZE, + g_param_spec_double ("page-size", + "Page Size", + "Page size", + 0.0, + G_MAXDOUBLE, + 0.0, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_ELASTIC, + g_param_spec_boolean ("elastic", + "Elastic", + "Make interpolation " + "behave in an " + "'elastic' way and " + "stop clamping value.", + FALSE, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /** + * StAdjustment::changed: + * + * Emitted when any of the adjustment values have changed + */ + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StAdjustmentClass, changed), + NULL, NULL, + _st_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +st_adjustment_init (StAdjustment *self) +{ + self->priv = ADJUSTMENT_PRIVATE (self); + + self->priv->is_constructing = TRUE; +} + +StAdjustment * +st_adjustment_new (gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size) +{ + return g_object_new (ST_TYPE_ADJUSTMENT, + "value", value, + "lower", lower, + "upper", upper, + "step-increment", step_increment, + "page-increment", page_increment, + "page-size", page_size, + NULL); +} + +gdouble +st_adjustment_get_value (StAdjustment *adjustment) +{ + StAdjustmentPrivate *priv; + + g_return_val_if_fail (ST_IS_ADJUSTMENT (adjustment), 0); + + priv = adjustment->priv; + + if (priv->interpolation) + { + return MAX (priv->lower, + MIN (priv->upper - priv->page_size, + priv->new_position)); + } + else + return priv->value; +} + +void +st_adjustment_set_value (StAdjustment *adjustment, + gdouble value) +{ + StAdjustmentPrivate *priv; + + g_return_if_fail (ST_IS_ADJUSTMENT (adjustment)); + + priv = adjustment->priv; + + stop_interpolation (adjustment); + + /* Defer clamp until after construction. */ + if (!priv->is_constructing) + { + if (!priv->elastic) + value = CLAMP (value, + priv->lower, + MAX (priv->lower, priv->upper - priv->page_size)); + } + + if (priv->value != value) + { + priv->value = value; + + g_object_notify (G_OBJECT (adjustment), "value"); + } +} + +void +st_adjustment_clamp_page (StAdjustment *adjustment, + gdouble lower, + gdouble upper) +{ + StAdjustmentPrivate *priv; + gboolean changed; + + g_return_if_fail (ST_IS_ADJUSTMENT (adjustment)); + + priv = adjustment->priv; + + stop_interpolation (adjustment); + + lower = CLAMP (lower, priv->lower, priv->upper - priv->page_size); + upper = CLAMP (upper, priv->lower + priv->page_size, priv->upper); + + changed = FALSE; + + if (priv->value + priv->page_size > upper) + { + priv->value = upper - priv->page_size; + changed = TRUE; + } + + if (priv->value < lower) + { + priv->value = lower; + changed = TRUE; + } + + if (changed) + g_object_notify (G_OBJECT (adjustment), "value"); +} + +static gboolean +st_adjustment_set_lower (StAdjustment *adjustment, + gdouble lower) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + if (priv->lower != lower) + { + priv->lower = lower; + + g_signal_emit (adjustment, signals[CHANGED], 0); + + g_object_notify (G_OBJECT (adjustment), "lower"); + + /* Defer clamp until after construction. */ + if (!priv->is_constructing) + st_adjustment_clamp_page (adjustment, priv->lower, priv->upper); + + return TRUE; + } + + return FALSE; +} + +static gboolean +st_adjustment_set_upper (StAdjustment *adjustment, + gdouble upper) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + if (priv->upper != upper) + { + priv->upper = upper; + + g_signal_emit (adjustment, signals[CHANGED], 0); + + g_object_notify (G_OBJECT (adjustment), "upper"); + + /* Defer clamp until after construction. */ + if (!priv->is_constructing) + st_adjustment_clamp_page (adjustment, priv->lower, priv->upper); + + return TRUE; + } + + return FALSE; +} + +static gboolean +st_adjustment_set_step_increment (StAdjustment *adjustment, + gdouble step) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + if (priv->step_increment != step) + { + priv->step_increment = step; + + g_signal_emit (adjustment, signals[CHANGED], 0); + + g_object_notify (G_OBJECT (adjustment), "step-increment"); + + return TRUE; + } + + return FALSE; +} + +static gboolean +st_adjustment_set_page_increment (StAdjustment *adjustment, + gdouble page) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + if (priv->page_increment != page) + { + priv->page_increment = page; + + g_signal_emit (adjustment, signals[CHANGED], 0); + + g_object_notify (G_OBJECT (adjustment), "page-increment"); + + return TRUE; + } + + return FALSE; +} + +static gboolean +st_adjustment_set_page_size (StAdjustment *adjustment, + gdouble size) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + if (priv->page_size != size) + { + priv->page_size = size; + + g_signal_emit (adjustment, signals[CHANGED], 0); + + g_object_notify (G_OBJECT (adjustment), "page_size"); + + /* Well explicitely clamp after construction. */ + if (!priv->is_constructing) + st_adjustment_clamp_page (adjustment, priv->lower, priv->upper); + + return TRUE; + } + + return FALSE; +} + +void +st_adjustment_set_values (StAdjustment *adjustment, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size) +{ + StAdjustmentPrivate *priv; + gboolean emit_changed = FALSE; + + g_return_if_fail (ST_IS_ADJUSTMENT (adjustment)); + g_return_if_fail (page_size >= 0 && page_size <= G_MAXDOUBLE); + g_return_if_fail (step_increment >= 0 && step_increment <= G_MAXDOUBLE); + g_return_if_fail (page_increment >= 0 && page_increment <= G_MAXDOUBLE); + + priv = adjustment->priv; + + stop_interpolation (adjustment); + + emit_changed = FALSE; + + g_object_freeze_notify (G_OBJECT (adjustment)); + + emit_changed |= st_adjustment_set_lower (adjustment, lower); + emit_changed |= st_adjustment_set_upper (adjustment, upper); + emit_changed |= st_adjustment_set_step_increment (adjustment, step_increment); + emit_changed |= st_adjustment_set_page_increment (adjustment, page_increment); + emit_changed |= st_adjustment_set_page_size (adjustment, page_size); + + if (value != priv->value) + { + st_adjustment_set_value (adjustment, value); + emit_changed = TRUE; + } + + if (emit_changed) + g_signal_emit (G_OBJECT (adjustment), signals[CHANGED], 0); + + g_object_thaw_notify (G_OBJECT (adjustment)); +} + +void +st_adjustment_get_values (StAdjustment *adjustment, + gdouble *value, + gdouble *lower, + gdouble *upper, + gdouble *step_increment, + gdouble *page_increment, + gdouble *page_size) +{ + StAdjustmentPrivate *priv; + + g_return_if_fail (ST_IS_ADJUSTMENT (adjustment)); + + priv = adjustment->priv; + + if (lower) + *lower = priv->lower; + + if (upper) + *upper = priv->upper; + + if (value) + *value = st_adjustment_get_value (adjustment); + + if (step_increment) + *step_increment = priv->step_increment; + + if (page_increment) + *page_increment = priv->page_increment; + + if (page_size) + *page_size = priv->page_size; +} + +static void +interpolation_new_frame_cb (ClutterTimeline *timeline, + guint msecs, + StAdjustment *adjustment) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + priv->interpolation = NULL; + + if (priv->elastic) + { + gdouble progress = clutter_alpha_get_alpha (priv->bounce_alpha) / 1.0; + gdouble dx = priv->old_position + + (priv->new_position - priv->old_position) + * progress; + + st_adjustment_set_value (adjustment, dx); + } + else + st_adjustment_set_value (adjustment, + priv->old_position + + (priv->new_position - priv->old_position) * + clutter_timeline_get_progress (timeline)); + + priv->interpolation = timeline; +} + +static void +interpolation_completed_cb (ClutterTimeline *timeline, + StAdjustment *adjustment) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + stop_interpolation (adjustment); + st_adjustment_set_value (adjustment, priv->new_position); +} + +/* Note, there's super-optimal code that does a similar thing in + * clutter-alpha.c + * + * Tried this instead of CLUTTER_ALPHA_SINE_INC, but I think SINE_INC looks + * better. Leaving code here in case this is revisited. + */ +/* + static guint32 + bounce_alpha_func (ClutterAlpha *alpha, + gpointer user_data) + { + ClutterFixed progress, angle; + ClutterTimeline *timeline = clutter_alpha_get_timeline (alpha); + + progress = clutter_timeline_get_progressx (timeline); + angle = clutter_qmulx (CFX_PI_2 + CFX_PI_4/2, progress); + + return clutter_sinx (angle) + + (CFX_ONE - clutter_sinx (CFX_PI_2 + CFX_PI_4/2)); + } + */ + +void +st_adjustment_interpolate (StAdjustment *adjustment, + gdouble value, + guint duration) +{ + StAdjustmentPrivate *priv = adjustment->priv; + + stop_interpolation (adjustment); + + if (duration <= 1) + { + st_adjustment_set_value (adjustment, value); + return; + } + + priv->old_position = priv->value; + priv->new_position = value; + + priv->interpolation = clutter_timeline_new (duration); + + if (priv->elastic) + priv->bounce_alpha = clutter_alpha_new_full (priv->interpolation, + CLUTTER_LINEAR); + + g_signal_connect (priv->interpolation, + "new-frame", + G_CALLBACK (interpolation_new_frame_cb), + adjustment); + g_signal_connect (priv->interpolation, + "completed", + G_CALLBACK (interpolation_completed_cb), + adjustment); + + clutter_timeline_start (priv->interpolation); +} + +gboolean +st_adjustment_get_elastic (StAdjustment *adjustment) +{ + return adjustment->priv->elastic; +} + +void +st_adjustment_set_elastic (StAdjustment *adjustment, + gboolean elastic) +{ + adjustment->priv->elastic = elastic; +} + +gboolean +st_adjustment_clamp (StAdjustment *adjustment, + gboolean interpolate, + guint duration) +{ + StAdjustmentPrivate *priv = adjustment->priv; + gdouble dest = priv->value; + + if (priv->value < priv->lower) + dest = priv->lower; + + if (priv->value > priv->upper - priv->page_size) + dest = priv->upper - priv->page_size; + + if (dest != priv->value) + { + if (interpolate) + st_adjustment_interpolate (adjustment, dest, duration); + else + st_adjustment_set_value (adjustment, dest); + + return TRUE; + } + + return FALSE; +} diff --git a/src/st/st-adjustment.h b/src/st/st-adjustment.h new file mode 100644 index 000000000..179ea915a --- /dev/null +++ b/src/st/st-adjustment.h @@ -0,0 +1,121 @@ +/* + * st-adjustment.h: Adjustment object + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Chris Lord , inspired by GtkAdjustment + * Port to St by: Robert Staudinger + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_ADJUSTMENT_H__ +#define __ST_ADJUSTMENT_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_ADJUSTMENT (st_adjustment_get_type()) +#define ST_ADJUSTMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_ADJUSTMENT, StAdjustment)) +#define ST_IS_ADJUSTMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_ADJUSTMENT)) +#define ST_ADJUSTMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_ADJUSTMENT, StAdjustmentClass)) +#define ST_IS_ADJUSTMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_ADJUSTMENT)) +#define ST_ADJUSTMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_ADJUSTMENT, StAdjustmentClass)) + +typedef struct _StAdjustment StAdjustment; +typedef struct _StAdjustmentPrivate StAdjustmentPrivate; +typedef struct _StAdjustmentClass StAdjustmentClass; + +/** + * StAdjustment: + * + * Class for handling an interval between to values. The contents of + * the #StAdjustment are private and should be accessed using the + * public API. + */ +struct _StAdjustment +{ + /*< private >*/ + GObject parent_instance; + + StAdjustmentPrivate *priv; +}; + +/** + * StAdjustmentClass + * @changed: Class handler for the ::changed signal. + * + * Base class for #StAdjustment. + */ +struct _StAdjustmentClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + void (* changed) (StAdjustment *adjustment); +}; + +GType st_adjustment_get_type (void) G_GNUC_CONST; + +StAdjustment *st_adjustment_new (gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size); +gdouble st_adjustment_get_value (StAdjustment *adjustment); +void st_adjustment_set_value (StAdjustment *adjustment, + gdouble value); +void st_adjustment_clamp_page (StAdjustment *adjustment, + gdouble lower, + gdouble upper); +void st_adjustment_set_values (StAdjustment *adjustment, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size); +void st_adjustment_get_values (StAdjustment *adjustment, + gdouble *value, + gdouble *lower, + gdouble *upper, + gdouble *step_increment, + gdouble *page_increment, + gdouble *page_size); + +void st_adjustment_interpolate (StAdjustment *adjustment, + gdouble value, + guint duration); + +gboolean st_adjustment_get_elastic (StAdjustment *adjustment); +void st_adjustment_set_elastic (StAdjustment *adjustment, + gboolean elastic); + +gboolean st_adjustment_clamp (StAdjustment *adjustment, + gboolean interpolate, + guint duration); + +G_END_DECLS + +#endif /* __ST_ADJUSTMENT_H__ */ diff --git a/src/st/st-button.c b/src/st/st-button.c new file mode 100644 index 000000000..0f8c5131c --- /dev/null +++ b/src/st/st-button.c @@ -0,0 +1,759 @@ +/* + * st-button.c: Plain button actor + * + * Copyright 2007 OpenedHand + * Copyright 2008, 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Emmanuele Bassi + * Thomas Wood + * + */ + +/** + * SECTION:st-button + * @short_description: Button widget + * + * A button widget with support for either a text label or icon, toggle mode + * and transitions effects between states. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include + +#include "st-button.h" + +#include "st-marshal.h" +#include "st-stylable.h" +#include "st-style.h" +#include "st-texture-frame.h" +#include "st-texture-cache.h" +#include "st-private.h" + +enum +{ + PROP_0, + + PROP_LABEL, + PROP_TOGGLE, + PROP_ACTIVE, + PROP_TRANSITION +}; + +enum +{ + CLICKED, + + LAST_SIGNAL +}; + +#define ST_BUTTON_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_BUTTON, StButtonPrivate)) + +struct _StButtonPrivate +{ + gchar *text; + + ClutterActor *old_bg; + gboolean old_bg_parented; /* TRUE if we have adopted old_bg */ + + guint8 old_opacity; + + guint is_pressed : 1; + guint is_hover : 1; + guint is_checked : 1; + guint is_toggle : 1; + + gint transition_duration; + + ClutterAnimation *animation; + + gint spacing; +}; + +static guint button_signals[LAST_SIGNAL] = { 0, }; + +static void st_stylable_iface_init (StStylableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (StButton, st_button, ST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE, + st_stylable_iface_init)); + +static void +st_stylable_iface_init (StStylableIface *iface) +{ + static gboolean is_initialized = FALSE; + + if (G_UNLIKELY (!is_initialized)) + { + ClutterColor bg_color = { 0xcc, 0xcc, 0xcc, 0x00 }; + GParamSpec *pspec; + + is_initialized = TRUE; + + pspec = g_param_spec_int ("border-spacing", + "Border Spacing", + "Spacing between internal elements", + 0, G_MAXINT, 6, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_BUTTON, pspec); + + + is_initialized = TRUE; + + pspec = clutter_param_spec_color ("background-color", + "Background Color", + "The background color of an actor", + &bg_color, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_BUTTON, pspec); + } +} + +static void +st_button_update_label_style (StButton *button) +{ + ClutterColor *real_color = NULL; + gchar *font_string = NULL; + gchar *font_name = NULL; + gint font_size = 0; + ClutterActor *label; + + label = st_bin_get_child ((StBin*) button); + + /* check the child is really a label */ + if (!CLUTTER_IS_TEXT (label)) + return; + + st_stylable_get (ST_STYLABLE (button), + "color", &real_color, + "font-family", &font_name, + "font-size", &font_size, + NULL); + + if (font_name || font_size) + { + if (font_name && font_size) + font_string = g_strdup_printf ("%s %dpx", font_name, font_size); + else + { + if (font_size) + font_string = g_strdup_printf ("%dpx", font_size); + else + font_string = font_name; + } + + clutter_text_set_font_name (CLUTTER_TEXT (label), font_string); + + if (font_string != font_name) + g_free (font_string); + } + + g_free (font_name); + + if (real_color) + { + clutter_text_set_color (CLUTTER_TEXT (label), real_color); + clutter_color_free (real_color); + } +} + +static void +st_button_dispose_old_bg (StButton *button) +{ + StButtonPrivate *priv = button->priv; + + if (priv->old_bg) + { + if (priv->old_bg_parented) + { + clutter_actor_unparent (priv->old_bg); + priv->old_bg_parented = FALSE; + } + g_object_unref (priv->old_bg); + priv->old_bg = NULL; + } +} + +static void +st_button_stylable_changed (StStylable *stylable) +{ + StButton *button = ST_BUTTON (stylable); + ClutterActor *bg_image; + + st_button_dispose_old_bg (button); + + bg_image = st_widget_get_border_image ((StWidget*) button); + if (bg_image) + button->priv->old_bg = g_object_ref (bg_image); +} + +static void +st_animation_completed (ClutterAnimation *animation, + StButton *button) +{ + st_button_dispose_old_bg (button); +} + +static void +st_button_style_changed (StWidget *widget) +{ + StButton *button = ST_BUTTON (widget); + StButtonPrivate *priv = button->priv; + StButtonClass *button_class = ST_BUTTON_GET_CLASS (button); + + /* get the spacing value */ + st_stylable_get (ST_STYLABLE (widget), + "border-spacing", &priv->spacing, + NULL); + + /* update the label styling */ + st_button_update_label_style (button); + + /* run a transition if applicable */ + if (button_class->transition) + { + button_class->transition (button, priv->old_bg); + } + else + { + if (priv->old_bg && + (!st_widget_get_style_pseudo_class (widget))) + { + ClutterAnimation *animation; + if (!clutter_actor_get_parent (priv->old_bg)) + { + clutter_actor_set_parent (priv->old_bg, (ClutterActor*) widget); + priv->old_bg_parented = TRUE; + } + if (priv->transition_duration > 0) + { + animation = clutter_actor_animate (priv->old_bg, + CLUTTER_LINEAR, + priv->transition_duration, + "opacity", 0, + NULL); + g_signal_connect (animation, "completed", + G_CALLBACK (st_animation_completed), button); + } + else + { + st_button_dispose_old_bg (button); + } + + } + } +} + +static void +st_button_real_pressed (StButton *button) +{ + st_widget_set_style_pseudo_class ((StWidget*) button, "active"); +} + +static void +st_button_real_released (StButton *button) +{ + StButtonPrivate *priv = button->priv; + + if (priv->is_checked) + st_widget_set_style_pseudo_class ((StWidget*) button, "checked"); + else if (!priv->is_hover) + st_widget_set_style_pseudo_class ((StWidget*) button, NULL); + else + st_widget_set_style_pseudo_class ((StWidget*) button, "hover"); + +} + +static gboolean +st_button_button_press (ClutterActor *actor, + ClutterButtonEvent *event) +{ + st_widget_hide_tooltip (ST_WIDGET (actor)); + + if (event->button == 1) + { + StButton *button = ST_BUTTON (actor); + StButtonClass *klass = ST_BUTTON_GET_CLASS (button); + + button->priv->is_pressed = TRUE; + + clutter_grab_pointer (actor); + + if (klass->pressed) + klass->pressed (button); + + return TRUE; + } + + return FALSE; +} + +static gboolean +st_button_button_release (ClutterActor *actor, + ClutterButtonEvent *event) +{ + if (event->button == 1) + { + StButton *button = ST_BUTTON (actor); + StButtonClass *klass = ST_BUTTON_GET_CLASS (button); + + if (!button->priv->is_pressed) + return FALSE; + + clutter_ungrab_pointer (); + + if (button->priv->is_toggle) + { + st_button_set_checked (button, !button->priv->is_checked); + } + + button->priv->is_pressed = FALSE; + + if (klass->released) + klass->released (button); + + g_signal_emit (button, button_signals[CLICKED], 0); + + return TRUE; + } + + return FALSE; +} + +static gboolean +st_button_enter (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + StButton *button = ST_BUTTON (actor); + + if (!button->priv->is_checked) + st_widget_set_style_pseudo_class ((StWidget*) button, "hover"); + + button->priv->is_hover = 1; + + return CLUTTER_ACTOR_CLASS (st_button_parent_class)->enter_event (actor, event); +} + +static gboolean +st_button_leave (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + StButton *button = ST_BUTTON (actor); + + button->priv->is_hover = 0; + + if (button->priv->is_pressed) + { + StButtonClass *klass = ST_BUTTON_GET_CLASS (button); + + clutter_ungrab_pointer (); + + button->priv->is_pressed = FALSE; + + if (klass->released) + klass->released (button); + } + + if (button->priv->is_checked) + st_widget_set_style_pseudo_class ((StWidget*) button, "checked"); + else + st_widget_set_style_pseudo_class ((StWidget*) button, NULL); + + return CLUTTER_ACTOR_CLASS (st_button_parent_class)->leave_event (actor, event); +} + +static void +st_button_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StButton *button = ST_BUTTON (gobject); + StButtonPrivate *priv = ST_BUTTON (gobject)->priv; + + switch (prop_id) + { + case PROP_LABEL: + st_button_set_label (button, g_value_get_string (value)); + break; + case PROP_TOGGLE: + st_button_set_toggle_mode (button, g_value_get_boolean (value)); + break; + case PROP_ACTIVE: + st_button_set_checked (button, g_value_get_boolean (value)); + break; + case PROP_TRANSITION: + priv->transition_duration = g_value_get_int (value); + break; + + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_button_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StButtonPrivate *priv = ST_BUTTON (gobject)->priv; + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, priv->text); + break; + case PROP_TOGGLE: + g_value_set_boolean (value, priv->is_toggle); + break; + case PROP_ACTIVE: + g_value_set_boolean (value, priv->is_checked); + break; + case PROP_TRANSITION: + g_value_set_int (value, priv->transition_duration); + break; + + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_button_finalize (GObject *gobject) +{ + StButtonPrivate *priv = ST_BUTTON (gobject)->priv; + + g_free (priv->text); + + G_OBJECT_CLASS (st_button_parent_class)->finalize (gobject); +} + +static void +st_button_dispose (GObject *gobject) +{ + st_button_dispose_old_bg (ST_BUTTON (gobject)); + + G_OBJECT_CLASS (st_button_parent_class)->dispose (gobject); +} + +static void +st_button_map (ClutterActor *self) +{ + StButtonPrivate *priv = ST_BUTTON (self)->priv; + + CLUTTER_ACTOR_CLASS (st_button_parent_class)->map (self); + + if (priv->old_bg && priv->old_bg_parented) + clutter_actor_map (priv->old_bg); +} + +static void +st_button_unmap (ClutterActor *self) +{ + StButtonPrivate *priv = ST_BUTTON (self)->priv; + + CLUTTER_ACTOR_CLASS (st_button_parent_class)->unmap (self); + + if (priv->old_bg && priv->old_bg_parented) + clutter_actor_unmap (priv->old_bg); +} + +static void +st_button_draw_background (StWidget *widget, + ClutterActor *background, + const ClutterColor *color) +{ + StButtonPrivate *priv; + + ST_WIDGET_CLASS (st_button_parent_class)->draw_background (widget, background, color); + + priv = ST_BUTTON (widget)->priv; + + if (priv->old_bg && priv->old_bg_parented) + clutter_actor_paint (priv->old_bg); +} + +static void +st_button_class_init (StButtonClass *klass) +{ + GObjectClass *gobject_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 (StButtonPrivate)); + + klass->pressed = st_button_real_pressed; + klass->released = st_button_real_released; + + gobject_class->set_property = st_button_set_property; + gobject_class->get_property = st_button_get_property; + gobject_class->dispose = st_button_dispose; + gobject_class->finalize = st_button_finalize; + + actor_class->button_press_event = st_button_button_press; + actor_class->button_release_event = st_button_button_release; + actor_class->enter_event = st_button_enter; + actor_class->leave_event = st_button_leave; + + actor_class->map = st_button_map; + actor_class->unmap = st_button_unmap; + + widget_class->draw_background = st_button_draw_background; + + pspec = g_param_spec_string ("label", + "Label", + "Label of the button", + NULL, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_LABEL, pspec); + + pspec = g_param_spec_boolean ("toggle-mode", + "Toggle Mode", + "Enable or disable toggling", + FALSE, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TOGGLE, pspec); + + pspec = g_param_spec_boolean ("checked", + "Checked", + "Indicates if a toggle button is \"on\"" + " or \"off\"", + FALSE, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_ACTIVE, pspec); + + pspec = g_param_spec_int ("transition-duration", + "Transition Duration", + "Duration of the state transition effect", + 0, G_MAXINT, 120, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TRANSITION, pspec); + + + /** + * StButton::clicked: + * @button: the object that received the signal + * + * Emitted when the user activates the button, either with a mouse press and + * release or with the keyboard. + */ + + button_signals[CLICKED] = + g_signal_new ("clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StButtonClass, clicked), + NULL, NULL, + _st_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +st_button_init (StButton *button) +{ + button->priv = ST_BUTTON_GET_PRIVATE (button); + button->priv->transition_duration = 120; + button->priv->spacing = 6; + + clutter_actor_set_reactive ((ClutterActor *) button, TRUE); + + g_signal_connect (button, "style-changed", + G_CALLBACK (st_button_style_changed), NULL); + + g_signal_connect (button, "stylable-changed", + G_CALLBACK (st_button_stylable_changed), NULL); +} + +/** + * st_button_new: + * + * Create a new button + * + * Returns: a new #StButton + */ +StWidget * +st_button_new (void) +{ + return g_object_new (ST_TYPE_BUTTON, NULL); +} + +/** + * st_button_new_with_label: + * @text: text to set the label to + * + * Create a new #StButton with the specified label + * + * Returns: a new #StButton + */ +StWidget * +st_button_new_with_label (const gchar *text) +{ + return g_object_new (ST_TYPE_BUTTON, "label", text, NULL); +} + +/** + * st_button_get_label: + * @button: a #StButton + * + * Get the text displayed on the button + * + * Returns: the text for the button. This must not be freed by the application + */ +G_CONST_RETURN gchar * +st_button_get_label (StButton *button) +{ + g_return_val_if_fail (ST_IS_BUTTON (button), NULL); + + return button->priv->text; +} + +/** + * st_button_set_label: + * @button: a #Stbutton + * @text: text to set the label to + * + * Sets the text displayed on the button + */ +void +st_button_set_label (StButton *button, + const gchar *text) +{ + StButtonPrivate *priv; + ClutterActor *label; + + g_return_if_fail (ST_IS_BUTTON (button)); + + priv = button->priv; + + g_free (priv->text); + + if (text) + priv->text = g_strdup (text); + else + priv->text = g_strdup (""); + + label = st_bin_get_child ((StBin*) button); + + if (label && CLUTTER_IS_TEXT (label)) + { + clutter_text_set_text (CLUTTER_TEXT (label), priv->text); + } + else + { + label = g_object_new (CLUTTER_TYPE_TEXT, + "text", priv->text, + "line-alignment", PANGO_ALIGN_CENTER, + "ellipsize", PANGO_ELLIPSIZE_END, + "use-markup", TRUE, + NULL); + st_bin_set_child ((StBin*) button, label); + } + + st_stylable_changed ((StStylable*) button); + + g_object_notify (G_OBJECT (button), "label"); +} + +/** + * st_button_get_toggle_mode: + * @button: a #StButton + * + * Get the toggle mode status of the button. + * + * Returns: #TRUE if toggle mode is set, otherwise #FALSE + */ +gboolean +st_button_get_toggle_mode (StButton *button) +{ + g_return_val_if_fail (ST_IS_BUTTON (button), FALSE); + + return button->priv->is_toggle; +} + +/** + * st_button_set_toggle_mode: + * @button: a #Stbutton + * @toggle: #TRUE or #FALSE + * + * Enables or disables toggle mode for the button. In toggle mode, the active + * state will be "toggled" when the user clicks the button. + */ +void +st_button_set_toggle_mode (StButton *button, + gboolean toggle) +{ + g_return_if_fail (ST_IS_BUTTON (button)); + + button->priv->is_toggle = toggle; + + g_object_notify (G_OBJECT (button), "toggle-mode"); +} + +/** + * st_button_get_checked: + * @button: a #StButton + * + * Get the state of the button that is in toggle mode. + * + * Returns: #TRUE if the button is checked, or #FALSE if not + */ +gboolean +st_button_get_checked (StButton *button) +{ + g_return_val_if_fail (ST_IS_BUTTON (button), FALSE); + + return button->priv->is_checked; +} + +/** + * st_button_set_checked: + * @button: a #Stbutton + * @checked: #TRUE or #FALSE + * + * Sets the pressed state of the button. This is only really useful if the + * button has #toggle-mode mode set to #TRUE. + */ +void +st_button_set_checked (StButton *button, + gboolean checked) +{ + g_return_if_fail (ST_IS_BUTTON (button)); + + if (button->priv->is_checked != checked) + { + button->priv->is_checked = checked; + + if (checked) + st_widget_set_style_pseudo_class ((StWidget*) button, "checked"); + else + if (button->priv->is_hover) + st_widget_set_style_pseudo_class ((StWidget*) button, "hover"); + else + st_widget_set_style_pseudo_class ((StWidget*) button, NULL); + } + + g_object_notify (G_OBJECT (button), "checked"); +} diff --git a/src/st/st-button.h b/src/st/st-button.h new file mode 100644 index 000000000..c09a04aa1 --- /dev/null +++ b/src/st/st-button.h @@ -0,0 +1,93 @@ +/* + * st-button.h: Plain button actor + * + * Copyright 2007 OpenedHand + * Copyright 2008, 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + * Thomas Wood + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_BUTTON_H__ +#define __ST_BUTTON_H__ + +G_BEGIN_DECLS + +#include + +#define ST_TYPE_BUTTON (st_button_get_type ()) +#define ST_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_BUTTON, StButton)) +#define ST_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_BUTTON)) +#define ST_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_BUTTON, StButtonClass)) +#define ST_IS_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_BUTTON)) +#define ST_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_BUTTON, StButtonClass)) + +typedef struct _StButton StButton; +typedef struct _StButtonPrivate StButtonPrivate; +typedef struct _StButtonClass StButtonClass; + +/** + * StButton: + * + * The contents of this structure is private and should only be accessed using + * the provided API. + */ + +struct _StButton +{ + /*< private >*/ + StBin parent_instance; + + StButtonPrivate *priv; +}; + +struct _StButtonClass +{ + StBinClass parent_class; + + /* vfuncs, not signals */ + void (* pressed) (StButton *button); + void (* released) (StButton *button); + void (* transition) (StButton *button, + ClutterActor *old_bg); + + /* signals */ + void (* clicked) (StButton *button); +}; + +GType st_button_get_type (void) G_GNUC_CONST; + +StWidget * st_button_new (void); +StWidget * st_button_new_with_label (const gchar *text); +G_CONST_RETURN gchar *st_button_get_label (StButton *button); +void st_button_set_label (StButton *button, + const gchar *text); +void st_button_set_toggle_mode (StButton *button, + gboolean toggle); +gboolean st_button_get_toggle_mode (StButton *button); +void st_button_set_checked (StButton *button, + gboolean checked); +gboolean st_button_get_checked (StButton *button); + +G_END_DECLS + +#endif /* __ST_BUTTON_H__ */ diff --git a/src/st/st-scroll-bar.c b/src/st/st-scroll-bar.c new file mode 100644 index 000000000..ad636dfc2 --- /dev/null +++ b/src/st/st-scroll-bar.c @@ -0,0 +1,1105 @@ +/* + * st-scroll-bar.c: Scroll bar actor + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Chris Lord + * Port to St by: Robert Staudinger + * + */ + +/** + * 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 + +#include "st-scroll-bar.h" +#include "st-bin.h" +#include "st-marshal.h" +#include "st-stylable.h" +#include "st-enum-types.h" +#include "st-private.h" +#include "st-button.h" + +static void st_stylable_iface_init (StStylableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (StScrollBar, st_scroll_bar, ST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE, + st_stylable_iface_init)) + +#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 *bw_stepper; + ClutterActor *fw_stepper; + 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; + + gboolean stepper_forward; + guint stepper_source_id; + + ClutterAnimation *paging_animation; + + gboolean vertical; +}; + +enum +{ + PROP_0, + + PROP_ADJUSTMENT, + PROP_VERTICAL +}; + +enum +{ + SCROLL_START, + SCROLL_STOP, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +static gboolean +handle_button_press_event_cb (ClutterActor *actor, + ClutterButtonEvent *event, + 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->bw_stepper), + "up-stepper"); + clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->fw_stepper), + "down-stepper"); + clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->handle), + "vhandle"); + } + else + { + clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->fw_stepper), + "forward-stepper"); + clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->bw_stepper), + "backward-stepper"); + 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) + { + g_signal_handlers_disconnect_by_func (priv->handle, + G_CALLBACK (handle_button_press_event_cb), + bar); + clutter_actor_unparent (priv->handle); + priv->handle = NULL; + } + + clutter_actor_unparent (priv->bw_stepper); + priv->bw_stepper = NULL; + + clutter_actor_unparent (priv->fw_stepper); + priv->fw_stepper = NULL; + + clutter_actor_unparent (priv->trough); + priv->trough = NULL; + + G_OBJECT_CLASS (st_scroll_bar_parent_class)->dispose (gobject); +} + +static void +st_scroll_bar_paint (ClutterActor *actor) +{ + StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->paint (actor); + + clutter_actor_paint (priv->bw_stepper); + + clutter_actor_paint (priv->fw_stepper); + + clutter_actor_paint (priv->trough); + + if (priv->handle && CLUTTER_ACTOR_IS_VISIBLE (priv->handle)) + clutter_actor_paint (priv->handle); +} + +static void +st_scroll_bar_pick (ClutterActor *actor, + const ClutterColor *pick_color) +{ + StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->pick (actor, pick_color); + + clutter_actor_paint (priv->bw_stepper); + clutter_actor_paint (priv->fw_stepper); + clutter_actor_paint (priv->trough); + + if (priv->handle && priv->adjustment) + clutter_actor_paint (priv->handle); +} + +static void +st_scroll_bar_map (ClutterActor *actor) +{ + StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->map (actor); + + clutter_actor_map (priv->bw_stepper); + clutter_actor_map (priv->fw_stepper); + clutter_actor_map (priv->trough); + + if (priv->handle) + clutter_actor_map (priv->handle); +} + +static void +st_scroll_bar_unmap (ClutterActor *actor) +{ + StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->unmap (actor); + + clutter_actor_unmap (priv->bw_stepper); + clutter_actor_unmap (priv->fw_stepper); + clutter_actor_unmap (priv->trough); + + if (priv->handle) + clutter_actor_unmap (priv->handle); +} + +static void +st_scroll_bar_allocate (ClutterActor *actor, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv; + StPadding padding; + ClutterActorBox bw_box, fw_box, trough_box; + gfloat x, y, width, height, stepper_size; + + /* Chain up */ + CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)-> + allocate (actor, box, flags); + + st_widget_get_padding (ST_WIDGET (actor), &padding); + + /* calculate the child area */ + x = padding.left; + y = padding.top; + width = (box->x2 - box->x1) - padding.left - padding.right; + height = (box->y2 - box->y1) - padding.top - padding.bottom; + + if (priv->vertical) + { + stepper_size = width; + + /* Backward stepper */ + bw_box.x1 = x; + bw_box.y1 = y; + bw_box.x2 = bw_box.x1 + stepper_size; + bw_box.y2 = bw_box.y1 + stepper_size; + clutter_actor_allocate (priv->bw_stepper, &bw_box, flags); + + /* Forward stepper */ + fw_box.x1 = x; + fw_box.y1 = y + height - stepper_size; + fw_box.x2 = fw_box.x1 + stepper_size; + fw_box.y2 = fw_box.y1 + stepper_size; + clutter_actor_allocate (priv->fw_stepper, &fw_box, flags); + + /* Trough */ + trough_box.x1 = x; + trough_box.y1 = y + stepper_size; + trough_box.x2 = x + width; + trough_box.y2 = y + height - stepper_size; + clutter_actor_allocate (priv->trough, &trough_box, flags); + + } + else + { + stepper_size = height; + + /* Backward stepper */ + bw_box.x1 = x; + bw_box.y1 = y; + bw_box.x2 = bw_box.x1 + stepper_size; + bw_box.y2 = bw_box.y1 + stepper_size; + clutter_actor_allocate (priv->bw_stepper, &bw_box, flags); + + /* Forward stepper */ + fw_box.x1 = x + width - stepper_size; + fw_box.y1 = y; + fw_box.x2 = fw_box.x1 + stepper_size; + fw_box.y2 = fw_box.y1 + stepper_size; + clutter_actor_allocate (priv->fw_stepper, &fw_box, flags); + + /* Trough */ + trough_box.x1 = x + stepper_size; + trough_box.y1 = y; + trough_box.x2 = x + width - stepper_size; + trough_box.y2 = y + height; + clutter_actor_allocate (priv->trough, &trough_box, flags); + } + + + if (priv->adjustment) + { + gfloat handle_size, position, avail_size; + gdouble value, lower, upper, page_size, increment; + ClutterActorBox handle_box = { 0, }; + guint min_size, max_size; + + 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); + + st_stylable_get (ST_STYLABLE (actor), + "min-size", &min_size, + "max-size", &max_size, + NULL); + + if (upper - lower - page_size <= 0) + position = 0; + else + position = (value - lower) / (upper - lower - page_size); + + if (priv->vertical) + { + avail_size = height - stepper_size * 2; + handle_size = increment * avail_size; + handle_size = CLAMP (handle_size, min_size, max_size); + + handle_box.x1 = x; + handle_box.y1 = bw_box.y2 + position * (avail_size - handle_size); + + handle_box.x2 = handle_box.x1 + width; + handle_box.y2 = handle_box.y1 + handle_size; + } + else + { + avail_size = width - stepper_size * 2; + handle_size = increment * avail_size; + handle_size = CLAMP (handle_size, min_size, max_size); + + handle_box.x1 = bw_box.x2 + position * (avail_size - handle_size); + handle_box.y1 = y; + + handle_box.x2 = handle_box.x1 + handle_size; + handle_box.y2 = handle_box.y1 + height; + } + + /* 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_style_changed (StWidget *widget) +{ + StScrollBarPrivate *priv = ST_SCROLL_BAR (widget)->priv; + + st_stylable_changed ((StStylable *) priv->bw_stepper); + st_stylable_changed ((StStylable *) priv->fw_stepper); + st_stylable_changed ((StStylable *) priv->trough); + st_stylable_changed ((StStylable *) priv->handle); + +} + +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; + StScrollBarPrivate *priv; + + gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class); + obj = gobject_class->constructor (type, n_properties, properties); + + bar = ST_SCROLL_BAR (obj); + priv = ST_SCROLL_BAR_GET_PRIVATE (bar); + + 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); + 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->allocate = st_scroll_bar_allocate; + actor_class->paint = st_scroll_bar_paint; + actor_class->pick = st_scroll_bar_pick; + actor_class->scroll_event = st_scroll_bar_scroll_event; + actor_class->map = st_scroll_bar_map; + actor_class->unmap = st_scroll_bar_unmap; + + 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, + g_cclosure_marshal_VOID__VOID, + 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, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +st_stylable_iface_init (StStylableIface *iface) +{ + static gboolean is_initialized = FALSE; + + if (!is_initialized) + { + GParamSpec *pspec; + + is_initialized = TRUE; + + pspec = g_param_spec_uint ("min-size", + "Minimum grabber size", + "Minimum size of the scroll grabber, in px", + 0, G_MAXUINT, 32, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, + ST_TYPE_SCROLL_BAR, pspec); + + pspec = g_param_spec_uint ("max-size", + "Maximum grabber size", + "Maximum size of the scroll grabber, in px", + 0, G_MAXINT16, G_MAXINT16, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, + ST_TYPE_SCROLL_BAR, pspec); + } +} + +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 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; + + stage = clutter_actor_get_stage(bar->priv->trough); + + if (bar->priv->capture_handler) + { + g_signal_handler_disconnect (stage, bar->priv->capture_handler); + bar->priv->capture_handler = 0; + } + + clutter_set_motion_events_enabled (TRUE); + g_signal_emit (bar, signals[SCROLL_STOP], 0); + + /* check if the mouse pointer has left the handle during the drag and + * remove the hover state if it has */ + 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_set_style_pseudo_class ((StWidget*) bar->priv->handle, NULL); + } + + + } + + return TRUE; +} + +static gboolean +handle_button_press_event_cb (ClutterActor *actor, + ClutterButtonEvent *event, + StScrollBar *bar) +{ + 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); + + /* Turn off picking for motion events */ + clutter_set_motion_events_enabled (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", PAGING_SUBSEQUENT_REPEAT_TIMEOUT, + "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 +stepper_animation_completed_cb (ClutterAnimation *a, + gpointer data) +{ + g_object_unref (a); +} + +static void +stepper_move_on (StScrollBarPrivate *priv, + gint mode) +{ + ClutterAnimation *a; + ClutterTimeline *t; + GValue v = { 0, }; + double value, inc; + + a = g_object_new (CLUTTER_TYPE_ANIMATION, + "object", priv->adjustment, + "duration", PAGING_SUBSEQUENT_REPEAT_TIMEOUT, + "mode", mode, + NULL); + + g_signal_connect (a, "completed", G_CALLBACK (stepper_animation_completed_cb), + NULL); + + g_object_get (priv->adjustment, + "step-increment", &inc, + "value", &value, + NULL); + + if (priv->stepper_forward) + value = value + inc; + else + value = value - inc; + + g_value_init (&v, G_TYPE_DOUBLE); + g_value_set_double (&v, value); + clutter_animation_bind (a, "value", &v); + + t = clutter_animation_get_timeline (a); + clutter_timeline_start (t); +} + +static gboolean +stepper_button_subsequent_timeout (StScrollBarPrivate *priv) +{ + stepper_move_on (priv, CLUTTER_LINEAR); + + return TRUE; +} + +static gboolean +stepper_button_repeat_timeout (StScrollBarPrivate *priv) +{ + priv->stepper_source_id = 0; + + stepper_move_on (priv, CLUTTER_EASE_IN_CUBIC); + + priv->stepper_source_id = g_timeout_add (PAGING_SUBSEQUENT_REPEAT_TIMEOUT, + (GSourceFunc) + stepper_button_subsequent_timeout, + priv); + return FALSE; +} + +static gboolean +stepper_button_press_event_cb (ClutterActor *actor, + ClutterButtonEvent *event, + StScrollBar *bar) +{ + StScrollBarPrivate *priv = bar->priv; + + if (event->button != 1) + return FALSE; + + if (bar->priv->adjustment == NULL) + return FALSE; + + bar->priv->stepper_forward = (actor == priv->fw_stepper); + + stepper_move_on (priv, CLUTTER_EASE_OUT_CUBIC); + + priv->stepper_source_id = g_timeout_add (PAGING_INITIAL_REPEAT_TIMEOUT, + (GSourceFunc) + stepper_button_repeat_timeout, + priv); + + return TRUE; +} + +static gboolean +stepper_button_release_cb (ClutterActor *actor, + ClutterButtonEvent *event, + StScrollBar *self) +{ + if (event->button != 1) + return FALSE; + + g_source_remove (self->priv->stepper_source_id); + + 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->bw_stepper), reactive); + clutter_actor_set_reactive (CLUTTER_ACTOR (priv->fw_stepper), reactive); + 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->bw_stepper = (ClutterActor *) st_button_new (); + clutter_actor_set_name (CLUTTER_ACTOR (self->priv->bw_stepper), + "backward-stepper"); + clutter_actor_set_parent (CLUTTER_ACTOR (self->priv->bw_stepper), + CLUTTER_ACTOR (self)); + g_signal_connect (self->priv->bw_stepper, "button-press-event", + G_CALLBACK (stepper_button_press_event_cb), self); + g_signal_connect (self->priv->bw_stepper, "button-release-event", + G_CALLBACK (stepper_button_release_cb), self); + + self->priv->fw_stepper = (ClutterActor *) st_button_new (); + clutter_actor_set_name (CLUTTER_ACTOR (self->priv->fw_stepper), + "forward-stepper"); + clutter_actor_set_parent (CLUTTER_ACTOR (self->priv->fw_stepper), + CLUTTER_ACTOR (self)); + g_signal_connect (self->priv->fw_stepper, "button-press-event", + G_CALLBACK (stepper_button_press_event_cb), self); + g_signal_connect (self->priv->fw_stepper, "button-release-event", + G_CALLBACK (stepper_button_release_cb), 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_set_parent (CLUTTER_ACTOR (self->priv->trough), + CLUTTER_ACTOR (self)); + 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_set_parent (CLUTTER_ACTOR (self->priv->handle), + self->priv->trough); + 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, "style-changed", + G_CALLBACK (st_scroll_bar_style_changed), NULL); + 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); +} + +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 (priv->adjustment) + { + g_signal_handlers_disconnect_by_func (priv->adjustment, + clutter_actor_queue_relayout, + bar); + g_signal_handlers_disconnect_by_func (priv->adjustment, + clutter_actor_queue_relayout, + bar); + g_object_unref (priv->adjustment); + priv->adjustment = NULL; + } + + if (adjustment) + { + priv->adjustment = g_object_ref (adjustment); + + g_signal_connect_swapped (priv->adjustment, "notify::value", + G_CALLBACK (clutter_actor_queue_relayout), + bar); + g_signal_connect_swapped (priv->adjustment, "changed", + G_CALLBACK (clutter_actor_queue_relayout), + bar); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (bar)); + } +} + +StAdjustment * +st_scroll_bar_get_adjustment (StScrollBar *bar) +{ + g_return_val_if_fail (ST_IS_SCROLL_BAR (bar), NULL); + + return bar->priv->adjustment; +} + diff --git a/src/st/st-scroll-bar.h b/src/st/st-scroll-bar.h new file mode 100644 index 000000000..33adc037d --- /dev/null +++ b/src/st/st-scroll-bar.h @@ -0,0 +1,82 @@ +/* + * st-scroll-bar.h: Scroll bar actor + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Chris Lord + * Port to St by: Robert Staudinger + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_SCROLL_BAR_H__ +#define __ST_SCROLL_BAR_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SCROLL_BAR (st_scroll_bar_get_type()) +#define ST_SCROLL_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SCROLL_BAR, StScrollBar)) +#define ST_IS_SCROLL_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SCROLL_BAR)) +#define ST_SCROLL_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SCROLL_BAR, StScrollBarClass)) +#define ST_IS_SCROLL_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SCROLL_BAR)) +#define ST_SCROLL_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SCROLL_BAR, StScrollBarClass)) + +typedef struct _StScrollBar StScrollBar; +typedef struct _StScrollBarPrivate StScrollBarPrivate; +typedef struct _StScrollBarClass StScrollBarClass; + +/** + * StScrollBar: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _StScrollBar +{ + /*< private >*/ + StBin parent_instance; + + StScrollBarPrivate *priv; +}; + +struct _StScrollBarClass +{ + StBinClass parent_class; + + /* signals */ + void (*scroll_start) (StScrollBar *bar); + void (*scroll_stop) (StScrollBar *bar); +}; + +GType st_scroll_bar_get_type (void) G_GNUC_CONST; + +StWidget *st_scroll_bar_new (StAdjustment *adjustment); + +void st_scroll_bar_set_adjustment (StScrollBar *bar, + StAdjustment *adjustment); +StAdjustment *st_scroll_bar_get_adjustment (StScrollBar *bar); + +G_END_DECLS + +#endif /* __ST_SCROLL_BAR_H__ */ diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c new file mode 100644 index 000000000..aba01e6bd --- /dev/null +++ b/src/st/st-scroll-view.c @@ -0,0 +1,850 @@ +/* + * st-scroll-view.h: Container with scroll-bars + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Chris Lord + * Port to St by: Robert Staudinger + * + */ + +/** + * SECTION:st-scroll-view + * @short_description: a container for scrollable children + * + * #StScrollView is a single child container for actors that implement + * #StScrollable. It provides scrollbars around the edge of the child to + * allow the user to move around the scrollable area. + */ + +#include "st-scroll-view.h" +#include "st-marshal.h" +#include "st-scroll-bar.h" +#include "st-scrollable.h" +#include "st-stylable.h" +#include + +static void clutter_container_iface_init (ClutterContainerIface *iface); +static void st_stylable_iface_init (StStylableIface *iface); + +static ClutterContainerIface *st_scroll_view_parent_iface = NULL; + +G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, + clutter_container_iface_init) + G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE, + st_stylable_iface_init)) + +#define SCROLL_VIEW_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + ST_TYPE_SCROLL_VIEW, \ + StScrollViewPrivate)) + +struct _StScrollViewPrivate +{ + /* a pointer to the child; this is actually stored + * inside StBin:child, but we keep it to avoid + * calling st_bin_get_child() every time we need it + */ + ClutterActor *child; + + ClutterActor *hscroll; + ClutterActor *vscroll; + + gfloat row_size; + gfloat column_size; + + gboolean row_size_set : 1; + gboolean column_size_set : 1; + guint mouse_scroll : 1; +}; + +enum { + PROP_0, + + PROP_HSCROLL, + PROP_VSCROLL, + PROP_MOUSE_SCROLL +}; + +static void +st_scroll_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + StScrollViewPrivate *priv = ((StScrollView *) object)->priv; + + switch (property_id) + { + case PROP_HSCROLL: + g_value_set_object (value, priv->hscroll); + break; + case PROP_VSCROLL: + g_value_set_object (value, priv->vscroll); + break; + case PROP_MOUSE_SCROLL: + g_value_set_boolean (value, priv->mouse_scroll); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +st_scroll_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_MOUSE_SCROLL: + st_scroll_view_set_mouse_scrolling ((StScrollView *) object, + g_value_get_boolean (value)); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +st_scroll_view_dispose (GObject *object) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv; + + priv->child = NULL; + + if (priv->vscroll) + { + clutter_actor_unparent (priv->vscroll); + priv->vscroll = NULL; + } + + if (priv->hscroll) + { + clutter_actor_unparent (priv->hscroll); + priv->hscroll = NULL; + } + + G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object); +} + +static void +st_scroll_view_finalize (GObject *object) +{ + G_OBJECT_CLASS (st_scroll_view_parent_class)->finalize (object); +} + +static void +st_scroll_view_paint (ClutterActor *actor) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; + + /* StBin will paint the child */ + CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->paint (actor); + + /* paint our custom children */ + if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) + clutter_actor_paint (priv->hscroll); + if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) + clutter_actor_paint (priv->vscroll); +} + +static void +st_scroll_view_pick (ClutterActor *actor, + const ClutterColor *color) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; + + /* Chain up so we get a bounding box pained (if we are reactive) */ + CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->pick (actor, color); + + /* paint our custom children */ + if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) + clutter_actor_paint (priv->hscroll); + if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) + clutter_actor_paint (priv->vscroll); +} + +static void +st_scroll_view_get_preferred_width (ClutterActor *actor, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + StPadding padding; + guint xthickness; + + StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; + + if (!priv->child) + return; + + st_widget_get_padding (ST_WIDGET (actor), &padding); + st_stylable_get (ST_STYLABLE (actor), + "scrollbar-width", &xthickness, + NULL); + + /* Our natural width is the natural width of the child */ + clutter_actor_get_preferred_width (priv->child, + for_height, + NULL, + natural_width_p); + + /* Add space for the scroll-bar if we can determine it will be necessary */ + if ((for_height >= 0) && natural_width_p) + { + gfloat natural_height; + + clutter_actor_get_preferred_height (priv->child, -1.0, + NULL, + &natural_height); + if (for_height < natural_height) + *natural_width_p += xthickness; + } + + /* Add space for padding */ + if (min_width_p) + *min_width_p = padding.left + padding.right; + + if (natural_width_p) + *natural_width_p += padding.left + padding.right; +} + +static void +st_scroll_view_get_preferred_height (ClutterActor *actor, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + StPadding padding; + guint ythickness; + + StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; + + if (!priv->child) + return; + + st_widget_get_padding (ST_WIDGET (actor), &padding); + st_stylable_get (ST_STYLABLE (actor), + "scrollbar-height", &ythickness, + NULL); + + /* Our natural height is the natural height of the child */ + clutter_actor_get_preferred_height (priv->child, + for_width, + NULL, + natural_height_p); + + /* Add space for the scroll-bar if we can determine it will be necessary */ + if ((for_width >= 0) && natural_height_p) + { + gfloat natural_width; + + clutter_actor_get_preferred_width (priv->child, -1.0, + NULL, + &natural_width); + if (for_width < natural_width) + *natural_height_p += ythickness; + } + + /* Add space for padding */ + if (min_height_p) + *min_height_p = padding.top + padding.bottom; + + if (natural_height_p) + *natural_height_p += padding.top + padding.bottom; +} + +static void +st_scroll_view_allocate (ClutterActor *actor, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + StPadding padding; + ClutterActorBox child_box; + ClutterActorClass *parent_parent_class; + gfloat avail_width, avail_height, sb_width, sb_height; + + StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; + + /* Chain up to the parent's parent class + * + * We do this because we do not want StBin to allocate the child, as we + * give it a different allocation later, depending on whether the scrollbars + * are visible + */ + parent_parent_class + = g_type_class_peek_parent (st_scroll_view_parent_class); + + CLUTTER_ACTOR_CLASS (parent_parent_class)-> + allocate (actor, box, flags); + + + st_widget_get_padding (ST_WIDGET (actor), &padding); + + avail_width = (box->x2 - box->x1) - padding.left - padding.right; + avail_height = (box->y2 - box->y1) - padding.top - padding.bottom; + + st_stylable_get (ST_STYLABLE (actor), + "scrollbar-width", &sb_width, + "scrollbar-height", &sb_height, + NULL); + sb_width = 28; + sb_height = 28; + + if (!CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) + sb_width = 0; + + if (!CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) + sb_height = 0; + + /* Vertical scrollbar */ + if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) + { + child_box.x1 = avail_width - sb_width; + child_box.y1 = padding.top; + child_box.x2 = avail_width; + child_box.y2 = child_box.y1 + avail_height - sb_height; + + clutter_actor_allocate (priv->vscroll, &child_box, flags); + } + + /* Horizontal scrollbar */ + if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) + { + child_box.x1 = padding.left; + child_box.x2 = child_box.x1 + avail_width - sb_width; + child_box.y1 = avail_height - sb_height; + child_box.y2 = avail_height; + + clutter_actor_allocate (priv->hscroll, &child_box, flags); + } + + + /* Child */ + child_box.x1 = padding.left; + child_box.x2 = avail_width - sb_width; + child_box.y1 = padding.top; + child_box.y2 = avail_height - sb_height; + + if (priv->child) + clutter_actor_allocate (priv->child, &child_box, flags); +} + +static void +st_scroll_view_style_changed (StWidget *widget) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (widget)->priv; + + st_stylable_changed ((StStylable *) priv->hscroll); + st_stylable_changed ((StStylable *) priv->vscroll); +} + +static gboolean +st_scroll_view_scroll_event (ClutterActor *self, + ClutterScrollEvent *event) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv; + gdouble lower, value, upper, step; + StAdjustment *vadjustment, *hadjustment; + + /* don't handle scroll events if requested not to */ + if (!priv->mouse_scroll) + return FALSE; + + hadjustment = st_scroll_bar_get_adjustment (ST_SCROLL_BAR(priv->hscroll)); + vadjustment = st_scroll_bar_get_adjustment (ST_SCROLL_BAR(priv->vscroll)); + + switch (event->direction) + { + case CLUTTER_SCROLL_UP: + case CLUTTER_SCROLL_DOWN: + if (vadjustment) + g_object_get (vadjustment, + "lower", &lower, + "step-increment", &step, + "value", &value, + "upper", &upper, + NULL); + else + return FALSE; + break; + case CLUTTER_SCROLL_LEFT: + case CLUTTER_SCROLL_RIGHT: + if (vadjustment) + g_object_get (hadjustment, + "lower", &lower, + "step-increment", &step, + "value", &value, + "upper", &upper, + NULL); + else + return FALSE; + break; + } + + switch (event->direction) + { + case CLUTTER_SCROLL_UP: + if (value == lower) + return FALSE; + else + st_adjustment_set_value (vadjustment, value - step); + break; + case CLUTTER_SCROLL_DOWN: + if (value == upper) + return FALSE; + else + st_adjustment_set_value (vadjustment, value + step); + break; + case CLUTTER_SCROLL_LEFT: + if (value == lower) + return FALSE; + else + st_adjustment_set_value (hadjustment, value - step); + break; + case CLUTTER_SCROLL_RIGHT: + if (value == upper) + return FALSE; + else + st_adjustment_set_value (hadjustment, value + step); + break; + } + + return TRUE; +} + +static void +st_scroll_view_class_init (StScrollViewClass *klass) +{ + GParamSpec *pspec; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (StScrollViewPrivate)); + + object_class->get_property = st_scroll_view_get_property; + object_class->set_property = st_scroll_view_set_property; + object_class->dispose= st_scroll_view_dispose; + object_class->finalize = st_scroll_view_finalize; + + actor_class->paint = st_scroll_view_paint; + actor_class->pick = st_scroll_view_pick; + actor_class->get_preferred_width = st_scroll_view_get_preferred_width; + actor_class->get_preferred_height = st_scroll_view_get_preferred_height; + actor_class->allocate = st_scroll_view_allocate; + actor_class->scroll_event = st_scroll_view_scroll_event; + + g_object_class_install_property (object_class, + PROP_HSCROLL, + g_param_spec_object ("hscroll", + "StScrollBar", + "Horizontal scroll indicator", + ST_TYPE_SCROLL_BAR, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_VSCROLL, + g_param_spec_object ("vscroll", + "StScrollBar", + "Vertical scroll indicator", + ST_TYPE_SCROLL_BAR, + G_PARAM_READABLE)); + + pspec = g_param_spec_boolean ("enable-mouse-scrolling", + "Enable Mouse Scrolling", + "Enable automatic mouse wheel scrolling", + TRUE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, + PROP_MOUSE_SCROLL, + pspec); + +} + +static void +st_stylable_iface_init (StStylableIface *iface) +{ + static gboolean is_initialized = FALSE; + + if (!is_initialized) + { + GParamSpec *pspec; + + is_initialized = TRUE; + + pspec = g_param_spec_uint ("scrollbar-width", + "Vertical scroll-bar thickness", + "Thickness of vertical scrollbar, in px", + 0, G_MAXUINT, 24, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_SCROLL_VIEW, pspec); + + pspec = g_param_spec_uint ("scrollbar-height", + "Horizontal scroll-bar thickness", + "Thickness of horizontal scrollbar, in px", + 0, G_MAXUINT, 24, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_SCROLL_VIEW, pspec); + } +} + +static void +child_adjustment_changed_cb (StAdjustment *adjustment, + ClutterActor *bar) +{ + StScrollView *scroll; + gdouble lower, upper, page_size; + + scroll = ST_SCROLL_VIEW (clutter_actor_get_parent (bar)); + + /* Determine if this scroll-bar should be visible */ + st_adjustment_get_values (adjustment, NULL, + &lower, &upper, + NULL, NULL, + &page_size); + + if ((upper - lower) > page_size) + clutter_actor_show (bar); + else + clutter_actor_hide (bar); + + /* Request a resize */ + clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll)); +} + +static void +child_hadjustment_notify_cb (GObject *gobject, + GParamSpec *arg1, + gpointer user_data) +{ + StAdjustment *hadjust; + + ClutterActor *actor = CLUTTER_ACTOR (gobject); + StScrollViewPrivate *priv = ST_SCROLL_VIEW (user_data)->priv; + + hadjust = st_scroll_bar_get_adjustment (ST_SCROLL_BAR(priv->hscroll)); + if (hadjust) + g_signal_handlers_disconnect_by_func (hadjust, + child_adjustment_changed_cb, + priv->hscroll); + + st_scrollable_get_adjustments (ST_SCROLLABLE (actor), &hadjust, NULL); + if (hadjust) + { + /* Force scroll step if neede. */ + if (priv->column_size_set) + { + g_object_set (hadjust, + "step-increment", priv->column_size, + NULL); + } + + st_scroll_bar_set_adjustment (ST_SCROLL_BAR(priv->hscroll), hadjust); + g_signal_connect (hadjust, "changed", G_CALLBACK ( + child_adjustment_changed_cb), priv->hscroll); + child_adjustment_changed_cb (hadjust, priv->hscroll); + } +} + +static void +child_vadjustment_notify_cb (GObject *gobject, + GParamSpec *arg1, + gpointer user_data) +{ + StAdjustment *vadjust; + + ClutterActor *actor = CLUTTER_ACTOR (gobject); + StScrollViewPrivate *priv = ST_SCROLL_VIEW (user_data)->priv; + + vadjust = st_scroll_bar_get_adjustment (ST_SCROLL_BAR(priv->vscroll)); + if (vadjust) + g_signal_handlers_disconnect_by_func (vadjust, + child_adjustment_changed_cb, + priv->vscroll); + + st_scrollable_get_adjustments (ST_SCROLLABLE(actor), NULL, &vadjust); + if (vadjust) + { + /* Force scroll step if neede. */ + if (priv->row_size_set) + { + g_object_set (vadjust, + "step-increment", priv->row_size, + NULL); + } + + st_scroll_bar_set_adjustment (ST_SCROLL_BAR(priv->vscroll), vadjust); + g_signal_connect (vadjust, "changed", G_CALLBACK ( + child_adjustment_changed_cb), priv->vscroll); + child_adjustment_changed_cb (vadjust, priv->vscroll); + } +} + +static void +st_scroll_view_init (StScrollView *self) +{ + StScrollViewPrivate *priv = self->priv = SCROLL_VIEW_PRIVATE (self); + + priv->hscroll = CLUTTER_ACTOR (st_scroll_bar_new (NULL)); + priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR, "vertical", TRUE, NULL); + + clutter_actor_set_parent (priv->hscroll, CLUTTER_ACTOR (self)); + clutter_actor_set_parent (priv->vscroll, CLUTTER_ACTOR (self)); + + /* mouse scroll is enabled by default, so we also need to be reactive */ + priv->mouse_scroll = TRUE; + g_object_set (G_OBJECT (self), "reactive", TRUE, "clip-to-allocation", TRUE, + NULL); + + g_signal_connect (self, "style-changed", + G_CALLBACK (st_scroll_view_style_changed), NULL); +} + +static void +st_scroll_view_add (ClutterContainer *container, + ClutterActor *actor) +{ + StScrollView *self = ST_SCROLL_VIEW (container); + StScrollViewPrivate *priv = self->priv; + + if (ST_IS_SCROLLABLE (actor)) + { + priv->child = actor; + + /* chain up to StBin::add() */ + st_scroll_view_parent_iface->add (container, actor); + + /* Get adjustments for scroll-bars */ + g_signal_connect (actor, "notify::hadjustment", + G_CALLBACK (child_hadjustment_notify_cb), + container); + g_signal_connect (actor, "notify::vadjustment", + G_CALLBACK (child_vadjustment_notify_cb), + container); + child_hadjustment_notify_cb (G_OBJECT (actor), NULL, container); + child_vadjustment_notify_cb (G_OBJECT (actor), NULL, container); + } + else + { + g_warning ("Attempting to add an actor of type %s to " + "a StScrollView, but the actor does " + "not implement StScrollable.", + g_type_name (G_OBJECT_TYPE (actor))); + } +} + +static void +st_scroll_view_remove (ClutterContainer *container, + ClutterActor *actor) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (container)->priv; + + if (actor == priv->child) + { + g_object_ref (priv->child); + + /* chain up to StBin::remove() */ + st_scroll_view_parent_iface->remove (container, actor); + + g_signal_handlers_disconnect_by_func (priv->child, + child_hadjustment_notify_cb, + container); + g_signal_handlers_disconnect_by_func (priv->child, + child_vadjustment_notify_cb, + container); + st_scrollable_set_adjustments ((StScrollable*) priv->child, NULL, NULL); + + g_object_unref (priv->child); + priv->child = NULL; + } +} + +static void +st_scroll_view_foreach_with_internals (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data) +{ + StScrollViewPrivate *priv = ST_SCROLL_VIEW (container)->priv; + + if (priv->child != NULL) + callback (priv->child, user_data); + + if (priv->hscroll != NULL) + callback (priv->hscroll, user_data); + + if (priv->vscroll != NULL) + callback (priv->vscroll, user_data); +} + +static void +clutter_container_iface_init (ClutterContainerIface *iface) +{ + /* store a pointer to the StBin implementation of + * ClutterContainer so that we can chain up when + * overriding the methods + */ + st_scroll_view_parent_iface = g_type_interface_peek_parent (iface); + + iface->add = st_scroll_view_add; + iface->remove = st_scroll_view_remove; + iface->foreach_with_internals = st_scroll_view_foreach_with_internals; +} + +StWidget * +st_scroll_view_new (void) +{ + return g_object_new (ST_TYPE_SCROLL_VIEW, NULL); +} + +ClutterActor * +st_scroll_view_get_hscroll_bar (StScrollView *scroll) +{ + g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL); + + return scroll->priv->hscroll; +} + +ClutterActor * +st_scroll_view_get_vscroll_bar (StScrollView *scroll) +{ + g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL); + + return scroll->priv->vscroll; +} + +gfloat +st_scroll_view_get_column_size (StScrollView *scroll) +{ + StAdjustment *adjustment; + gdouble column_size; + + g_return_val_if_fail (scroll, 0); + + adjustment = st_scroll_bar_get_adjustment ( + ST_SCROLL_BAR (scroll->priv->hscroll)); + g_object_get (adjustment, + "step-increment", &column_size, + NULL); + + return column_size; +} + +void +st_scroll_view_set_column_size (StScrollView *scroll, + gfloat column_size) +{ + StAdjustment *adjustment; + + g_return_if_fail (scroll); + + if (column_size < 0) + { + scroll->priv->column_size_set = FALSE; + scroll->priv->column_size = -1; + } + else + { + scroll->priv->column_size_set = TRUE; + scroll->priv->column_size = column_size; + + adjustment = st_scroll_bar_get_adjustment ( + ST_SCROLL_BAR (scroll->priv->hscroll)); + + if (adjustment) + g_object_set (adjustment, + "step-increment", (gdouble) scroll->priv->column_size, + NULL); + } +} + +gfloat +st_scroll_view_get_row_size (StScrollView *scroll) +{ + StAdjustment *adjustment; + gdouble row_size; + + g_return_val_if_fail (scroll, 0); + + adjustment = st_scroll_bar_get_adjustment ( + ST_SCROLL_BAR (scroll->priv->vscroll)); + g_object_get (adjustment, + "step-increment", &row_size, + NULL); + + return row_size; +} + +void +st_scroll_view_set_row_size (StScrollView *scroll, + gfloat row_size) +{ + StAdjustment *adjustment; + + g_return_if_fail (scroll); + + if (row_size < 0) + { + scroll->priv->row_size_set = FALSE; + scroll->priv->row_size = -1; + } + else + { + scroll->priv->row_size_set = TRUE; + scroll->priv->row_size = row_size; + + adjustment = st_scroll_bar_get_adjustment ( + ST_SCROLL_BAR (scroll->priv->vscroll)); + + if (adjustment) + g_object_set (adjustment, + "step-increment", (gdouble) scroll->priv->row_size, + NULL); + } +} + +void +st_scroll_view_set_mouse_scrolling (StScrollView *scroll, + gboolean enabled) +{ + StScrollViewPrivate *priv; + + g_return_if_fail (ST_IS_SCROLL_VIEW (scroll)); + + priv = ST_SCROLL_VIEW (scroll)->priv; + + if (priv->mouse_scroll != enabled) + { + priv->mouse_scroll = enabled; + + /* make sure we can receive mouse wheel events */ + if (enabled) + clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE); + } +} + +gboolean +st_scroll_view_get_mouse_scrolling (StScrollView *scroll) +{ + StScrollViewPrivate *priv; + + g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE); + + priv = ST_SCROLL_VIEW (scroll)->priv; + + return priv->mouse_scroll; +} diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h new file mode 100644 index 000000000..c2a3553fb --- /dev/null +++ b/src/st/st-scroll-view.h @@ -0,0 +1,89 @@ +/* + * st-scroll-view.h: Container with scroll-bars + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Chris Lord + * Port to St by: Robert Staudinger + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_SCROLL_VIEW_H__ +#define __ST_SCROLL_VIEW_H__ + +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SCROLL_VIEW (st_scroll_view_get_type()) +#define ST_SCROLL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SCROLL_VIEW, StScrollView)) +#define ST_IS_SCROLL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SCROLL_VIEW)) +#define ST_SCROLL_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SCROLL_VIEW, StScrollViewClass)) +#define ST_IS_SCROLL_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SCROLL_VIEW)) +#define ST_SCROLL_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SCROLL_VIEW, StScrollViewClass)) + +typedef struct _StScrollView StScrollView; +typedef struct _StScrollViewPrivate StScrollViewPrivate; +typedef struct _StScrollViewClass StScrollViewClass; + +/** + * StScrollView: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _StScrollView +{ + /*< private >*/ + StBin parent_instance; + + StScrollViewPrivate *priv; +}; + +struct _StScrollViewClass +{ + StBinClass parent_class; +}; + +GType st_scroll_view_get_type (void) G_GNUC_CONST; + +StWidget *st_scroll_view_new (void); + +ClutterActor *st_scroll_view_get_hscroll_bar (StScrollView *scroll); +ClutterActor *st_scroll_view_get_vscroll_bar (StScrollView *scroll); +ClutterActor *st_scroll_view_get_child (StScrollView *scroll); + +gfloat st_scroll_view_get_column_size (StScrollView *scroll); +void st_scroll_view_set_column_size (StScrollView *scroll, + gfloat column_size); + +gfloat st_scroll_view_get_row_size (StScrollView *scroll); +void st_scroll_view_set_row_size (StScrollView *scroll, + gfloat row_size); + +void st_scroll_view_set_mouse_scrolling (StScrollView *scroll, + gboolean enabled); +gboolean st_scroll_view_get_mouse_scrolling (StScrollView *scroll); + +G_END_DECLS + +#endif /* __ST_SCROLL_VIEW_H__ */ diff --git a/src/st/st-scrollable.c b/src/st/st-scrollable.c new file mode 100644 index 000000000..1c3fc7b2f --- /dev/null +++ b/src/st/st-scrollable.c @@ -0,0 +1,88 @@ +/* + * st-scrollable.c: Scrollable interface + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Chris Lord + * Port to St by: Robert Staudinger + * + */ + +#include "st-scrollable.h" + +static void +st_scrollable_base_init (gpointer g_iface) +{ + static gboolean initialized = FALSE; + + if (!initialized) + { + g_object_interface_install_property (g_iface, + g_param_spec_object ("hadjustment", + "StAdjustment", + "Horizontal adjustment", + ST_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + + g_object_interface_install_property (g_iface, + g_param_spec_object ("vadjustment", + "StAdjustment", + "Vertical adjustment", + ST_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + + initialized = TRUE; + } +} + +GType +st_scrollable_get_type (void) +{ + static GType type = 0; + if (type == 0) + { + static const GTypeInfo info = + { + sizeof (StScrollableInterface), + st_scrollable_base_init, /* base_init */ + NULL, + }; + type = g_type_register_static (G_TYPE_INTERFACE, + "StScrollable", &info, 0); + } + return type; +} + +void +st_scrollable_set_adjustments (StScrollable *scrollable, + StAdjustment *hadjustment, + StAdjustment *vadjustment) +{ + ST_SCROLLABLE_GET_INTERFACE (scrollable)->set_adjustments (scrollable, + hadjustment, + vadjustment); +} + +void +st_scrollable_get_adjustments (StScrollable *scrollable, + StAdjustment **hadjustment, + StAdjustment **vadjustment) +{ + ST_SCROLLABLE_GET_INTERFACE (scrollable)->get_adjustments (scrollable, + hadjustment, + vadjustment); +} diff --git a/src/st/st-scrollable.h b/src/st/st-scrollable.h new file mode 100644 index 000000000..f62d02fe6 --- /dev/null +++ b/src/st/st-scrollable.h @@ -0,0 +1,69 @@ +/* + * st-scrollable.h: Scrollable interface + * + * Copyright 2008 OpenedHand + * Copyright 2009 Intel Corporation. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Chris Lord + * Port to St by: Robert Staudinger + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_SCROLLABLE_H__ +#define __ST_SCROLLABLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SCROLLABLE (st_scrollable_get_type ()) +#define ST_SCROLLABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SCROLLABLE, StScrollable)) +#define ST_IS_SCROLLABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SCROLLABLE)) +#define ST_SCROLLABLE_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), ST_TYPE_SCROLLABLE, StScrollableInterface)) + +typedef struct _StScrollable StScrollable; /* Dummy object */ +typedef struct _StScrollableInterface StScrollableInterface; + +struct _StScrollableInterface +{ + GTypeInterface parent; + + void (* set_adjustments) (StScrollable *scrollable, + StAdjustment *hadjustment, + StAdjustment *vadjustment); + void (* get_adjustments) (StScrollable *scrollable, + StAdjustment **hadjustment, + StAdjustment **vadjustment); +}; + +GType st_scrollable_get_type (void) G_GNUC_CONST; + +void st_scrollable_set_adjustments (StScrollable *scrollable, + StAdjustment *hadjustment, + StAdjustment *vadjustment); +void st_scrollable_get_adjustments (StScrollable *scrollable, + StAdjustment **hadjustment, + StAdjustment **vadjustment); + +G_END_DECLS + +#endif /* __ST_SCROLLABLE_H__ */