d7086b081ccdc9b3a771d0ba3bae5a8c209dfa1a
[hildon] / src / hildon-pannable-area.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Karl Lattimer <karl.lattimer@nokia.com>
7  *
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.
12  *
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.
16  *
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.
21  *
22  */
23
24 /**
25  * SECTION: hildon-pannable-area
26  * @short_description: A scrolling widget designed for touch screens
27  * @see_also: #GtkScrolledWindow
28  *
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.
32  *
33  */
34
35 /* TODO:
36  * todo items should be marked with a number and a relative position in the code where they are relevant.
37  * 
38  * 1  - Make OVERSHOOT a property, the default of this property is half of the pannable-area height
39  * 2  - Add method of scrolling to the selected element of a treeview and have it align to half way point
40  * 3  - Add a property for initial scrolling of the widget, so it scrolls on realize
41  * 4  - Add a method of removing children from the alignment, gtk-container-remove override?
42  * 5  - Remove elasticity if it is no longer required?
43  * 6  x Reduce calls to queue_resize to improve performance
44  * 7  - Make 'fast' factor a property
45  * 8  - Strip out g_print/g_debug calls
46  * 9  - Scroll policies
47  * 10 - Delay click mode (only send synthetic clicks on mouse-up, as in previous
48  *      versions.
49  * 11 - 'Physical' mode for acceleration scrolling
50  */
51
52 #define SMOOTH_FACTOR 0.85
53 #define FORCE 5
54 #define ELASTICITY 0.2
55 #define OVERSHOOT 150
56
57 #include <gdk/gdkx.h>
58 #include "hildon-pannable-area.h"
59
60 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
61 #define PANNABLE_AREA_PRIVATE(o) \
62     (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA,       \
63                                   HildonPannableAreaPrivate))
64      typedef struct _HildonPannableAreaPrivate HildonPannableAreaPrivate;
65
66      struct _HildonPannableAreaPrivate {
67        HildonPannableAreaMode mode;
68        GdkWindow *event_window;
69        gdouble x;               /* Used to store mouse co-ordinates of the first or */
70        gdouble y;               /* previous events in a press-motion pair */
71        gdouble ex;              /* Used to store mouse co-ordinates of the last */
72        gdouble ey;              /* motion event in acceleration mode */
73        gboolean enabled;
74        gboolean clicked;
75        guint32 last_time;       /* Last event time, to stop infinite loops */
76        gint last_type;
77        gboolean moved;
78        gdouble vmin;
79        gdouble vmax;
80        gdouble decel;
81        guint sps;
82        gdouble vel_x;
83        gdouble vel_y;
84        GdkWindow *child;
85        gint ix;                 /* Initial click mouse co-ordinates */
86        gint iy;
87        gint cx;                 /* Initial click child window mouse co-ordinates */
88        gint cy;
89        guint idle_id;
90        gint overshot_dist_x;
91        gint overshot_dist_y;
92        gint overshooting_y;
93        gint overshooting_x;
94
95        GtkWidget *align;
96        gboolean hscroll;
97        gboolean vscroll;
98        GdkRectangle hscroll_rect;
99        GdkRectangle vscroll_rect;
100        guint area_width;
101
102        GtkAdjustment *hadjust;
103        GtkAdjustment *vadjust;
104
105        gdouble click_x;
106        gdouble click_y;
107
108        guint event_mode;
109
110        HildonPannableAreaIndicatorMode vindicator_mode;
111        HildonPannableAreaIndicatorMode hindicator_mode;
112
113      };
114
115      enum {
116        PROP_ENABLED = 1,
117        PROP_MODE,
118        PROP_VELOCITY_MIN,
119        PROP_VELOCITY_MAX,
120        PROP_DECELERATION,
121        PROP_SPS,
122        PROP_VINDICATOR,
123        PROP_HINDICATOR,
124
125      };
126
127      static GdkWindow *hildon_pannable_area_get_topmost (GdkWindow * window,
128                                                          gint x, gint y,
129                                                          gint * tx, gint * ty)
130 {
131   /* Find the GdkWindow at the given point, by recursing from a given
132    * parent GdkWindow. Optionally return the co-ordinates transformed
133    * relative to the child window.
134    */
135   gint width, height;
136
137   gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
138   if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
139     return NULL;
140
141   /*g_debug ("Finding window at (%d, %d) in %p", x, y, window); */
142
143   while (window) {
144     gint child_x = 0, child_y = 0;
145     GList *c, *children = gdk_window_get_children (window);
146     GdkWindow *old_window = window;
147
148     for (c = children; c; c = c->next) {
149       GdkWindow *child = (GdkWindow *) c->data;
150       gint wx, wy;
151
152       gdk_window_get_geometry (child, &wx, &wy, &width, &height, NULL);
153       /*g_debug ("Child: %p, (%dx%d+%d,%d)", child,
154          width, height, wx, wy); */
155
156       if ((x >= wx) && (x < (wx + width)) && (y >= wy)
157           && (y < (wy + height))) {
158         child_x = x - wx;
159         child_y = y - wy;
160         window = child;
161       }
162     }
163
164     g_list_free (children);
165
166     /*g_debug ("\\|/"); */
167     if (window == old_window)
168       break;
169
170     x = child_x;
171     y = child_y;
172   }
173
174   if (tx)
175     *tx = x;
176   if (ty)
177     *ty = y;
178
179   /*g_debug ("Returning: %p", window); */
180
181   return window;
182 }
183
184 static void
185 synth_crossing (GdkWindow * child,
186                 gint x, gint y,
187                 gint x_root, gint y_root, guint32 time, gboolean in)
188 {
189   GdkEventCrossing *crossing_event;
190   GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
191
192   /* Send synthetic enter event */
193   crossing_event = (GdkEventCrossing *) gdk_event_new (type);
194   ((GdkEventAny *) crossing_event)->type = type;
195   ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
196   ((GdkEventAny *) crossing_event)->send_event = FALSE;
197   crossing_event->subwindow = g_object_ref (child);
198   crossing_event->time = time;
199   crossing_event->x = x;
200   crossing_event->y = y;
201   crossing_event->x_root = x_root;
202   crossing_event->y_root = y_root;
203   crossing_event->mode = GDK_CROSSING_NORMAL;
204   crossing_event->detail = GDK_NOTIFY_UNKNOWN;
205   crossing_event->focus = FALSE;
206   crossing_event->state = 0;
207   gdk_event_put ((GdkEvent *) crossing_event);
208   gdk_event_free ((GdkEvent *) crossing_event);
209 }
210
211 static gboolean
212 hildon_pannable_area_button_press_cb (GtkWidget * widget,
213                                       GdkEventButton * event)
214 {
215   gint x, y;
216   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
217
218   if ((!priv->enabled) || (event->button != 1) ||
219       ((event->time == priv->last_time) &&
220        (priv->last_type == 1)) || (priv->overshot_dist_y))
221     return TRUE;
222
223   priv->last_time = event->time;
224   priv->last_type = 1;
225
226   priv->click_x = event->x;
227   priv->click_y = event->y;
228
229   if (priv->clicked && priv->child) {
230     /* Widget stole focus on last click, send crossing-out event */
231     synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
232                     event->time, FALSE);
233   }
234
235   priv->x = event->x;
236   priv->y = event->y;
237   priv->ix = priv->x;
238   priv->iy = priv->y;
239   /* Don't allow a click if we're still moving fast, where fast is
240    * defined as a quarter of our top possible speed.
241    * TODO 7: Make 'fast' a property
242    */
243   if ((ABS (priv->vel_x) < (priv->vmax * 0.25)) &&
244       (ABS (priv->vel_y) < (priv->vmax * 0.25)))
245     priv->child =
246       hildon_pannable_area_get_topmost (GTK_BIN (priv->align)->child->window,
247                                         event->x, event->y, &x, &y);
248   else
249     priv->child = NULL;
250
251   priv->clicked = TRUE;
252   /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
253   priv->vel_x = 0;
254   priv->vel_y = 0;
255
256   if ((priv->child) && (priv->child != GTK_BIN (priv->align)->child->window)) {
257
258     g_object_add_weak_pointer ((GObject *) priv->child,
259                                (gpointer *) & priv->child);
260
261     event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
262     event->x = x;
263     event->y = y;
264     priv->cx = x;
265     priv->cy = y;
266
267     synth_crossing (priv->child, x, y, event->x_root,
268                     event->y_root, event->time, TRUE);
269
270     /* Send synthetic click (button press/release) event */
271     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
272     gdk_event_put ((GdkEvent *) event);
273     gdk_event_free ((GdkEvent *) event);
274   } else
275     priv->child = NULL;
276
277   return TRUE;
278 }
279
280 static void
281 hildon_pannable_area_redraw (HildonPannableArea * area)
282 {
283   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
284
285   /* Redraw scroll indicators */
286   if (priv->hscroll) {
287     if (GTK_WIDGET (area)->window) {
288       gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
289                                   &priv->hscroll_rect, FALSE);
290     }
291   }
292   if (priv->vscroll) {
293     if (GTK_WIDGET (area)->window) {
294       gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
295                                   &priv->vscroll_rect, FALSE);
296     }
297   }
298 }
299
300 static void
301 hildon_pannable_area_refresh (HildonPannableArea * area)
302 {
303   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
304   GtkWidget *widget = GTK_BIN (priv->align)->child;
305   gboolean vscroll, hscroll;
306
307   if (!widget)
308     return;
309
310   /* Calculate if we need scroll indicators */
311   gtk_widget_size_request (widget, NULL);
312
313   switch (priv->hindicator_mode) {
314   case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
315     hscroll = TRUE;
316     break;
317   case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
318     hscroll = FALSE;
319     break;
320   default:
321     hscroll = (priv->hadjust->upper - priv->hadjust->lower >
322                priv->hadjust->page_size) ? TRUE : FALSE;
323   }
324
325   switch (priv->vindicator_mode) {
326   case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
327     vscroll = TRUE;
328     break;
329   case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
330     vscroll = FALSE;
331     break;
332   default:
333     vscroll = (priv->vadjust->upper - priv->vadjust->lower >
334                priv->vadjust->page_size) ? TRUE : FALSE;
335   }
336
337   /* TODO: Read ltr settings to decide which corner gets scroll
338    * indicators?
339    */
340   if ((priv->vscroll != vscroll) || (priv->hscroll != hscroll)) {
341     gtk_alignment_set_padding (GTK_ALIGNMENT (priv->align), 0,
342                                hscroll ? priv->area_width : 0, 0,
343                                vscroll ? priv->area_width : 0);
344   }
345
346   /* Store the vscroll/hscroll areas for redrawing */
347   if (vscroll) {
348     GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
349     priv->vscroll_rect.x = allocation->x + allocation->width -
350       priv->area_width;
351     priv->vscroll_rect.y = allocation->y;
352     priv->vscroll_rect.width = priv->area_width;
353     priv->vscroll_rect.height = allocation->height -
354       (hscroll ? priv->area_width : 0);
355   }
356   if (hscroll) {
357     GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
358     priv->hscroll_rect.y = allocation->y + allocation->height -
359       priv->area_width;
360     priv->hscroll_rect.x = allocation->x;
361     priv->hscroll_rect.height = priv->area_width;
362     priv->hscroll_rect.width = allocation->width -
363       (vscroll ? priv->area_width : 0);
364   }
365
366   priv->vscroll = vscroll;
367   priv->hscroll = hscroll;
368
369   hildon_pannable_area_redraw (area);
370 }
371
372 static void
373 hildon_pannable_area_scroll (HildonPannableArea * area, gdouble x, gdouble y,
374                              gboolean * sx, gboolean * sy)
375 {
376   /* Scroll by a particular amount (in pixels). Optionally, return if
377    * the scroll on a particular axis was successful.
378    */
379   gdouble h, v;
380   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
381
382   if (!GTK_BIN (priv->align)->child) return;
383
384   if (priv->hadjust) {
385     h = gtk_adjustment_get_value (priv->hadjust) - x;
386     if (h > priv->hadjust->upper - priv->hadjust->page_size) {
387       if (sx) *sx = FALSE;
388       h = priv->hadjust->upper - priv->hadjust->page_size;
389     } else if (h < priv->hadjust->lower) {
390       if (sx) *sx = FALSE;
391       h = priv->hadjust->lower;
392     } else if (sx)
393       *sx = TRUE;
394     gtk_adjustment_set_value (priv->hadjust, h);
395   }
396
397   /** Overshooting
398    * We use overshot_dist_* to define the distance of the current overshoot,
399    * and overshooting_* to define the direction/whether or not we are overshot
400    */
401
402   if (priv->vadjust) {
403     v = gtk_adjustment_get_value (priv->vadjust) - y;
404     if (!priv->overshooting_y) {
405
406       /* Initiation of the overshoot happens when the finger is released
407        * and the current position of the pannable contents are out of range
408        */
409       if (v < priv->vadjust->lower) {
410         if (sy) *sy = FALSE;
411
412         v = priv->vadjust->lower;
413
414         if ((!priv->clicked) && (priv->vel_y > 0) &&
415             (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO)) {
416           priv->overshooting_y = 1;
417           priv->overshot_dist_y = MIN (priv->overshot_dist_y + priv->vel_y, OVERSHOOT);
418           gtk_widget_queue_resize (GTK_WIDGET (area));
419         }
420       } else if (v > priv->vadjust->upper - priv->vadjust->page_size) {
421         if (sy) *sy = FALSE;
422
423         v = priv->vadjust->upper - priv->vadjust->page_size;
424
425         if ((!priv->clicked) && (priv->vel_y < 0) &&
426             (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO)) {
427           priv->overshooting_y = 1;
428           priv->overshot_dist_y = MAX (priv->overshot_dist_y + priv->vel_y, -1*OVERSHOOT);
429           gtk_widget_queue_resize (GTK_WIDGET (area));
430         }
431       } else if (sy) {
432         *sy = TRUE;
433       }
434       gtk_adjustment_set_value (priv->vadjust, v); 
435     } else {
436       if (!priv->clicked) {
437         if (sy) *sy = TRUE;
438
439         /* When the overshoot has started we continue for 3 more steps into the overshoot
440          * before we reverse direction. The deceleration factor is calculated based on
441          * the percentage distance from the first item with each iteration, therefore always
442          * returning us to the top/bottom most element
443          */
444         if (priv->overshot_dist_y > 0) {
445           if ((priv->overshooting_y < 3) && (priv->vel_y > 0)) {
446             priv->overshooting_y++;
447             priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y;
448
449           } else if ((priv->overshooting_y >= 3) && (priv->vel_y > 0)) {
450             priv->vel_y *= -1;
451             priv->overshooting_y--;
452           } else if ((priv->overshooting_y > 1) && (priv->vel_y < 0)) {
453             priv->overshooting_y--;
454             priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y;
455           }             
456           priv->overshot_dist_y = MIN (priv->overshot_dist_y + priv->vel_y, OVERSHOOT);
457           if (priv->overshot_dist_y < 0) priv->overshot_dist_y = 0;
458           gtk_widget_queue_resize (GTK_WIDGET (area));
459
460         } else if (priv->overshot_dist_y < 0) {
461           if ((priv->overshooting_y < 3) && (priv->vel_y < 0)) {
462             priv->overshooting_y++;
463             priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y * -1;
464
465           } else if ((priv->overshooting_y >= 3) && (priv->vel_y < 0)) {
466             priv->vel_y *= -1;
467             priv->overshooting_y--;
468           } else if ((priv->overshooting_y > 1) && (priv->vel_y > 0)) {
469             priv->overshooting_y--;
470             priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y * -1;
471           }
472           priv->overshot_dist_y = MAX (priv->overshot_dist_y + priv->vel_y, -1*OVERSHOOT);
473           if (priv->overshot_dist_y > 0) priv->overshot_dist_y = 0;
474           gtk_widget_queue_resize (GTK_WIDGET (area));
475
476         } else {
477           priv->vel_y = 0;
478           priv->overshooting_y = 0;
479         }
480       } else {
481         gtk_adjustment_set_value (priv->vadjust, v); 
482       }
483     }
484   }
485   //g_print("Overshoot %d\n", priv->overshot_dist_y);
486   hildon_pannable_area_redraw (area);
487 }
488
489 static gboolean
490 hildon_pannable_area_timeout (HildonPannableArea * area)
491 {
492   gboolean sx, sy;
493   HildonPannableAreaPrivate *priv;
494
495   GDK_THREADS_ENTER ();
496
497   priv = PANNABLE_AREA_PRIVATE (area);
498
499   if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
500     priv->idle_id = 0;
501     return FALSE;
502   }
503
504   if (!priv->clicked) {                           
505     /* Decelerate gradually when pointer is raised */
506     if (!priv->overshot_dist_y) {
507       priv->vel_x *= priv->decel;
508       priv->vel_y *= priv->decel;
509       if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
510         priv->idle_id = 0;
511         return FALSE;
512       }
513     }
514   } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
515     priv->idle_id = 0;
516     return FALSE;
517   }
518
519   hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y, &sx, &sy);
520   /* If the scroll on a particular axis wasn't succesful, reset the
521    * initial scroll position to the new mouse co-ordinate. This means
522    * when you get to the top of the page, dragging down works immediately.
523    */
524   if (!sx) {
525     priv->x = priv->ex;
526     priv->vel_x *= -1 * priv->decel * ELASTICITY;
527   }
528
529   if (!sy) {
530     priv->y = priv->ey;
531     priv->vel_y *= -1 * priv->decel * ELASTICITY;
532   }
533
534   GDK_THREADS_LEAVE ();
535
536   return TRUE;
537 }
538
539 static gboolean
540 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
541                                        GdkEventMotion * event)
542 {
543   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
544   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
545   gint dnd_threshold;
546   gdouble x, y;
547   gdouble delta, rawvel_x, rawvel_y;
548   gdouble v;
549   gint direction_x, direction_y;
550
551   if ((!priv->enabled) || (!priv->clicked) ||
552       ((event->time == priv->last_time) && (priv->last_type == 2))) {
553     gdk_window_get_pointer (widget->window, NULL, NULL, 0);
554     return TRUE;
555   }
556
557   /* Only start the scroll if the mouse cursor passes beyond the
558    * DnD threshold for dragging.
559    */
560   g_object_get (G_OBJECT (gtk_settings_get_default ()),
561                 "gtk-dnd-drag-threshold", &dnd_threshold, NULL);
562   x = event->x - priv->x;
563   y = event->y - priv->y;
564
565   if ((!priv->moved) && ((ABS (x) > dnd_threshold)
566                          || (ABS (y) > dnd_threshold))) {
567     priv->moved = TRUE;
568     if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
569         (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
570
571       if (priv->idle_id)
572         g_source_remove (priv->idle_id);
573
574       priv->idle_id = g_timeout_add ((gint)
575                                      (1000.0 / (gdouble) priv->sps),
576                                      (GSourceFunc)
577                                      hildon_pannable_area_timeout, area);
578     }
579   }
580
581   if (priv->moved) {
582     switch (priv->mode) {
583     case HILDON_PANNABLE_AREA_MODE_PUSH:
584       /* Scroll by the amount of pixels the cursor has moved
585        * since the last motion event.
586        */
587       hildon_pannable_area_scroll (area, x, y, NULL, NULL);
588       priv->x = event->x;
589       priv->y = event->y;
590       break;
591     case HILDON_PANNABLE_AREA_MODE_ACCEL:
592       /* Set acceleration relative to the initial click */
593       priv->ex = event->x;
594       priv->ey = event->y;
595       priv->vel_x = ((x > 0) ? 1 : -1) *
596         (((ABS (x) /
597            (gdouble) widget->allocation.width) *
598           (priv->vmax - priv->vmin)) + priv->vmin);
599       priv->vel_y = ((y > 0) ? 1 : -1) *
600         (((ABS (y) /
601            (gdouble) widget->allocation.height) *
602           (priv->vmax - priv->vmin)) + priv->vmin);
603       break;
604     case HILDON_PANNABLE_AREA_MODE_AUTO:
605
606       delta = event->time - priv->last_time;
607
608       rawvel_x = (((event->x - priv->x) / ABS (delta)) *
609                   (gdouble) priv->sps) * FORCE;
610       rawvel_y = (((event->y - priv->y) / ABS (delta)) *
611                   (gdouble) priv->sps) * FORCE;
612
613       /* we store the direction and after the calculation we
614          change it, this reduces the ifs for the calculation */
615       direction_x = rawvel_x < 0 ? -1 : 1;
616       direction_y = rawvel_y < 0 ? -1 : 1;
617
618       rawvel_y = ABS (rawvel_y);
619       rawvel_x = ABS (rawvel_x);
620
621       priv->vel_x = priv->vel_x * (1 - SMOOTH_FACTOR) +
622         direction_x * rawvel_x * SMOOTH_FACTOR;
623       priv->vel_y = priv->vel_y * (1 - SMOOTH_FACTOR) +
624         direction_y * rawvel_y * SMOOTH_FACTOR;
625
626       priv->vel_x = priv->vel_x > 0 ? MIN (priv->vel_x, priv->vmax)
627         : MAX (priv->vel_x, -1 * priv->vmax);
628       priv->vel_y = priv->vel_y > 0 ? MIN (priv->vel_y, priv->vmax)
629         : MAX (priv->vel_y, -1 * priv->vmax);
630
631       /* Overshooting while the finger is down */
632       if (priv->overshot_dist_y > 0) {
633         priv->overshot_dist_y = CLAMP (priv->overshot_dist_y + y, 0, OVERSHOOT);
634         gtk_widget_queue_resize (GTK_WIDGET (area));
635       } else if (priv->overshot_dist_y < 0) {
636         priv->overshot_dist_y = CLAMP (priv->overshot_dist_y + y, -1 * OVERSHOOT, 0);
637         gtk_widget_queue_resize (GTK_WIDGET (area));
638       } else {
639         v = priv->vadjust->value - y;
640
641         if (v < priv->vadjust->lower) {
642           priv->overshooting_y = 1;
643           priv->overshot_dist_y = MIN (priv->overshot_dist_y + y, OVERSHOOT);
644           gtk_widget_queue_resize (GTK_WIDGET (area));
645         } else if (v > priv->vadjust->upper - priv->vadjust->page_size) {
646           priv->overshooting_y = 1;
647           priv->overshot_dist_y = MAX (priv->overshot_dist_y + y, -1 * OVERSHOOT);
648           gtk_widget_queue_resize (GTK_WIDGET (area));
649         } else {
650           hildon_pannable_area_scroll (area, x, y, NULL, NULL);
651         }
652       }
653       priv->x = event->x;
654       priv->y = event->y;
655
656       break;
657
658     default:
659       break;
660     }
661   }
662
663   if (priv->child) {
664     /* Send motion notify to child */
665     priv->last_time = event->time;
666     priv->last_type = 2;
667     event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
668     event->x = priv->cx + (event->x - priv->ix);
669     event->y = priv->cy + (event->y - priv->iy);
670     event->window = g_object_ref (priv->child);
671     gdk_event_put ((GdkEvent *) event);
672     gdk_event_free ((GdkEvent *) event);
673   }
674
675   gdk_window_get_pointer (widget->window, NULL, NULL, 0);
676
677   return TRUE;
678 }
679
680 static gboolean
681 hildon_pannable_area_button_release_cb (GtkWidget * widget,
682                                         GdkEventButton * event)
683 {
684   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
685   gint x, y;
686   GdkWindow *child;
687
688   if ((!priv->clicked) || (!priv->enabled) || (event->button != 1) ||
689       ((event->time == priv->last_time) && (priv->last_type == 3)))
690     return TRUE;
691   priv->clicked = FALSE;
692
693   if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
694     if (priv->idle_id)
695       g_source_remove (priv->idle_id);
696
697     /* If overshoot has been initiated with a finger down, on release set max speed */
698     if (priv->overshot_dist_y != 0) {
699         priv->vel_y = priv->vmax;
700         priv->overshooting_y = 3; // Hack to stop a bounce in the finger down case
701     }
702     priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
703                                    (GSourceFunc)
704                                    hildon_pannable_area_timeout, widget);
705   }
706
707   priv->last_time = event->time;
708   priv->last_type = 3;
709
710   if (!priv->child) {
711     priv->moved = FALSE;
712     return TRUE;
713   }
714
715   child =
716     hildon_pannable_area_get_topmost (GTK_BIN (priv->align)->child->window,
717                                       event->x, event->y, &x, &y);
718
719   event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
720   event->x = x;
721   event->y = y;
722
723   /* Leave the widget if we've moved - This doesn't break selection,
724    * but stops buttons from being clicked.
725    */
726   if ((child != priv->child) || (priv->moved)) {
727     /* Send synthetic leave event */
728     synth_crossing (priv->child, x, y, event->x_root,
729                     event->y_root, event->time, FALSE);
730     /* Send synthetic button release event */
731     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
732     gdk_event_put ((GdkEvent *) event);
733   } else {
734     /* Send synthetic button release event */
735     ((GdkEventAny *) event)->window = g_object_ref (child);
736     gdk_event_put ((GdkEvent *) event);
737     /* Send synthetic leave event */
738     synth_crossing (priv->child, x, y, event->x_root,
739                     event->y_root, event->time, FALSE);
740   }
741   g_object_remove_weak_pointer ((GObject *) priv->child,
742                                 (gpointer *) & priv->child);
743
744   priv->moved = FALSE;
745   gdk_event_free ((GdkEvent *) event);
746
747   return TRUE;
748 }
749
750 static gboolean
751 hildon_pannable_area_expose_event (GtkWidget * widget, GdkEventExpose * event)
752 {
753   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
754
755   if (GTK_BIN (priv->align)->child) {
756     if (priv->vscroll) {
757       gint y, height;
758       gdk_draw_rectangle (widget->window,
759                           widget->style->fg_gc[GTK_STATE_INSENSITIVE],
760                           TRUE,
761                           priv->vscroll_rect.x, priv->vscroll_rect.y,
762                           priv->vscroll_rect.width,
763                           priv->vscroll_rect.height);
764
765       y = widget->allocation.y +
766         ((priv->vadjust->value / priv->vadjust->upper) *
767          (widget->allocation.height -
768           (priv->hscroll ? priv->area_width : 0)));
769       height = (widget->allocation.y +
770                 (((priv->vadjust->value +
771                    priv->vadjust->page_size) /
772                   priv->vadjust->upper) *
773                  (widget->allocation.height -
774                   (priv->hscroll ? priv->area_width : 0)))) - y;
775
776       gdk_draw_rectangle (widget->window,
777                           widget->style->base_gc[GTK_STATE_SELECTED],
778                           TRUE, priv->vscroll_rect.x, y,
779                           priv->vscroll_rect.width, height);
780     }
781
782     if (priv->hscroll) {
783       gint x, width;
784       gdk_draw_rectangle (widget->window,
785                           widget->style->fg_gc[GTK_STATE_INSENSITIVE],
786                           TRUE,
787                           priv->hscroll_rect.x, priv->hscroll_rect.y,
788                           priv->hscroll_rect.width,
789                           priv->hscroll_rect.height);
790
791       x = widget->allocation.x +
792         ((priv->hadjust->value / priv->hadjust->upper) *
793          (widget->allocation.width - (priv->vscroll ? priv->area_width : 0)));
794       width =
795         (widget->allocation.x +
796          (((priv->hadjust->value +
797             priv->hadjust->page_size) / priv->hadjust->upper) *
798           (widget->allocation.width -
799            (priv->vscroll ? priv->area_width : 0)))) - x;
800
801       gdk_draw_rectangle (widget->window,
802                           widget->style->base_gc[GTK_STATE_SELECTED],
803                           TRUE, x, priv->hscroll_rect.y, width,
804                           priv->hscroll_rect.height);
805     }
806
807     if (priv->overshot_dist_y > 0) {
808       gdk_draw_rectangle (widget->window,
809                           widget->style->bg_gc[GTK_STATE_NORMAL],
810                           TRUE,
811                           widget->allocation.x,
812                           widget->allocation.y,
813                           widget->allocation.width -
814                           priv->vscroll_rect.width, priv->overshot_dist_y);
815     } else if (priv->overshot_dist_y < 0) {
816       gdk_draw_rectangle (widget->window,
817                           widget->style->bg_gc[GTK_STATE_NORMAL],
818                           TRUE,
819                           widget->allocation.x,
820                           widget->allocation.y +
821                           widget->allocation.height +
822                           priv->overshot_dist_y,
823                           widget->allocation.width -
824                           priv->vscroll_rect.width,
825                           -1 * priv->overshot_dist_y);
826     }
827   }
828
829   return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
830 }
831
832 static void
833 hildon_pannable_area_destroy (GtkObject * object)
834 {
835   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
836
837   if (priv->hadjust) {
838     g_object_unref (G_OBJECT (priv->hadjust));
839     priv->hadjust = NULL;
840   }
841
842   if (priv->vadjust) {
843     g_object_unref (G_OBJECT (priv->vadjust));
844     priv->vadjust = NULL;
845   }
846
847   GTK_OBJECT_CLASS (hildon_pannable_area_parent_class)->destroy (object);
848 }
849
850 static void
851 parent_set_cb (GtkWidget * widget, GtkObject * parent,
852                HildonPannableArea * area)
853 {
854   if (!parent) {
855     g_signal_handlers_disconnect_by_func (widget,
856                                           hildon_pannable_area_refresh, area);
857     g_signal_handlers_disconnect_by_func (widget, gtk_widget_queue_resize,
858                                           area);
859     g_signal_handlers_disconnect_by_func (widget, parent_set_cb, area);
860     gtk_widget_set_scroll_adjustments (widget, NULL, NULL);
861   }
862 }
863
864 static void
865 hildon_pannable_area_add (GtkContainer * container, GtkWidget * child)
866 {
867   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (container);
868
869   gtk_container_add (GTK_CONTAINER (priv->align), child);
870   g_signal_connect_swapped (child, "size-allocate",
871                             G_CALLBACK (hildon_pannable_area_refresh),
872                             container);
873   g_signal_connect_swapped (child, "size-request",
874                             G_CALLBACK (gtk_widget_queue_resize), container);
875   g_signal_connect (child, "parent-set", G_CALLBACK (parent_set_cb),
876                     container);
877
878   if (!gtk_widget_set_scroll_adjustments
879       (child, priv->hadjust, priv->vadjust))
880     g_warning ("%s: cannot add non scrollable widget, "
881                "wrap it in a viewport", __FUNCTION__);
882 }
883
884 static void
885 hildon_pannable_area_get_property (GObject * object, guint property_id,
886                                    GValue * value, GParamSpec * pspec)
887 {
888   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
889
890   switch (property_id) {
891   case PROP_ENABLED:
892     g_value_set_boolean (value, priv->enabled);
893     break;
894   case PROP_MODE:
895     g_value_set_enum (value, priv->mode);
896     break;
897   case PROP_VELOCITY_MIN:
898     g_value_set_double (value, priv->vmin);
899     break;
900   case PROP_VELOCITY_MAX:
901     g_value_set_double (value, priv->vmax);
902     break;
903   case PROP_DECELERATION:
904     g_value_set_double (value, priv->decel);
905     break;
906   case PROP_SPS:
907     g_value_set_uint (value, priv->sps);
908     break;
909   case PROP_VINDICATOR:
910     g_value_set_enum (value, priv->vindicator_mode);
911     break;
912   case PROP_HINDICATOR:
913     g_value_set_enum (value, priv->hindicator_mode);
914     break;
915
916   default:
917     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
918   }
919 }
920
921 static void
922 hildon_pannable_area_set_property (GObject * object, guint property_id,
923                                    const GValue * value, GParamSpec * pspec)
924 {
925   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
926   gboolean enabled;
927
928   switch (property_id) {
929   case PROP_ENABLED:
930     enabled = g_value_get_boolean (value);
931
932     if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
933       if (enabled)
934         gdk_window_raise (priv->event_window);
935       else
936         gdk_window_lower (priv->event_window);
937     }
938
939     priv->enabled = enabled;
940     break;
941   case PROP_MODE:
942     priv->mode = g_value_get_enum (value);
943     break;
944   case PROP_VELOCITY_MIN:
945     priv->vmin = g_value_get_double (value);
946     break;
947   case PROP_VELOCITY_MAX:
948     priv->vmax = g_value_get_double (value);
949     break;
950   case PROP_DECELERATION:
951     priv->decel = g_value_get_double (value);
952     break;
953   case PROP_SPS:
954     priv->sps = g_value_get_uint (value);
955     break;
956   case PROP_VINDICATOR:
957     priv->vindicator_mode = g_value_get_enum (value);
958     break;
959   case PROP_HINDICATOR:
960     priv->hindicator_mode = g_value_get_enum (value);
961     break;
962
963   default:
964     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
965   }
966 }
967
968 static void
969 hildon_pannable_area_dispose (GObject * object)
970 {
971   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
972
973   if (priv->idle_id) {
974     g_source_remove (priv->idle_id);
975     priv->idle_id = 0;
976   }
977
978   if (priv->hadjust) {
979     g_object_unref (priv->hadjust);
980     priv->hadjust = NULL;
981   }
982   if (priv->vadjust) {
983     g_object_unref (priv->vadjust);
984     priv->vadjust = NULL;
985   }
986
987   if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
988     G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
989 }
990
991 static void
992 hildon_pannable_area_finalize (GObject * object)
993 {
994   G_OBJECT_CLASS (hildon_pannable_area_parent_class)->finalize (object);
995 }
996
997 static void
998 hildon_pannable_area_realize (GtkWidget * widget)
999 {
1000   GdkWindowAttr attributes;
1001   gint attributes_mask;
1002   gint border_width;
1003   HildonPannableAreaPrivate *priv;
1004
1005   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1006
1007   border_width = GTK_CONTAINER (widget)->border_width;
1008
1009   attributes.x = widget->allocation.x + border_width;
1010   attributes.y = widget->allocation.y + border_width;
1011   attributes.width = widget->allocation.width - 2 * border_width;
1012   attributes.height = widget->allocation.height - 2 * border_width;
1013   attributes.window_type = GDK_WINDOW_CHILD;
1014   attributes.event_mask = gtk_widget_get_events (widget)
1015     | GDK_BUTTON_MOTION_MASK
1016     | GDK_BUTTON_PRESS_MASK
1017     | GDK_BUTTON_RELEASE_MASK
1018     | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
1019
1020   priv = PANNABLE_AREA_PRIVATE (widget);
1021
1022   widget->window = gtk_widget_get_parent_window (widget);
1023   g_object_ref (widget->window);
1024
1025   attributes.wclass = GDK_INPUT_ONLY;
1026   attributes_mask = GDK_WA_X | GDK_WA_Y;
1027
1028   priv->event_window = gdk_window_new (widget->window,
1029                                        &attributes, attributes_mask);
1030   gdk_window_set_user_data (priv->event_window, widget);
1031
1032   widget->style = gtk_style_attach (widget->style, widget->window);
1033 }
1034
1035 static void
1036 hildon_pannable_area_unrealize (GtkWidget * widget)
1037 {
1038   HildonPannableAreaPrivate *priv;
1039
1040   priv = PANNABLE_AREA_PRIVATE (widget);
1041
1042   if (priv->event_window != NULL) {
1043     gdk_window_set_user_data (priv->event_window, NULL);
1044     gdk_window_destroy (priv->event_window);
1045     priv->event_window = NULL;
1046   }
1047
1048   if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1049     (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1050      unrealize) (widget);
1051 }
1052
1053 static void
1054 hildon_pannable_area_map (GtkWidget * widget)
1055 {
1056   HildonPannableAreaPrivate *priv;
1057
1058   priv = PANNABLE_AREA_PRIVATE (widget);
1059
1060   if (priv->event_window != NULL && !priv->enabled)
1061     gdk_window_show (priv->event_window);
1062
1063   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1064
1065   if (priv->event_window != NULL && priv->enabled)
1066     gdk_window_show (priv->event_window);
1067
1068   if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
1069     // TODO 3: This should only be active if this hint has been set
1070     //priv->overshot_dist_y = OVERSHOOT;
1071
1072     //priv->vel_y = priv->vmax * 0.1;
1073
1074     priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1075                                    (GSourceFunc)
1076                                    hildon_pannable_area_timeout, widget);
1077   }
1078
1079 }
1080
1081 static void
1082 hildon_pannable_area_unmap (GtkWidget * widget)
1083 {
1084   HildonPannableAreaPrivate *priv;
1085
1086   priv = PANNABLE_AREA_PRIVATE (widget);
1087
1088   if (priv->event_window != NULL)
1089     gdk_window_hide (priv->event_window);
1090
1091   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1092 }
1093
1094 static void
1095 hildon_pannable_area_size_request (GtkWidget * widget,
1096                                    GtkRequisition * requisition)
1097 {
1098   /* Request tiny size, seeing as we have no decoration of our own. */
1099   requisition->width = 32;
1100   requisition->height = 32;
1101 }
1102
1103 static void
1104 hildon_pannable_area_size_allocate (GtkWidget * widget,
1105                                     GtkAllocation * allocation)
1106 {
1107   GtkBin *bin;
1108   GtkAllocation child_allocation;
1109   HildonPannableAreaPrivate *priv;
1110
1111   widget->allocation = *allocation;
1112   bin = GTK_BIN (widget);
1113
1114   priv = PANNABLE_AREA_PRIVATE (widget);
1115
1116   child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
1117   child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
1118   child_allocation.width = MAX (allocation->width -
1119                                 GTK_CONTAINER (widget)->border_width * 2, 0);
1120   child_allocation.height = MAX (allocation->height -
1121                                  GTK_CONTAINER (widget)->border_width * 2, 0);
1122
1123   if (GTK_WIDGET_REALIZED (widget)) {
1124     if (priv->event_window != NULL)
1125       gdk_window_move_resize (priv->event_window,
1126                               child_allocation.x,
1127                               child_allocation.y,
1128                               child_allocation.width,
1129                               child_allocation.height);
1130   }
1131
1132   /* negative overshooting? fix it */
1133   if (priv->overshot_dist_y > 0) {
1134     child_allocation.x = child_allocation.x;
1135     child_allocation.y += priv->overshot_dist_y;
1136     child_allocation.width = child_allocation.width;
1137     child_allocation.height -= priv->overshot_dist_y;
1138
1139     gtk_adjustment_set_value (priv->vadjust, priv->vadjust->lower);
1140
1141   } else if (priv->overshot_dist_y < 0) {
1142     /* change this assignations */
1143     child_allocation.x = child_allocation.x;
1144     child_allocation.y = child_allocation.y;
1145     child_allocation.width = child_allocation.width;
1146     child_allocation.height += priv->overshot_dist_y;
1147
1148     gtk_adjustment_set_value (priv->vadjust, priv->vadjust->upper -
1149                               priv->vadjust->page_size);
1150   }
1151
1152   if (bin->child)
1153     gtk_widget_size_allocate (bin->child, &child_allocation);
1154 }
1155
1156 static void
1157 hildon_pannable_area_style_set (GtkWidget * widget, GtkStyle * previous_style)
1158 {
1159   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1160
1161   GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1162     style_set (widget, previous_style);
1163
1164   gtk_widget_style_get (widget, "indicator-width", &priv->area_width, NULL);
1165 }
1166
1167 static void
1168 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
1169 {
1170   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1171   GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
1172   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1173   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1174
1175   g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
1176
1177   object_class->get_property = hildon_pannable_area_get_property;
1178   object_class->set_property = hildon_pannable_area_set_property;
1179   object_class->dispose = hildon_pannable_area_dispose;
1180   object_class->finalize = hildon_pannable_area_finalize;
1181
1182   gtkobject_class->destroy = hildon_pannable_area_destroy;
1183
1184   widget_class->realize = hildon_pannable_area_realize;
1185   widget_class->unrealize = hildon_pannable_area_unrealize;
1186   widget_class->map = hildon_pannable_area_map;
1187   widget_class->unmap = hildon_pannable_area_unmap;
1188   widget_class->size_request = hildon_pannable_area_size_request;
1189   widget_class->size_allocate = hildon_pannable_area_size_allocate;
1190   widget_class->expose_event = hildon_pannable_area_expose_event;
1191   widget_class->style_set = hildon_pannable_area_style_set;
1192   widget_class->button_press_event = hildon_pannable_area_button_press_cb;
1193   widget_class->button_release_event = hildon_pannable_area_button_release_cb;
1194   widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
1195
1196   container_class->add = hildon_pannable_area_add;
1197
1198   g_object_class_install_property (object_class,
1199                                    PROP_ENABLED,
1200                                    g_param_spec_boolean ("enabled",
1201                                                          "Enabled",
1202                                                          "Enable or disable finger-scroll.",
1203                                                          TRUE,
1204                                                          G_PARAM_READWRITE |
1205                                                          G_PARAM_CONSTRUCT));
1206
1207   g_object_class_install_property (object_class,
1208                                    PROP_VINDICATOR,
1209                                    g_param_spec_enum ("vindicator_mode",
1210                                                       "vindicator mode",
1211                                                       "Mode of the vertical scrolling indicator",
1212                                                       HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1213                                                       HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1214                                                       G_PARAM_READWRITE |
1215                                                       G_PARAM_CONSTRUCT));
1216
1217   g_object_class_install_property (object_class,
1218                                    PROP_HINDICATOR,
1219                                    g_param_spec_enum ("hindicator_mode",
1220                                                       "hindicator mode",
1221                                                       "Mode of the horizontal scrolling indicator",
1222                                                       HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1223                                                       HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1224                                                       G_PARAM_READWRITE |
1225                                                       G_PARAM_CONSTRUCT));
1226
1227   g_object_class_install_property (object_class,
1228                                    PROP_MODE,
1229                                    g_param_spec_enum ("mode",
1230                                                       "Scroll mode",
1231                                                       "Change the finger-scrolling mode.",
1232                                                       HILDON_TYPE_PANNABLE_AREA_MODE,
1233                                                       HILDON_PANNABLE_AREA_MODE_AUTO,
1234                                                       G_PARAM_READWRITE |
1235                                                       G_PARAM_CONSTRUCT));
1236
1237   g_object_class_install_property (object_class,
1238                                    PROP_VELOCITY_MIN,
1239                                    g_param_spec_double ("velocity_min",
1240                                                         "Minimum scroll velocity",
1241                                                         "Minimum distance the child widget should scroll "
1242                                                         "per 'frame', in pixels.",
1243                                                         0, G_MAXDOUBLE, 0,
1244                                                         G_PARAM_READWRITE |
1245                                                         G_PARAM_CONSTRUCT));
1246
1247   g_object_class_install_property (object_class,
1248                                    PROP_VELOCITY_MAX,
1249                                    g_param_spec_double ("velocity_max",
1250                                                         "Maximum scroll velocity",
1251                                                         "Maximum distance the child widget should scroll "
1252                                                         "per 'frame', in pixels.",
1253                                                         0, G_MAXDOUBLE, 48,
1254                                                         G_PARAM_READWRITE |
1255                                                         G_PARAM_CONSTRUCT));
1256
1257   g_object_class_install_property (object_class,
1258                                    PROP_DECELERATION,
1259                                    g_param_spec_double ("deceleration",
1260                                                         "Deceleration multiplier",
1261                                                         "The multiplier used when decelerating when in "
1262                                                         "acceleration scrolling mode.",
1263                                                         0, 1.0, 0.95,
1264                                                         G_PARAM_READWRITE |
1265                                                         G_PARAM_CONSTRUCT));
1266
1267   g_object_class_install_property (object_class,
1268                                    PROP_SPS,
1269                                    g_param_spec_uint ("sps",
1270                                                       "Scrolls per second",
1271                                                       "Amount of scroll events to generate per second.",
1272                                                       0, G_MAXUINT, 15,
1273                                                       G_PARAM_READWRITE |
1274                                                       G_PARAM_CONSTRUCT));
1275
1276   gtk_widget_class_install_style_property (widget_class,
1277                                            g_param_spec_uint
1278                                            ("indicator-width",
1279                                             "Width of the scroll indicators",
1280                                             "Pixel width used to draw the scroll indicators.",
1281                                             0, G_MAXUINT, 6,
1282                                             G_PARAM_READWRITE));
1283 }
1284
1285 static void
1286 hildon_pannable_area_init (HildonPannableArea * self)
1287 {
1288   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1289
1290   priv->moved = FALSE;
1291   priv->clicked = FALSE;
1292   priv->last_time = 0;
1293   priv->last_type = 0;
1294   priv->vscroll = TRUE;
1295   priv->hscroll = TRUE;
1296   priv->area_width = 6;
1297   priv->overshot_dist_x = 0;
1298   priv->overshot_dist_y = 0;
1299   priv->overshooting_y = 0;
1300   priv->overshooting_x = 0;
1301   priv->idle_id = 0;
1302
1303   priv->align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1304   GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->
1305     add (GTK_CONTAINER (self), priv->align);
1306   gtk_alignment_set_padding (GTK_ALIGNMENT (priv->align), 0, priv->area_width,
1307                              0, priv->area_width);
1308   gtk_widget_show (priv->align);
1309
1310   gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);
1311
1312   priv->hadjust =
1313     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1314   priv->vadjust =
1315     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1316
1317   g_object_ref_sink (G_OBJECT (priv->hadjust));
1318   g_object_ref_sink (G_OBJECT (priv->vadjust));
1319
1320   g_signal_connect_swapped (G_OBJECT (priv->hadjust), "changed",
1321                             G_CALLBACK (hildon_pannable_area_refresh), self);
1322   g_signal_connect_swapped (G_OBJECT (priv->vadjust), "changed",
1323                             G_CALLBACK (hildon_pannable_area_refresh), self);
1324   g_signal_connect_swapped (G_OBJECT (priv->hadjust), "value-changed",
1325                             G_CALLBACK (hildon_pannable_area_redraw), self);
1326   g_signal_connect_swapped (G_OBJECT (priv->vadjust), "value-changed",
1327                             G_CALLBACK (hildon_pannable_area_redraw), self);
1328 }
1329
1330 /**
1331  * hildon_pannable_area_new:
1332  *
1333  * Create a new pannable area widget
1334  *
1335  * Returns: the newly created #HildonPannableArea
1336  */
1337
1338 GtkWidget *
1339 hildon_pannable_area_new (void)
1340 {
1341   return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
1342 }
1343
1344 /**
1345  * hildon_pannable_area_new_full:
1346  * @mode: #HildonPannableAreaMode
1347  * @enabled: Value for the enabled property
1348  * @vel_min: Value for the velocity-min property
1349  * @vel_max: Value for the velocity-max property
1350  * @decel: Value for the deceleration property
1351  * @sps: Value for the sps property
1352  *
1353  * Create a new #HildonPannableArea widget and set various properties
1354  *
1355  * returns: the newly create #HildonPannableArea
1356  */
1357
1358 GtkWidget *
1359 hildon_pannable_area_new_full (gint mode, gboolean enabled,
1360                                gdouble vel_min, gdouble vel_max,
1361                                gdouble decel, guint sps)
1362 {
1363   return g_object_new (HILDON_TYPE_PANNABLE_AREA,
1364                        "mode", mode,
1365                        "enabled", enabled,
1366                        "velocity_min", vel_min,
1367                        "velocity_max", vel_max,
1368                        "deceleration", decel, "sps", sps, NULL);
1369 }
1370
1371 /**
1372  * hildon_pannable_area_add_with_viewport:
1373  * @area: A #HildonPannableArea
1374  * @child: Child widget to add to the viewport
1375  *
1376  * Convenience function used to add a child to a #GtkViewport, and add the
1377  * viewport to the scrolled window.
1378  */
1379
1380 void
1381 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
1382                                         GtkWidget * child)
1383 {
1384   GtkWidget *viewport = gtk_viewport_new (NULL, NULL);
1385   gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1386   gtk_container_add (GTK_CONTAINER (viewport), child);
1387   gtk_widget_show (viewport);
1388   gtk_container_add (GTK_CONTAINER (area), viewport);
1389 }