2 * This file is a part of hildon
4 * Copyright (C) 2008 Nokia Corporation, all rights reserved.
6 * Contact: Karl Lattimer <karl.lattimer@nokia.com>
8 * This widget is based on MokoFingerScroll from libmokoui
9 * OpenMoko Application Framework UI Library
10 * Authored by Chris Lord <chris@openedhand.com>
11 * Copyright (C) 2006-2007 OpenMoko Inc.
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU Lesser Public License as published by
15 * the Free Software Foundation; version 2 of the license.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Lesser Public License for more details.
25 * SECTION: hildon-pannable-area
26 * @short_description: A scrolling widget designed for touch screens
27 * @see_also: #GtkScrolledWindow
29 * #HildonPannableArea implements a scrolled window designed to be used with a
30 * touch screen interface. The user scrolls the child widget by activating the
31 * pointing device and dragging it over the widget.
36 * todo items should be marked with a number and a relative position in the code where they are relevant.
38 * 1 x Make OVERSHOOT a property, the default of this property is fixed
39 * 2 x Add method of scrolling to the selected element of a treeview and have it align to half way point
40 * 3 x Add a property for initial scrolling of the widget, so it scrolls on realize
41 * 4 X Add a method of removing children from the alignment, gtk-container-remove override?
42 * 5 x Remove elasticity if it is no longer required?
43 * 6 x Reduce calls to queue_resize to improve performance
44 * 7 x Make 'fast' factor a property
45 * 8 - Strip out g_print/g_debug calls
46 * 9 - Scroll policies (horizontal/vertical/both) suggest 1,2,3 for different policies (binary OR'd)
47 * 10 x Delay click mode (only send synthetic clicks on mouse-up, as in previous
49 * 11 - 'Physical' mode for acceleration scrolling
50 * 12 X Add a property to disable overshoot entirely
51 * 13 x Fix problem with kinetics when pannable has been in overshoot (a flick stops rather than continues) (Search TODO 13)
52 * 14 x Review the other modes, probably they are not working well after overshooting patches
53 * 15 x Very small scrollbars when overshooting are not correctly rendered (fixed adding a minimum size)
59 #include "hildon-pannable-area.h"
61 #define SMOOTH_FACTOR 0.85
63 #define BOUNCE_STEPS 6
64 #define SCROLL_BAR_MIN_SIZE 5
65 #define RATIO_TOLERANCE 0.000001
67 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
68 #define PANNABLE_AREA_PRIVATE(o) \
69 (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA, \
70 HildonPannableAreaPrivate))
71 typedef struct _HildonPannableAreaPrivate HildonPannableAreaPrivate;
73 struct _HildonPannableAreaPrivate {
74 HildonPannableAreaMode mode;
75 GdkWindow *event_window;
76 gdouble x; /* Used to store mouse co-ordinates of the first or */
77 gdouble y; /* previous events in a press-motion pair */
78 gdouble ex; /* Used to store mouse co-ordinates of the last */
79 gdouble ey; /* motion event in acceleration mode */
82 guint32 last_time; /* Last event time, to stop infinite loops */
95 gint ix; /* Initial click mouse co-ordinates */
97 gint cx; /* Initial click child window mouse co-ordinates */
102 gint overshot_dist_x;
103 gint overshot_dist_y;
106 gdouble scroll_indicator_alpha;
107 gint scroll_indicator_timeout;
108 gint scroll_indicator_event_interrupt;
109 gint scroll_delay_counter;
111 gboolean initial_hint;
115 GdkRectangle hscroll_rect;
116 GdkRectangle vscroll_rect;
119 GtkAdjustment *hadjust;
120 GtkAdjustment *vadjust;
127 HildonPannableAreaIndicatorMode vindicator_mode;
128 HildonPannableAreaIndicatorMode hindicator_mode;
137 PROP_VELOCITY_FAST_FACTOR,
147 static GdkWindow *hildon_pannable_area_get_topmost (GdkWindow * window,
149 gint * tx, gint * ty)
151 /* Find the GdkWindow at the given point, by recursing from a given
152 * parent GdkWindow. Optionally return the co-ordinates transformed
153 * relative to the child window.
157 gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
158 if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
161 /*g_debug ("Finding window at (%d, %d) in %p", x, y, window); */
164 gint child_x = 0, child_y = 0;
165 GList *c, *children = gdk_window_peek_children (window);
166 GdkWindow *old_window = window;
168 for (c = children; c; c = c->next) {
169 GdkWindow *child = (GdkWindow *) c->data;
172 gdk_window_get_geometry (child, &wx, &wy, &width, &height, NULL);
173 /*g_debug ("Child: %p, (%dx%d+%d,%d)", child,
174 width, height, wx, wy); */
176 if ((x >= wx) && (x < (wx + width)) && (y >= wy)
177 && (y < (wy + height))) {
184 /*g_debug ("\\|/"); */
185 if (window == old_window)
197 /*g_debug ("Returning: %p", window); */
203 synth_crossing (GdkWindow * child,
205 gint x_root, gint y_root, guint32 time, gboolean in)
207 GdkEventCrossing *crossing_event;
208 GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
210 /* Send synthetic enter event */
211 crossing_event = (GdkEventCrossing *) gdk_event_new (type);
212 ((GdkEventAny *) crossing_event)->type = type;
213 ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
214 ((GdkEventAny *) crossing_event)->send_event = FALSE;
215 crossing_event->subwindow = g_object_ref (child);
216 crossing_event->time = time;
217 crossing_event->x = x;
218 crossing_event->y = y;
219 crossing_event->x_root = x_root;
220 crossing_event->y_root = y_root;
221 crossing_event->mode = GDK_CROSSING_NORMAL;
222 crossing_event->detail = GDK_NOTIFY_UNKNOWN;
223 crossing_event->focus = FALSE;
224 crossing_event->state = 0;
225 gdk_event_put ((GdkEvent *) crossing_event);
226 gdk_event_free ((GdkEvent *) crossing_event);
230 hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area)
233 HildonPannableAreaPrivate *priv;
235 GDK_THREADS_ENTER ();
237 priv = PANNABLE_AREA_PRIVATE (area);
239 /* if moving do not fade out */
240 if (((ABS (priv->vel_y)>1.0)||
241 (ABS (priv->vel_x)>1.0))&&(!priv->clicked)) {
245 if (!priv->scroll_indicator_timeout) {
249 if (priv->scroll_indicator_event_interrupt) {
250 /* Stop a fade out, and fade back in */
251 if (priv->scroll_indicator_alpha >= 0.9) {
252 priv->scroll_indicator_timeout = 0;
253 priv->scroll_indicator_alpha = 1;
256 priv->scroll_indicator_alpha += 0.2;
258 gtk_widget_queue_draw_area (GTK_WIDGET(area),
259 priv->vscroll_rect.x,
260 priv->vscroll_rect.y,
261 priv->vscroll_rect.width,
262 priv->vscroll_rect.height);
264 gtk_widget_queue_draw_area (GTK_WIDGET(area),
265 priv->hscroll_rect.x,
266 priv->hscroll_rect.y,
267 priv->hscroll_rect.width,
268 priv->hscroll_rect.height);
272 if ((priv->scroll_indicator_alpha > 0.9) &&
273 (priv->scroll_delay_counter < 20)) {
274 priv->scroll_delay_counter++;
278 if (!priv->scroll_indicator_event_interrupt) {
279 /* Continue fade out */
280 if (priv->scroll_indicator_alpha <= 0.1) {
281 priv->scroll_indicator_timeout = 0;
282 priv->scroll_delay_counter = 0;
283 priv->scroll_indicator_alpha = 0;
286 priv->scroll_indicator_alpha -= 0.2;
288 gtk_widget_queue_draw_area (GTK_WIDGET(area),
289 priv->vscroll_rect.x,
290 priv->vscroll_rect.y,
291 priv->vscroll_rect.width,
292 priv->vscroll_rect.height);
294 gtk_widget_queue_draw_area (GTK_WIDGET(area),
295 priv->hscroll_rect.x,
296 priv->hscroll_rect.y,
297 priv->hscroll_rect.width,
298 priv->hscroll_rect.height);
301 GDK_THREADS_LEAVE ();
307 hildon_pannable_area_button_press_cb (GtkWidget * widget,
308 GdkEventButton * event)
311 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
313 if ((!priv->enabled) || (event->button != 1) ||
314 ((event->time == priv->last_time) &&
315 (priv->last_type == 1)) || (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
318 priv->scroll_indicator_event_interrupt = 1;
319 if (priv->scroll_indicator_timeout){
320 g_source_remove (priv->scroll_indicator_timeout);
322 priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) (priv->sps*2)),
323 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
324 priv->last_time = event->time;
327 priv->click_x = event->x;
328 priv->click_y = event->y;
330 priv->scroll_to_x = -1;
331 priv->scroll_to_y = -1;
333 if (priv->clicked && priv->child) {
334 /* Widget stole focus on last click, send crossing-out event */
335 synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
343 /* Don't allow a click if we're still moving fast, where fast is
344 * defined as a quarter of our top possible speed.
346 if ((ABS (priv->vel_x) < (priv->vmax * priv->vfast_factor)) &&
347 (ABS (priv->vel_y) < (priv->vmax * priv->vfast_factor)))
349 hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
350 event->x, event->y, &x, &y);
354 priv->clicked = TRUE;
355 /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
359 if ((priv->child) && (priv->child != gtk_bin_get_child (GTK_BIN (widget))->window)) {
361 g_object_add_weak_pointer ((GObject *) priv->child,
362 (gpointer *) & priv->child);
364 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
370 synth_crossing (priv->child, x, y, event->x_root,
371 event->y_root, event->time, TRUE);
373 /* Send synthetic click (button press/release) event */
374 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
376 gdk_event_put ((GdkEvent *) event);
377 gdk_event_free ((GdkEvent *) event);
385 hildon_pannable_area_redraw (HildonPannableArea * area)
387 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
389 /* Redraw scroll indicators */
391 if (GTK_WIDGET (area)->window) {
392 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
393 &priv->hscroll_rect, FALSE);
397 if (GTK_WIDGET (area)->window) {
398 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
399 &priv->vscroll_rect, FALSE);
405 hildon_pannable_area_refresh (HildonPannableArea * area)
407 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
408 GtkWidget *widget = gtk_bin_get_child (GTK_BIN (area));
409 gboolean vscroll, hscroll;
412 priv->vscroll = FALSE;
413 priv->hscroll = FALSE;
417 /* Calculate if we need scroll indicators */
418 gtk_widget_size_request (widget, NULL);
420 switch (priv->hindicator_mode) {
421 case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
424 case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
428 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
429 priv->hadjust->page_size) ? TRUE : FALSE;
432 switch (priv->vindicator_mode) {
433 case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
436 case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
440 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
441 priv->vadjust->page_size) ? TRUE : FALSE;
444 /* Store the vscroll/hscroll areas for redrawing */
446 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
447 priv->vscroll_rect.x = allocation->x + allocation->width -
449 priv->vscroll_rect.y = allocation->y;
450 priv->vscroll_rect.width = priv->area_width;
451 priv->vscroll_rect.height = allocation->height -
452 (hscroll ? priv->area_width : 0);
455 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
456 priv->hscroll_rect.y = allocation->y + allocation->height -
458 priv->hscroll_rect.x = allocation->x;
459 priv->hscroll_rect.height = priv->area_width;
460 priv->hscroll_rect.width = allocation->width -
461 (vscroll ? priv->area_width : 0);
464 priv->vscroll = vscroll;
465 priv->hscroll = hscroll;
468 /* Scroll by a particular amount (in pixels). Optionally, return if
469 * the scroll on a particular axis was successful.
472 hildon_pannable_axis_scroll (HildonPannableArea *area,
473 GtkAdjustment *adjust,
482 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
484 dist = gtk_adjustment_get_value (adjust) - inc;
487 * We use overshot_dist to define the distance of the current overshoot,
488 * and overshooting to define the direction/whether or not we are overshot
490 if (!(*overshooting)) {
492 /* Initiation of the overshoot happens when the finger is released
493 * and the current position of the pannable contents are out of range
495 if (dist < adjust->lower) {
498 dist = adjust->lower;
500 if (priv->overshoot_max!=0) {
503 *overshot_dist = CLAMP (*overshot_dist + *vel, 0, priv->overshoot_max);
504 gtk_widget_queue_resize (GTK_WIDGET (area));
506 } else if (dist > adjust->upper - adjust->page_size) {
509 dist = adjust->upper - adjust->page_size;
511 if (priv->overshoot_max!=0) {
514 *overshot_dist = CLAMP (*overshot_dist + *vel, -1*priv->overshoot_max, 0);
515 gtk_widget_queue_resize (GTK_WIDGET (area));
518 if ((*scroll_to) != -1) {
519 if (((inc < 0)&&(*scroll_to <= dist))||
520 ((inc > 0)&&(*scroll_to >= dist))) {
528 gtk_adjustment_set_value (adjust, dist);
530 if (!priv->clicked) {
532 /* When the overshoot has started we continue for BOUNCE_STEPS more steps into the overshoot
533 * before we reverse direction. The deceleration factor is calculated based on
534 * the percentage distance from the first item with each iteration, therefore always
535 * returning us to the top/bottom most element
537 if (*overshot_dist > 0) {
539 if ((*overshooting < BOUNCE_STEPS) && (*vel > 0)) {
541 *vel = (((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel);
542 } else if ((*overshooting >= BOUNCE_STEPS) && (*vel > 0)) {
545 } else if ((*overshooting > 1) && (*vel < 0)) {
547 /* we add the MAX in order to avoid very small speeds */
548 *vel = MIN ((((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel), -10.0);
551 *overshot_dist = CLAMP (*overshot_dist + *vel, 0, priv->overshoot_max);
553 gtk_widget_queue_resize (GTK_WIDGET (area));
555 } else if (*overshot_dist < 0) {
557 if ((*overshooting < BOUNCE_STEPS) && (*vel < 0)) {
559 *vel = (((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel) * -1;
560 } else if ((*overshooting >= BOUNCE_STEPS) && (*vel < 0)) {
563 } else if ((*overshooting > 1) && (*vel > 0)) {
565 /* we add the MIN in order to avoid very small speeds */
566 *vel = MAX ((((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel) * -1, 10.0);
569 *overshot_dist = CLAMP (*overshot_dist + (*vel), -1*priv->overshoot_max, 0);
571 gtk_widget_queue_resize (GTK_WIDGET (area));
576 gtk_widget_queue_resize (GTK_WIDGET (area));
579 if (*overshot_dist > 0) {
580 *overshot_dist = CLAMP ((*overshot_dist) + inc, 0, priv->overshoot_max);
581 } else if (*overshot_dist < 0) {
582 *overshot_dist = CLAMP ((*overshot_dist) + inc, -1 * priv->overshoot_max, 0);
585 gtk_adjustment_set_value (adjust, dist);
587 gtk_widget_queue_resize (GTK_WIDGET (area));
593 hildon_pannable_area_scroll (HildonPannableArea *area,
594 gdouble x, gdouble y)
597 HildonPannableAreaPrivate *priv;
598 gboolean hscroll, vscroll;
600 priv = PANNABLE_AREA_PRIVATE (area);
602 if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
605 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
606 priv->vadjust->page_size) ? TRUE : FALSE;
607 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
608 priv->hadjust->page_size) ? TRUE : FALSE;
614 hildon_pannable_axis_scroll (area, priv->vadjust, &priv->vel_y, y,
615 &priv->overshooting_y, &priv->overshot_dist_y,
616 &priv->scroll_to_y, &sy);
620 hildon_pannable_axis_scroll (area, priv->hadjust, &priv->vel_x, x,
621 &priv->overshooting_x, &priv->overshot_dist_x,
622 &priv->scroll_to_x, &sx);
625 /* If the scroll on a particular axis wasn't succesful, reset the
626 * initial scroll position to the new mouse co-ordinate. This means
627 * when you get to the top of the page, dragging down works immediately.
640 hildon_pannable_area_timeout (HildonPannableArea * area)
642 HildonPannableAreaPrivate *priv;
644 GDK_THREADS_ENTER ();
646 priv = PANNABLE_AREA_PRIVATE (area);
648 if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
653 if (!priv->clicked) {
654 /* Decelerate gradually when pointer is raised */
655 if ((!priv->overshot_dist_y) &&
656 (!priv->overshot_dist_x)) {
658 /* in case we move to a specific point do not decelerate when arriving */
659 if ((priv->scroll_to_x != -1)||(priv->scroll_to_y != -1)) {
661 if (ABS (priv->vel_x) >= 1.5) {
662 priv->vel_x *= priv->decel;
665 if (ABS (priv->vel_y) >= 1.5) {
666 priv->vel_y *= priv->decel;
670 priv->vel_x *= priv->decel;
671 priv->vel_y *= priv->decel;
673 if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
679 } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
684 hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
686 GDK_THREADS_LEAVE ();
692 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
693 GdkEventMotion * event)
695 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
696 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
699 gdouble delta, rawvel_x, rawvel_y;
700 gint direction_x, direction_y;
702 if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
705 if ((!priv->enabled) || (!priv->clicked) ||
706 ((event->time == priv->last_time) && (priv->last_type == 2))) {
707 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
711 /* Only start the scroll if the mouse cursor passes beyond the
712 * DnD threshold for dragging.
714 g_object_get (G_OBJECT (gtk_settings_get_default ()),
715 "gtk-dnd-drag-threshold", &dnd_threshold, NULL);
716 x = event->x - priv->x;
717 y = event->y - priv->y;
719 if ((!priv->moved) && ((ABS (x) > dnd_threshold)
720 || (ABS (y) > dnd_threshold))) {
723 if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
724 (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
727 g_source_remove (priv->idle_id);
729 priv->idle_id = g_timeout_add ((gint)
730 (1000.0 / (gdouble) priv->sps),
732 hildon_pannable_area_timeout, area);
737 switch (priv->mode) {
738 case HILDON_PANNABLE_AREA_MODE_PUSH:
739 /* Scroll by the amount of pixels the cursor has moved
740 * since the last motion event.
742 hildon_pannable_area_scroll (area, x, y);
746 case HILDON_PANNABLE_AREA_MODE_ACCEL:
747 /* Set acceleration relative to the initial click */
750 priv->vel_x = ((x > 0) ? 1 : -1) *
752 (gdouble) widget->allocation.width) *
753 (priv->vmax - priv->vmin)) + priv->vmin);
754 priv->vel_y = ((y > 0) ? 1 : -1) *
756 (gdouble) widget->allocation.height) *
757 (priv->vmax - priv->vmin)) + priv->vmin);
759 case HILDON_PANNABLE_AREA_MODE_AUTO:
761 delta = event->time - priv->last_time;
763 rawvel_x = (((event->x - priv->x) / ABS (delta)) *
764 (gdouble) priv->sps) * FORCE;
765 rawvel_y = (((event->y - priv->y) / ABS (delta)) *
766 (gdouble) priv->sps) * FORCE;
768 /* we store the direction and after the calculation we
769 change it, this reduces the ifs for the calculation */
770 direction_x = rawvel_x < 0 ? -1 : 1;
771 direction_y = rawvel_y < 0 ? -1 : 1;
773 rawvel_y = ABS (rawvel_y);
774 rawvel_x = ABS (rawvel_x);
776 priv->vel_x = priv->vel_x * (1 - SMOOTH_FACTOR) +
777 direction_x * rawvel_x * SMOOTH_FACTOR;
778 priv->vel_y = priv->vel_y * (1 - SMOOTH_FACTOR) +
779 direction_y * rawvel_y * SMOOTH_FACTOR;
781 priv->vel_x = priv->vel_x > 0 ? MIN (priv->vel_x, priv->vmax)
782 : MAX (priv->vel_x, -1 * priv->vmax);
783 priv->vel_y = priv->vel_y > 0 ? MIN (priv->vel_y, priv->vmax)
784 : MAX (priv->vel_y, -1 * priv->vmax);
786 hildon_pannable_area_scroll (area, x, y);
799 /* Send motion notify to child */
800 priv->last_time = event->time;
802 event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
803 event->x = priv->cx + (event->x - priv->ix);
804 event->y = priv->cy + (event->y - priv->iy);
805 event->window = g_object_ref (priv->child);
806 gdk_event_put ((GdkEvent *) event);
807 gdk_event_free ((GdkEvent *) event);
810 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
816 hildon_pannable_area_button_release_cb (GtkWidget * widget,
817 GdkEventButton * event)
819 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
823 if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
826 priv->scroll_indicator_event_interrupt = 0;
827 priv->scroll_delay_counter = 0;
829 if (priv->scroll_indicator_timeout) {
830 g_source_remove (priv->scroll_indicator_timeout);
833 if ((ABS (priv->vel_y) > 1.0)||
834 (ABS (priv->vel_x) > 1.0)) {
835 priv->scroll_indicator_alpha = 1.0;
838 priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
839 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
841 if ((!priv->clicked) || (!priv->enabled) || (event->button != 1) ||
842 ((event->time == priv->last_time) && (priv->last_type == 3)))
845 priv->clicked = FALSE;
847 if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO ||
848 priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
850 g_source_remove (priv->idle_id);
852 /* If overshoot has been initiated with a finger down, on release set max speed */
853 if (priv->overshot_dist_y != 0) {
854 priv->overshooting_y = BOUNCE_STEPS; /* Hack to stop a bounce in the finger down case */
855 priv->vel_y = priv->vmax;
858 if (priv->overshot_dist_x != 0) {
859 priv->overshooting_x = BOUNCE_STEPS; /* Hack to stop a bounce in the finger down case */
860 priv->vel_x = priv->vmax;
863 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
865 hildon_pannable_area_timeout, widget);
868 priv->last_time = event->time;
877 hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
878 event->x, event->y, &x, &y);
880 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
884 /* Leave the widget if we've moved - This doesn't break selection,
885 * but stops buttons from being clicked.
887 if ((child != priv->child) || (priv->moved)) {
888 /* Send synthetic leave event */
889 synth_crossing (priv->child, x, y, event->x_root,
890 event->y_root, event->time, FALSE);
891 /* Send synthetic button release event */
892 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
893 gdk_event_put ((GdkEvent *) event);
895 /* Send synthetic button release event */
896 ((GdkEventAny *) event)->window = g_object_ref (child);
897 gdk_event_put ((GdkEvent *) event);
898 /* Send synthetic leave event */
899 synth_crossing (priv->child, x, y, event->x_root,
900 event->y_root, event->time, FALSE);
902 g_object_remove_weak_pointer ((GObject *) priv->child,
903 (gpointer *) & priv->child);
906 gdk_event_free ((GdkEvent *) event);
912 rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b)
914 *r = (color->red >> 8) / 255.0;
915 *g = (color->green >> 8) / 255.0;
916 *b = (color->blue >> 8) / 255.0;
920 hildon_pannable_draw_vscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
922 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
925 cairo_pattern_t *pattern;
927 gint radius = (priv->vscroll_rect.width/2) - 1;
929 cr = gdk_cairo_create(widget->window);
931 /* Draw the background */
932 rgb_from_gdkcolor (back_color, &r, &g, &b);
933 cairo_set_source_rgb (cr, r, g, b);
934 cairo_rectangle(cr, priv->vscroll_rect.x, priv->vscroll_rect.y,
935 priv->vscroll_rect.width,
936 priv->vscroll_rect.height);
937 cairo_fill_preserve (cr);
940 /* Calculate the scroll bar height and position */
941 y = widget->allocation.y +
942 ((priv->vadjust->value / priv->vadjust->upper) *
943 (widget->allocation.height -
944 (priv->hscroll ? priv->area_width : 0)));
945 height = (widget->allocation.y +
946 (((priv->vadjust->value +
947 priv->vadjust->page_size) /
948 priv->vadjust->upper) *
949 (widget->allocation.height -
950 (priv->hscroll ? priv->area_width : 0)))) - y;
952 /* Set a minimum height */
953 height = MAX (SCROLL_BAR_MIN_SIZE, height);
955 /* Check the max y position */
956 y = MIN (y, widget->allocation.height -
957 (priv->hscroll ? priv->hscroll_rect.height : 0) -
960 /* Draw the scrollbar */
961 rgb_from_gdkcolor (scroll_color, &r, &g, &b);
963 pattern = cairo_pattern_create_linear(radius+1, y, radius+1,y + height);
964 cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
965 cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
966 cairo_set_source(cr, pattern);
968 cairo_pattern_destroy(pattern);
970 cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + radius + 1, radius, G_PI, 0);
971 cairo_line_to(cr, priv->vscroll_rect.x + (radius * 2) + 1, y + height - radius);
972 cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + height - radius, radius, 0, G_PI);
973 cairo_line_to(cr, priv->vscroll_rect.x + 1, y + height - radius);
976 cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
982 hildon_pannable_draw_hscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
984 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
987 cairo_pattern_t *pattern;
989 gint radius = (priv->hscroll_rect.height/2) - 1;
991 cr = gdk_cairo_create(widget->window);
993 /* Draw the background */
994 rgb_from_gdkcolor (back_color, &r, &g, &b);
995 cairo_set_source_rgb (cr, r, g, b);
996 cairo_rectangle(cr, priv->hscroll_rect.x, priv->hscroll_rect.y,
997 priv->hscroll_rect.width,
998 priv->hscroll_rect.height);
999 cairo_fill_preserve (cr);
1002 /* calculate the scrollbar width and position */
1003 x = widget->allocation.x +
1004 ((priv->hadjust->value / priv->hadjust->upper) *
1005 (widget->allocation.width - (priv->vscroll ? priv->area_width : 0)));
1007 (widget->allocation.x +
1008 (((priv->hadjust->value +
1009 priv->hadjust->page_size) / priv->hadjust->upper) *
1010 (widget->allocation.width -
1011 (priv->vscroll ? priv->area_width : 0)))) - x;
1013 /* Set a minimum width */
1014 width = MAX (SCROLL_BAR_MIN_SIZE, width);
1016 /* Check the max x position */
1017 x = MIN (x, widget->allocation.width -
1018 (priv->vscroll ? priv->vscroll_rect.width : 0) -
1021 /* Draw the scrollbar */
1022 rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1024 pattern = cairo_pattern_create_linear(x, radius+1, x+width, radius+1);
1025 cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1026 cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1027 cairo_set_source(cr, pattern);
1029 cairo_pattern_destroy(pattern);
1031 cairo_arc_negative(cr, x + radius + 1, priv->hscroll_rect.y + radius + 1, radius, 3*G_PI_2, G_PI_2);
1032 cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + (radius * 2) + 1);
1033 cairo_arc_negative(cr, x + width - radius, priv->hscroll_rect.y + radius + 1, radius, G_PI_2, 3*G_PI_2);
1034 cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + 1);
1037 cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1043 hildon_pannable_area_expose_event (GtkWidget * widget, GdkEventExpose * event)
1046 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1047 GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1048 GdkColor scroll_color = widget->style->base[GTK_STATE_SELECTED];
1050 if (gtk_bin_get_child (GTK_BIN (widget))) {
1052 if (priv->scroll_indicator_alpha > 0) {
1053 if (priv->vscroll) {
1054 hildon_pannable_draw_vscroll (widget, &back_color, &scroll_color);
1056 if (priv->hscroll) {
1057 hildon_pannable_draw_hscroll (widget, &back_color, &scroll_color);
1061 /* draw overshooting rectangles */
1062 if (priv->overshot_dist_y > 0) {
1063 gint overshot_height;
1065 overshot_height = MIN (priv->overshot_dist_y, widget->allocation.height -
1066 (priv->hscroll ? priv->hscroll_rect.height : 0));
1068 gdk_draw_rectangle (widget->window,
1069 widget->style->bg_gc[GTK_STATE_NORMAL],
1071 widget->allocation.x,
1072 widget->allocation.y,
1073 widget->allocation.width -
1074 (priv->vscroll ? priv->vscroll_rect.width : 0),
1076 } else if (priv->overshot_dist_y < 0) {
1077 gint overshot_height;
1081 MAX (priv->overshot_dist_y,
1082 -1*(widget->allocation.height -
1083 (priv->hscroll ? priv->hscroll_rect.height : 0)));
1085 overshot_y = MAX (widget->allocation.y +
1086 widget->allocation.height +
1088 (priv->hscroll ? priv->hscroll_rect.height : 0), 0);
1090 gdk_draw_rectangle (widget->window,
1091 widget->style->bg_gc[GTK_STATE_NORMAL],
1093 widget->allocation.x,
1095 widget->allocation.width -
1096 priv->vscroll_rect.width,
1100 if (priv->overshot_dist_x > 0) {
1101 gint overshot_width;
1103 overshot_width = MIN (priv->overshot_dist_x, widget->allocation.width -
1104 (priv->vscroll ? priv->vscroll_rect.width : 0));
1106 gdk_draw_rectangle (widget->window,
1107 widget->style->bg_gc[GTK_STATE_NORMAL],
1109 widget->allocation.x,
1110 widget->allocation.y,
1112 widget->allocation.height -
1113 (priv->hscroll ? priv->hscroll_rect.height : 0));
1114 } else if (priv->overshot_dist_x < 0) {
1115 gint overshot_width;
1119 MAX (priv->overshot_dist_x,
1120 -1*(widget->allocation.width -
1121 (priv->vscroll ? priv->vscroll_rect.width : 0)));
1123 overshot_x = MAX (widget->allocation.x +
1124 widget->allocation.width +
1126 (priv->vscroll ? priv->vscroll_rect.width : 0), 0);
1128 gdk_draw_rectangle (widget->window,
1129 widget->style->bg_gc[GTK_STATE_NORMAL],
1132 widget->allocation.y,
1134 widget->allocation.height -
1135 priv->hscroll_rect.height);
1140 return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
1144 hildon_pannable_area_destroy (GtkObject * object)
1146 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1148 if (priv->hadjust) {
1149 g_object_unref (G_OBJECT (priv->hadjust));
1150 priv->hadjust = NULL;
1153 if (priv->vadjust) {
1154 g_object_unref (G_OBJECT (priv->vadjust));
1155 priv->vadjust = NULL;
1158 GTK_OBJECT_CLASS (hildon_pannable_area_parent_class)->destroy (object);
1162 hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
1164 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (container);
1167 bin = GTK_BIN (container);
1168 g_return_if_fail (bin->child == NULL);
1171 gtk_widget_set_parent (child, GTK_WIDGET (bin));
1173 if (!gtk_widget_set_scroll_adjustments (child, priv->hadjust, priv->vadjust)) {
1174 g_warning ("%s: cannot add non scrollable widget, "
1175 "wrap it in a viewport", __FUNCTION__);
1180 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
1182 g_return_if_fail (HILDON_IS_PANNABLE_AREA (container));
1183 g_return_if_fail (child != NULL);
1184 g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == child);
1186 gtk_widget_set_scroll_adjustments (child, NULL, NULL);
1188 /* chain parent class handler to remove child */
1189 GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->remove (container, child);
1193 hildon_pannable_calculate_vel_factor (HildonPannableArea * self)
1195 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1200 n = ceil (priv->sps * priv->scroll_time);
1202 for (i = 0; i < n && fct_i >= RATIO_TOLERANCE; i++) {
1203 fct_i *= priv->decel;
1207 priv->vel_factor = fct;
1211 hildon_pannable_area_get_property (GObject * object, guint property_id,
1212 GValue * value, GParamSpec * pspec)
1214 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1216 switch (property_id) {
1218 g_value_set_boolean (value, priv->enabled);
1221 g_value_set_enum (value, priv->mode);
1223 case PROP_VELOCITY_MIN:
1224 g_value_set_double (value, priv->vmin);
1226 case PROP_VELOCITY_MAX:
1227 g_value_set_double (value, priv->vmax);
1229 case PROP_VELOCITY_FAST_FACTOR:
1230 g_value_set_double (value, priv->vfast_factor);
1232 case PROP_DECELERATION:
1233 g_value_set_double (value, priv->decel);
1236 g_value_set_uint (value, priv->sps);
1238 case PROP_VINDICATOR:
1239 g_value_set_enum (value, priv->vindicator_mode);
1241 case PROP_HINDICATOR:
1242 g_value_set_enum (value, priv->hindicator_mode);
1244 case PROP_OVERSHOOT_MAX:
1245 g_value_set_int (value, priv->overshoot_max);
1247 case PROP_SCROLL_TIME:
1248 g_value_set_double (value, priv->scroll_time);
1250 case PROP_INITIAL_HINT:
1251 g_value_set_boolean (value, priv->initial_hint);
1255 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1260 hildon_pannable_area_set_property (GObject * object, guint property_id,
1261 const GValue * value, GParamSpec * pspec)
1263 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1266 switch (property_id) {
1268 enabled = g_value_get_boolean (value);
1270 if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
1272 gdk_window_raise (priv->event_window);
1274 gdk_window_lower (priv->event_window);
1277 priv->enabled = enabled;
1280 priv->mode = g_value_get_enum (value);
1282 case PROP_VELOCITY_MIN:
1283 priv->vmin = g_value_get_double (value);
1285 case PROP_VELOCITY_MAX:
1286 priv->vmax = g_value_get_double (value);
1288 case PROP_VELOCITY_FAST_FACTOR:
1289 priv->vfast_factor = g_value_get_double (value);
1291 case PROP_DECELERATION:
1292 hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1294 priv->decel = g_value_get_double (value);
1297 priv->sps = g_value_get_uint (value);
1299 case PROP_VINDICATOR:
1300 priv->vindicator_mode = g_value_get_enum (value);
1302 case PROP_HINDICATOR:
1303 priv->hindicator_mode = g_value_get_enum (value);
1305 case PROP_OVERSHOOT_MAX:
1306 priv->overshoot_max = g_value_get_int (value);
1308 case PROP_SCROLL_TIME:
1309 priv->scroll_time = g_value_get_double (value);
1311 hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1313 case PROP_INITIAL_HINT:
1314 priv->initial_hint = g_value_get_boolean (value);
1318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1323 hildon_pannable_area_dispose (GObject * object)
1325 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1327 if (priv->idle_id) {
1328 g_source_remove (priv->idle_id);
1332 if (priv->scroll_indicator_timeout){
1333 g_source_remove (priv->scroll_indicator_timeout);
1334 priv->scroll_indicator_timeout = 0;
1337 if (priv->hadjust) {
1338 g_object_unref (priv->hadjust);
1339 priv->hadjust = NULL;
1341 if (priv->vadjust) {
1342 g_object_unref (priv->vadjust);
1343 priv->vadjust = NULL;
1346 if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
1347 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
1351 hildon_pannable_area_finalize (GObject * object)
1353 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->finalize (object);
1357 hildon_pannable_area_realize (GtkWidget * widget)
1359 GdkWindowAttr attributes;
1360 gint attributes_mask;
1362 HildonPannableAreaPrivate *priv;
1364 priv = PANNABLE_AREA_PRIVATE (widget);
1366 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1368 border_width = GTK_CONTAINER (widget)->border_width;
1370 attributes.x = widget->allocation.x + border_width;
1371 attributes.y = widget->allocation.y + border_width;
1372 attributes.width = widget->allocation.width - 2 * border_width -
1373 (priv->vscroll ? priv->vscroll_rect.width : 0);
1374 attributes.height = widget->allocation.height - 2 * border_width -
1375 (priv->hscroll ? priv->hscroll_rect.height : 0);
1376 attributes.window_type = GDK_WINDOW_CHILD;
1377 attributes.event_mask = gtk_widget_get_events (widget)
1378 | GDK_BUTTON_MOTION_MASK
1379 | GDK_BUTTON_PRESS_MASK
1380 | GDK_BUTTON_RELEASE_MASK
1381 | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
1383 widget->window = gtk_widget_get_parent_window (widget);
1384 g_object_ref (widget->window);
1386 attributes.wclass = GDK_INPUT_ONLY;
1387 attributes_mask = GDK_WA_X | GDK_WA_Y;
1389 priv->event_window = gdk_window_new (widget->window,
1390 &attributes, attributes_mask);
1391 gdk_window_set_user_data (priv->event_window, widget);
1393 widget->style = gtk_style_attach (widget->style, widget->window);
1397 hildon_pannable_area_unrealize (GtkWidget * widget)
1399 HildonPannableAreaPrivate *priv;
1401 priv = PANNABLE_AREA_PRIVATE (widget);
1403 if (priv->event_window != NULL) {
1404 gdk_window_set_user_data (priv->event_window, NULL);
1405 gdk_window_destroy (priv->event_window);
1406 priv->event_window = NULL;
1409 if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1410 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1411 unrealize) (widget);
1415 hildon_pannable_area_map (GtkWidget * widget)
1417 HildonPannableAreaPrivate *priv;
1418 gboolean hscroll, vscroll;
1420 priv = PANNABLE_AREA_PRIVATE (widget);
1422 if (priv->event_window != NULL && !priv->enabled)
1423 gdk_window_show (priv->event_window);
1425 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1427 if (priv->event_window != NULL && priv->enabled)
1428 gdk_window_show (priv->event_window);
1430 if (priv->initial_hint) {
1431 if ((priv->overshoot_max != 0) &&
1432 ((priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) ||
1433 (priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL))) {
1434 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
1435 priv->vadjust->page_size) ? TRUE : FALSE;
1436 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
1437 priv->hadjust->page_size) ? TRUE : FALSE;
1438 /* If scrolling is possible in both axes, only hint about scrolling in
1439 the vertical one. */
1441 priv->overshot_dist_y = priv->overshoot_max;
1442 priv->vel_y = priv->vmax * 0.1;
1443 } else if (hscroll) {
1444 priv->overshot_dist_x = priv->overshoot_max;
1445 priv->vel_x = priv->vmax * 0.1;
1448 if (vscroll || hscroll) {
1449 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1451 hildon_pannable_area_timeout, widget);
1455 if (priv->vscroll || priv->hscroll) {
1456 priv->scroll_indicator_alpha = 1;
1458 priv->scroll_indicator_timeout =
1459 g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1460 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
1467 hildon_pannable_area_unmap (GtkWidget * widget)
1469 HildonPannableAreaPrivate *priv;
1471 priv = PANNABLE_AREA_PRIVATE (widget);
1473 if (priv->event_window != NULL)
1474 gdk_window_hide (priv->event_window);
1476 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1480 hildon_pannable_area_size_request (GtkWidget * widget,
1481 GtkRequisition * requisition)
1483 /* Request tiny size, seeing as we have no decoration of our own. */
1484 requisition->width = 32;
1485 requisition->height = 32;
1489 hildon_pannable_area_size_allocate (GtkWidget * widget,
1490 GtkAllocation * allocation)
1493 GtkAllocation child_allocation;
1494 HildonPannableAreaPrivate *priv;
1496 widget->allocation = *allocation;
1497 bin = GTK_BIN (widget);
1499 priv = PANNABLE_AREA_PRIVATE (widget);
1501 child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
1502 child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
1503 child_allocation.width = MAX (allocation->width -
1504 GTK_CONTAINER (widget)->border_width * 2, 0);
1505 child_allocation.height = MAX (allocation->height -
1506 GTK_CONTAINER (widget)->border_width * 2, 0);
1508 if (GTK_WIDGET_REALIZED (widget)) {
1509 if (priv->event_window != NULL)
1510 gdk_window_move_resize (priv->event_window,
1513 child_allocation.width,
1514 child_allocation.height);
1517 hildon_pannable_area_refresh (HILDON_PANNABLE_AREA (widget));
1519 child_allocation.width = MAX (child_allocation.width - (priv->vscroll ?
1520 priv->vscroll_rect.width : 0),
1522 child_allocation.height = MAX (child_allocation.height - (priv->hscroll ?
1523 priv->hscroll_rect.height : 0),
1526 if (priv->overshot_dist_y > 0) {
1527 child_allocation.y = MIN (child_allocation.y + priv->overshot_dist_y,
1528 allocation->y + child_allocation.height);
1529 child_allocation.height = MAX (child_allocation.height - priv->overshot_dist_y, 0);
1530 } else if (priv->overshot_dist_y < 0) {
1531 child_allocation.height = MAX (child_allocation.height + priv->overshot_dist_y, 0);
1534 if (priv->overshot_dist_x > 0) {
1535 child_allocation.x = MIN (child_allocation.x + priv->overshot_dist_x,
1536 allocation->x + child_allocation.width);
1537 child_allocation.width = MAX (child_allocation.width - priv->overshot_dist_x, 0);
1538 } else if (priv->overshot_dist_x < 0) {
1539 child_allocation.width = MAX (child_allocation.width + priv->overshot_dist_x, 0);
1543 gtk_widget_size_allocate (bin->child, &child_allocation);
1545 /* we have to this after child size_allocate because page_size is
1546 * changed when we allocate the size of the children */
1547 if (priv->overshot_dist_y < 0) {
1548 gtk_adjustment_set_value (priv->vadjust, priv->vadjust->upper -
1549 priv->vadjust->page_size);
1552 if (priv->overshot_dist_x < 0) {
1553 gtk_adjustment_set_value (priv->hadjust, priv->hadjust->upper -
1554 priv->hadjust->page_size);
1559 hildon_pannable_area_style_set (GtkWidget * widget, GtkStyle * previous_style)
1561 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1563 GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1564 style_set (widget, previous_style);
1566 gtk_widget_style_get (widget, "indicator-width", &priv->area_width, NULL);
1570 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
1572 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1573 GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
1574 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1575 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1578 g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
1580 object_class->get_property = hildon_pannable_area_get_property;
1581 object_class->set_property = hildon_pannable_area_set_property;
1582 object_class->dispose = hildon_pannable_area_dispose;
1583 object_class->finalize = hildon_pannable_area_finalize;
1585 gtkobject_class->destroy = hildon_pannable_area_destroy;
1587 widget_class->realize = hildon_pannable_area_realize;
1588 widget_class->unrealize = hildon_pannable_area_unrealize;
1589 widget_class->map = hildon_pannable_area_map;
1590 widget_class->unmap = hildon_pannable_area_unmap;
1591 widget_class->size_request = hildon_pannable_area_size_request;
1592 widget_class->size_allocate = hildon_pannable_area_size_allocate;
1593 widget_class->expose_event = hildon_pannable_area_expose_event;
1594 widget_class->style_set = hildon_pannable_area_style_set;
1595 widget_class->button_press_event = hildon_pannable_area_button_press_cb;
1596 widget_class->button_release_event = hildon_pannable_area_button_release_cb;
1597 widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
1599 container_class->add = hildon_pannable_area_add;
1600 container_class->remove = hildon_pannable_area_remove;
1602 g_object_class_install_property (object_class,
1604 g_param_spec_boolean ("enabled",
1606 "Enable or disable finger-scroll.",
1609 G_PARAM_CONSTRUCT));
1611 g_object_class_install_property (object_class,
1613 g_param_spec_enum ("vindicator_mode",
1615 "Mode of the vertical scrolling indicator",
1616 HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1617 HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1619 G_PARAM_CONSTRUCT));
1621 g_object_class_install_property (object_class,
1623 g_param_spec_enum ("hindicator_mode",
1625 "Mode of the horizontal scrolling indicator",
1626 HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1627 HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1629 G_PARAM_CONSTRUCT));
1631 g_object_class_install_property (object_class,
1633 g_param_spec_enum ("mode",
1635 "Change the finger-scrolling mode.",
1636 HILDON_TYPE_PANNABLE_AREA_MODE,
1637 HILDON_PANNABLE_AREA_MODE_AUTO,
1639 G_PARAM_CONSTRUCT));
1641 g_object_class_install_property (object_class,
1643 g_param_spec_double ("velocity_min",
1644 "Minimum scroll velocity",
1645 "Minimum distance the child widget should scroll "
1646 "per 'frame', in pixels.",
1649 G_PARAM_CONSTRUCT));
1651 g_object_class_install_property (object_class,
1653 g_param_spec_double ("velocity_max",
1654 "Maximum scroll velocity",
1655 "Maximum distance the child widget should scroll "
1656 "per 'frame', in pixels.",
1659 G_PARAM_CONSTRUCT));
1661 g_object_class_install_property (object_class,
1662 PROP_VELOCITY_FAST_FACTOR,
1663 g_param_spec_double ("velocity_fast_factor",
1664 "Fast velocity factor",
1665 "Minimum velocity that is considered 'fast': "
1666 "children widgets won't receive button presses. "
1667 "Expressed as a fraction of the maximum velocity.",
1670 G_PARAM_CONSTRUCT));
1672 g_object_class_install_property (object_class,
1674 g_param_spec_double ("deceleration",
1675 "Deceleration multiplier",
1676 "The multiplier used when decelerating when in "
1677 "acceleration scrolling mode.",
1680 G_PARAM_CONSTRUCT));
1682 g_object_class_install_property (object_class,
1684 g_param_spec_uint ("sps",
1685 "Scrolls per second",
1686 "Amount of scroll events to generate per second.",
1689 G_PARAM_CONSTRUCT));
1691 g_object_class_install_property (object_class,
1693 g_param_spec_int ("overshoot",
1694 "Overshoot distance",
1695 "Space we allow the widget to pass over its limits when hitting the edges, set 0 in order to deactivate overshooting.",
1696 G_MININT, G_MAXINT, 150,
1698 G_PARAM_CONSTRUCT));
1700 g_object_class_install_property (object_class,
1702 g_param_spec_double ("scroll_time",
1703 "Time to scroll to a position",
1704 "The time to scroll to a position when calling the hildon_pannable_scroll_to function"
1705 "acceleration scrolling mode.",
1708 G_PARAM_CONSTRUCT));
1710 g_object_class_install_property (object_class,
1712 g_param_spec_boolean ("initial-hint",
1714 "Whether to hint the user about the pannability of the container.",
1717 G_PARAM_CONSTRUCT));
1719 gtk_widget_class_install_style_property (widget_class,
1722 "Width of the scroll indicators",
1723 "Pixel width used to draw the scroll indicators.",
1725 G_PARAM_READWRITE));
1729 hildon_pannable_area_init (HildonPannableArea * self)
1731 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1733 priv->moved = FALSE;
1734 priv->clicked = FALSE;
1735 priv->last_time = 0;
1736 priv->last_type = 0;
1737 priv->vscroll = TRUE;
1738 priv->hscroll = TRUE;
1739 priv->area_width = 6;
1740 priv->overshot_dist_x = 0;
1741 priv->overshot_dist_y = 0;
1742 priv->overshooting_y = 0;
1743 priv->overshooting_x = 0;
1747 priv->scroll_indicator_alpha = 0;
1748 priv->scroll_indicator_timeout = 0;
1749 priv->scroll_indicator_event_interrupt = 0;
1750 priv->scroll_delay_counter = 0;
1751 priv->scroll_to_x = -1;
1752 priv->scroll_to_y = -1;
1754 hildon_pannable_calculate_vel_factor (self);
1756 gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);
1759 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1761 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1763 g_object_ref_sink (G_OBJECT (priv->hadjust));
1764 g_object_ref_sink (G_OBJECT (priv->vadjust));
1766 g_signal_connect_swapped (G_OBJECT (priv->hadjust), "changed",
1767 G_CALLBACK (hildon_pannable_area_refresh), self);
1768 g_signal_connect_swapped (G_OBJECT (priv->vadjust), "changed",
1769 G_CALLBACK (hildon_pannable_area_refresh), self);
1770 g_signal_connect_swapped (G_OBJECT (priv->hadjust), "value-changed",
1771 G_CALLBACK (hildon_pannable_area_redraw), self);
1772 g_signal_connect_swapped (G_OBJECT (priv->vadjust), "value-changed",
1773 G_CALLBACK (hildon_pannable_area_redraw), self);
1777 * hildon_pannable_area_new:
1779 * Create a new pannable area widget
1781 * Returns: the newly created #HildonPannableArea
1785 hildon_pannable_area_new (void)
1787 return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
1791 * hildon_pannable_area_new_full:
1792 * @mode: #HildonPannableAreaMode
1793 * @enabled: Value for the enabled property
1794 * @vel_min: Value for the velocity-min property
1795 * @vel_max: Value for the velocity-max property
1796 * @decel: Value for the deceleration property
1797 * @sps: Value for the sps property
1799 * Create a new #HildonPannableArea widget and set various properties
1801 * returns: the newly create #HildonPannableArea
1805 hildon_pannable_area_new_full (gint mode, gboolean enabled,
1806 gdouble vel_min, gdouble vel_max,
1807 gdouble decel, guint sps)
1809 return g_object_new (HILDON_TYPE_PANNABLE_AREA,
1812 "velocity_min", vel_min,
1813 "velocity_max", vel_max,
1814 "deceleration", decel, "sps", sps, NULL);
1818 * hildon_pannable_area_add_with_viewport:
1819 * @area: A #HildonPannableArea
1820 * @child: Child widget to add to the viewport
1822 * Convenience function used to add a child to a #GtkViewport, and add the
1823 * viewport to the scrolled window.
1827 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
1830 GtkWidget *viewport = gtk_viewport_new (NULL, NULL);
1831 gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1832 gtk_container_add (GTK_CONTAINER (viewport), child);
1833 gtk_widget_show (viewport);
1834 gtk_container_add (GTK_CONTAINER (area), viewport);
1838 * hildon_pannable_area_scroll_to:
1839 * @area: A #HildonPannableArea.
1840 * @x: The x coordinate of the destination point or -1 to ignore this axis.
1841 * @y: The y coordinate of the destination point or -1 to ignore this axis.
1843 * Smoothly scrolls @area to ensure that (@x, @y) is a visible point
1844 * on the widget. To move in only one coordinate, you must set the other one
1845 * to -1. Notice that, in %HILDON_PANNABLE_AREA_MODE_PUSH mode, this function
1846 * works just like hildon_pannable_area_jump_to().
1848 * This function is useful if you need to present the user with a particular
1849 * element inside a scrollable widget, like #GtkTreeView. For instance,
1850 * the following example shows how to scroll inside a #GtkTreeView to
1851 * make visible an item, indicated by the #GtkTreeIter @iter.
1853 * <informalexample><programlisting>
1854 * GtkTreePath *path;
1855 * GdkRectangle *rect;
1857 * path = gtk_tree_model_get_path (model, &iter);
1858 * gtk_tree_view_get_background_area (GTK_TREE_VIEW (treeview),
1859 * path, NULL, &rect);
1860 * gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (treeview),
1861 * 0, rect.y, NULL, &y);
1862 * hildon_pannable_area_scroll_to (panarea, -1, y);
1863 * gtk_tree_path_free (path);
1864 * </programlisting></informalexample>
1866 * If you want to present a child widget in simpler scenarios,
1867 * use hildon_pannable_area_scroll_to_child() instead.
1871 hildon_pannable_area_scroll_to (HildonPannableArea *area,
1872 const gint x, const gint y)
1874 HildonPannableAreaPrivate *priv;
1876 gint dist_x, dist_y;
1878 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
1880 priv = PANNABLE_AREA_PRIVATE (area);
1882 if (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)
1883 hildon_pannable_area_jump_to (area, x, y);
1885 g_return_if_fail (x >= -1 && y >= -1);
1887 if (x == -1 && y == -1) {
1891 width = priv->hadjust->upper - priv->hadjust->lower;
1892 height = priv->vadjust->upper - priv->vadjust->lower;
1894 g_return_if_fail (x < width || y < height);
1897 priv->scroll_to_x = x - priv->hadjust->page_size/2;
1898 dist_x = priv->scroll_to_x - priv->hadjust->value;
1900 priv->scroll_to_x = -1;
1902 priv->vel_x = - dist_x/priv->vel_factor;
1905 priv->scroll_to_x = -1;
1909 priv->scroll_to_y = y - priv->vadjust->page_size/2;
1910 dist_y = priv->scroll_to_y - priv->vadjust->value;
1912 priv->scroll_to_y = -1;
1914 priv->vel_y = - dist_y/priv->vel_factor;
1917 priv->scroll_to_y = y;
1920 if ((priv->scroll_to_y == -1) && (priv->scroll_to_y == -1)) {
1924 priv->scroll_indicator_alpha = 1.0;
1926 if (priv->scroll_indicator_timeout)
1927 g_source_remove (priv->scroll_indicator_timeout);
1928 priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1929 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, area);
1932 g_source_remove (priv->idle_id);
1933 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1935 hildon_pannable_area_timeout, area);
1939 * hildon_pannable_area_jump_to:
1940 * @area: A #HildonPannableArea.
1941 * @x: The x coordinate of the destination point or -1 to ignore this axis.
1942 * @y: The y coordinate of the destination point or -1 to ignore this axis.
1944 * Jumps the position of @area to ensure that (@x, @y) is a visible
1945 * point in the widget. In order to move in only one coordinate, you
1946 * must set the other one to -1. See hildon_pannable_area_scroll_to()
1947 * function for an example of how to calculate the position of
1948 * children in scrollable widgets like #GtkTreeview.
1952 hildon_pannable_area_jump_to (HildonPannableArea *area,
1953 const gint x, const gint y)
1955 HildonPannableAreaPrivate *priv;
1958 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
1959 g_return_if_fail (x >= -1 && y >= -1);
1961 if (x == -1 && y == -1) {
1965 priv = PANNABLE_AREA_PRIVATE (area);
1967 width = priv->hadjust->upper - priv->hadjust->lower;
1968 height = priv->vadjust->upper - priv->vadjust->lower;
1970 g_return_if_fail (x < width || y < height);
1973 gdouble jump_to = x - priv->hadjust->page_size/2;
1975 if (jump_to > priv->hadjust->upper - priv->hadjust->page_size) {
1976 jump_to = priv->hadjust->upper - priv->hadjust->page_size;
1979 gtk_adjustment_set_value (priv->hadjust, jump_to);
1983 gdouble jump_to = y - priv->vadjust->page_size/2;
1985 if (jump_to > priv->vadjust->upper - priv->vadjust->page_size) {
1986 jump_to = priv->vadjust->upper - priv->vadjust->page_size;
1989 gtk_adjustment_set_value (priv->vadjust, jump_to);
1992 priv->scroll_indicator_alpha = 1.0;
1994 if (priv->scroll_indicator_timeout) {
1998 priv->overshooting_x = 0;
1999 priv->overshooting_y = 0;
2001 if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
2002 priv->overshot_dist_x = 0;
2003 priv->overshot_dist_y = 0;
2005 gtk_widget_queue_resize (GTK_WIDGET (area));
2007 g_source_remove (priv->scroll_indicator_timeout);
2011 g_source_remove (priv->idle_id);
2015 * hildon_pannable_area_scroll_to_child:
2016 * @area: A #HildonPannableArea.
2017 * @child: A #GtkWidget, descendant of @area.
2019 * Smoothly scrolls until @child is visible inside @area. @child must
2020 * be a descendant of @area. If you need to scroll inside a scrollable
2021 * widget, e.g., #GtkTreeview, see hildon_pannable_area_scroll_to().
2025 hildon_pannable_area_scroll_to_child (HildonPannableArea *area, GtkWidget *child)
2027 GtkWidget *bin_child;
2030 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2031 g_return_if_fail (GTK_IS_WIDGET (child));
2032 g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
2034 if (GTK_BIN (area)->child == NULL)
2037 /* We need to get to check the child of the inside the area */
2038 bin_child = GTK_BIN (area)->child;
2040 /* we check if we added a viewport */
2041 if (GTK_IS_VIEWPORT (bin_child)) {
2042 bin_child = GTK_BIN (bin_child)->child;
2045 if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2046 hildon_pannable_area_scroll_to (area, x, y);
2050 * hildon_pannable_area_jump_to_child:
2051 * @area: A #HildonPannableArea.
2052 * @child: A #GtkWidget, descendant of @area.
2054 * Jumps to make sure @child is visible inside @area. @child must
2055 * be a descendant of @area. If you want to move inside a scrollable
2056 * widget, like, #GtkTreeview, see hildon_pannable_area_scroll_to().
2060 hildon_pannable_area_jump_to_child (HildonPannableArea *area, GtkWidget *child)
2062 GtkWidget *bin_child;
2065 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2066 g_return_if_fail (GTK_IS_WIDGET (child));
2067 g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
2069 if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
2072 /* We need to get to check the child of the inside the area */
2073 bin_child = gtk_bin_get_child (GTK_BIN (area));
2075 /* we check if we added a viewport */
2076 if (GTK_IS_VIEWPORT (bin_child)) {
2077 bin_child = gtk_bin_get_child (GTK_BIN (bin_child));
2080 if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2081 hildon_pannable_area_jump_to (area, x, y);