diff --git a/src/Makefile-nbtk.am b/src/Makefile-nbtk.am index adfec211d..19c21e09d 100644 --- a/src/Makefile-nbtk.am +++ b/src/Makefile-nbtk.am @@ -64,8 +64,13 @@ nbtk-enum-types.c: stamp-nbtk-enum-types.h nbtk/nbtk-enum-types.c.in # please, keep this sorted alphabetically nbtk_source_h = \ + nbtk/nbtk-adjustment.h \ nbtk/nbtk-bin.h \ + nbtk/nbtk-button.h \ nbtk/nbtk-private.h \ + nbtk/nbtk-scrollable.h \ + nbtk/nbtk-scroll-bar.h \ + nbtk/nbtk-scroll-view.h \ nbtk/nbtk-stylable.h \ nbtk/nbtk-style.h \ nbtk/nbtk-subtexture.h \ @@ -73,19 +78,26 @@ nbtk_source_h = \ nbtk/nbtk-texture-frame.h \ nbtk/nbtk-tooltip.h \ nbtk/nbtk-types.h \ + nbtk/nbtk-viewport.h \ nbtk/nbtk-widget.h \ $(NULL) # please, keep this sorted alphabetically nbtk_source_c = \ + nbtk/nbtk-adjustment.c \ nbtk/nbtk-bin.c \ + nbtk/nbtk-button.c \ nbtk/nbtk-private.c \ + nbtk/nbtk-scrollable.c \ + nbtk/nbtk-scroll-bar.c \ + nbtk/nbtk-scroll-view.c \ nbtk/nbtk-stylable.c \ nbtk/nbtk-style.c \ nbtk/nbtk-subtexture.c \ nbtk/nbtk-texture-cache.c \ nbtk/nbtk-texture-frame.c \ nbtk/nbtk-tooltip.c \ + nbtk/nbtk-viewport.c \ nbtk/nbtk-widget.c \ $(NULL) diff --git a/src/nbtk/nbtk-adjustment.c b/src/nbtk/nbtk-adjustment.c new file mode 100644 index 000000000..4a604725e --- /dev/null +++ b/src/nbtk/nbtk-adjustment.c @@ -0,0 +1,768 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "nbtk-adjustment.h" +#include "nbtk-marshal.h" +#include "nbtk-private.h" + +G_DEFINE_TYPE (NbtkAdjustment, nbtk_adjustment, G_TYPE_OBJECT) + +#define ADJUSTMENT_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NBTK_TYPE_ADJUSTMENT, NbtkAdjustmentPrivate)) + +struct _NbtkAdjustmentPrivate +{ + /* 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 nbtk_adjustment_set_lower (NbtkAdjustment *adjustment, + gdouble lower); +static gboolean nbtk_adjustment_set_upper (NbtkAdjustment *adjustment, + gdouble upper); +static gboolean nbtk_adjustment_set_step_increment (NbtkAdjustment *adjustment, + gdouble step); +static gboolean nbtk_adjustment_set_page_increment (NbtkAdjustment *adjustment, + gdouble page); +static gboolean nbtk_adjustment_set_page_size (NbtkAdjustment *adjustment, + gdouble size); + +static void +nbtk_adjustment_constructed (GObject *object) +{ + GObjectClass *g_class; + NbtkAdjustment *self = NBTK_ADJUSTMENT (object); + + g_class = G_OBJECT_CLASS (nbtk_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 != nbtk_adjustment_constructed) + { + g_class->constructed (object); + } + + NBTK_ADJUSTMENT (self)->priv->is_constructing = FALSE; + nbtk_adjustment_clamp_page (self, self->priv->lower, self->priv->upper); +} + +static void +nbtk_adjustment_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NbtkAdjustmentPrivate *priv = NBTK_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 +nbtk_adjustment_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NbtkAdjustment *adj = NBTK_ADJUSTMENT (gobject); + + switch (prop_id) + { + case PROP_LOWER: + nbtk_adjustment_set_lower (adj, g_value_get_double (value)); + break; + + case PROP_UPPER: + nbtk_adjustment_set_upper (adj, g_value_get_double (value)); + break; + + case PROP_VALUE: + nbtk_adjustment_set_value (adj, g_value_get_double (value)); + break; + + case PROP_STEP_INC: + nbtk_adjustment_set_step_increment (adj, g_value_get_double (value)); + break; + + case PROP_PAGE_INC: + nbtk_adjustment_set_page_increment (adj, g_value_get_double (value)); + break; + + case PROP_PAGE_SIZE: + nbtk_adjustment_set_page_size (adj, g_value_get_double (value)); + break; + + case PROP_ELASTIC: + nbtk_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 (NbtkAdjustment *adjustment) +{ + NbtkAdjustmentPrivate *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 +nbtk_adjustment_dispose (GObject *object) +{ + stop_interpolation (NBTK_ADJUSTMENT (object)); + + G_OBJECT_CLASS (nbtk_adjustment_parent_class)->dispose (object); +} + +static void +nbtk_adjustment_class_init (NbtkAdjustmentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NbtkAdjustmentPrivate)); + + object_class->constructed = nbtk_adjustment_constructed; + object_class->get_property = nbtk_adjustment_get_property; + object_class->set_property = nbtk_adjustment_set_property; + object_class->dispose = nbtk_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, + NBTK_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, + NBTK_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, + NBTK_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, + NBTK_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, + NBTK_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, + NBTK_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, + NBTK_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /** + * NbtkAdjustment::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 (NbtkAdjustmentClass, changed), + NULL, NULL, + _nbtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +nbtk_adjustment_init (NbtkAdjustment *self) +{ + self->priv = ADJUSTMENT_PRIVATE (self); + + self->priv->is_constructing = TRUE; +} + +NbtkAdjustment * +nbtk_adjustment_new (gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size) +{ + return g_object_new (NBTK_TYPE_ADJUSTMENT, + "value", value, + "lower", lower, + "upper", upper, + "step-increment", step_increment, + "page-increment", page_increment, + "page-size", page_size, + NULL); +} + +gdouble +nbtk_adjustment_get_value (NbtkAdjustment *adjustment) +{ + NbtkAdjustmentPrivate *priv; + + g_return_val_if_fail (NBTK_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 +nbtk_adjustment_set_value (NbtkAdjustment *adjustment, + gdouble value) +{ + NbtkAdjustmentPrivate *priv; + + g_return_if_fail (NBTK_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 +nbtk_adjustment_clamp_page (NbtkAdjustment *adjustment, + gdouble lower, + gdouble upper) +{ + NbtkAdjustmentPrivate *priv; + gboolean changed; + + g_return_if_fail (NBTK_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 +nbtk_adjustment_set_lower (NbtkAdjustment *adjustment, + gdouble lower) +{ + NbtkAdjustmentPrivate *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) + nbtk_adjustment_clamp_page (adjustment, priv->lower, priv->upper); + + return TRUE; + } + + return FALSE; +} + +static gboolean +nbtk_adjustment_set_upper (NbtkAdjustment *adjustment, + gdouble upper) +{ + NbtkAdjustmentPrivate *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) + nbtk_adjustment_clamp_page (adjustment, priv->lower, priv->upper); + + return TRUE; + } + + return FALSE; +} + +static gboolean +nbtk_adjustment_set_step_increment (NbtkAdjustment *adjustment, + gdouble step) +{ + NbtkAdjustmentPrivate *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 +nbtk_adjustment_set_page_increment (NbtkAdjustment *adjustment, + gdouble page) +{ + NbtkAdjustmentPrivate *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 +nbtk_adjustment_set_page_size (NbtkAdjustment *adjustment, + gdouble size) +{ + NbtkAdjustmentPrivate *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) + nbtk_adjustment_clamp_page (adjustment, priv->lower, priv->upper); + + return TRUE; + } + + return FALSE; +} + +void +nbtk_adjustment_set_values (NbtkAdjustment *adjustment, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size) +{ + NbtkAdjustmentPrivate *priv; + gboolean emit_changed = FALSE; + + g_return_if_fail (NBTK_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 |= nbtk_adjustment_set_lower (adjustment, lower); + emit_changed |= nbtk_adjustment_set_upper (adjustment, upper); + emit_changed |= nbtk_adjustment_set_step_increment (adjustment, step_increment); + emit_changed |= nbtk_adjustment_set_page_increment (adjustment, page_increment); + emit_changed |= nbtk_adjustment_set_page_size (adjustment, page_size); + + if (value != priv->value) + { + nbtk_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 +nbtk_adjustment_get_values (NbtkAdjustment *adjustment, + gdouble *value, + gdouble *lower, + gdouble *upper, + gdouble *step_increment, + gdouble *page_increment, + gdouble *page_size) +{ + NbtkAdjustmentPrivate *priv; + + g_return_if_fail (NBTK_IS_ADJUSTMENT (adjustment)); + + priv = adjustment->priv; + + if (lower) + *lower = priv->lower; + + if (upper) + *upper = priv->upper; + + if (value) + *value = nbtk_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, + NbtkAdjustment *adjustment) +{ + NbtkAdjustmentPrivate *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; + + nbtk_adjustment_set_value (adjustment, dx); + } + else + nbtk_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, + NbtkAdjustment *adjustment) +{ + NbtkAdjustmentPrivate *priv = adjustment->priv; + + stop_interpolation (adjustment); + nbtk_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 +nbtk_adjustment_interpolate (NbtkAdjustment *adjustment, + gdouble value, + guint duration) +{ + NbtkAdjustmentPrivate *priv = adjustment->priv; + + stop_interpolation (adjustment); + + if (duration <= 1) + { + nbtk_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 +nbtk_adjustment_get_elastic (NbtkAdjustment *adjustment) +{ + return adjustment->priv->elastic; +} + +void +nbtk_adjustment_set_elastic (NbtkAdjustment *adjustment, + gboolean elastic) +{ + adjustment->priv->elastic = elastic; +} + +gboolean +nbtk_adjustment_clamp (NbtkAdjustment *adjustment, + gboolean interpolate, + guint duration) +{ + NbtkAdjustmentPrivate *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) + nbtk_adjustment_interpolate (adjustment, dest, duration); + else + nbtk_adjustment_set_value (adjustment, dest); + + return TRUE; + } + + return FALSE; +} diff --git a/src/nbtk/nbtk-adjustment.h b/src/nbtk/nbtk-adjustment.h new file mode 100644 index 000000000..1fdfb04fd --- /dev/null +++ b/src/nbtk/nbtk-adjustment.h @@ -0,0 +1,121 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#if !defined(NBTK_H_INSIDE) && !defined(NBTK_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __NBTK_ADJUSTMENT_H__ +#define __NBTK_ADJUSTMENT_H__ + +#include +#include + +G_BEGIN_DECLS + +#define NBTK_TYPE_ADJUSTMENT (nbtk_adjustment_get_type()) +#define NBTK_ADJUSTMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NBTK_TYPE_ADJUSTMENT, NbtkAdjustment)) +#define NBTK_IS_ADJUSTMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NBTK_TYPE_ADJUSTMENT)) +#define NBTK_ADJUSTMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NBTK_TYPE_ADJUSTMENT, NbtkAdjustmentClass)) +#define NBTK_IS_ADJUSTMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NBTK_TYPE_ADJUSTMENT)) +#define NBTK_ADJUSTMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NBTK_TYPE_ADJUSTMENT, NbtkAdjustmentClass)) + +typedef struct _NbtkAdjustment NbtkAdjustment; +typedef struct _NbtkAdjustmentPrivate NbtkAdjustmentPrivate; +typedef struct _NbtkAdjustmentClass NbtkAdjustmentClass; + +/** + * NbtkAdjustment: + * + * Class for handling an interval between to values. The contents of + * the #NbtkAdjustment are private and should be accessed using the + * public API. + */ +struct _NbtkAdjustment +{ + /*< private >*/ + GObject parent_instance; + + NbtkAdjustmentPrivate *priv; +}; + +/** + * NbtkAdjustmentClass + * @changed: Class handler for the ::changed signal. + * + * Base class for #NbtkAdjustment. + */ +struct _NbtkAdjustmentClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + void (* changed) (NbtkAdjustment *adjustment); +}; + +GType nbtk_adjustment_get_type (void) G_GNUC_CONST; + +NbtkAdjustment *nbtk_adjustment_new (gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size); +gdouble nbtk_adjustment_get_value (NbtkAdjustment *adjustment); +void nbtk_adjustment_set_value (NbtkAdjustment *adjustment, + gdouble value); +void nbtk_adjustment_clamp_page (NbtkAdjustment *adjustment, + gdouble lower, + gdouble upper); +void nbtk_adjustment_set_values (NbtkAdjustment *adjustment, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size); +void nbtk_adjustment_get_values (NbtkAdjustment *adjustment, + gdouble *value, + gdouble *lower, + gdouble *upper, + gdouble *step_increment, + gdouble *page_increment, + gdouble *page_size); + +void nbtk_adjustment_interpolate (NbtkAdjustment *adjustment, + gdouble value, + guint duration); + +gboolean nbtk_adjustment_get_elastic (NbtkAdjustment *adjustment); +void nbtk_adjustment_set_elastic (NbtkAdjustment *adjustment, + gboolean elastic); + +gboolean nbtk_adjustment_clamp (NbtkAdjustment *adjustment, + gboolean interpolate, + guint duration); + +G_END_DECLS + +#endif /* __NBTK_ADJUSTMENT_H__ */ diff --git a/src/nbtk/nbtk-button.c b/src/nbtk/nbtk-button.c new file mode 100644 index 000000000..558852dfd --- /dev/null +++ b/src/nbtk/nbtk-button.c @@ -0,0 +1,759 @@ +/* + * nbtk-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:nbtk-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 "nbtk-button.h" + +#include "nbtk-marshal.h" +#include "nbtk-stylable.h" +#include "nbtk-style.h" +#include "nbtk-texture-frame.h" +#include "nbtk-texture-cache.h" +#include "nbtk-private.h" + +enum +{ + PROP_0, + + PROP_LABEL, + PROP_TOGGLE, + PROP_ACTIVE, + PROP_TRANSITION +}; + +enum +{ + CLICKED, + + LAST_SIGNAL +}; + +#define NBTK_BUTTON_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), NBTK_TYPE_BUTTON, NbtkButtonPrivate)) + +struct _NbtkButtonPrivate +{ + 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 nbtk_stylable_iface_init (NbtkStylableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (NbtkButton, nbtk_button, NBTK_TYPE_BIN, + G_IMPLEMENT_INTERFACE (NBTK_TYPE_STYLABLE, + nbtk_stylable_iface_init)); + +static void +nbtk_stylable_iface_init (NbtkStylableIface *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); + nbtk_stylable_iface_install_property (iface, NBTK_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); + nbtk_stylable_iface_install_property (iface, NBTK_TYPE_BUTTON, pspec); + } +} + +static void +nbtk_button_update_label_style (NbtkButton *button) +{ + ClutterColor *real_color = NULL; + gchar *font_string = NULL; + gchar *font_name = NULL; + gint font_size = 0; + ClutterActor *label; + + label = nbtk_bin_get_child ((NbtkBin*) button); + + /* check the child is really a label */ + if (!CLUTTER_IS_TEXT (label)) + return; + + nbtk_stylable_get (NBTK_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 +nbtk_button_dispose_old_bg (NbtkButton *button) +{ + NbtkButtonPrivate *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 +nbtk_button_stylable_changed (NbtkStylable *stylable) +{ + NbtkButton *button = NBTK_BUTTON (stylable); + ClutterActor *bg_image; + + nbtk_button_dispose_old_bg (button); + + bg_image = nbtk_widget_get_border_image ((NbtkWidget*) button); + if (bg_image) + button->priv->old_bg = g_object_ref (bg_image); +} + +static void +nbtk_animation_completed (ClutterAnimation *animation, + NbtkButton *button) +{ + nbtk_button_dispose_old_bg (button); +} + +static void +nbtk_button_style_changed (NbtkWidget *widget) +{ + NbtkButton *button = NBTK_BUTTON (widget); + NbtkButtonPrivate *priv = button->priv; + NbtkButtonClass *button_class = NBTK_BUTTON_GET_CLASS (button); + + /* get the spacing value */ + nbtk_stylable_get (NBTK_STYLABLE (widget), + "border-spacing", &priv->spacing, + NULL); + + /* update the label styling */ + nbtk_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 && + (!nbtk_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 (nbtk_animation_completed), button); + } + else + { + nbtk_button_dispose_old_bg (button); + } + + } + } +} + +static void +nbtk_button_real_pressed (NbtkButton *button) +{ + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "active"); +} + +static void +nbtk_button_real_released (NbtkButton *button) +{ + NbtkButtonPrivate *priv = button->priv; + + if (priv->is_checked) + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "checked"); + else if (!priv->is_hover) + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, NULL); + else + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "hover"); + +} + +static gboolean +nbtk_button_button_press (ClutterActor *actor, + ClutterButtonEvent *event) +{ + nbtk_widget_hide_tooltip (NBTK_WIDGET (actor)); + + if (event->button == 1) + { + NbtkButton *button = NBTK_BUTTON (actor); + NbtkButtonClass *klass = NBTK_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 +nbtk_button_button_release (ClutterActor *actor, + ClutterButtonEvent *event) +{ + if (event->button == 1) + { + NbtkButton *button = NBTK_BUTTON (actor); + NbtkButtonClass *klass = NBTK_BUTTON_GET_CLASS (button); + + if (!button->priv->is_pressed) + return FALSE; + + clutter_ungrab_pointer (); + + if (button->priv->is_toggle) + { + nbtk_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 +nbtk_button_enter (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + NbtkButton *button = NBTK_BUTTON (actor); + + if (!button->priv->is_checked) + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "hover"); + + button->priv->is_hover = 1; + + return CLUTTER_ACTOR_CLASS (nbtk_button_parent_class)->enter_event (actor, event); +} + +static gboolean +nbtk_button_leave (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + NbtkButton *button = NBTK_BUTTON (actor); + + button->priv->is_hover = 0; + + if (button->priv->is_pressed) + { + NbtkButtonClass *klass = NBTK_BUTTON_GET_CLASS (button); + + clutter_ungrab_pointer (); + + button->priv->is_pressed = FALSE; + + if (klass->released) + klass->released (button); + } + + if (button->priv->is_checked) + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "checked"); + else + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, NULL); + + return CLUTTER_ACTOR_CLASS (nbtk_button_parent_class)->leave_event (actor, event); +} + +static void +nbtk_button_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NbtkButton *button = NBTK_BUTTON (gobject); + NbtkButtonPrivate *priv = NBTK_BUTTON (gobject)->priv; + + switch (prop_id) + { + case PROP_LABEL: + nbtk_button_set_label (button, g_value_get_string (value)); + break; + case PROP_TOGGLE: + nbtk_button_set_toggle_mode (button, g_value_get_boolean (value)); + break; + case PROP_ACTIVE: + nbtk_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 +nbtk_button_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NbtkButtonPrivate *priv = NBTK_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 +nbtk_button_finalize (GObject *gobject) +{ + NbtkButtonPrivate *priv = NBTK_BUTTON (gobject)->priv; + + g_free (priv->text); + + G_OBJECT_CLASS (nbtk_button_parent_class)->finalize (gobject); +} + +static void +nbtk_button_dispose (GObject *gobject) +{ + nbtk_button_dispose_old_bg (NBTK_BUTTON (gobject)); + + G_OBJECT_CLASS (nbtk_button_parent_class)->dispose (gobject); +} + +static void +nbtk_button_map (ClutterActor *self) +{ + NbtkButtonPrivate *priv = NBTK_BUTTON (self)->priv; + + CLUTTER_ACTOR_CLASS (nbtk_button_parent_class)->map (self); + + if (priv->old_bg && priv->old_bg_parented) + clutter_actor_map (priv->old_bg); +} + +static void +nbtk_button_unmap (ClutterActor *self) +{ + NbtkButtonPrivate *priv = NBTK_BUTTON (self)->priv; + + CLUTTER_ACTOR_CLASS (nbtk_button_parent_class)->unmap (self); + + if (priv->old_bg && priv->old_bg_parented) + clutter_actor_unmap (priv->old_bg); +} + +static void +nbtk_button_draw_background (NbtkWidget *widget, + ClutterActor *background, + const ClutterColor *color) +{ + NbtkButtonPrivate *priv; + + NBTK_WIDGET_CLASS (nbtk_button_parent_class)->draw_background (widget, background, color); + + priv = NBTK_BUTTON (widget)->priv; + + if (priv->old_bg && priv->old_bg_parented) + clutter_actor_paint (priv->old_bg); +} + +static void +nbtk_button_class_init (NbtkButtonClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + NbtkWidgetClass *widget_class = NBTK_WIDGET_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (NbtkButtonPrivate)); + + klass->pressed = nbtk_button_real_pressed; + klass->released = nbtk_button_real_released; + + gobject_class->set_property = nbtk_button_set_property; + gobject_class->get_property = nbtk_button_get_property; + gobject_class->dispose = nbtk_button_dispose; + gobject_class->finalize = nbtk_button_finalize; + + actor_class->button_press_event = nbtk_button_button_press; + actor_class->button_release_event = nbtk_button_button_release; + actor_class->enter_event = nbtk_button_enter; + actor_class->leave_event = nbtk_button_leave; + + actor_class->map = nbtk_button_map; + actor_class->unmap = nbtk_button_unmap; + + widget_class->draw_background = nbtk_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); + + + /** + * NbtkButton::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 (NbtkButtonClass, clicked), + NULL, NULL, + _nbtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +nbtk_button_init (NbtkButton *button) +{ + button->priv = NBTK_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 (nbtk_button_style_changed), NULL); + + g_signal_connect (button, "stylable-changed", + G_CALLBACK (nbtk_button_stylable_changed), NULL); +} + +/** + * nbtk_button_new: + * + * Create a new button + * + * Returns: a new #NbtkButton + */ +NbtkWidget * +nbtk_button_new (void) +{ + return g_object_new (NBTK_TYPE_BUTTON, NULL); +} + +/** + * nbtk_button_new_with_label: + * @text: text to set the label to + * + * Create a new #NbtkButton with the specified label + * + * Returns: a new #NbtkButton + */ +NbtkWidget * +nbtk_button_new_with_label (const gchar *text) +{ + return g_object_new (NBTK_TYPE_BUTTON, "label", text, NULL); +} + +/** + * nbtk_button_get_label: + * @button: a #NbtkButton + * + * 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 * +nbtk_button_get_label (NbtkButton *button) +{ + g_return_val_if_fail (NBTK_IS_BUTTON (button), NULL); + + return button->priv->text; +} + +/** + * nbtk_button_set_label: + * @button: a #Nbtkbutton + * @text: text to set the label to + * + * Sets the text displayed on the button + */ +void +nbtk_button_set_label (NbtkButton *button, + const gchar *text) +{ + NbtkButtonPrivate *priv; + ClutterActor *label; + + g_return_if_fail (NBTK_IS_BUTTON (button)); + + priv = button->priv; + + g_free (priv->text); + + if (text) + priv->text = g_strdup (text); + else + priv->text = g_strdup (""); + + label = nbtk_bin_get_child ((NbtkBin*) 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); + nbtk_bin_set_child ((NbtkBin*) button, label); + } + + nbtk_stylable_changed ((NbtkStylable*) button); + + g_object_notify (G_OBJECT (button), "label"); +} + +/** + * nbtk_button_get_toggle_mode: + * @button: a #NbtkButton + * + * Get the toggle mode status of the button. + * + * Returns: #TRUE if toggle mode is set, otherwise #FALSE + */ +gboolean +nbtk_button_get_toggle_mode (NbtkButton *button) +{ + g_return_val_if_fail (NBTK_IS_BUTTON (button), FALSE); + + return button->priv->is_toggle; +} + +/** + * nbtk_button_set_toggle_mode: + * @button: a #Nbtkbutton + * @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 +nbtk_button_set_toggle_mode (NbtkButton *button, + gboolean toggle) +{ + g_return_if_fail (NBTK_IS_BUTTON (button)); + + button->priv->is_toggle = toggle; + + g_object_notify (G_OBJECT (button), "toggle-mode"); +} + +/** + * nbtk_button_get_checked: + * @button: a #NbtkButton + * + * Get the state of the button that is in toggle mode. + * + * Returns: #TRUE if the button is checked, or #FALSE if not + */ +gboolean +nbtk_button_get_checked (NbtkButton *button) +{ + g_return_val_if_fail (NBTK_IS_BUTTON (button), FALSE); + + return button->priv->is_checked; +} + +/** + * nbtk_button_set_checked: + * @button: a #Nbtkbutton + * @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 +nbtk_button_set_checked (NbtkButton *button, + gboolean checked) +{ + g_return_if_fail (NBTK_IS_BUTTON (button)); + + if (button->priv->is_checked != checked) + { + button->priv->is_checked = checked; + + if (checked) + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "checked"); + else + if (button->priv->is_hover) + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, "hover"); + else + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) button, NULL); + } + + g_object_notify (G_OBJECT (button), "checked"); +} diff --git a/src/nbtk/nbtk-button.h b/src/nbtk/nbtk-button.h new file mode 100644 index 000000000..711417ca4 --- /dev/null +++ b/src/nbtk/nbtk-button.h @@ -0,0 +1,90 @@ +/* + * nbtk-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(NBTK_H_INSIDE) && !defined(NBTK_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __NBTK_BUTTON_H__ +#define __NBTK_BUTTON_H__ + +G_BEGIN_DECLS + +#include + +#define NBTK_TYPE_BUTTON (nbtk_button_get_type ()) +#define NBTK_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NBTK_TYPE_BUTTON, NbtkButton)) +#define NBTK_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NBTK_TYPE_BUTTON)) +#define NBTK_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NBTK_TYPE_BUTTON, NbtkButtonClass)) +#define NBTK_IS_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NBTK_TYPE_BUTTON)) +#define NBTK_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NBTK_TYPE_BUTTON, NbtkButtonClass)) + +typedef struct _NbtkButton NbtkButton; +typedef struct _NbtkButtonPrivate NbtkButtonPrivate; +typedef struct _NbtkButtonClass NbtkButtonClass; + +/** + * NbtkButton: + * + * The contents of this structure is private and should only be accessed using + * the provided API. + */ + +struct _NbtkButton +{ + /*< private >*/ + NbtkBin parent_instance; + + NbtkButtonPrivate *priv; +}; + +struct _NbtkButtonClass +{ + NbtkBinClass parent_class; + + /* vfuncs, not signals */ + void (* pressed) (NbtkButton *button); + void (* released) (NbtkButton *button); + void (* transition) (NbtkButton *button, ClutterActor *old_bg); + + /* signals */ + void (* clicked) (NbtkButton *button); +}; + +GType nbtk_button_get_type (void) G_GNUC_CONST; + +NbtkWidget * nbtk_button_new (void); +NbtkWidget * nbtk_button_new_with_label (const gchar *text); +G_CONST_RETURN gchar *nbtk_button_get_label (NbtkButton *button); +void nbtk_button_set_label (NbtkButton *button, + const gchar *text); +void nbtk_button_set_toggle_mode (NbtkButton *button, gboolean toggle); +gboolean nbtk_button_get_toggle_mode (NbtkButton *button); +void nbtk_button_set_checked (NbtkButton *button, gboolean checked); +gboolean nbtk_button_get_checked (NbtkButton *button); + +G_END_DECLS + +#endif /* __NBTK_BUTTON_H__ */ diff --git a/src/nbtk/nbtk-scroll-bar.c b/src/nbtk/nbtk-scroll-bar.c new file mode 100644 index 000000000..2c678337d --- /dev/null +++ b/src/nbtk/nbtk-scroll-bar.c @@ -0,0 +1,1094 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "nbtk-scroll-bar.h" +#include "nbtk-bin.h" +#include "nbtk-marshal.h" +#include "nbtk-stylable.h" +#include "nbtk-enum-types.h" +#include "nbtk-private.h" +#include "nbtk-button.h" + +static void nbtk_stylable_iface_init (NbtkStylableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (NbtkScrollBar, nbtk_scroll_bar, NBTK_TYPE_BIN, + G_IMPLEMENT_INTERFACE (NBTK_TYPE_STYLABLE, + nbtk_stylable_iface_init)) + +#define NBTK_SCROLL_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NBTK_TYPE_SCROLL_BAR, NbtkScrollBarPrivate)) + +#define PAGING_INITIAL_REPEAT_TIMEOUT 500 +#define PAGING_SUBSEQUENT_REPEAT_TIMEOUT 200 + +struct _NbtkScrollBarPrivate +{ + NbtkAdjustment *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, + NbtkScrollBar *bar); + +static void +nbtk_scroll_bar_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NbtkScrollBarPrivate *priv = NBTK_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 +nbtk_scroll_bar_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NbtkScrollBar *bar = NBTK_SCROLL_BAR (gobject); + + switch (prop_id) + { + case PROP_ADJUSTMENT: + nbtk_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 +nbtk_scroll_bar_dispose (GObject *gobject) +{ + NbtkScrollBar *bar = NBTK_SCROLL_BAR (gobject); + NbtkScrollBarPrivate *priv = bar->priv; + + if (priv->adjustment) + nbtk_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 (nbtk_scroll_bar_parent_class)->dispose (gobject); +} + +static void +nbtk_scroll_bar_paint (ClutterActor *actor) +{ + NbtkScrollBarPrivate *priv = NBTK_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (nbtk_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 +nbtk_scroll_bar_pick (ClutterActor *actor, + const ClutterColor *pick_color) +{ + NbtkScrollBarPrivate *priv = NBTK_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (nbtk_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 +nbtk_scroll_bar_map (ClutterActor *actor) +{ + NbtkScrollBarPrivate *priv = NBTK_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (nbtk_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 +nbtk_scroll_bar_unmap (ClutterActor *actor) +{ + NbtkScrollBarPrivate *priv = NBTK_SCROLL_BAR (actor)->priv; + + CLUTTER_ACTOR_CLASS (nbtk_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 +nbtk_scroll_bar_allocate (ClutterActor *actor, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + NbtkScrollBarPrivate *priv = NBTK_SCROLL_BAR (actor)->priv; + NbtkPadding padding; + ClutterActorBox bw_box, fw_box, trough_box; + gfloat x, y, width, height, stepper_size; + + /* Chain up */ + CLUTTER_ACTOR_CLASS (nbtk_scroll_bar_parent_class)-> + allocate (actor, box, flags); + + nbtk_widget_get_padding (NBTK_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; + + nbtk_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); + + nbtk_stylable_get (NBTK_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 +nbtk_scroll_bar_style_changed (NbtkWidget *widget) +{ + NbtkScrollBarPrivate *priv = NBTK_SCROLL_BAR (widget)->priv; + + nbtk_stylable_changed ((NbtkStylable *) priv->bw_stepper); + nbtk_stylable_changed ((NbtkStylable *) priv->fw_stepper); + nbtk_stylable_changed ((NbtkStylable *) priv->trough); + nbtk_stylable_changed ((NbtkStylable *) priv->handle); + +} + +static void +bar_reactive_notify_cb (GObject *gobject, + GParamSpec *arg1, + gpointer user_data) +{ + NbtkScrollBar *bar = NBTK_SCROLL_BAR (gobject); + + clutter_actor_set_reactive (bar->priv->handle, + clutter_actor_get_reactive (CLUTTER_ACTOR (bar))); +} + +static GObject* +nbtk_scroll_bar_constructor (GType type, + guint n_properties, + GObjectConstructParam *properties) +{ + GObjectClass *gobject_class; + GObject *obj; + NbtkScrollBar *bar; + NbtkScrollBarPrivate *priv; + + gobject_class = G_OBJECT_CLASS (nbtk_scroll_bar_parent_class); + obj = gobject_class->constructor (type, n_properties, properties); + + bar = NBTK_SCROLL_BAR (obj); + priv = NBTK_SCROLL_BAR_GET_PRIVATE (bar); + + g_signal_connect (bar, "notify::reactive", + G_CALLBACK (bar_reactive_notify_cb), NULL); + + return obj; +} + +static gboolean +nbtk_scroll_bar_scroll_event (ClutterActor *actor, + ClutterScrollEvent *event) +{ + NbtkScrollBarPrivate *priv = NBTK_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 + nbtk_adjustment_set_value (priv->adjustment, value - step); + break; + case CLUTTER_SCROLL_DOWN: + case CLUTTER_SCROLL_RIGHT: + if (value == upper) + return FALSE; + else + nbtk_adjustment_set_value (priv->adjustment, value + step); + break; + } + + return TRUE; +} + +static void +nbtk_scroll_bar_class_init (NbtkScrollBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (NbtkScrollBarPrivate)); + + object_class->get_property = nbtk_scroll_bar_get_property; + object_class->set_property = nbtk_scroll_bar_set_property; + object_class->dispose = nbtk_scroll_bar_dispose; + object_class->constructor = nbtk_scroll_bar_constructor; + + actor_class->allocate = nbtk_scroll_bar_allocate; + actor_class->paint = nbtk_scroll_bar_paint; + actor_class->pick = nbtk_scroll_bar_pick; + actor_class->scroll_event = nbtk_scroll_bar_scroll_event; + actor_class->map = nbtk_scroll_bar_map; + actor_class->unmap = nbtk_scroll_bar_unmap; + + g_object_class_install_property + (object_class, + PROP_ADJUSTMENT, + g_param_spec_object ("adjustment", + "Adjustment", + "The adjustment", + NBTK_TYPE_ADJUSTMENT, + NBTK_PARAM_READWRITE)); + + pspec = g_param_spec_boolean ("vertical", + "Vertical Orientation", + "Vertical Orientation", + FALSE, + NBTK_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 (NbtkScrollBarClass, 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 (NbtkScrollBarClass, scroll_stop), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +nbtk_stylable_iface_init (NbtkStylableIface *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); + nbtk_stylable_iface_install_property (iface, + NBTK_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); + nbtk_stylable_iface_install_property (iface, + NBTK_TYPE_SCROLL_BAR, pspec); + } +} + +static void +move_slider (NbtkScrollBar *bar, gfloat x, gfloat y) +{ + NbtkScrollBarPrivate *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); + + nbtk_adjustment_get_values (priv->adjustment, + NULL, + &lower, + &upper, + NULL, + NULL, + &page_size); + + position = ((pos / size) + * (upper - lower - page_size)) + + lower; + + nbtk_adjustment_set_value (priv->adjustment, position); +} + +static gboolean +handle_capture_event_cb (ClutterActor *trough, + ClutterEvent *event, + NbtkScrollBar *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) + { + nbtk_widget_set_style_pseudo_class ((NbtkWidget*) bar->priv->handle, NULL); + } + + + } + + return TRUE; +} + +static gboolean +handle_button_press_event_cb (ClutterActor *actor, + ClutterButtonEvent *event, + NbtkScrollBar *bar) +{ + NbtkScrollBarPrivate *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, + NbtkScrollBarPrivate *priv) +{ + g_object_unref (priv->paging_animation); + priv->paging_animation = NULL; +} + +static gboolean +trough_paging_cb (NbtkScrollBar *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 */ + nbtk_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, + NbtkScrollBar *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, + NbtkScrollBar *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, + NbtkScrollBar *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 (NbtkScrollBarPrivate *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 (NbtkScrollBarPrivate *priv) +{ + stepper_move_on (priv, CLUTTER_LINEAR); + + return TRUE; +} + +static gboolean +stepper_button_repeat_timeout (NbtkScrollBarPrivate *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, + NbtkScrollBar *bar) +{ + NbtkScrollBarPrivate *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, + NbtkScrollBar *self) +{ + if (event->button != 1) + return FALSE; + + g_source_remove (self->priv->stepper_source_id); + + return FALSE; +} + +static void +nbtk_scroll_bar_notify_reactive (NbtkScrollBar *self) +{ + NbtkScrollBarPrivate *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 +nbtk_scroll_bar_init (NbtkScrollBar *self) +{ + self->priv = NBTK_SCROLL_BAR_GET_PRIVATE (self); + + self->priv->bw_stepper = (ClutterActor *) nbtk_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 *) nbtk_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 *) nbtk_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 *) nbtk_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 (nbtk_scroll_bar_style_changed), NULL); + g_signal_connect (self, "notify::reactive", + G_CALLBACK (nbtk_scroll_bar_notify_reactive), NULL); +} + +NbtkWidget * +nbtk_scroll_bar_new (NbtkAdjustment *adjustment) +{ + return g_object_new (NBTK_TYPE_SCROLL_BAR, + "adjustment", adjustment, + NULL); +} + +void +nbtk_scroll_bar_set_adjustment (NbtkScrollBar *bar, + NbtkAdjustment *adjustment) +{ + NbtkScrollBarPrivate *priv; + + g_return_if_fail (NBTK_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)); + } +} + +NbtkAdjustment * +nbtk_scroll_bar_get_adjustment (NbtkScrollBar *bar) +{ + g_return_val_if_fail (NBTK_IS_SCROLL_BAR (bar), NULL); + + return bar->priv->adjustment; +} diff --git a/src/nbtk/nbtk-scroll-bar.h b/src/nbtk/nbtk-scroll-bar.h new file mode 100644 index 000000000..5dfd24d1c --- /dev/null +++ b/src/nbtk/nbtk-scroll-bar.h @@ -0,0 +1,81 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#if !defined(NBTK_H_INSIDE) && !defined(NBTK_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __NBTK_SCROLL_BAR_H__ +#define __NBTK_SCROLL_BAR_H__ + +#include +#include + +G_BEGIN_DECLS + +#define NBTK_TYPE_SCROLL_BAR (nbtk_scroll_bar_get_type()) +#define NBTK_SCROLL_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NBTK_TYPE_SCROLL_BAR, NbtkScrollBar)) +#define NBTK_IS_SCROLL_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NBTK_TYPE_SCROLL_BAR)) +#define NBTK_SCROLL_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NBTK_TYPE_SCROLL_BAR, NbtkScrollBarClass)) +#define NBTK_IS_SCROLL_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NBTK_TYPE_SCROLL_BAR)) +#define NBTK_SCROLL_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NBTK_TYPE_SCROLL_BAR, NbtkScrollBarClass)) + +typedef struct _NbtkScrollBar NbtkScrollBar; +typedef struct _NbtkScrollBarPrivate NbtkScrollBarPrivate; +typedef struct _NbtkScrollBarClass NbtkScrollBarClass; + +/** + * NbtkScrollBar: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _NbtkScrollBar +{ + /*< private >*/ + NbtkBin parent_instance; + + NbtkScrollBarPrivate *priv; +}; + +struct _NbtkScrollBarClass +{ + NbtkBinClass parent_class; + + /* signals */ + void (*scroll_start) (NbtkScrollBar *bar); + void (*scroll_stop) (NbtkScrollBar *bar); +}; + +GType nbtk_scroll_bar_get_type (void) G_GNUC_CONST; + +NbtkWidget * nbtk_scroll_bar_new (NbtkAdjustment *adjustment); +void nbtk_scroll_bar_set_adjustment (NbtkScrollBar *bar, + NbtkAdjustment *adjustment); +NbtkAdjustment *nbtk_scroll_bar_get_adjustment (NbtkScrollBar *bar); + +G_END_DECLS + +#endif /* __NBTK_SCROLL_BAR_H__ */ diff --git a/src/nbtk/nbtk-scroll-view.c b/src/nbtk/nbtk-scroll-view.c new file mode 100644 index 000000000..7ea0e6682 --- /dev/null +++ b/src/nbtk/nbtk-scroll-view.c @@ -0,0 +1,836 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#include "nbtk-scroll-view.h" +#include "nbtk-marshal.h" +#include "nbtk-scroll-bar.h" +#include "nbtk-scrollable.h" +#include "nbtk-stylable.h" +#include + +static void clutter_container_iface_init (ClutterContainerIface *iface); +static void nbtk_stylable_iface_init (NbtkStylableIface *iface); + +static ClutterContainerIface *nbtk_scroll_view_parent_iface = NULL; + +G_DEFINE_TYPE_WITH_CODE (NbtkScrollView, nbtk_scroll_view, NBTK_TYPE_BIN, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, + clutter_container_iface_init) + G_IMPLEMENT_INTERFACE (NBTK_TYPE_STYLABLE, + nbtk_stylable_iface_init)) + +#define SCROLL_VIEW_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + NBTK_TYPE_SCROLL_VIEW, \ + NbtkScrollViewPrivate)) + +struct _NbtkScrollViewPrivate +{ + /* a pointer to the child; this is actually stored + * inside NbtkBin:child, but we keep it to avoid + * calling nbtk_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 +nbtk_scroll_view_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + NbtkScrollViewPrivate *priv = ((NbtkScrollView *)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 +nbtk_scroll_view_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_MOUSE_SCROLL: + nbtk_scroll_view_set_mouse_scrolling ((NbtkScrollView *) object, + g_value_get_boolean (value)); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +nbtk_scroll_view_dispose (GObject *object) +{ + NbtkScrollViewPrivate *priv = NBTK_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 (nbtk_scroll_view_parent_class)->dispose (object); +} + +static void +nbtk_scroll_view_finalize (GObject *object) +{ + G_OBJECT_CLASS (nbtk_scroll_view_parent_class)->finalize (object); +} + +static void +nbtk_scroll_view_paint (ClutterActor *actor) +{ + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (actor)->priv; + + /* NbtkBin will paint the child */ + CLUTTER_ACTOR_CLASS (nbtk_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 +nbtk_scroll_view_pick (ClutterActor *actor, const ClutterColor *color) +{ + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (actor)->priv; + + /* Chain up so we get a bounding box pained (if we are reactive) */ + CLUTTER_ACTOR_CLASS (nbtk_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 +nbtk_scroll_view_get_preferred_width (ClutterActor *actor, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + NbtkPadding padding; + guint xthickness; + + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (actor)->priv; + + if (!priv->child) + return; + + nbtk_widget_get_padding (NBTK_WIDGET (actor), &padding); + nbtk_stylable_get (NBTK_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 +nbtk_scroll_view_get_preferred_height (ClutterActor *actor, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + NbtkPadding padding; + guint ythickness; + + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (actor)->priv; + + if (!priv->child) + return; + + nbtk_widget_get_padding (NBTK_WIDGET (actor), &padding); + nbtk_stylable_get (NBTK_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 +nbtk_scroll_view_allocate (ClutterActor *actor, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + NbtkPadding padding; + ClutterActorBox child_box; + ClutterActorClass *parent_parent_class; + gfloat avail_width, avail_height, sb_width, sb_height; + + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (actor)->priv; + + /* Chain up to the parent's parent class + * + * We do this because we do not want NbtkBin 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 (nbtk_scroll_view_parent_class); + + CLUTTER_ACTOR_CLASS (parent_parent_class)-> + allocate (actor, box, flags); + + + nbtk_widget_get_padding (NBTK_WIDGET (actor), &padding); + + avail_width = (box->x2 - box->x1) - padding.left - padding.right; + avail_height = (box->y2 - box->y1) - padding.top - padding.bottom; + + nbtk_stylable_get (NBTK_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 +nbtk_scroll_view_style_changed (NbtkWidget *widget) +{ + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (widget)->priv; + + nbtk_stylable_changed ((NbtkStylable *) priv->hscroll); + nbtk_stylable_changed ((NbtkStylable *) priv->vscroll); +} + +static gboolean +nbtk_scroll_view_scroll_event (ClutterActor *self, + ClutterScrollEvent *event) +{ + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (self)->priv; + gdouble lower, value, upper, step; + NbtkAdjustment *vadjustment, *hadjustment; + + /* don't handle scroll events if requested not to */ + if (!priv->mouse_scroll) + return FALSE; + + hadjustment = nbtk_scroll_bar_get_adjustment (NBTK_SCROLL_BAR(priv->hscroll)); + vadjustment = nbtk_scroll_bar_get_adjustment (NBTK_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 + nbtk_adjustment_set_value (vadjustment, value - step); + break; + case CLUTTER_SCROLL_DOWN: + if (value == upper) + return FALSE; + else + nbtk_adjustment_set_value (vadjustment, value + step); + break; + case CLUTTER_SCROLL_LEFT: + if (value == lower) + return FALSE; + else + nbtk_adjustment_set_value (hadjustment, value - step); + break; + case CLUTTER_SCROLL_RIGHT: + if (value == upper) + return FALSE; + else + nbtk_adjustment_set_value (hadjustment, value + step); + break; + } + + return TRUE; +} + +static void +nbtk_scroll_view_class_init (NbtkScrollViewClass *klass) +{ + GParamSpec *pspec; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NbtkScrollViewPrivate)); + + object_class->get_property = nbtk_scroll_view_get_property; + object_class->set_property = nbtk_scroll_view_set_property; + object_class->dispose= nbtk_scroll_view_dispose; + object_class->finalize = nbtk_scroll_view_finalize; + + actor_class->paint = nbtk_scroll_view_paint; + actor_class->pick = nbtk_scroll_view_pick; + actor_class->get_preferred_width = nbtk_scroll_view_get_preferred_width; + actor_class->get_preferred_height = nbtk_scroll_view_get_preferred_height; + actor_class->allocate = nbtk_scroll_view_allocate; + actor_class->scroll_event = nbtk_scroll_view_scroll_event; + + g_object_class_install_property (object_class, + PROP_HSCROLL, + g_param_spec_object ("hscroll", + "NbtkScrollBar", + "Horizontal scroll indicator", + NBTK_TYPE_SCROLL_BAR, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_VSCROLL, + g_param_spec_object ("vscroll", + "NbtkScrollBar", + "Vertical scroll indicator", + NBTK_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 +nbtk_stylable_iface_init (NbtkStylableIface *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); + nbtk_stylable_iface_install_property (iface, NBTK_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); + nbtk_stylable_iface_install_property (iface, NBTK_TYPE_SCROLL_VIEW, pspec); + } +} + +static void +child_adjustment_changed_cb (NbtkAdjustment *adjustment, + ClutterActor *bar) +{ + NbtkScrollView *scroll; + gdouble lower, upper, page_size; + + scroll = NBTK_SCROLL_VIEW (clutter_actor_get_parent (bar)); + + /* Determine if this scroll-bar should be visible */ + nbtk_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) +{ + NbtkAdjustment *hadjust; + + ClutterActor *actor = CLUTTER_ACTOR (gobject); + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (user_data)->priv; + + hadjust = nbtk_scroll_bar_get_adjustment (NBTK_SCROLL_BAR(priv->hscroll)); + if (hadjust) + g_signal_handlers_disconnect_by_func (hadjust, + child_adjustment_changed_cb, + priv->hscroll); + + nbtk_scrollable_get_adjustments (NBTK_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); + } + + nbtk_scroll_bar_set_adjustment (NBTK_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) +{ + NbtkAdjustment *vadjust; + + ClutterActor *actor = CLUTTER_ACTOR (gobject); + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (user_data)->priv; + + vadjust = nbtk_scroll_bar_get_adjustment (NBTK_SCROLL_BAR(priv->vscroll)); + if (vadjust) + g_signal_handlers_disconnect_by_func (vadjust, + child_adjustment_changed_cb, + priv->vscroll); + + nbtk_scrollable_get_adjustments (NBTK_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); + } + + nbtk_scroll_bar_set_adjustment (NBTK_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 +nbtk_scroll_view_init (NbtkScrollView *self) +{ + NbtkScrollViewPrivate *priv = self->priv = SCROLL_VIEW_PRIVATE (self); + + priv->hscroll = CLUTTER_ACTOR (nbtk_scroll_bar_new (NULL)); + priv->vscroll = g_object_new (NBTK_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 (nbtk_scroll_view_style_changed), NULL); +} + +static void +nbtk_scroll_view_add (ClutterContainer *container, + ClutterActor *actor) +{ + NbtkScrollView *self = NBTK_SCROLL_VIEW (container); + NbtkScrollViewPrivate *priv = self->priv; + + if (NBTK_IS_SCROLLABLE (actor)) + { + priv->child = actor; + + /* chain up to NbtkBin::add() */ + nbtk_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 NbtkScrollView, but the actor does " + "not implement NbtkScrollable.", + g_type_name (G_OBJECT_TYPE (actor))); + } +} + +static void +nbtk_scroll_view_remove (ClutterContainer *container, + ClutterActor *actor) +{ + NbtkScrollViewPrivate *priv = NBTK_SCROLL_VIEW (container)->priv; + + if (actor == priv->child) + { + g_object_ref (priv->child); + + /* chain up to NbtkBin::remove() */ + nbtk_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); + nbtk_scrollable_set_adjustments ((NbtkScrollable*) priv->child, NULL, NULL); + + g_object_unref (priv->child); + priv->child = NULL; + } +} + +static void +nbtk_scroll_view_foreach_with_internals (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data) +{ + NbtkScrollViewPrivate *priv = NBTK_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 NbtkBin implementation of + * ClutterContainer so that we can chain up when + * overriding the methods + */ + nbtk_scroll_view_parent_iface = g_type_interface_peek_parent (iface); + + iface->add = nbtk_scroll_view_add; + iface->remove = nbtk_scroll_view_remove; + iface->foreach_with_internals = nbtk_scroll_view_foreach_with_internals; +} + +NbtkWidget * +nbtk_scroll_view_new (void) +{ + return g_object_new (NBTK_TYPE_SCROLL_VIEW, NULL); +} + +ClutterActor * +nbtk_scroll_view_get_hscroll_bar (NbtkScrollView *scroll) +{ + g_return_val_if_fail (NBTK_IS_SCROLL_VIEW (scroll), NULL); + + return scroll->priv->hscroll; +} + +ClutterActor * +nbtk_scroll_view_get_vscroll_bar (NbtkScrollView *scroll) +{ + g_return_val_if_fail (NBTK_IS_SCROLL_VIEW (scroll), NULL); + + return scroll->priv->vscroll; +} + +gfloat +nbtk_scroll_view_get_column_size (NbtkScrollView *scroll) +{ + NbtkAdjustment *adjustment; + gdouble column_size; + + g_return_val_if_fail (scroll, 0); + + adjustment = nbtk_scroll_bar_get_adjustment ( + NBTK_SCROLL_BAR (scroll->priv->hscroll)); + g_object_get (adjustment, + "step-increment", &column_size, + NULL); + + return column_size; +} + +void +nbtk_scroll_view_set_column_size (NbtkScrollView *scroll, + gfloat column_size) +{ + NbtkAdjustment *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 = nbtk_scroll_bar_get_adjustment ( + NBTK_SCROLL_BAR (scroll->priv->hscroll)); + + if (adjustment) + g_object_set (adjustment, + "step-increment", (gdouble) scroll->priv->column_size, + NULL); + } +} + +gfloat +nbtk_scroll_view_get_row_size (NbtkScrollView *scroll) +{ + NbtkAdjustment *adjustment; + gdouble row_size; + + g_return_val_if_fail (scroll, 0); + + adjustment = nbtk_scroll_bar_get_adjustment ( + NBTK_SCROLL_BAR (scroll->priv->vscroll)); + g_object_get (adjustment, + "step-increment", &row_size, + NULL); + + return row_size; +} + +void +nbtk_scroll_view_set_row_size (NbtkScrollView *scroll, + gfloat row_size) +{ + NbtkAdjustment *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 = nbtk_scroll_bar_get_adjustment ( + NBTK_SCROLL_BAR (scroll->priv->vscroll)); + + if (adjustment) + g_object_set (adjustment, + "step-increment", (gdouble) scroll->priv->row_size, + NULL); + } +} + +void +nbtk_scroll_view_set_mouse_scrolling (NbtkScrollView *scroll, + gboolean enabled) +{ + NbtkScrollViewPrivate *priv; + + g_return_if_fail (NBTK_IS_SCROLL_VIEW (scroll)); + + priv = NBTK_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 +nbtk_scroll_view_get_mouse_scrolling (NbtkScrollView *scroll) +{ + NbtkScrollViewPrivate *priv; + + g_return_val_if_fail (NBTK_IS_SCROLL_VIEW (scroll), FALSE); + + priv = NBTK_SCROLL_VIEW (scroll)->priv; + + return priv->mouse_scroll; +} diff --git a/src/nbtk/nbtk-scroll-view.h b/src/nbtk/nbtk-scroll-view.h new file mode 100644 index 000000000..0c07398e3 --- /dev/null +++ b/src/nbtk/nbtk-scroll-view.h @@ -0,0 +1,88 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#if !defined(NBTK_H_INSIDE) && !defined(NBTK_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __NBTK_SCROLL_VIEW_H__ +#define __NBTK_SCROLL_VIEW_H__ + +#include + +G_BEGIN_DECLS + +#define NBTK_TYPE_SCROLL_VIEW (nbtk_scroll_view_get_type()) +#define NBTK_SCROLL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NBTK_TYPE_SCROLL_VIEW, NbtkScrollView)) +#define NBTK_IS_SCROLL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NBTK_TYPE_SCROLL_VIEW)) +#define NBTK_SCROLL_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NBTK_TYPE_SCROLL_VIEW, NbtkScrollViewClass)) +#define NBTK_IS_SCROLL_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NBTK_TYPE_SCROLL_VIEW)) +#define NBTK_SCROLL_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NBTK_TYPE_SCROLL_VIEW, NbtkScrollViewClass)) + +typedef struct _NbtkScrollView NbtkScrollView; +typedef struct _NbtkScrollViewPrivate NbtkScrollViewPrivate; +typedef struct _NbtkScrollViewClass NbtkScrollViewClass; + +/** + * NbtkScrollView: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _NbtkScrollView +{ + /*< private >*/ + NbtkBin parent_instance; + + NbtkScrollViewPrivate *priv; +}; + +struct _NbtkScrollViewClass +{ + NbtkBinClass parent_class; +}; + +GType nbtk_scroll_view_get_type (void) G_GNUC_CONST; + +NbtkWidget * nbtk_scroll_view_new (void); + +ClutterActor * nbtk_scroll_view_get_hscroll_bar (NbtkScrollView *scroll); +ClutterActor * nbtk_scroll_view_get_vscroll_bar (NbtkScrollView *scroll); +ClutterActor * nbtk_scroll_view_get_child (NbtkScrollView *scroll); + +gfloat nbtk_scroll_view_get_column_size (NbtkScrollView *scroll); +void nbtk_scroll_view_set_column_size (NbtkScrollView *scroll, + gfloat column_size); + +gfloat nbtk_scroll_view_get_row_size (NbtkScrollView *scroll); +void nbtk_scroll_view_set_row_size (NbtkScrollView *scroll, + gfloat row_size); + +void nbtk_scroll_view_set_mouse_scrolling (NbtkScrollView *scroll, gboolean enabled); +gboolean nbtk_scroll_view_get_mouse_scrolling (NbtkScrollView *scroll); + +G_END_DECLS + +#endif /* __NBTK_SCROLL_VIEW_H__ */ diff --git a/src/nbtk/nbtk-scrollable.c b/src/nbtk/nbtk-scrollable.c new file mode 100644 index 000000000..aed1e9eea --- /dev/null +++ b/src/nbtk/nbtk-scrollable.c @@ -0,0 +1,88 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#include "nbtk-scrollable.h" + +static void +nbtk_scrollable_base_init (gpointer g_iface) +{ + static gboolean initialized = FALSE; + + if (!initialized) + { + g_object_interface_install_property (g_iface, + g_param_spec_object ("hadjustment", + "NbtkAdjustment", + "Horizontal adjustment", + NBTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + + g_object_interface_install_property (g_iface, + g_param_spec_object ("vadjustment", + "NbtkAdjustment", + "Vertical adjustment", + NBTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + + initialized = TRUE; + } +} + +GType +nbtk_scrollable_get_type (void) +{ + static GType type = 0; + if (type == 0) + { + static const GTypeInfo info = + { + sizeof (NbtkScrollableInterface), + nbtk_scrollable_base_init, /* base_init */ + NULL, + }; + type = g_type_register_static (G_TYPE_INTERFACE, + "NbtkScrollable", &info, 0); + } + return type; +} + +void +nbtk_scrollable_set_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment *hadjustment, + NbtkAdjustment *vadjustment) +{ + NBTK_SCROLLABLE_GET_INTERFACE (scrollable)->set_adjustments (scrollable, + hadjustment, + vadjustment); +} + +void +nbtk_scrollable_get_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment **hadjustment, + NbtkAdjustment **vadjustment) +{ + NBTK_SCROLLABLE_GET_INTERFACE (scrollable)->get_adjustments (scrollable, + hadjustment, + vadjustment); +} diff --git a/src/nbtk/nbtk-scrollable.h b/src/nbtk/nbtk-scrollable.h new file mode 100644 index 000000000..d8a623430 --- /dev/null +++ b/src/nbtk/nbtk-scrollable.h @@ -0,0 +1,69 @@ +/* + * nbtk-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 Nbtk by: Robert Staudinger + * + */ + +#if !defined(NBTK_H_INSIDE) && !defined(NBTK_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __NBTK_SCROLLABLE_H__ +#define __NBTK_SCROLLABLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define NBTK_TYPE_SCROLLABLE (nbtk_scrollable_get_type ()) +#define NBTK_SCROLLABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NBTK_TYPE_SCROLLABLE, NbtkScrollable)) +#define NBTK_IS_SCROLLABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NBTK_TYPE_SCROLLABLE)) +#define NBTK_SCROLLABLE_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), NBTK_TYPE_SCROLLABLE, NbtkScrollableInterface)) + +typedef struct _NbtkScrollable NbtkScrollable; /* Dummy object */ +typedef struct _NbtkScrollableInterface NbtkScrollableInterface; + +struct _NbtkScrollableInterface +{ + GTypeInterface parent; + + void (* set_adjustments) (NbtkScrollable *scrollable, + NbtkAdjustment *hadjustment, + NbtkAdjustment *vadjustment); + void (* get_adjustments) (NbtkScrollable *scrollable, + NbtkAdjustment **hadjustment, + NbtkAdjustment **vadjustment); +}; + +GType nbtk_scrollable_get_type (void) G_GNUC_CONST; + +void nbtk_scrollable_set_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment *hadjustment, + NbtkAdjustment *vadjustment); +void nbtk_scrollable_get_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment **hadjustment, + NbtkAdjustment **vadjustment); + +G_END_DECLS + +#endif /* __NBTK_SCROLLABLE_H__ */ diff --git a/src/nbtk/nbtk-viewport.c b/src/nbtk/nbtk-viewport.c new file mode 100644 index 000000000..c7de348e2 --- /dev/null +++ b/src/nbtk/nbtk-viewport.c @@ -0,0 +1,668 @@ +/* + * nbtk-viewport.c: Viewport 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 Nbtk by: Robert Staudinger + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "nbtk-viewport.h" +#include "nbtk-adjustment.h" +#include "nbtk-scrollable.h" +#include "nbtk-private.h" +#include "nbtk-bin.h" + +static void scrollable_interface_init (NbtkScrollableInterface *iface); + +static void scrollable_set_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment *hadjustment, + NbtkAdjustment *vadjustment); + +static void scrollable_get_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment **hadjustment, + NbtkAdjustment **vadjustment); + +G_DEFINE_TYPE_WITH_CODE (NbtkViewport, nbtk_viewport, NBTK_TYPE_BIN, + G_IMPLEMENT_INTERFACE (NBTK_TYPE_SCROLLABLE, + scrollable_interface_init)) + +#define VIEWPORT_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), NBTK_TYPE_VIEWPORT, \ + NbtkViewportPrivate)) + +struct _NbtkViewportPrivate +{ + gfloat x; + gfloat y; + gfloat z; + + NbtkAdjustment *hadjustment; + NbtkAdjustment *vadjustment; + + gboolean sync_adjustments; +}; + +enum +{ + PROP_0, + + PROP_X_ORIGIN, + PROP_Y_ORIGIN, + PROP_Z_ORIGIN, + PROP_HADJUST, + PROP_VADJUST, + PROP_SYNC_ADJUST +}; + +static void +nbtk_viewport_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NbtkAdjustment *adjustment; + + NbtkViewportPrivate *priv = NBTK_VIEWPORT (object)->priv; + + switch (prop_id) + { + case PROP_X_ORIGIN: + g_value_set_int (value, (int) priv->x); + break; + + case PROP_Y_ORIGIN: + g_value_set_int (value, (int) priv->y); + break; + + case PROP_Z_ORIGIN: + g_value_set_int (value, (int) priv->z); + break; + + case PROP_HADJUST : + scrollable_get_adjustments (NBTK_SCROLLABLE (object), &adjustment, NULL); + g_value_set_object (value, adjustment); + break; + + case PROP_VADJUST : + scrollable_get_adjustments (NBTK_SCROLLABLE (object), NULL, &adjustment); + g_value_set_object (value, adjustment); + break; + + case PROP_SYNC_ADJUST : + g_value_set_boolean (value, priv->sync_adjustments); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nbtk_viewport_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NbtkViewport *viewport = NBTK_VIEWPORT (object); + NbtkViewportPrivate *priv = viewport->priv; + + switch (prop_id) + { + case PROP_X_ORIGIN: + nbtk_viewport_set_originu (viewport, + g_value_get_int (value), + priv->y, + priv->z); + break; + + case PROP_Y_ORIGIN: + nbtk_viewport_set_originu (viewport, + priv->x, + g_value_get_int (value), + priv->z); + break; + + case PROP_Z_ORIGIN: + nbtk_viewport_set_originu (viewport, + priv->x, + priv->y, + g_value_get_int (value)); + break; + + case PROP_HADJUST : + scrollable_set_adjustments (NBTK_SCROLLABLE (object), + g_value_get_object (value), + priv->vadjustment); + break; + + case PROP_VADJUST : + scrollable_set_adjustments (NBTK_SCROLLABLE (object), + priv->hadjustment, + g_value_get_object (value)); + break; + + case PROP_SYNC_ADJUST : + priv->sync_adjustments = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nbtk_viewport_dispose (GObject *gobject) +{ + NbtkViewportPrivate *priv = NBTK_VIEWPORT (gobject)->priv; + + if (priv->hadjustment) + { + g_object_unref (priv->hadjustment); + priv->hadjustment = NULL; + } + + if (priv->vadjustment) + { + g_object_unref (priv->vadjustment); + priv->vadjustment = NULL; + } + + G_OBJECT_CLASS (nbtk_viewport_parent_class)->dispose (gobject); +} + +static ClutterActor * +get_child_and_natural_size (NbtkViewport *self, + gfloat *natural_width, + gfloat *natural_height) +{ + /* NbtkBin is a single-child container, + * let it grow as big as it wants. */ + ClutterActor *child; + ClutterRequestMode mode; + + child = nbtk_bin_get_child (NBTK_BIN (self)); + if (child) + { + + g_object_get (G_OBJECT (child), "request-mode", &mode, NULL); + if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) + { + clutter_actor_get_preferred_width (child, -1, NULL, + natural_width); + clutter_actor_get_preferred_height (child, *natural_width, NULL, + natural_height); + } + else + { + clutter_actor_get_preferred_height (child, -1, NULL, + natural_height); + clutter_actor_get_preferred_width (child, *natural_height, NULL, + natural_width); + } + + return child; + } + + return NULL; +} + +static void +nbtk_viewport_paint (ClutterActor *self) +{ + NbtkViewportPrivate *priv = NBTK_VIEWPORT (self)->priv; + + cogl_push_matrix (); + + cogl_translate ((int) priv->x * -1, + (int) priv->y * -1, + (int) priv->z * -1); + + CLUTTER_ACTOR_CLASS (nbtk_viewport_parent_class)->paint (self); + + cogl_pop_matrix (); +} + +static void +nbtk_viewport_pick (ClutterActor *self, + const ClutterColor *color) +{ + nbtk_viewport_paint (self); +} + +static void +nbtk_viewport_allocate (ClutterActor *self, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + NbtkViewportPrivate *priv = NBTK_VIEWPORT (self)->priv; + ClutterActor *child; + ClutterActorBox natural_box; + gfloat natural_width, natural_height; + gfloat available_width, available_height; + + /* Chain up. */ + CLUTTER_ACTOR_CLASS (nbtk_viewport_parent_class)-> + allocate (self, box, flags); + + available_width = box->x2 - box->x1; + available_height = box->y2 - box->y1; + + natural_box.x1 = 0; + natural_box.y1 = 0; + + if (NULL != (child = get_child_and_natural_size (NBTK_VIEWPORT (self), + &natural_width, + &natural_height))) + { + natural_box.x2 = natural_width; + natural_box.y2 = natural_height; + clutter_actor_allocate (child, &natural_box, flags); + } + else + { + natural_box.x2 = available_width; + natural_box.y2 = available_height; + } + + /* Refresh adjustments */ + if (priv->sync_adjustments) + { + gdouble prev_value; + + if (priv->hadjustment) + { + g_object_set (G_OBJECT (priv->hadjustment), + "lower", 0.0, + "page-size", available_width, + "upper", natural_width, + NULL); + + /* Make sure value is clamped */ + prev_value = nbtk_adjustment_get_value (priv->hadjustment); + nbtk_adjustment_set_value (priv->hadjustment, prev_value); + } + + if (priv->vadjustment) + { + g_object_set (G_OBJECT (priv->vadjustment), + "lower", 0.0, + "page-size", available_height, + "upper", natural_height, + NULL); + + prev_value = nbtk_adjustment_get_value (priv->vadjustment); + nbtk_adjustment_set_value (priv->vadjustment, prev_value); + } + } +} + +static void +nbtk_viewport_class_init (NbtkViewportClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NbtkViewportPrivate)); + + gobject_class->get_property = nbtk_viewport_get_property; + gobject_class->set_property = nbtk_viewport_set_property; + gobject_class->dispose = nbtk_viewport_dispose; + + actor_class->paint = nbtk_viewport_paint; + actor_class->pick = nbtk_viewport_pick; + actor_class->allocate = nbtk_viewport_allocate; + + g_object_class_install_property (gobject_class, + PROP_X_ORIGIN, + g_param_spec_int ("x-origin", + "X Origin", + "Origin's X coordinate in pixels", + -G_MAXINT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_Y_ORIGIN, + g_param_spec_int ("y-origin", + "Y Origin", + "Origin's Y coordinate in pixels", + -G_MAXINT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_Z_ORIGIN, + g_param_spec_int ("z-origin", + "Z Origin", + "Origin's Z coordinate in pixels", + -G_MAXINT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_SYNC_ADJUST, + g_param_spec_boolean ("sync-adjustments", + "Synchronise " + "adjustments", + "Whether to " + "synchronise " + "adjustments with " + "viewport size", + TRUE, + G_PARAM_READWRITE)); + + g_object_class_override_property (gobject_class, + PROP_HADJUST, + "hadjustment"); + + g_object_class_override_property (gobject_class, + PROP_VADJUST, + "vadjustment"); +} + +static void +hadjustment_value_notify_cb (NbtkAdjustment *adjustment, + GParamSpec *pspec, + NbtkViewport *viewport) +{ + NbtkViewportPrivate *priv = viewport->priv; + gdouble value; + + value = nbtk_adjustment_get_value (adjustment); + + nbtk_viewport_set_originu (viewport, + (float) (value), + priv->y, + priv->z); +} + +static void +vadjustment_value_notify_cb (NbtkAdjustment *adjustment, GParamSpec *arg1, + NbtkViewport *viewport) +{ + NbtkViewportPrivate *priv = viewport->priv; + gdouble value; + + value = nbtk_adjustment_get_value (adjustment); + + nbtk_viewport_set_originu (viewport, + priv->x, + (float) (value), + priv->z); +} + +static void +scrollable_set_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment *hadjustment, + NbtkAdjustment *vadjustment) +{ + NbtkViewportPrivate *priv = NBTK_VIEWPORT (scrollable)->priv; + + if (hadjustment != priv->hadjustment) + { + if (priv->hadjustment) + { + g_signal_handlers_disconnect_by_func (priv->hadjustment, + hadjustment_value_notify_cb, + scrollable); + g_object_unref (priv->hadjustment); + } + + if (hadjustment) + { + g_object_ref (hadjustment); + g_signal_connect (hadjustment, "notify::value", + G_CALLBACK (hadjustment_value_notify_cb), + scrollable); + } + + priv->hadjustment = hadjustment; + } + + if (vadjustment != priv->vadjustment) + { + if (priv->vadjustment) + { + g_signal_handlers_disconnect_by_func (priv->vadjustment, + vadjustment_value_notify_cb, + scrollable); + g_object_unref (priv->vadjustment); + } + + if (vadjustment) + { + g_object_ref (vadjustment); + g_signal_connect (vadjustment, "notify::value", + G_CALLBACK (vadjustment_value_notify_cb), + scrollable); + } + + priv->vadjustment = vadjustment; + } +} + +static void +scrollable_get_adjustments (NbtkScrollable *scrollable, + NbtkAdjustment **hadjustment, + NbtkAdjustment **vadjustment) +{ + NbtkViewportPrivate *priv; + ClutterActor *actor, *stage; + + g_return_if_fail (NBTK_IS_VIEWPORT (scrollable)); + + priv = ((NbtkViewport *) scrollable)->priv; + + actor = CLUTTER_ACTOR (scrollable); + stage = clutter_actor_get_stage (actor); + if (G_UNLIKELY (stage == NULL)) + stage = clutter_stage_get_default (); + + if (hadjustment) + { + if (priv->hadjustment) + *hadjustment = priv->hadjustment; + else + { + NbtkAdjustment *adjustment; + gdouble width, stage_width, increment; + + width = clutter_actor_get_width (actor); + stage_width = clutter_actor_get_width (stage); + increment = MAX (1.0, MIN (stage_width, width)); + + adjustment = nbtk_adjustment_new (priv->x, + 0, + width, + 1.0, + increment, + increment); + + scrollable_set_adjustments (scrollable, + adjustment, + priv->vadjustment); + + *hadjustment = adjustment; + } + } + + if (vadjustment) + { + if (priv->vadjustment) + *vadjustment = priv->vadjustment; + else + { + NbtkAdjustment *adjustment; + gdouble height, stage_height, increment; + + height = clutter_actor_get_height (actor); + stage_height = clutter_actor_get_height (stage); + increment = MAX (1.0, MIN (stage_height, height)); + + adjustment = nbtk_adjustment_new (priv->y, + 0, + height, + 1.0, + increment, + increment); + + scrollable_set_adjustments (scrollable, + priv->hadjustment, + adjustment); + + *vadjustment = adjustment; + } + } +} + +static void +scrollable_interface_init (NbtkScrollableInterface *iface) +{ + iface->set_adjustments = scrollable_set_adjustments; + iface->get_adjustments = scrollable_get_adjustments; +} + +static void +nbtk_viewport_init (NbtkViewport *self) +{ + self->priv = VIEWPORT_PRIVATE (self); + + self->priv->sync_adjustments = TRUE; + + g_object_set (G_OBJECT (self), "reactive", FALSE, NULL); +} + +NbtkWidget * +nbtk_viewport_new (void) +{ + return g_object_new (NBTK_TYPE_VIEWPORT, NULL); +} + +void +nbtk_viewport_set_originu (NbtkViewport *viewport, + gfloat x, + gfloat y, + gfloat z) +{ + NbtkViewportPrivate *priv; + + g_return_if_fail (NBTK_IS_VIEWPORT (viewport)); + + priv = viewport->priv; + + g_object_freeze_notify (G_OBJECT (viewport)); + + if (x != priv->x) + { + priv->x = x; + g_object_notify (G_OBJECT (viewport), "x-origin"); + + if (priv->hadjustment) + nbtk_adjustment_set_value (priv->hadjustment, + (float) (x)); + } + + if (y != priv->y) + { + priv->y = y; + g_object_notify (G_OBJECT (viewport), "y-origin"); + + if (priv->vadjustment) + nbtk_adjustment_set_value (priv->vadjustment, + (float) (y)); + } + + if (z != priv->z) + { + priv->z = z; + g_object_notify (G_OBJECT (viewport), "z-origin"); + } + + g_object_thaw_notify (G_OBJECT (viewport)); + + clutter_actor_queue_redraw (CLUTTER_ACTOR (viewport)); +} + +void +nbtk_viewport_set_origin (NbtkViewport *viewport, + gint x, + gint y, + gint z) +{ + g_return_if_fail (NBTK_IS_VIEWPORT (viewport)); + + nbtk_viewport_set_originu (viewport, + (float) (x), + (float) (y), + (float) (z)); +} + +void +nbtk_viewport_get_originu (NbtkViewport *viewport, + gfloat *x, + gfloat *y, + gfloat *z) +{ + NbtkViewportPrivate *priv; + + g_return_if_fail (NBTK_IS_VIEWPORT (viewport)); + + priv = viewport->priv; + + if (x) + *x = priv->x; + + if (y) + *y = priv->y; + + if (z) + *z = priv->z; +} + +void +nbtk_viewport_get_origin (NbtkViewport *viewport, + gint *x, + gint *y, + gint *z) +{ + NbtkViewportPrivate *priv; + + g_return_if_fail (NBTK_IS_VIEWPORT (viewport)); + + priv = viewport->priv; + + if (x) + *x = (int) priv->x; + + if (y) + *y = (int) priv->y; + + if (z) + *z = (int) priv->z; +} diff --git a/src/nbtk/nbtk-viewport.h b/src/nbtk/nbtk-viewport.h new file mode 100644 index 000000000..8dadda8a9 --- /dev/null +++ b/src/nbtk/nbtk-viewport.h @@ -0,0 +1,94 @@ +/* + * nbtk-viewport.h: Viewport 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 Nbtk by: Robert Staudinger + * + */ + +#if !defined(NBTK_H_INSIDE) && !defined(NBTK_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __NBTK_VIEWPORT_H__ +#define __NBTK_VIEWPORT_H__ + +#include +#include + +G_BEGIN_DECLS + +#define NBTK_TYPE_VIEWPORT (nbtk_viewport_get_type()) +#define NBTK_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NBTK_TYPE_VIEWPORT, NbtkViewport)) +#define NBTK_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NBTK_TYPE_VIEWPORT)) +#define NBTK_VIEWPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NBTK_TYPE_VIEWPORT, NbtkViewportClass)) +#define NBTK_IS_VIEWPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NBTK_TYPE_VIEWPORT)) +#define NBTK_VIEWPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NBTK_TYPE_VIEWPORT, NbtkViewportClass)) + +typedef struct _NbtkViewport NbtkViewport; +typedef struct _NbtkViewportPrivate NbtkViewportPrivate; +typedef struct _NbtkViewportClass NbtkViewportClass; + +/** + * NbtkViewport: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _NbtkViewport +{ + /*< private >*/ + NbtkBin parent; + + NbtkViewportPrivate *priv; +}; + +struct _NbtkViewportClass +{ + NbtkBinClass parent_class; +}; + +GType nbtk_viewport_get_type (void) G_GNUC_CONST; + +NbtkWidget * nbtk_viewport_new (void); + +void nbtk_viewport_set_originu (NbtkViewport *viewport, + gfloat x, + gfloat y, + gfloat z); + +void nbtk_viewport_set_origin (NbtkViewport *viewport, + gint x, + gint y, + gint z); + +void nbtk_viewport_get_originu (NbtkViewport *viewport, + gfloat *x, + gfloat *y, + gfloat *z); + +void nbtk_viewport_get_origin (NbtkViewport *viewport, + gint *x, + gint *y, + gint *z); + +G_END_DECLS + +#endif /* __NBTK_VIEWPORT_H__ */