1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-scroll-bar.c: Scroll bar actor
4 *
5 * Copyright 2008 OpenedHand
6 * Copyright 2008, 2009 Intel Corporation.
7 * Copyright 2009, 2010 Red Hat, Inc.
8 * Copyright 2010 Maxim Ermilov
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms and conditions of the GNU Lesser General Public License,
12 * version 2.1, as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
17 * more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 /**
24 * SECTION:st-scroll-bar
25 * @short_description: a user interface element to control scrollable areas.
26 *
27 * The #StScrollBar allows users to scroll scrollable actors, either by
28 * the step or page amount, or by manually dragging the handle.
29 */
30
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34
35 #include <math.h>
36 #include <clutter/clutter.h>
37
38 #include "st-scroll-bar.h"
39 #include "st-bin.h"
40 #include "st-enum-types.h"
41 #include "st-private.h"
42 #include "st-button.h"
43
44 G_DEFINE_TYPE (StScrollBar, st_scroll_bar, ST_TYPE_WIDGET)
45
46 #define ST_SCROLL_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_SCROLL_BAR, StScrollBarPrivate))
47
48 #define PAGING_INITIAL_REPEAT_TIMEOUT 500
49 #define PAGING_SUBSEQUENT_REPEAT_TIMEOUT 200
50
51 struct _StScrollBarPrivate
52 {
53 StAdjustment *adjustment;
54
55 gboolean grabbed;
56 gfloat x_origin;
57 gfloat y_origin;
58
59 ClutterActor *trough;
60 ClutterActor *handle;
61
62 gfloat move_x;
63 gfloat move_y;
64
65 /* Trough-click handling. */
66 enum { NONE, UP, DOWN } paging_direction;
67 guint paging_source_id;
68 guint paging_event_no;
69
70 ClutterAnimation *paging_animation;
71
72 guint vertical : 1;
73 };
74
75 enum
76 {
77 PROP_0,
78
79 PROP_ADJUSTMENT,
80 PROP_VERTICAL
81 };
82
83 enum
84 {
85 SCROLL_START,
86 SCROLL_STOP,
87
88 LAST_SIGNAL
89 };
90
91 static guint signals[LAST_SIGNAL] = { 0, };
92
93 extern gfloat st_slow_down_factor;
94
95 static gboolean
96 handle_button_press_event_cb (ClutterActor *actor,
97 ClutterButtonEvent *event,
98 StScrollBar *bar);
99
100 static void stop_scrolling (StScrollBar *bar);
101
102 static void
103 st_scroll_bar_get_property (GObject *gobject,
104 guint prop_id,
105 GValue *value,
106 GParamSpec *pspec)
107 {
108 StScrollBarPrivate *priv = ST_SCROLL_BAR (gobject)->priv;
109
110 switch (prop_id)
111 {
112 case PROP_ADJUSTMENT:
113 g_value_set_object (value, priv->adjustment);
114 break;
115
116 case PROP_VERTICAL:
117 g_value_set_boolean (value, priv->vertical);
118 break;
119
120 default:
121 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
122 break;
123 }
124 }
125
126 static void
127 st_scroll_bar_set_property (GObject *gobject,
128 guint prop_id,
129 const GValue *value,
130 GParamSpec *pspec)
131 {
132 StScrollBar *bar = ST_SCROLL_BAR (gobject);
133
134 switch (prop_id)
135 {
136 case PROP_ADJUSTMENT:
137 st_scroll_bar_set_adjustment (bar, g_value_get_object (value));
138 break;
139
140 case PROP_VERTICAL:
141 bar->priv->vertical = g_value_get_boolean (value);
142 if (bar->priv->vertical)
143 clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->handle),
144 "vhandle");
145 else
146 clutter_actor_set_name (CLUTTER_ACTOR (bar->priv->handle),
147 "hhandle");
148 clutter_actor_queue_relayout ((ClutterActor*) gobject);
149 break;
150
151 default:
152 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
153 break;
154 }
155 }
156
157 static void
158 st_scroll_bar_dispose (GObject *gobject)
159 {
160 StScrollBar *bar = ST_SCROLL_BAR (gobject);
161 StScrollBarPrivate *priv = bar->priv;
162
163 if (priv->adjustment)
164 st_scroll_bar_set_adjustment (bar, NULL);
165
166 if (priv->handle)
167 {
168 clutter_actor_destroy (priv->handle);
169 priv->handle = NULL;
170 }
171
172 if (priv->trough)
173 {
174 clutter_actor_destroy (priv->trough);
175 priv->trough = NULL;
176 }
177
178 G_OBJECT_CLASS (st_scroll_bar_parent_class)->dispose (gobject);
179 }
180
181 static void
182 st_scroll_bar_unmap (ClutterActor *actor)
183 {
184 CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->unmap (actor);
185
186 stop_scrolling (ST_SCROLL_BAR (actor));
187 }
188
189 static void
190 scroll_bar_allocate_children (StScrollBar *bar,
191 const ClutterActorBox *box,
192 ClutterAllocationFlags flags)
193 {
194 StScrollBarPrivate *priv = bar->priv;
195 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (bar));
196 ClutterActorBox content_box, trough_box;
197
198 st_theme_node_get_content_box (theme_node, box, &content_box);
199
200 if (priv->vertical)
201 {
202 trough_box.x1 = content_box.x1;
203 trough_box.y1 = content_box.y1;
204 trough_box.x2 = content_box.x2;
205 trough_box.y2 = content_box.y2;
206 clutter_actor_allocate (priv->trough, &trough_box, flags);
207 }
208 else
209 {
210 trough_box.x1 = content_box.x1;
211 trough_box.y1 = content_box.y1;
212 trough_box.x2 = content_box.x2;
213 trough_box.y2 = content_box.y2;
214 clutter_actor_allocate (priv->trough, &trough_box, flags);
215 }
216
217
218 if (priv->adjustment)
219 {
220 float handle_size, position, avail_size;
221 gdouble value, lower, upper, page_size, increment, min_size, max_size;
222 ClutterActorBox handle_box = { 0, };
223
224 st_adjustment_get_values (priv->adjustment,
225 &value,
226 &lower,
227 &upper,
228 NULL,
229 NULL,
230 &page_size);
231
232 if ((upper == lower)
233 || (page_size >= (upper - lower)))
234 increment = 1.0;
235 else
236 increment = page_size / (upper - lower);
237
238 min_size = 32.;
239 st_theme_node_lookup_length (theme_node, "min-size", FALSE, &min_size);
240 max_size = G_MAXINT16;
241 st_theme_node_lookup_length (theme_node, "max-size", FALSE, &max_size);
242
243 if (upper - lower - page_size <= 0)
244 position = 0;
245 else
246 position = (value - lower) / (upper - lower - page_size);
247
248 if (priv->vertical)
249 {
250 avail_size = content_box.y2 - content_box.y1;
251 handle_size = increment * avail_size;
252 handle_size = CLAMP (handle_size, min_size, max_size);
253
254 handle_box.x1 = content_box.x1;
255 handle_box.y1 = content_box.y1 + position * (avail_size - handle_size);
256
257 handle_box.x2 = content_box.x2;
258 handle_box.y2 = handle_box.y1 + handle_size;
259 }
260 else
261 {
262 avail_size = content_box.x2 - content_box.x1;
263 handle_size = increment * avail_size;
264 handle_size = CLAMP (handle_size, min_size, max_size);
265
266 handle_box.x1 = content_box.x1 + position * (avail_size - handle_size);
267 handle_box.y1 = content_box.y1;
268
269 handle_box.x2 = handle_box.x1 + handle_size;
270 handle_box.y2 = content_box.y2;
271 }
272
273 /* snap to pixel */
274 handle_box.x1 = (int) handle_box.x1;
275 handle_box.y1 = (int) handle_box.y1;
276 handle_box.x2 = (int) handle_box.x2;
277 handle_box.y2 = (int) handle_box.y2;
278
279 clutter_actor_allocate (priv->handle,
280 &handle_box,
281 flags);
282 }
283 }
284
285 static void
286 st_scroll_bar_get_preferred_width (ClutterActor *self,
287 gfloat for_height,
288 gfloat *min_width_p,
289 gfloat *natural_width_p)
290 {
291 StScrollBar *bar = ST_SCROLL_BAR (self);
292 StScrollBarPrivate *priv = bar->priv;
293 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
294 gfloat trough_min_width, trough_natural_width;
295 gfloat handle_min_width, handle_natural_width;
296
297 st_theme_node_adjust_for_height (theme_node, &for_height);
298
299 _st_actor_get_preferred_width (priv->trough, for_height, TRUE,
300 &trough_min_width, &trough_natural_width);
301
302 _st_actor_get_preferred_width (priv->handle, for_height, TRUE,
303 &handle_min_width, &handle_natural_width);
304
305 if (priv->vertical)
306 {
307 if (min_width_p)
308 *min_width_p = MAX (trough_min_width, handle_min_width);
309
310 if (natural_width_p)
311 *natural_width_p = MAX (trough_natural_width, handle_natural_width);
312 }
313 else
314 {
315 if (min_width_p)
316 *min_width_p = trough_min_width + handle_min_width;
317
318 if (natural_width_p)
319 *natural_width_p = trough_natural_width + handle_natural_width;
320 }
321
322 st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
323 }
324
325 static void
326 st_scroll_bar_get_preferred_height (ClutterActor *self,
327 gfloat for_width,
328 gfloat *min_height_p,
329 gfloat *natural_height_p)
330 {
331 StScrollBar *bar = ST_SCROLL_BAR (self);
332 StScrollBarPrivate *priv = bar->priv;
333 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
334 gfloat trough_min_height, trough_natural_height;
335 gfloat handle_min_height, handle_natural_height;
336
337 st_theme_node_adjust_for_width (theme_node, &for_width);
338
339 _st_actor_get_preferred_height (priv->trough, for_width, TRUE,
340 &trough_min_height, &trough_natural_height);
341
342 _st_actor_get_preferred_height (priv->handle, for_width, TRUE,
343 &handle_min_height, &handle_natural_height);
344
345 if (priv->vertical)
346 {
347 if (min_height_p)
348 *min_height_p = trough_min_height + handle_min_height;
349
350 if (natural_height_p)
351 *natural_height_p = trough_natural_height + handle_natural_height;
352 }
353 else
354 {
355 if (min_height_p)
356 *min_height_p = MAX (trough_min_height, handle_min_height);
357
358 if (natural_height_p)
359 *natural_height_p = MAX (trough_natural_height, handle_natural_height);
360 }
361
362 st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
363 }
364
365 static void
366 st_scroll_bar_allocate (ClutterActor *actor,
367 const ClutterActorBox *box,
368 ClutterAllocationFlags flags)
369 {
370 StScrollBar *bar = ST_SCROLL_BAR (actor);
371
372 clutter_actor_set_allocation (actor, box, flags);
373
374 scroll_bar_allocate_children (bar, box, flags);
375 }
376
377 static void
378 scroll_bar_update_positions (StScrollBar *bar)
379 {
380 ClutterActorBox box;
381
382 /* Due to a change in the adjustments, we need to reposition our
383 * children; since adjustments changes can come from allocation
384 * changes in the scrolled area, we can't just queue a new relayout -
385 * we may already be in a relayout cycle. On the other hand, if
386 * a relayout is already queued, we can't just go ahead and allocate
387 * our children, since we don't have a valid allocation, and calling
388 * clutter_actor_get_allocation_box() will trigger an immediate
389 * stage relayout. So what we do is go ahead and immediately
390 * allocate our children if we already have a valid allocation, and
391 * otherwise just wait for the queued relayout.
392 */
393 if (!clutter_actor_has_allocation (CLUTTER_ACTOR (bar)))
394 return;
395
396 clutter_actor_get_allocation_box (CLUTTER_ACTOR (bar), &box);
397 scroll_bar_allocate_children (bar, &box, CLUTTER_ALLOCATION_NONE);
398 }
399
400 static void
401 st_scroll_bar_style_changed (StWidget *widget)
402 {
403 StScrollBarPrivate *priv = ST_SCROLL_BAR (widget)->priv;
404
405 st_widget_style_changed (ST_WIDGET (priv->trough));
406 st_widget_style_changed (ST_WIDGET (priv->handle));
407
408 ST_WIDGET_CLASS (st_scroll_bar_parent_class)->style_changed (widget);
409 }
410
411 static void
412 bar_reactive_notify_cb (GObject *gobject,
413 GParamSpec *arg1,
414 gpointer user_data)
415 {
416 StScrollBar *bar = ST_SCROLL_BAR (gobject);
417
418 clutter_actor_set_reactive (bar->priv->handle,
419 clutter_actor_get_reactive (CLUTTER_ACTOR (bar)));
420 }
421
422 static GObject*
423 st_scroll_bar_constructor (GType type,
424 guint n_properties,
425 GObjectConstructParam *properties)
426 {
427 GObjectClass *gobject_class;
428 GObject *obj;
429 StScrollBar *bar;
430
431 gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class);
432 obj = gobject_class->constructor (type, n_properties, properties);
433
434 bar = ST_SCROLL_BAR (obj);
435
436 g_signal_connect (bar, "notify::reactive",
437 G_CALLBACK (bar_reactive_notify_cb), NULL);
438
439 return obj;
440 }
441
442 static gboolean
443 st_scroll_bar_scroll_event (ClutterActor *actor,
444 ClutterScrollEvent *event)
445 {
446 StScrollBarPrivate *priv = ST_SCROLL_BAR (actor)->priv;
447 gdouble step, value, delta_x, delta_y;
448
449 if (priv->adjustment)
450 {
451 g_object_get (priv->adjustment,
452 "step-increment", &step,
453 "value", &value,
454 NULL);
455 }
456 else
457 {
458 return FALSE;
459 }
460
461 switch (event->direction)
462 {
463 case CLUTTER_SCROLL_SMOOTH:
464 clutter_event_get_scroll_delta ((ClutterEvent *)event,
465 &delta_x, &delta_y);
466 if (fabs (delta_x) > fabs (delta_y))
467 st_adjustment_set_value (priv->adjustment, value + delta_x);
468 else
469 st_adjustment_set_value (priv->adjustment, value + delta_y);
470 break;
471 case CLUTTER_SCROLL_UP:
472 case CLUTTER_SCROLL_LEFT:
473 st_adjustment_set_value (priv->adjustment, value - step);
474 break;
475 case CLUTTER_SCROLL_DOWN:
476 case CLUTTER_SCROLL_RIGHT:
477 st_adjustment_set_value (priv->adjustment, value + step);
478 break;
479 }
480
481 return TRUE;
482 }
483
484 static void
485 st_scroll_bar_class_init (StScrollBarClass *klass)
486 {
487 GObjectClass *object_class = G_OBJECT_CLASS (klass);
488 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
489 StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
490 GParamSpec *pspec;
491
492 g_type_class_add_private (klass, sizeof (StScrollBarPrivate));
493
494 object_class->get_property = st_scroll_bar_get_property;
495 object_class->set_property = st_scroll_bar_set_property;
496 object_class->dispose = st_scroll_bar_dispose;
497 object_class->constructor = st_scroll_bar_constructor;
498
499 actor_class->get_preferred_width = st_scroll_bar_get_preferred_width;
500 actor_class->get_preferred_height = st_scroll_bar_get_preferred_height;
501 actor_class->allocate = st_scroll_bar_allocate;
502 actor_class->scroll_event = st_scroll_bar_scroll_event;
503 actor_class->unmap = st_scroll_bar_unmap;
504
505 widget_class->style_changed = st_scroll_bar_style_changed;
506
507 g_object_class_install_property
508 (object_class,
509 PROP_ADJUSTMENT,
510 g_param_spec_object ("adjustment",
511 "Adjustment",
512 "The adjustment",
513 ST_TYPE_ADJUSTMENT,
514 ST_PARAM_READWRITE));
515
516 pspec = g_param_spec_boolean ("vertical",
517 "Vertical Orientation",
518 "Vertical Orientation",
519 FALSE,
520 ST_PARAM_READWRITE);
521 g_object_class_install_property (object_class, PROP_VERTICAL, pspec);
522
523 signals[SCROLL_START] =
524 g_signal_new ("scroll-start",
525 G_TYPE_FROM_CLASS (klass),
526 G_SIGNAL_RUN_LAST,
527 G_STRUCT_OFFSET (StScrollBarClass, scroll_start),
528 NULL, NULL, NULL,
529 G_TYPE_NONE, 0);
530
531 signals[SCROLL_STOP] =
532 g_signal_new ("scroll-stop",
533 G_TYPE_FROM_CLASS (klass),
534 G_SIGNAL_RUN_LAST,
535 G_STRUCT_OFFSET (StScrollBarClass, scroll_stop),
536 NULL, NULL, NULL,
537 G_TYPE_NONE, 0);
538 }
539
540 static void
541 move_slider (StScrollBar *bar,
542 gfloat x,
543 gfloat y)
544 {
545 StScrollBarPrivate *priv = bar->priv;
546 gdouble position, lower, upper, page_size;
547 gfloat ux, uy, pos, size;
548
549 if (!priv->adjustment)
550 return;
551
552 if (!clutter_actor_transform_stage_point (priv->trough, x, y, &ux, &uy))
553 return;
554
555 if (priv->vertical)
556 size = clutter_actor_get_height (priv->trough)
557 - clutter_actor_get_height (priv->handle);
558 else
559 size = clutter_actor_get_width (priv->trough)
560 - clutter_actor_get_width (priv->handle);
561
562 if (size == 0)
563 return;
564
565 if (priv->vertical)
566 pos = uy - priv->y_origin;
567 else
568 pos = ux - priv->x_origin;
569 pos = CLAMP (pos, 0, size);
570
571 st_adjustment_get_values (priv->adjustment,
572 NULL,
573 &lower,
574 &upper,
575 NULL,
576 NULL,
577 &page_size);
578
579 position = ((pos / size)
580 * (upper - lower - page_size))
581 + lower;
582
583 st_adjustment_set_value (priv->adjustment, position);
584 }
585
586 static void
587 stop_scrolling (StScrollBar *bar)
588 {
589 if (!bar->priv->grabbed)
590 return;
591
592 st_widget_remove_style_pseudo_class (ST_WIDGET (bar->priv->handle), "active");
593
594 clutter_ungrab_pointer ();
595 bar->priv->grabbed = FALSE;
596 g_signal_emit (bar, signals[SCROLL_STOP], 0);
597 }
598
599 static gboolean
600 handle_motion_event_cb (ClutterActor *trough,
601 ClutterMotionEvent *event,
602 StScrollBar *bar)
603 {
604 if (!bar->priv->grabbed)
605 return FALSE;
606
607 move_slider (bar, event->x, event->y);
608 return TRUE;
609 }
610
611 static gboolean
612 handle_button_release_event_cb (ClutterActor *trough,
613 ClutterButtonEvent *event,
614 StScrollBar *bar)
615 {
616 if (event->button != 1)
617 return FALSE;
618
619 stop_scrolling (bar);
620 return TRUE;
621 }
622
623 static gboolean
624 handle_button_press_event_cb (ClutterActor *actor,
625 ClutterButtonEvent *event,
626 StScrollBar *bar)
627 {
628 StScrollBarPrivate *priv = bar->priv;
629
630 if (event->button != 1)
631 return FALSE;
632
633 if (!clutter_actor_transform_stage_point (priv->handle,
634 event->x,
635 event->y,
636 &priv->x_origin,
637 &priv->y_origin))
638 return FALSE;
639
640 st_widget_add_style_pseudo_class (ST_WIDGET (priv->handle), "active");
641
642 /* Account for the scrollbar-trough-handle nesting. */
643 priv->x_origin += clutter_actor_get_x (priv->trough);
644 priv->y_origin += clutter_actor_get_y (priv->trough);
645
646 g_assert (!priv->grabbed);
647
648 clutter_grab_pointer (priv->handle);
649 priv->grabbed = TRUE;
650 g_signal_emit (bar, signals[SCROLL_START], 0);
651
652 return TRUE;
653 }
654
655 static void
656 animation_completed_cb (ClutterAnimation *animation,
657 StScrollBarPrivate *priv)
658 {
659 g_object_unref (priv->paging_animation);
660 priv->paging_animation = NULL;
661 }
662
663 static gboolean
664 trough_paging_cb (StScrollBar *self)
665 {
666 gfloat handle_pos, event_pos, tx, ty;
667 gdouble value;
668 gdouble page_increment;
669 gboolean ret;
670
671 gulong mode;
672 ClutterAnimation *a;
673 GValue v = { 0, };
674 ClutterTimeline *t;
675
676 if (self->priv->paging_event_no == 0)
677 {
678 /* Scroll on after initial timeout. */
679 mode = CLUTTER_EASE_OUT_CUBIC;
680 ret = FALSE;
681 self->priv->paging_event_no = 1;
682 self->priv->paging_source_id = g_timeout_add (
683 PAGING_INITIAL_REPEAT_TIMEOUT,
684 (GSourceFunc) trough_paging_cb,
685 self);
686 }
687 else if (self->priv->paging_event_no == 1)
688 {
689 /* Scroll on after subsequent timeout. */
690 ret = FALSE;
691 mode = CLUTTER_EASE_IN_CUBIC;
692 self->priv->paging_event_no = 2;
693 self->priv->paging_source_id = g_timeout_add (
694 PAGING_SUBSEQUENT_REPEAT_TIMEOUT,
695 (GSourceFunc) trough_paging_cb,
696 self);
697 }
698 else
699 {
700 /* Keep scrolling. */
701 ret = TRUE;
702 mode = CLUTTER_LINEAR;
703 self->priv->paging_event_no++;
704 }
705
706 /* Do the scrolling */
707 st_adjustment_get_values (self->priv->adjustment,
708 &value, NULL, NULL,
709 NULL, &page_increment, NULL);
710
711 if (self->priv->vertical)
712 handle_pos = clutter_actor_get_y (self->priv->handle);
713 else
714 handle_pos = clutter_actor_get_x (self->priv->handle);
715
716 clutter_actor_transform_stage_point (CLUTTER_ACTOR (self->priv->trough),
717 self->priv->move_x,
718 self->priv->move_y,
719 &tx, &ty);
720
721 if (self->priv->vertical)
722 event_pos = ty;
723 else
724 event_pos = tx;
725
726 if (event_pos > handle_pos)
727 {
728 if (self->priv->paging_direction == NONE)
729 {
730 /* Remember direction. */
731 self->priv->paging_direction = DOWN;
732 }
733 if (self->priv->paging_direction == UP)
734 {
735 /* Scrolled far enough. */
736 return FALSE;
737 }
738 value += page_increment;
739 }
740 else
741 {
742 if (self->priv->paging_direction == NONE)
743 {
744 /* Remember direction. */
745 self->priv->paging_direction = UP;
746 }
747 if (self->priv->paging_direction == DOWN)
748 {
749 /* Scrolled far enough. */
750 return FALSE;
751 }
752 value -= page_increment;
753 }
754
755 if (self->priv->paging_animation)
756 {
757 clutter_animation_completed (self->priv->paging_animation);
'clutter_animation_completed' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-animation.h:155)
(emitted by gcc)
758 }
759
760 /* FIXME: Creating a new animation for each scroll is probably not the best
761 * idea, but it's a lot less involved than extenind the current animation */
762 a = self->priv->paging_animation = g_object_new (CLUTTER_TYPE_ANIMATION,
'clutter_animation_get_type' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-animation.h:94)
(emitted by gcc)
763 "object", self->priv->adjustment,
764 "duration", (guint)(PAGING_SUBSEQUENT_REPEAT_TIMEOUT * st_slow_down_factor),
765 "mode", mode,
766 NULL);
767 g_value_init (&v, G_TYPE_DOUBLE);
768 g_value_set_double (&v, value);
769 clutter_animation_bind (self->priv->paging_animation, "value", &v);
'clutter_animation_bind' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-animation.h:130)
(emitted by gcc)
770 t = clutter_animation_get_timeline (self->priv->paging_animation);
'clutter_animation_get_timeline' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-animation.h:123)
(emitted by gcc)
771 g_signal_connect (a, "completed", G_CALLBACK (animation_completed_cb),
772 self->priv);
773 clutter_timeline_start (t);
774
775 return ret;
776 }
777
778 static gboolean
779 trough_button_press_event_cb (ClutterActor *actor,
780 ClutterButtonEvent *event,
781 StScrollBar *self)
782 {
783 g_return_val_if_fail (self, FALSE);
784
785 if (event->button != 1)
786 return FALSE;
787
788 if (self->priv->adjustment == NULL)
789 return FALSE;
790
791 self->priv->move_x = event->x;
792 self->priv->move_y = event->y;
793 self->priv->paging_direction = NONE;
794 self->priv->paging_event_no = 0;
795 trough_paging_cb (self);
796
797 return TRUE;
798 }
799
800 static gboolean
801 trough_button_release_event_cb (ClutterActor *actor,
802 ClutterButtonEvent *event,
803 StScrollBar *self)
804 {
805 if (event->button != 1)
806 return FALSE;
807
808 if (self->priv->paging_source_id)
809 {
810 g_source_remove (self->priv->paging_source_id);
811 self->priv->paging_source_id = 0;
812 }
813
814 return TRUE;
815 }
816
817 static gboolean
818 trough_leave_event_cb (ClutterActor *actor,
819 ClutterEvent *event,
820 StScrollBar *self)
821 {
822 if (self->priv->paging_source_id)
823 {
824 g_source_remove (self->priv->paging_source_id);
825 self->priv->paging_source_id = 0;
826 return TRUE;
827 }
828
829 return FALSE;
830 }
831
832 static void
833 st_scroll_bar_notify_reactive (StScrollBar *self)
834 {
835 StScrollBarPrivate *priv = self->priv;
836
837 gboolean reactive = CLUTTER_ACTOR_IS_REACTIVE (self);
838
839 clutter_actor_set_reactive (CLUTTER_ACTOR (priv->trough), reactive);
840 clutter_actor_set_reactive (CLUTTER_ACTOR (priv->handle), reactive);
841 }
842
843 static void
844 st_scroll_bar_init (StScrollBar *self)
845 {
846 self->priv = ST_SCROLL_BAR_GET_PRIVATE (self);
847
848 self->priv->trough = (ClutterActor *) st_bin_new ();
849 clutter_actor_set_reactive ((ClutterActor *) self->priv->trough, TRUE);
850 clutter_actor_set_name (CLUTTER_ACTOR (self->priv->trough), "trough");
851 clutter_actor_add_child (CLUTTER_ACTOR (self),
852 CLUTTER_ACTOR (self->priv->trough));
853 g_signal_connect (self->priv->trough, "button-press-event",
854 G_CALLBACK (trough_button_press_event_cb), self);
855 g_signal_connect (self->priv->trough, "button-release-event",
856 G_CALLBACK (trough_button_release_event_cb), self);
857 g_signal_connect (self->priv->trough, "leave-event",
858 G_CALLBACK (trough_leave_event_cb), self);
859
860 self->priv->handle = (ClutterActor *) st_button_new ();
861 clutter_actor_set_name (CLUTTER_ACTOR (self->priv->handle), "hhandle");
862 clutter_actor_add_child (CLUTTER_ACTOR (self),
863 CLUTTER_ACTOR (self->priv->handle));
864 g_signal_connect (self->priv->handle, "button-press-event",
865 G_CALLBACK (handle_button_press_event_cb), self);
866 g_signal_connect (self->priv->handle, "button-release-event",
867 G_CALLBACK (handle_button_release_event_cb), self);
868 g_signal_connect (self->priv->handle, "motion-event",
869 G_CALLBACK (handle_motion_event_cb), self);
870
871 clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
872
873 g_signal_connect (self, "notify::reactive",
874 G_CALLBACK (st_scroll_bar_notify_reactive), NULL);
875 }
876
877 StWidget *
878 st_scroll_bar_new (StAdjustment *adjustment)
879 {
880 return g_object_new (ST_TYPE_SCROLL_BAR,
881 "adjustment", adjustment,
882 NULL);
883 }
884
885 static void
886 on_notify_value (GObject *object,
887 GParamSpec *pspec,
888 StScrollBar *bar)
889 {
890 scroll_bar_update_positions (bar);
891 }
892
893 static void
894 on_changed (StAdjustment *adjustment,
895 StScrollBar *bar)
896 {
897 scroll_bar_update_positions (bar);
898 }
899
900 void
901 st_scroll_bar_set_adjustment (StScrollBar *bar,
902 StAdjustment *adjustment)
903 {
904 StScrollBarPrivate *priv;
905
906 g_return_if_fail (ST_IS_SCROLL_BAR (bar));
907
908 priv = bar->priv;
909
910 if (adjustment == priv->adjustment)
911 return;
912
913 if (priv->adjustment)
914 {
915 g_signal_handlers_disconnect_by_func (priv->adjustment,
916 on_notify_value,
917 bar);
918 g_signal_handlers_disconnect_by_func (priv->adjustment,
919 on_changed,
920 bar);
921 g_object_unref (priv->adjustment);
922 priv->adjustment = NULL;
923 }
924
925 if (adjustment)
926 {
927 priv->adjustment = g_object_ref (adjustment);
928
929 g_signal_connect (priv->adjustment, "notify::value",
930 G_CALLBACK (on_notify_value),
931 bar);
932 g_signal_connect (priv->adjustment, "changed",
933 G_CALLBACK (on_changed),
934 bar);
935
936 clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
937 }
938
939 g_object_notify (G_OBJECT (bar), "adjustment");
940 }
941
942 /**
943 * st_scroll_bar_get_adjustment:
944 * @bar: a #StScrollbar
945 *
946 * Gets the adjustment object that stores the current position
947 * of the scrollbar.
948 *
949 * Return value: (transfer none): the adjustment
950 */
951 StAdjustment *
952 st_scroll_bar_get_adjustment (StScrollBar *bar)
953 {
954 g_return_val_if_fail (ST_IS_SCROLL_BAR (bar), NULL);
955
956 return bar->priv->adjustment;
957 }