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.
38 #include "hildon-pannable-area.h"
39 #include "hildon-marshalers.h"
40 #include "hildon-enum-types.h"
42 #define SMOOTH_FACTOR 0.85
44 #define BOUNCE_STEPS 6
45 #define SCROLL_BAR_MIN_SIZE 5
46 #define RATIO_TOLERANCE 0.000001
47 #define DND_THRESHOLD_INC 20
49 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
50 #define PANNABLE_AREA_PRIVATE(o) \
51 (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA, \
52 HildonPannableAreaPrivate))
53 typedef struct _HildonPannableAreaPrivate HildonPannableAreaPrivate;
55 struct _HildonPannableAreaPrivate {
56 HildonPannableAreaMode mode;
57 HildonPannableAreaMovMode mov_mode;
58 GdkWindow *event_window;
59 gdouble x; /* Used to store mouse co-ordinates of the first or */
60 gdouble y; /* previous events in a press-motion pair */
61 gdouble ex; /* Used to store mouse co-ordinates of the last */
62 gdouble ey; /* motion event in acceleration mode */
65 guint32 last_time; /* Last event time, to stop infinite loops */
78 gint ix; /* Initial click mouse co-ordinates */
80 gint cx; /* Initial click child window mouse co-ordinates */
89 gdouble scroll_indicator_alpha;
90 gint scroll_indicator_timeout;
91 gint scroll_indicator_event_interrupt;
92 gint scroll_delay_counter;
95 gboolean initial_hint;
100 GdkRectangle hscroll_rect;
101 GdkRectangle vscroll_rect;
104 GtkAdjustment *hadjust;
105 GtkAdjustment *vadjust;
112 HildonPannableAreaIndicatorMode vindicator_mode;
113 HildonPannableAreaIndicatorMode hindicator_mode;
124 static guint pannable_area_signals [LAST_SIGNAL] = { 0 };
132 PROP_VELOCITY_FAST_FACTOR,
143 static GdkWindow *hildon_pannable_area_get_topmost (GdkWindow * window,
145 gint * tx, gint * ty)
147 /* Find the GdkWindow at the given point, by recursing from a given
148 * parent GdkWindow. Optionally return the co-ordinates transformed
149 * relative to the child window.
153 gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
154 if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
158 gint child_x = 0, child_y = 0;
159 GList *c, *children = gdk_window_peek_children (window);
160 GdkWindow *old_window = window;
162 for (c = children; c; c = c->next) {
163 GdkWindow *child = (GdkWindow *) c->data;
166 gdk_window_get_geometry (child, &wx, &wy, &width, &height, NULL);
168 if ((x >= wx) && (x < (wx + width)) && (y >= wy)
169 && (y < (wy + height))) {
176 if (window == old_window)
192 synth_crossing (GdkWindow * child,
194 gint x_root, gint y_root, guint32 time, gboolean in)
196 GdkEventCrossing *crossing_event;
197 GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
199 /* Send synthetic enter event */
200 crossing_event = (GdkEventCrossing *) gdk_event_new (type);
201 ((GdkEventAny *) crossing_event)->type = type;
202 ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
203 ((GdkEventAny *) crossing_event)->send_event = FALSE;
204 crossing_event->subwindow = g_object_ref (child);
205 crossing_event->time = time;
206 crossing_event->x = x;
207 crossing_event->y = y;
208 crossing_event->x_root = x_root;
209 crossing_event->y_root = y_root;
210 crossing_event->mode = GDK_CROSSING_NORMAL;
211 crossing_event->detail = GDK_NOTIFY_UNKNOWN;
212 crossing_event->focus = FALSE;
213 crossing_event->state = 0;
214 gdk_event_put ((GdkEvent *) crossing_event);
215 gdk_event_free ((GdkEvent *) crossing_event);
219 hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area)
222 HildonPannableAreaPrivate *priv;
224 GDK_THREADS_ENTER ();
226 priv = PANNABLE_AREA_PRIVATE (area);
228 /* if moving do not fade out */
229 if (((ABS (priv->vel_y)>1.0)||
230 (ABS (priv->vel_x)>1.0))&&(!priv->clicked)) {
234 if (!priv->scroll_indicator_timeout) {
238 if (priv->scroll_indicator_event_interrupt) {
239 /* Stop a fade out, and fade back in */
240 if (priv->scroll_indicator_alpha >= 0.9) {
241 priv->scroll_indicator_timeout = 0;
242 priv->scroll_indicator_alpha = 1;
245 priv->scroll_indicator_alpha += 0.2;
247 gtk_widget_queue_draw_area (GTK_WIDGET(area),
248 priv->vscroll_rect.x,
249 priv->vscroll_rect.y,
250 priv->vscroll_rect.width,
251 priv->vscroll_rect.height);
253 gtk_widget_queue_draw_area (GTK_WIDGET(area),
254 priv->hscroll_rect.x,
255 priv->hscroll_rect.y,
256 priv->hscroll_rect.width,
257 priv->hscroll_rect.height);
261 if ((priv->scroll_indicator_alpha > 0.9) &&
262 (priv->scroll_delay_counter < 20)) {
263 priv->scroll_delay_counter++;
267 if (!priv->scroll_indicator_event_interrupt) {
268 /* Continue fade out */
269 if (priv->scroll_indicator_alpha <= 0.1) {
270 priv->scroll_indicator_timeout = 0;
271 priv->scroll_delay_counter = 0;
272 priv->scroll_indicator_alpha = 0;
275 priv->scroll_indicator_alpha -= 0.2;
277 gtk_widget_queue_draw_area (GTK_WIDGET(area),
278 priv->vscroll_rect.x,
279 priv->vscroll_rect.y,
280 priv->vscroll_rect.width,
281 priv->vscroll_rect.height);
283 gtk_widget_queue_draw_area (GTK_WIDGET(area),
284 priv->hscroll_rect.x,
285 priv->hscroll_rect.y,
286 priv->hscroll_rect.width,
287 priv->hscroll_rect.height);
290 GDK_THREADS_LEAVE ();
296 hildon_pannable_area_button_press_cb (GtkWidget * widget,
297 GdkEventButton * event)
300 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
302 if ((!priv->enabled) || (event->button != 1) ||
303 ((event->time == priv->last_time) &&
304 (priv->last_type == 1)) || (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
307 priv->scroll_indicator_event_interrupt = 1;
308 if (priv->scroll_indicator_timeout){
309 g_source_remove (priv->scroll_indicator_timeout);
311 priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) (priv->sps*2)),
312 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
313 priv->last_time = event->time;
316 priv->click_x = event->x;
317 priv->click_y = event->y;
319 priv->scroll_to_x = -1;
320 priv->scroll_to_y = -1;
322 if (priv->clicked && priv->child) {
323 /* Widget stole focus on last click, send crossing-out event */
324 synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
332 /* Don't allow a click if we're still moving fast, where fast is
333 * defined as a quarter of our top possible speed.
335 if ((ABS (priv->vel_x) <= (priv->vmax * priv->vfast_factor)) &&
336 (ABS (priv->vel_y) <= (priv->vmax * priv->vfast_factor)))
338 hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
339 event->x, event->y, &x, &y);
343 priv->clicked = TRUE;
344 /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
348 if ((priv->child) && (priv->child != gtk_bin_get_child (GTK_BIN (widget))->window)) {
350 g_object_add_weak_pointer ((GObject *) priv->child,
351 (gpointer *) & priv->child);
353 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
359 synth_crossing (priv->child, x, y, event->x_root,
360 event->y_root, event->time, TRUE);
362 /* Send synthetic click (button press/release) event */
363 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
365 gdk_event_put ((GdkEvent *) event);
366 gdk_event_free ((GdkEvent *) event);
374 hildon_pannable_area_redraw (HildonPannableArea * area)
376 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
378 /* Redraw scroll indicators */
380 if (GTK_WIDGET (area)->window) {
381 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
382 &priv->hscroll_rect, FALSE);
386 if (GTK_WIDGET (area)->window) {
387 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
388 &priv->vscroll_rect, FALSE);
394 hildon_pannable_area_refresh (HildonPannableArea * area)
396 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
397 GtkWidget *widget = gtk_bin_get_child (GTK_BIN (area));
398 gboolean vscroll, hscroll;
401 priv->vscroll = FALSE;
402 priv->hscroll = FALSE;
406 /* Calculate if we need scroll indicators */
407 gtk_widget_size_request (widget, NULL);
409 switch (priv->hindicator_mode) {
410 case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
413 case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
417 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
418 priv->hadjust->page_size) ? TRUE : FALSE;
421 switch (priv->vindicator_mode) {
422 case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
425 case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
429 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
430 priv->vadjust->page_size) ? TRUE : FALSE;
433 /* Store the vscroll/hscroll areas for redrawing */
435 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
436 priv->vscroll_rect.x = allocation->x + allocation->width -
438 priv->vscroll_rect.y = allocation->y;
439 priv->vscroll_rect.width = priv->area_width;
440 priv->vscroll_rect.height = allocation->height -
441 (hscroll ? priv->area_width : 0);
444 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
445 priv->hscroll_rect.y = allocation->y + allocation->height -
447 priv->hscroll_rect.x = allocation->x;
448 priv->hscroll_rect.height = priv->area_width;
449 priv->hscroll_rect.width = allocation->width -
450 (vscroll ? priv->area_width : 0);
453 priv->vscroll = vscroll;
454 priv->hscroll = hscroll;
457 /* Scroll by a particular amount (in pixels). Optionally, return if
458 * the scroll on a particular axis was successful.
461 hildon_pannable_axis_scroll (HildonPannableArea *area,
462 GtkAdjustment *adjust,
472 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
474 dist = gtk_adjustment_get_value (adjust) - inc;
477 * We use overshot_dist to define the distance of the current overshoot,
478 * and overshooting to define the direction/whether or not we are overshot
480 if (!(*overshooting)) {
482 /* Initiation of the overshoot happens when the finger is released
483 * and the current position of the pannable contents are out of range
485 if (dist < adjust->lower) {
488 dist = adjust->lower;
490 if (overshoot_max!=0) {
493 *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
494 gtk_widget_queue_resize (GTK_WIDGET (area));
498 } else if (dist > adjust->upper - adjust->page_size) {
501 dist = adjust->upper - adjust->page_size;
503 if (overshoot_max!=0) {
506 *overshot_dist = CLAMP (*overshot_dist + *vel, -1*overshoot_max, 0);
507 gtk_widget_queue_resize (GTK_WIDGET (area));
512 if ((*scroll_to) != -1) {
513 if (((inc < 0)&&(*scroll_to <= dist))||
514 ((inc > 0)&&(*scroll_to >= dist))) {
522 gtk_adjustment_set_value (adjust, dist);
524 if (!priv->clicked) {
526 /* When the overshoot has started we continue for BOUNCE_STEPS more steps into the overshoot
527 * before we reverse direction. The deceleration factor is calculated based on
528 * the percentage distance from the first item with each iteration, therefore always
529 * returning us to the top/bottom most element
531 if (*overshot_dist > 0) {
533 if ((*overshooting < BOUNCE_STEPS) && (*vel > 0)) {
535 *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel);
536 } else if ((*overshooting >= BOUNCE_STEPS) && (*vel > 0)) {
539 } else if ((*overshooting > 1) && (*vel < 0)) {
541 /* we add the MAX in order to avoid very small speeds */
542 *vel = MIN ((((gdouble)*overshot_dist)/overshoot_max) * (*vel), -10.0);
545 *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
547 gtk_widget_queue_resize (GTK_WIDGET (area));
549 } else if (*overshot_dist < 0) {
551 if ((*overshooting < BOUNCE_STEPS) && (*vel < 0)) {
553 *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel) * -1;
554 } else if ((*overshooting >= BOUNCE_STEPS) && (*vel < 0)) {
557 } else if ((*overshooting > 1) && (*vel > 0)) {
559 /* we add the MIN in order to avoid very small speeds */
560 *vel = MAX ((((gdouble)*overshot_dist)/overshoot_max) * (*vel) * -1, 10.0);
563 *overshot_dist = CLAMP (*overshot_dist + (*vel), -1*overshoot_max, 0);
565 gtk_widget_queue_resize (GTK_WIDGET (area));
570 gtk_widget_queue_resize (GTK_WIDGET (area));
573 if (*overshot_dist > 0) {
574 *overshot_dist = CLAMP ((*overshot_dist) + inc, 0, overshoot_max);
575 } else if (*overshot_dist < 0) {
576 *overshot_dist = CLAMP ((*overshot_dist) + inc, -1 * overshoot_max, 0);
579 gtk_adjustment_set_value (adjust, dist);
581 gtk_widget_queue_resize (GTK_WIDGET (area));
587 hildon_pannable_area_scroll (HildonPannableArea *area,
588 gdouble x, gdouble y)
591 HildonPannableAreaPrivate *priv;
592 gboolean hscroll, vscroll;
594 priv = PANNABLE_AREA_PRIVATE (area);
596 if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
599 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
600 priv->vadjust->page_size) ? TRUE : FALSE;
601 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
602 priv->hadjust->page_size) ? TRUE : FALSE;
608 hildon_pannable_axis_scroll (area, priv->vadjust, &priv->vel_y, y,
609 &priv->overshooting_y, &priv->overshot_dist_y,
610 &priv->scroll_to_y, priv->vovershoot_max, &sy);
614 hildon_pannable_axis_scroll (area, priv->hadjust, &priv->vel_x, x,
615 &priv->overshooting_x, &priv->overshot_dist_x,
616 &priv->scroll_to_x, priv->hovershoot_max, &sx);
619 /* If the scroll on a particular axis wasn't succesful, reset the
620 * initial scroll position to the new mouse co-ordinate. This means
621 * when you get to the top of the page, dragging down works immediately.
634 hildon_pannable_area_timeout (HildonPannableArea * area)
636 HildonPannableAreaPrivate *priv;
638 GDK_THREADS_ENTER ();
640 priv = PANNABLE_AREA_PRIVATE (area);
642 if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
647 if (!priv->clicked) {
648 /* Decelerate gradually when pointer is raised */
649 if ((!priv->overshot_dist_y) &&
650 (!priv->overshot_dist_x)) {
652 /* in case we move to a specific point do not decelerate when arriving */
653 if ((priv->scroll_to_x != -1)||(priv->scroll_to_y != -1)) {
655 if (ABS (priv->vel_x) >= 1.5) {
656 priv->vel_x *= priv->decel;
659 if (ABS (priv->vel_y) >= 1.5) {
660 priv->vel_y *= priv->decel;
664 priv->vel_x *= priv->decel;
665 priv->vel_y *= priv->decel;
667 if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
675 } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
680 hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
682 GDK_THREADS_LEAVE ();
688 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
689 GdkEventMotion * event)
691 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
692 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
695 gdouble delta, rawvel_x, rawvel_y;
696 gint direction_x, direction_y;
698 if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
701 if ((!priv->enabled) || (!priv->clicked) ||
702 ((event->time == priv->last_time) && (priv->last_type == 2))) {
703 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
707 if (priv->last_type == 1) {
708 priv->first_drag = TRUE;
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->first_drag && (!priv->moved) &&
720 ((ABS (x) > (dnd_threshold+DND_THRESHOLD_INC))
721 || (ABS (y) > (dnd_threshold+DND_THRESHOLD_INC)))) {
724 if (priv->first_drag) {
726 if (ABS (priv->click_y - event->y) >=
727 ABS (priv->click_x - event->x)) {
731 pannable_area_signals[VERTICAL_MOVEMENT],
732 0, (priv->click_y > event->y) ?
733 HILDON_PANNABLE_AREA_MOV_UP :
734 HILDON_PANNABLE_AREA_MOV_DOWN,
735 priv->click_x, priv->click_y);
737 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
738 priv->vadjust->page_size) ? TRUE : FALSE;
741 (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_VERT)))
748 pannable_area_signals[HORIZONTAL_MOVEMENT],
749 0, (priv->click_x > event->x) ?
750 HILDON_PANNABLE_AREA_MOV_LEFT :
751 HILDON_PANNABLE_AREA_MOV_RIGHT,
752 priv->click_x, priv->click_y);
754 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
755 priv->hadjust->page_size) ? TRUE : FALSE;
758 (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_HORI)))
763 priv->first_drag = FALSE;
765 if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
766 (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
769 g_source_remove (priv->idle_id);
771 priv->idle_id = g_timeout_add ((gint)
772 (1000.0 / (gdouble) priv->sps),
774 hildon_pannable_area_timeout, area);
779 switch (priv->mode) {
780 case HILDON_PANNABLE_AREA_MODE_PUSH:
781 /* Scroll by the amount of pixels the cursor has moved
782 * since the last motion event.
784 hildon_pannable_area_scroll (area, x, y);
788 case HILDON_PANNABLE_AREA_MODE_ACCEL:
789 /* Set acceleration relative to the initial click */
792 priv->vel_x = ((x > 0) ? 1 : -1) *
794 (gdouble) widget->allocation.width) *
795 (priv->vmax - priv->vmin)) + priv->vmin);
796 priv->vel_y = ((y > 0) ? 1 : -1) *
798 (gdouble) widget->allocation.height) *
799 (priv->vmax - priv->vmin)) + priv->vmin);
801 case HILDON_PANNABLE_AREA_MODE_AUTO:
803 delta = event->time - priv->last_time;
805 if (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_HORI) {
806 rawvel_x = (((event->x - priv->x) / ABS (delta)) *
807 (gdouble) priv->sps) * FORCE;
808 /* we store the direction and after the calculation we
809 change it, this reduces the ifs for the calculation */
810 direction_x = rawvel_x < 0 ? -1 : 1;
811 rawvel_x = ABS (rawvel_x);
812 priv->vel_x = priv->vel_x * (1 - SMOOTH_FACTOR) +
813 direction_x * rawvel_x * SMOOTH_FACTOR;
814 priv->vel_x = priv->vel_x > 0 ? MIN (priv->vel_x, priv->vmax)
815 : MAX (priv->vel_x, -1 * priv->vmax);
821 if (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_VERT) {
822 rawvel_y = (((event->y - priv->y) / ABS (delta)) *
823 (gdouble) priv->sps) * FORCE;
824 direction_y = rawvel_y < 0 ? -1 : 1;
825 rawvel_y = ABS (rawvel_y);
826 priv->vel_y = priv->vel_y * (1 - SMOOTH_FACTOR) +
827 direction_y * rawvel_y * SMOOTH_FACTOR;
828 priv->vel_y = priv->vel_y > 0 ? MIN (priv->vel_y, priv->vmax)
829 : MAX (priv->vel_y, -1 * priv->vmax);
835 hildon_pannable_area_scroll (area, x, y);
837 if (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_HORI)
839 if (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_VERT)
850 /* Send motion notify to child */
851 priv->last_time = event->time;
853 event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
854 event->x = priv->cx + (event->x - priv->ix);
855 event->y = priv->cy + (event->y - priv->iy);
856 event->window = g_object_ref (priv->child);
857 gdk_event_put ((GdkEvent *) event);
858 gdk_event_free ((GdkEvent *) event);
861 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
867 hildon_pannable_area_button_release_cb (GtkWidget * widget,
868 GdkEventButton * event)
870 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
874 if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
877 priv->scroll_indicator_event_interrupt = 0;
878 priv->scroll_delay_counter = 0;
880 if (priv->scroll_indicator_timeout) {
881 g_source_remove (priv->scroll_indicator_timeout);
884 if ((ABS (priv->vel_y) > 1.0)||
885 (ABS (priv->vel_x) > 1.0)) {
886 priv->scroll_indicator_alpha = 1.0;
889 priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
890 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
892 if ((!priv->clicked) || (!priv->enabled) || (event->button != 1) ||
893 ((event->time == priv->last_time) && (priv->last_type == 3)))
896 priv->clicked = FALSE;
898 if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO ||
899 priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
901 g_source_remove (priv->idle_id);
903 /* If overshoot has been initiated with a finger down, on release set max speed */
904 if (priv->overshot_dist_y != 0) {
905 priv->overshooting_y = BOUNCE_STEPS; /* Hack to stop a bounce in the finger down case */
906 priv->vel_y = priv->vmax;
909 if (priv->overshot_dist_x != 0) {
910 priv->overshooting_x = BOUNCE_STEPS; /* Hack to stop a bounce in the finger down case */
911 priv->vel_x = priv->vmax;
914 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
916 hildon_pannable_area_timeout, widget);
919 priv->last_time = event->time;
928 hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
929 event->x, event->y, &x, &y);
931 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
935 /* Leave the widget if we've moved - This doesn't break selection,
936 * but stops buttons from being clicked.
938 if ((child != priv->child) || (priv->moved)) {
939 /* Send synthetic leave event */
940 synth_crossing (priv->child, x, y, event->x_root,
941 event->y_root, event->time, FALSE);
942 /* Send synthetic button release event */
943 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
944 gdk_event_put ((GdkEvent *) event);
946 /* Send synthetic button release event */
947 ((GdkEventAny *) event)->window = g_object_ref (child);
948 gdk_event_put ((GdkEvent *) event);
949 /* Send synthetic leave event */
950 synth_crossing (priv->child, x, y, event->x_root,
951 event->y_root, event->time, FALSE);
953 g_object_remove_weak_pointer ((GObject *) priv->child,
954 (gpointer *) & priv->child);
957 gdk_event_free ((GdkEvent *) event);
963 rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b)
965 *r = (color->red >> 8) / 255.0;
966 *g = (color->green >> 8) / 255.0;
967 *b = (color->blue >> 8) / 255.0;
971 hildon_pannable_draw_vscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
973 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
976 cairo_pattern_t *pattern;
978 gint radius = (priv->vscroll_rect.width/2) - 1;
980 cr = gdk_cairo_create(widget->window);
982 /* Draw the background */
983 rgb_from_gdkcolor (back_color, &r, &g, &b);
984 cairo_set_source_rgb (cr, r, g, b);
985 cairo_rectangle(cr, priv->vscroll_rect.x, priv->vscroll_rect.y,
986 priv->vscroll_rect.width,
987 priv->vscroll_rect.height);
988 cairo_fill_preserve (cr);
991 /* Calculate the scroll bar height and position */
992 y = widget->allocation.y +
993 ((priv->vadjust->value / priv->vadjust->upper) *
994 (widget->allocation.height -
995 (priv->hscroll ? priv->area_width : 0)));
996 height = (widget->allocation.y +
997 (((priv->vadjust->value +
998 priv->vadjust->page_size) /
999 priv->vadjust->upper) *
1000 (widget->allocation.height -
1001 (priv->hscroll ? priv->area_width : 0)))) - y;
1003 /* Set a minimum height */
1004 height = MAX (SCROLL_BAR_MIN_SIZE, height);
1006 /* Check the max y position */
1007 y = MIN (y, widget->allocation.height -
1008 (priv->hscroll ? priv->hscroll_rect.height : 0) -
1011 /* Draw the scrollbar */
1012 rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1014 pattern = cairo_pattern_create_linear(radius+1, y, radius+1,y + height);
1015 cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1016 cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1017 cairo_set_source(cr, pattern);
1019 cairo_pattern_destroy(pattern);
1021 cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + radius + 1, radius, G_PI, 0);
1022 cairo_line_to(cr, priv->vscroll_rect.x + (radius * 2) + 1, y + height - radius);
1023 cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + height - radius, radius, 0, G_PI);
1024 cairo_line_to(cr, priv->vscroll_rect.x + 1, y + height - radius);
1027 cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1033 hildon_pannable_draw_hscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
1035 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1038 cairo_pattern_t *pattern;
1040 gint radius = (priv->hscroll_rect.height/2) - 1;
1042 cr = gdk_cairo_create(widget->window);
1044 /* Draw the background */
1045 rgb_from_gdkcolor (back_color, &r, &g, &b);
1046 cairo_set_source_rgb (cr, r, g, b);
1047 cairo_rectangle(cr, priv->hscroll_rect.x, priv->hscroll_rect.y,
1048 priv->hscroll_rect.width,
1049 priv->hscroll_rect.height);
1050 cairo_fill_preserve (cr);
1053 /* calculate the scrollbar width and position */
1054 x = widget->allocation.x +
1055 ((priv->hadjust->value / priv->hadjust->upper) *
1056 (widget->allocation.width - (priv->vscroll ? priv->area_width : 0)));
1058 (widget->allocation.x +
1059 (((priv->hadjust->value +
1060 priv->hadjust->page_size) / priv->hadjust->upper) *
1061 (widget->allocation.width -
1062 (priv->vscroll ? priv->area_width : 0)))) - x;
1064 /* Set a minimum width */
1065 width = MAX (SCROLL_BAR_MIN_SIZE, width);
1067 /* Check the max x position */
1068 x = MIN (x, widget->allocation.width -
1069 (priv->vscroll ? priv->vscroll_rect.width : 0) -
1072 /* Draw the scrollbar */
1073 rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1075 pattern = cairo_pattern_create_linear(x, radius+1, x+width, radius+1);
1076 cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1077 cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1078 cairo_set_source(cr, pattern);
1080 cairo_pattern_destroy(pattern);
1082 cairo_arc_negative(cr, x + radius + 1, priv->hscroll_rect.y + radius + 1, radius, 3*G_PI_2, G_PI_2);
1083 cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + (radius * 2) + 1);
1084 cairo_arc_negative(cr, x + width - radius, priv->hscroll_rect.y + radius + 1, radius, G_PI_2, 3*G_PI_2);
1085 cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + 1);
1088 cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1094 hildon_pannable_area_expose_event (GtkWidget * widget, GdkEventExpose * event)
1097 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1098 GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1099 GdkColor scroll_color = widget->style->base[GTK_STATE_SELECTED];
1101 if (gtk_bin_get_child (GTK_BIN (widget))) {
1103 if (priv->scroll_indicator_alpha > 0) {
1104 if (priv->vscroll) {
1105 hildon_pannable_draw_vscroll (widget, &back_color, &scroll_color);
1107 if (priv->hscroll) {
1108 hildon_pannable_draw_hscroll (widget, &back_color, &scroll_color);
1112 /* draw overshooting rectangles */
1113 if (priv->overshot_dist_y > 0) {
1114 gint overshot_height;
1116 overshot_height = MIN (priv->overshot_dist_y, widget->allocation.height -
1117 (priv->hscroll ? priv->hscroll_rect.height : 0));
1119 gdk_draw_rectangle (widget->window,
1120 widget->style->bg_gc[GTK_STATE_NORMAL],
1122 widget->allocation.x,
1123 widget->allocation.y,
1124 widget->allocation.width -
1125 (priv->vscroll ? priv->vscroll_rect.width : 0),
1127 } else if (priv->overshot_dist_y < 0) {
1128 gint overshot_height;
1132 MAX (priv->overshot_dist_y,
1133 -1*(widget->allocation.height -
1134 (priv->hscroll ? priv->hscroll_rect.height : 0)));
1136 overshot_y = MAX (widget->allocation.y +
1137 widget->allocation.height +
1139 (priv->hscroll ? priv->hscroll_rect.height : 0), 0);
1141 gdk_draw_rectangle (widget->window,
1142 widget->style->bg_gc[GTK_STATE_NORMAL],
1144 widget->allocation.x,
1146 widget->allocation.width -
1147 priv->vscroll_rect.width,
1151 if (priv->overshot_dist_x > 0) {
1152 gint overshot_width;
1154 overshot_width = MIN (priv->overshot_dist_x, widget->allocation.width -
1155 (priv->vscroll ? priv->vscroll_rect.width : 0));
1157 gdk_draw_rectangle (widget->window,
1158 widget->style->bg_gc[GTK_STATE_NORMAL],
1160 widget->allocation.x,
1161 widget->allocation.y,
1163 widget->allocation.height -
1164 (priv->hscroll ? priv->hscroll_rect.height : 0));
1165 } else if (priv->overshot_dist_x < 0) {
1166 gint overshot_width;
1170 MAX (priv->overshot_dist_x,
1171 -1*(widget->allocation.width -
1172 (priv->vscroll ? priv->vscroll_rect.width : 0)));
1174 overshot_x = MAX (widget->allocation.x +
1175 widget->allocation.width +
1177 (priv->vscroll ? priv->vscroll_rect.width : 0), 0);
1179 gdk_draw_rectangle (widget->window,
1180 widget->style->bg_gc[GTK_STATE_NORMAL],
1183 widget->allocation.y,
1185 widget->allocation.height -
1186 priv->hscroll_rect.height);
1191 return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
1195 hildon_pannable_area_destroy (GtkObject * object)
1197 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1199 if (priv->hadjust) {
1200 g_object_unref (G_OBJECT (priv->hadjust));
1201 priv->hadjust = NULL;
1204 if (priv->vadjust) {
1205 g_object_unref (G_OBJECT (priv->vadjust));
1206 priv->vadjust = NULL;
1209 GTK_OBJECT_CLASS (hildon_pannable_area_parent_class)->destroy (object);
1213 hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
1215 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (container);
1218 bin = GTK_BIN (container);
1219 g_return_if_fail (bin->child == NULL);
1222 gtk_widget_set_parent (child, GTK_WIDGET (bin));
1224 if (!gtk_widget_set_scroll_adjustments (child, priv->hadjust, priv->vadjust)) {
1225 g_warning ("%s: cannot add non scrollable widget, "
1226 "wrap it in a viewport", __FUNCTION__);
1231 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
1233 g_return_if_fail (HILDON_IS_PANNABLE_AREA (container));
1234 g_return_if_fail (child != NULL);
1235 g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == child);
1237 gtk_widget_set_scroll_adjustments (child, NULL, NULL);
1239 /* chain parent class handler to remove child */
1240 GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->remove (container, child);
1244 hildon_pannable_calculate_vel_factor (HildonPannableArea * self)
1246 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1251 n = ceil (priv->sps * priv->scroll_time);
1253 for (i = 0; i < n && fct_i >= RATIO_TOLERANCE; i++) {
1254 fct_i *= priv->decel;
1258 priv->vel_factor = fct;
1262 hildon_pannable_area_get_property (GObject * object, guint property_id,
1263 GValue * value, GParamSpec * pspec)
1265 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1267 switch (property_id) {
1269 g_value_set_boolean (value, priv->enabled);
1272 g_value_set_enum (value, priv->mode);
1275 g_value_set_flags (value, priv->mov_mode);
1277 case PROP_VELOCITY_MIN:
1278 g_value_set_double (value, priv->vmin);
1280 case PROP_VELOCITY_MAX:
1281 g_value_set_double (value, priv->vmax);
1283 case PROP_VELOCITY_FAST_FACTOR:
1284 g_value_set_double (value, priv->vfast_factor);
1286 case PROP_DECELERATION:
1287 g_value_set_double (value, priv->decel);
1290 g_value_set_uint (value, priv->sps);
1292 case PROP_VINDICATOR:
1293 g_value_set_enum (value, priv->vindicator_mode);
1295 case PROP_HINDICATOR:
1296 g_value_set_enum (value, priv->hindicator_mode);
1298 case PROP_VOVERSHOOT_MAX:
1299 g_value_set_int (value, priv->vovershoot_max);
1301 case PROP_HOVERSHOOT_MAX:
1302 g_value_set_int (value, priv->hovershoot_max);
1304 case PROP_SCROLL_TIME:
1305 g_value_set_double (value, priv->scroll_time);
1307 case PROP_INITIAL_HINT:
1308 g_value_set_boolean (value, priv->initial_hint);
1312 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1317 hildon_pannable_area_set_property (GObject * object, guint property_id,
1318 const GValue * value, GParamSpec * pspec)
1320 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1323 switch (property_id) {
1325 enabled = g_value_get_boolean (value);
1327 if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
1329 gdk_window_raise (priv->event_window);
1331 gdk_window_lower (priv->event_window);
1334 priv->enabled = enabled;
1337 priv->mode = g_value_get_enum (value);
1340 priv->mov_mode = g_value_get_flags (value);
1342 case PROP_VELOCITY_MIN:
1343 priv->vmin = g_value_get_double (value);
1345 case PROP_VELOCITY_MAX:
1346 priv->vmax = g_value_get_double (value);
1348 case PROP_VELOCITY_FAST_FACTOR:
1349 priv->vfast_factor = g_value_get_double (value);
1351 case PROP_DECELERATION:
1352 hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1354 priv->decel = g_value_get_double (value);
1357 priv->sps = g_value_get_uint (value);
1359 case PROP_VINDICATOR:
1360 priv->vindicator_mode = g_value_get_enum (value);
1362 case PROP_HINDICATOR:
1363 priv->hindicator_mode = g_value_get_enum (value);
1365 case PROP_VOVERSHOOT_MAX:
1366 priv->vovershoot_max = g_value_get_int (value);
1368 case PROP_HOVERSHOOT_MAX:
1369 priv->hovershoot_max = g_value_get_int (value);
1371 case PROP_SCROLL_TIME:
1372 priv->scroll_time = g_value_get_double (value);
1374 hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1376 case PROP_INITIAL_HINT:
1377 priv->initial_hint = g_value_get_boolean (value);
1381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1386 hildon_pannable_area_dispose (GObject * object)
1388 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1390 if (priv->idle_id) {
1391 g_source_remove (priv->idle_id);
1395 if (priv->scroll_indicator_timeout){
1396 g_source_remove (priv->scroll_indicator_timeout);
1397 priv->scroll_indicator_timeout = 0;
1400 if (priv->hadjust) {
1401 g_object_unref (priv->hadjust);
1402 priv->hadjust = NULL;
1404 if (priv->vadjust) {
1405 g_object_unref (priv->vadjust);
1406 priv->vadjust = NULL;
1409 if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
1410 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
1414 hildon_pannable_area_finalize (GObject * object)
1416 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->finalize (object);
1420 hildon_pannable_area_realize (GtkWidget * widget)
1422 GdkWindowAttr attributes;
1423 gint attributes_mask;
1425 HildonPannableAreaPrivate *priv;
1427 priv = PANNABLE_AREA_PRIVATE (widget);
1429 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1431 border_width = GTK_CONTAINER (widget)->border_width;
1433 attributes.x = widget->allocation.x + border_width;
1434 attributes.y = widget->allocation.y + border_width;
1435 attributes.width = widget->allocation.width - 2 * border_width -
1436 (priv->vscroll ? priv->vscroll_rect.width : 0);
1437 attributes.height = widget->allocation.height - 2 * border_width -
1438 (priv->hscroll ? priv->hscroll_rect.height : 0);
1439 attributes.window_type = GDK_WINDOW_CHILD;
1440 attributes.event_mask = gtk_widget_get_events (widget)
1441 | GDK_BUTTON_MOTION_MASK
1442 | GDK_BUTTON_PRESS_MASK
1443 | GDK_BUTTON_RELEASE_MASK
1444 | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
1446 widget->window = gtk_widget_get_parent_window (widget);
1447 g_object_ref (widget->window);
1449 attributes.wclass = GDK_INPUT_ONLY;
1450 attributes_mask = GDK_WA_X | GDK_WA_Y;
1452 priv->event_window = gdk_window_new (widget->window,
1453 &attributes, attributes_mask);
1454 gdk_window_set_user_data (priv->event_window, widget);
1456 widget->style = gtk_style_attach (widget->style, widget->window);
1460 hildon_pannable_area_unrealize (GtkWidget * widget)
1462 HildonPannableAreaPrivate *priv;
1464 priv = PANNABLE_AREA_PRIVATE (widget);
1466 if (priv->event_window != NULL) {
1467 gdk_window_set_user_data (priv->event_window, NULL);
1468 gdk_window_destroy (priv->event_window);
1469 priv->event_window = NULL;
1472 if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1473 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1474 unrealize) (widget);
1478 hildon_pannable_area_map (GtkWidget * widget)
1480 HildonPannableAreaPrivate *priv;
1481 gboolean hscroll, vscroll;
1483 priv = PANNABLE_AREA_PRIVATE (widget);
1485 if (priv->event_window != NULL && !priv->enabled)
1486 gdk_window_show (priv->event_window);
1488 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1490 if (priv->event_window != NULL && priv->enabled)
1491 gdk_window_show (priv->event_window);
1493 if (priv->initial_hint) {
1494 if (((priv->vovershoot_max != 0)||(priv->hovershoot_max != 0)) &&
1495 ((priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) ||
1496 (priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL))) {
1497 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
1498 priv->vadjust->page_size) ? TRUE : FALSE;
1499 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
1500 priv->hadjust->page_size) ? TRUE : FALSE;
1501 /* If scrolling is possible in both axes, only hint about scrolling in
1502 the vertical one. */
1503 if ((vscroll)&&(priv->vovershoot_max != 0)) {
1504 priv->overshot_dist_y = priv->vovershoot_max;
1505 priv->vel_y = priv->vmax * 0.1;
1506 } else if ((hscroll)&&(priv->hovershoot_max != 0)) {
1507 priv->overshot_dist_x = priv->hovershoot_max;
1508 priv->vel_x = priv->vmax * 0.1;
1511 if (vscroll || hscroll) {
1512 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1514 hildon_pannable_area_timeout, widget);
1518 if (priv->vscroll || priv->hscroll) {
1519 priv->scroll_indicator_alpha = 1;
1521 priv->scroll_indicator_timeout =
1522 g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1523 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
1530 hildon_pannable_area_unmap (GtkWidget * widget)
1532 HildonPannableAreaPrivate *priv;
1534 priv = PANNABLE_AREA_PRIVATE (widget);
1536 if (priv->event_window != NULL)
1537 gdk_window_hide (priv->event_window);
1539 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1543 hildon_pannable_area_size_request (GtkWidget * widget,
1544 GtkRequisition * requisition)
1546 /* Request tiny size, seeing as we have no decoration of our own. */
1547 requisition->width = 32;
1548 requisition->height = 32;
1552 hildon_pannable_area_size_allocate (GtkWidget * widget,
1553 GtkAllocation * allocation)
1556 GtkAllocation child_allocation;
1557 HildonPannableAreaPrivate *priv;
1559 widget->allocation = *allocation;
1560 bin = GTK_BIN (widget);
1562 priv = PANNABLE_AREA_PRIVATE (widget);
1564 child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
1565 child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
1566 child_allocation.width = MAX (allocation->width -
1567 GTK_CONTAINER (widget)->border_width * 2, 0);
1568 child_allocation.height = MAX (allocation->height -
1569 GTK_CONTAINER (widget)->border_width * 2, 0);
1571 if (GTK_WIDGET_REALIZED (widget)) {
1572 if (priv->event_window != NULL)
1573 gdk_window_move_resize (priv->event_window,
1576 child_allocation.width,
1577 child_allocation.height);
1580 hildon_pannable_area_refresh (HILDON_PANNABLE_AREA (widget));
1582 child_allocation.width = MAX (child_allocation.width - (priv->vscroll ?
1583 priv->vscroll_rect.width : 0),
1585 child_allocation.height = MAX (child_allocation.height - (priv->hscroll ?
1586 priv->hscroll_rect.height : 0),
1589 if (priv->overshot_dist_y > 0) {
1590 child_allocation.y = MIN (child_allocation.y + priv->overshot_dist_y,
1591 allocation->y + child_allocation.height);
1592 child_allocation.height = MAX (child_allocation.height - priv->overshot_dist_y, 0);
1593 } else if (priv->overshot_dist_y < 0) {
1594 child_allocation.height = MAX (child_allocation.height + priv->overshot_dist_y, 0);
1597 if (priv->overshot_dist_x > 0) {
1598 child_allocation.x = MIN (child_allocation.x + priv->overshot_dist_x,
1599 allocation->x + child_allocation.width);
1600 child_allocation.width = MAX (child_allocation.width - priv->overshot_dist_x, 0);
1601 } else if (priv->overshot_dist_x < 0) {
1602 child_allocation.width = MAX (child_allocation.width + priv->overshot_dist_x, 0);
1606 gtk_widget_size_allocate (bin->child, &child_allocation);
1608 /* we have to this after child size_allocate because page_size is
1609 * changed when we allocate the size of the children */
1610 if (priv->overshot_dist_y < 0) {
1611 gtk_adjustment_set_value (priv->vadjust, priv->vadjust->upper -
1612 priv->vadjust->page_size);
1615 if (priv->overshot_dist_x < 0) {
1616 gtk_adjustment_set_value (priv->hadjust, priv->hadjust->upper -
1617 priv->hadjust->page_size);
1622 hildon_pannable_area_style_set (GtkWidget * widget, GtkStyle * previous_style)
1624 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1626 GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1627 style_set (widget, previous_style);
1629 gtk_widget_style_get (widget, "indicator-width", &priv->area_width, NULL);
1633 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
1635 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1636 GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
1637 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1638 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1641 g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
1643 object_class->get_property = hildon_pannable_area_get_property;
1644 object_class->set_property = hildon_pannable_area_set_property;
1645 object_class->dispose = hildon_pannable_area_dispose;
1646 object_class->finalize = hildon_pannable_area_finalize;
1648 gtkobject_class->destroy = hildon_pannable_area_destroy;
1650 widget_class->realize = hildon_pannable_area_realize;
1651 widget_class->unrealize = hildon_pannable_area_unrealize;
1652 widget_class->map = hildon_pannable_area_map;
1653 widget_class->unmap = hildon_pannable_area_unmap;
1654 widget_class->size_request = hildon_pannable_area_size_request;
1655 widget_class->size_allocate = hildon_pannable_area_size_allocate;
1656 widget_class->expose_event = hildon_pannable_area_expose_event;
1657 widget_class->style_set = hildon_pannable_area_style_set;
1658 widget_class->button_press_event = hildon_pannable_area_button_press_cb;
1659 widget_class->button_release_event = hildon_pannable_area_button_release_cb;
1660 widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
1662 container_class->add = hildon_pannable_area_add;
1663 container_class->remove = hildon_pannable_area_remove;
1665 klass->horizontal_movement = NULL;
1666 klass->vertical_movement = NULL;
1668 g_object_class_install_property (object_class,
1670 g_param_spec_boolean ("enabled",
1672 "Enable or disable finger-scroll.",
1675 G_PARAM_CONSTRUCT));
1677 g_object_class_install_property (object_class,
1679 g_param_spec_enum ("vindicator_mode",
1681 "Mode of the vertical scrolling indicator",
1682 HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1683 HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1685 G_PARAM_CONSTRUCT));
1687 g_object_class_install_property (object_class,
1689 g_param_spec_enum ("hindicator_mode",
1691 "Mode of the horizontal scrolling indicator",
1692 HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1693 HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1695 G_PARAM_CONSTRUCT));
1697 g_object_class_install_property (object_class,
1699 g_param_spec_enum ("mode",
1701 "Change the finger-scrolling mode.",
1702 HILDON_TYPE_PANNABLE_AREA_MODE,
1703 HILDON_PANNABLE_AREA_MODE_AUTO,
1705 G_PARAM_CONSTRUCT));
1707 g_object_class_install_property (object_class,
1709 g_param_spec_flags ("mov_mode",
1710 "Scroll movement mode",
1711 "Controls if the widget can scroll vertically, horizontally or both",
1712 HILDON_TYPE_PANNABLE_AREA_MOV_MODE,
1713 HILDON_PANNABLE_AREA_MOV_MODE_BOTH,
1715 G_PARAM_CONSTRUCT));
1717 g_object_class_install_property (object_class,
1719 g_param_spec_double ("velocity_min",
1720 "Minimum scroll velocity",
1721 "Minimum distance the child widget should scroll "
1722 "per 'frame', in pixels.",
1725 G_PARAM_CONSTRUCT));
1727 g_object_class_install_property (object_class,
1729 g_param_spec_double ("velocity_max",
1730 "Maximum scroll velocity",
1731 "Maximum distance the child widget should scroll "
1732 "per 'frame', in pixels.",
1735 G_PARAM_CONSTRUCT));
1737 g_object_class_install_property (object_class,
1738 PROP_VELOCITY_FAST_FACTOR,
1739 g_param_spec_double ("velocity_fast_factor",
1740 "Fast velocity factor",
1741 "Minimum velocity that is considered 'fast': "
1742 "children widgets won't receive button presses. "
1743 "Expressed as a fraction of the maximum velocity.",
1746 G_PARAM_CONSTRUCT));
1748 g_object_class_install_property (object_class,
1750 g_param_spec_double ("deceleration",
1751 "Deceleration multiplier",
1752 "The multiplier used when decelerating when in "
1753 "acceleration scrolling mode.",
1756 G_PARAM_CONSTRUCT));
1758 g_object_class_install_property (object_class,
1760 g_param_spec_uint ("sps",
1761 "Scrolls per second",
1762 "Amount of scroll events to generate per second.",
1765 G_PARAM_CONSTRUCT));
1767 g_object_class_install_property (object_class,
1768 PROP_VOVERSHOOT_MAX,
1769 g_param_spec_int ("vovershoot_max",
1770 "Vertical overshoot distance",
1771 "Space we allow the widget to pass over its vertical limits when hitting the edges, set 0 in order to deactivate overshooting.",
1774 G_PARAM_CONSTRUCT));
1776 g_object_class_install_property (object_class,
1777 PROP_HOVERSHOOT_MAX,
1778 g_param_spec_int ("hovershoot_max",
1779 "Horizontal overshoot distance",
1780 "Space we allow the widget to pass over its horizontal limits when hitting the edges, set 0 in order to deactivate overshooting.",
1783 G_PARAM_CONSTRUCT));
1785 g_object_class_install_property (object_class,
1787 g_param_spec_double ("scroll_time",
1788 "Time to scroll to a position",
1789 "The time to scroll to a position when calling the hildon_pannable_scroll_to function"
1790 "acceleration scrolling mode.",
1793 G_PARAM_CONSTRUCT));
1795 g_object_class_install_property (object_class,
1797 g_param_spec_boolean ("initial-hint",
1799 "Whether to hint the user about the pannability of the container.",
1802 G_PARAM_CONSTRUCT));
1804 gtk_widget_class_install_style_property (widget_class,
1807 "Width of the scroll indicators",
1808 "Pixel width used to draw the scroll indicators.",
1810 G_PARAM_READWRITE));
1812 pannable_area_signals[HORIZONTAL_MOVEMENT] =
1813 g_signal_new ("horizontal_movement",
1814 G_TYPE_FROM_CLASS (object_class),
1815 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1816 G_STRUCT_OFFSET (HildonPannableAreaClass, horizontal_movement),
1818 _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
1824 pannable_area_signals[VERTICAL_MOVEMENT] =
1825 g_signal_new ("vertical_movement",
1826 G_TYPE_FROM_CLASS (object_class),
1827 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1828 G_STRUCT_OFFSET (HildonPannableAreaClass, vertical_movement),
1830 _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
1840 hildon_pannable_area_init (HildonPannableArea * self)
1842 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1844 priv->moved = FALSE;
1845 priv->clicked = FALSE;
1846 priv->last_time = 0;
1847 priv->last_type = 0;
1848 priv->vscroll = TRUE;
1849 priv->hscroll = TRUE;
1850 priv->area_width = 6;
1851 priv->overshot_dist_x = 0;
1852 priv->overshot_dist_y = 0;
1853 priv->overshooting_y = 0;
1854 priv->overshooting_x = 0;
1858 priv->scroll_indicator_alpha = 0;
1859 priv->scroll_indicator_timeout = 0;
1860 priv->scroll_indicator_event_interrupt = 0;
1861 priv->scroll_delay_counter = 0;
1862 priv->scroll_to_x = -1;
1863 priv->scroll_to_y = -1;
1864 priv->first_drag = TRUE;
1866 hildon_pannable_calculate_vel_factor (self);
1868 gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);
1871 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1873 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1875 g_object_ref_sink (G_OBJECT (priv->hadjust));
1876 g_object_ref_sink (G_OBJECT (priv->vadjust));
1878 g_signal_connect_swapped (G_OBJECT (priv->hadjust), "changed",
1879 G_CALLBACK (hildon_pannable_area_refresh), self);
1880 g_signal_connect_swapped (G_OBJECT (priv->vadjust), "changed",
1881 G_CALLBACK (hildon_pannable_area_refresh), self);
1882 g_signal_connect_swapped (G_OBJECT (priv->hadjust), "value-changed",
1883 G_CALLBACK (hildon_pannable_area_redraw), self);
1884 g_signal_connect_swapped (G_OBJECT (priv->vadjust), "value-changed",
1885 G_CALLBACK (hildon_pannable_area_redraw), self);
1889 * hildon_pannable_area_new:
1891 * Create a new pannable area widget
1893 * Returns: the newly created #HildonPannableArea
1897 hildon_pannable_area_new (void)
1899 return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
1903 * hildon_pannable_area_new_full:
1904 * @mode: #HildonPannableAreaMode
1905 * @enabled: Value for the enabled property
1906 * @vel_min: Value for the velocity-min property
1907 * @vel_max: Value for the velocity-max property
1908 * @decel: Value for the deceleration property
1909 * @sps: Value for the sps property
1911 * Create a new #HildonPannableArea widget and set various properties
1913 * returns: the newly create #HildonPannableArea
1917 hildon_pannable_area_new_full (gint mode, gboolean enabled,
1918 gdouble vel_min, gdouble vel_max,
1919 gdouble decel, guint sps)
1921 return g_object_new (HILDON_TYPE_PANNABLE_AREA,
1924 "velocity_min", vel_min,
1925 "velocity_max", vel_max,
1926 "deceleration", decel, "sps", sps, NULL);
1930 * hildon_pannable_area_add_with_viewport:
1931 * @area: A #HildonPannableArea
1932 * @child: Child widget to add to the viewport
1934 * Convenience function used to add a child to a #GtkViewport, and add the
1935 * viewport to the scrolled window.
1939 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
1942 GtkWidget *viewport = gtk_viewport_new (NULL, NULL);
1943 gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1944 gtk_container_add (GTK_CONTAINER (viewport), child);
1945 gtk_widget_show (viewport);
1946 gtk_container_add (GTK_CONTAINER (area), viewport);
1950 * hildon_pannable_area_scroll_to:
1951 * @area: A #HildonPannableArea.
1952 * @x: The x coordinate of the destination point or -1 to ignore this axis.
1953 * @y: The y coordinate of the destination point or -1 to ignore this axis.
1955 * Smoothly scrolls @area to ensure that (@x, @y) is a visible point
1956 * on the widget. To move in only one coordinate, you must set the other one
1957 * to -1. Notice that, in %HILDON_PANNABLE_AREA_MODE_PUSH mode, this function
1958 * works just like hildon_pannable_area_jump_to().
1960 * This function is useful if you need to present the user with a particular
1961 * element inside a scrollable widget, like #GtkTreeView. For instance,
1962 * the following example shows how to scroll inside a #GtkTreeView to
1963 * make visible an item, indicated by the #GtkTreeIter @iter.
1965 * <informalexample><programlisting>
1966 * GtkTreePath *path;
1967 * GdkRectangle *rect;
1969 * path = gtk_tree_model_get_path (model, &iter);
1970 * gtk_tree_view_get_background_area (GTK_TREE_VIEW (treeview),
1971 * path, NULL, &rect);
1972 * gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (treeview),
1973 * 0, rect.y, NULL, &y);
1974 * hildon_pannable_area_scroll_to (panarea, -1, y);
1975 * gtk_tree_path_free (path);
1976 * </programlisting></informalexample>
1978 * If you want to present a child widget in simpler scenarios,
1979 * use hildon_pannable_area_scroll_to_child() instead.
1983 hildon_pannable_area_scroll_to (HildonPannableArea *area,
1984 const gint x, const gint y)
1986 HildonPannableAreaPrivate *priv;
1988 gint dist_x, dist_y;
1990 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
1992 priv = PANNABLE_AREA_PRIVATE (area);
1994 if (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)
1995 hildon_pannable_area_jump_to (area, x, y);
1997 g_return_if_fail (x >= -1 && y >= -1);
1999 if (x == -1 && y == -1) {
2003 width = priv->hadjust->upper - priv->hadjust->lower;
2004 height = priv->vadjust->upper - priv->vadjust->lower;
2006 g_return_if_fail (x < width || y < height);
2009 priv->scroll_to_x = x - priv->hadjust->page_size/2;
2010 dist_x = priv->scroll_to_x - priv->hadjust->value;
2012 priv->scroll_to_x = -1;
2014 priv->vel_x = - dist_x/priv->vel_factor;
2017 priv->scroll_to_x = -1;
2021 priv->scroll_to_y = y - priv->vadjust->page_size/2;
2022 dist_y = priv->scroll_to_y - priv->vadjust->value;
2024 priv->scroll_to_y = -1;
2026 priv->vel_y = - dist_y/priv->vel_factor;
2029 priv->scroll_to_y = y;
2032 if ((priv->scroll_to_y == -1) && (priv->scroll_to_y == -1)) {
2036 priv->scroll_indicator_alpha = 1.0;
2038 if (priv->scroll_indicator_timeout)
2039 g_source_remove (priv->scroll_indicator_timeout);
2040 priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
2041 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, area);
2044 g_source_remove (priv->idle_id);
2045 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
2047 hildon_pannable_area_timeout, area);
2051 * hildon_pannable_area_jump_to:
2052 * @area: A #HildonPannableArea.
2053 * @x: The x coordinate of the destination point or -1 to ignore this axis.
2054 * @y: The y coordinate of the destination point or -1 to ignore this axis.
2056 * Jumps the position of @area to ensure that (@x, @y) is a visible
2057 * point in the widget. In order to move in only one coordinate, you
2058 * must set the other one to -1. See hildon_pannable_area_scroll_to()
2059 * function for an example of how to calculate the position of
2060 * children in scrollable widgets like #GtkTreeview.
2064 hildon_pannable_area_jump_to (HildonPannableArea *area,
2065 const gint x, const gint y)
2067 HildonPannableAreaPrivate *priv;
2070 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2071 g_return_if_fail (x >= -1 && y >= -1);
2073 if (x == -1 && y == -1) {
2077 priv = PANNABLE_AREA_PRIVATE (area);
2079 width = priv->hadjust->upper - priv->hadjust->lower;
2080 height = priv->vadjust->upper - priv->vadjust->lower;
2082 g_return_if_fail (x < width || y < height);
2085 gdouble jump_to = x - priv->hadjust->page_size/2;
2087 if (jump_to > priv->hadjust->upper - priv->hadjust->page_size) {
2088 jump_to = priv->hadjust->upper - priv->hadjust->page_size;
2091 gtk_adjustment_set_value (priv->hadjust, jump_to);
2095 gdouble jump_to = y - priv->vadjust->page_size/2;
2097 if (jump_to > priv->vadjust->upper - priv->vadjust->page_size) {
2098 jump_to = priv->vadjust->upper - priv->vadjust->page_size;
2101 gtk_adjustment_set_value (priv->vadjust, jump_to);
2104 priv->scroll_indicator_alpha = 1.0;
2106 if (priv->scroll_indicator_timeout) {
2110 priv->overshooting_x = 0;
2111 priv->overshooting_y = 0;
2113 if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
2114 priv->overshot_dist_x = 0;
2115 priv->overshot_dist_y = 0;
2117 gtk_widget_queue_resize (GTK_WIDGET (area));
2119 g_source_remove (priv->scroll_indicator_timeout);
2120 priv->scroll_indicator_timeout = 0;
2124 g_source_remove (priv->idle_id);
2129 * hildon_pannable_area_scroll_to_child:
2130 * @area: A #HildonPannableArea.
2131 * @child: A #GtkWidget, descendant of @area.
2133 * Smoothly scrolls until @child is visible inside @area. @child must
2134 * be a descendant of @area. If you need to scroll inside a scrollable
2135 * widget, e.g., #GtkTreeview, see hildon_pannable_area_scroll_to().
2139 hildon_pannable_area_scroll_to_child (HildonPannableArea *area, GtkWidget *child)
2141 GtkWidget *bin_child;
2144 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2145 g_return_if_fail (GTK_IS_WIDGET (child));
2146 g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
2148 if (GTK_BIN (area)->child == NULL)
2151 /* We need to get to check the child of the inside the area */
2152 bin_child = GTK_BIN (area)->child;
2154 /* we check if we added a viewport */
2155 if (GTK_IS_VIEWPORT (bin_child)) {
2156 bin_child = GTK_BIN (bin_child)->child;
2159 if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2160 hildon_pannable_area_scroll_to (area, x, y);
2164 * hildon_pannable_area_jump_to_child:
2165 * @area: A #HildonPannableArea.
2166 * @child: A #GtkWidget, descendant of @area.
2168 * Jumps to make sure @child is visible inside @area. @child must
2169 * be a descendant of @area. If you want to move inside a scrollable
2170 * widget, like, #GtkTreeview, see hildon_pannable_area_scroll_to().
2174 hildon_pannable_area_jump_to_child (HildonPannableArea *area, GtkWidget *child)
2176 GtkWidget *bin_child;
2179 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2180 g_return_if_fail (GTK_IS_WIDGET (child));
2181 g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
2183 if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
2186 /* We need to get to check the child of the inside the area */
2187 bin_child = gtk_bin_get_child (GTK_BIN (area));
2189 /* we check if we added a viewport */
2190 if (GTK_IS_VIEWPORT (bin_child)) {
2191 bin_child = gtk_bin_get_child (GTK_BIN (bin_child));
2194 if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2195 hildon_pannable_area_jump_to (area, x, y);
2199 * hildon_pannable_get_child_widget_at:
2200 * @area: A #HildonPannableArea.
2201 * @x: horizontal coordinate of the point
2202 * @y: vertical coordinate of the point
2204 * Get the widget at the point (x, y) inside the pannable area. In
2205 * case no widget found it returns NULL.
2207 * returns: the #GtkWidget if we find a widget, NULL in any other case
2210 hildon_pannable_get_child_widget_at (HildonPannableArea *area,
2211 gdouble x, gdouble y)
2213 GdkWindow *window = NULL;
2214 GtkWidget *child_widget = NULL;
2216 window = hildon_pannable_area_get_topmost
2217 (gtk_bin_get_child (GTK_BIN (area))->window,
2220 gdk_window_get_user_data (window, (void**) &child_widget);
2222 return child_widget;