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