2008-07-29 Claudio Saavedra <csaavedra@igalia.com>
[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 #include <gdk/gdkx.h>
36 #include <cairo.h>
37 #include <math.h>
38 #include "hildon-pannable-area.h"
39 #include "hildon-marshalers.h"
40 #include "hildon-enum-types.h"
41
42 #define SMOOTH_FACTOR 0.85
43 #define FORCE 5
44 #define BOUNCE_STEPS 6
45 #define SCROLL_BAR_MIN_SIZE 5
46 #define RATIO_TOLERANCE 0.000001
47 #define DND_THRESHOLD_INC 20
48
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;
54
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 */
63   gboolean enabled;
64   gboolean clicked;
65   guint32 last_time;    /* Last event time, to stop infinite loops */
66   gint last_type;
67   gboolean moved;
68   gdouble vmin;
69   gdouble vmax;
70   gdouble vfast_factor;
71   gdouble decel;
72   gdouble scroll_time;
73   gdouble vel_factor;
74   guint sps;
75   gdouble vel_x;
76   gdouble vel_y;
77   GdkWindow *child;
78   gint ix;                      /* Initial click mouse co-ordinates */
79   gint iy;
80   gint cx;                      /* Initial click child window mouse co-ordinates */
81   gint cy;
82   guint idle_id;
83   gdouble scroll_to_x;
84   gdouble scroll_to_y;
85   gint overshot_dist_x;
86   gint overshot_dist_y;
87   gint overshooting_y;
88   gint overshooting_x;
89   gdouble scroll_indicator_alpha;
90   gint scroll_indicator_timeout;
91   gint scroll_indicator_event_interrupt;
92   gint scroll_delay_counter;
93   gint vovershoot_max;
94   gint hovershoot_max;
95   gboolean initial_hint;
96   gboolean first_drag;
97
98   gboolean hscroll;
99   gboolean vscroll;
100   GdkRectangle hscroll_rect;
101   GdkRectangle vscroll_rect;
102   guint area_width;
103
104   GtkAdjustment *hadjust;
105   GtkAdjustment *vadjust;
106
107   gdouble click_x;
108   gdouble click_y;
109
110   guint event_mode;
111
112   HildonPannableAreaIndicatorMode vindicator_mode;
113   HildonPannableAreaIndicatorMode hindicator_mode;
114
115 };
116
117 /*signals*/
118 enum {
119   HORIZONTAL_MOVEMENT,
120   VERTICAL_MOVEMENT,
121   LAST_SIGNAL
122 };
123
124 static guint pannable_area_signals [LAST_SIGNAL] = { 0 };
125
126 enum {
127   PROP_ENABLED = 1,
128   PROP_MODE,
129   PROP_MOV_MODE,
130   PROP_VELOCITY_MIN,
131   PROP_VELOCITY_MAX,
132   PROP_VELOCITY_FAST_FACTOR,
133   PROP_DECELERATION,
134   PROP_SPS,
135   PROP_VINDICATOR,
136   PROP_HINDICATOR,
137   PROP_VOVERSHOOT_MAX,
138   PROP_HOVERSHOOT_MAX,
139   PROP_SCROLL_TIME,
140   PROP_INITIAL_HINT
141 };
142
143 static GdkWindow *hildon_pannable_area_get_topmost (GdkWindow * window,
144                                                     gint x, gint y,
145                                                     gint * tx, gint * ty)
146 {
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.
150    */
151   gint width, height;
152
153   gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
154   if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
155     return NULL;
156
157   while (window) {
158     gint child_x = 0, child_y = 0;
159     GList *c, *children = gdk_window_peek_children (window);
160     GdkWindow *old_window = window;
161
162     for (c = children; c; c = c->next) {
163       GdkWindow *child = (GdkWindow *) c->data;
164       gint wx, wy;
165
166       gdk_window_get_geometry (child, &wx, &wy, &width, &height, NULL);
167
168       if ((x >= wx) && (x < (wx + width)) && (y >= wy)
169           && (y < (wy + height))) {
170         child_x = x - wx;
171         child_y = y - wy;
172         window = child;
173       }
174     }
175
176     if (window == old_window)
177       break;
178
179     x = child_x;
180     y = child_y;
181   }
182
183   if (tx)
184     *tx = x;
185   if (ty)
186     *ty = y;
187
188   return window;
189 }
190
191 static void
192 synth_crossing (GdkWindow * child,
193                 gint x, gint y,
194                 gint x_root, gint y_root, guint32 time, gboolean in)
195 {
196   GdkEventCrossing *crossing_event;
197   GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
198
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);
216 }
217
218 static gboolean
219 hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area)
220 {
221   gint retval = TRUE;
222   HildonPannableAreaPrivate *priv;
223
224   GDK_THREADS_ENTER ();
225
226   priv = PANNABLE_AREA_PRIVATE (area);
227
228   /* if moving do not fade out */
229   if (((ABS (priv->vel_y)>1.0)||
230        (ABS (priv->vel_x)>1.0))&&(!priv->clicked)) {
231     return TRUE;
232   }
233
234   if (!priv->scroll_indicator_timeout) {
235     return FALSE;
236   }
237
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;
243       retval = FALSE;
244     } else {
245       priv->scroll_indicator_alpha += 0.2;
246     }
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);
252
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);
258
259   }
260
261   if ((priv->scroll_indicator_alpha > 0.9) &&
262       (priv->scroll_delay_counter < 20)) {
263     priv->scroll_delay_counter++;
264     return TRUE;
265   }
266
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;
273       retval = FALSE;
274     } else {
275       priv->scroll_indicator_alpha -= 0.2;
276     }
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);
282
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);
288   }
289
290   GDK_THREADS_LEAVE ();
291
292   return retval;
293 }
294
295 static gboolean
296 hildon_pannable_area_button_press_cb (GtkWidget * widget,
297                                       GdkEventButton * event)
298 {
299   gint x, y;
300   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
301
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))
305     return TRUE;
306
307   priv->scroll_indicator_event_interrupt = 1;
308   if (priv->scroll_indicator_timeout){
309     g_source_remove (priv->scroll_indicator_timeout);
310   }
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;
314   priv->last_type = 1;
315
316   priv->click_x = event->x;
317   priv->click_y = event->y;
318
319   priv->scroll_to_x = -1;
320   priv->scroll_to_y = -1;
321
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,
325                     event->time, FALSE);
326   }
327
328   priv->x = event->x;
329   priv->y = event->y;
330   priv->ix = priv->x;
331   priv->iy = priv->y;
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.
334    */
335   if ((ABS (priv->vel_x) <= (priv->vmax * priv->vfast_factor)) &&
336       (ABS (priv->vel_y) <= (priv->vmax * priv->vfast_factor)))
337     priv->child =
338       hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
339                                         event->x, event->y, &x, &y);
340   else
341     priv->child = NULL;
342
343   priv->clicked = TRUE;
344   /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
345   priv->vel_x = 0;
346   priv->vel_y = 0;
347
348   if ((priv->child) && (priv->child != gtk_bin_get_child (GTK_BIN (widget))->window)) {
349
350     g_object_add_weak_pointer ((GObject *) priv->child,
351                                (gpointer *) & priv->child);
352
353     event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
354     event->x = x;
355     event->y = y;
356     priv->cx = x;
357     priv->cy = y;
358
359     synth_crossing (priv->child, x, y, event->x_root,
360                     event->y_root, event->time, TRUE);
361
362     /* Send synthetic click (button press/release) event */
363     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
364
365     gdk_event_put ((GdkEvent *) event);
366     gdk_event_free ((GdkEvent *) event);
367   } else
368     priv->child = NULL;
369
370   return TRUE;
371 }
372
373 static void
374 hildon_pannable_area_redraw (HildonPannableArea * area)
375 {
376   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
377
378   /* Redraw scroll indicators */
379   if (priv->hscroll) {
380     if (GTK_WIDGET (area)->window) {
381       gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
382                                   &priv->hscroll_rect, FALSE);
383     }
384   }
385   if (priv->vscroll) {
386     if (GTK_WIDGET (area)->window) {
387       gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
388                                   &priv->vscroll_rect, FALSE);
389     }
390   }
391 }
392
393 static void
394 hildon_pannable_area_refresh (HildonPannableArea * area)
395 {
396   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
397   GtkWidget *widget = gtk_bin_get_child (GTK_BIN (area));
398   gboolean vscroll, hscroll;
399
400   if (!widget) {
401     priv->vscroll = FALSE;
402     priv->hscroll = FALSE;
403     return;
404   }
405
406   /* Calculate if we need scroll indicators */
407    gtk_widget_size_request (widget, NULL);
408
409   switch (priv->hindicator_mode) {
410   case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
411     hscroll = TRUE;
412     break;
413   case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
414     hscroll = FALSE;
415     break;
416   default:
417     hscroll = (priv->hadjust->upper - priv->hadjust->lower >
418                priv->hadjust->page_size) ? TRUE : FALSE;
419   }
420
421   switch (priv->vindicator_mode) {
422   case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
423     vscroll = TRUE;
424     break;
425   case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
426     vscroll = FALSE;
427     break;
428   default:
429     vscroll = (priv->vadjust->upper - priv->vadjust->lower >
430                priv->vadjust->page_size) ? TRUE : FALSE;
431   }
432
433   /* Store the vscroll/hscroll areas for redrawing */
434   if (vscroll) {
435     GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
436     priv->vscroll_rect.x = allocation->x + allocation->width -
437       priv->area_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);
442   }
443   if (hscroll) {
444     GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
445     priv->hscroll_rect.y = allocation->y + allocation->height -
446       priv->area_width;
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);
451   }
452
453   priv->vscroll = vscroll;
454   priv->hscroll = hscroll;
455 }
456
457 /* Scroll by a particular amount (in pixels). Optionally, return if
458  * the scroll on a particular axis was successful.
459  */
460 static void
461 hildon_pannable_axis_scroll (HildonPannableArea *area,
462                              GtkAdjustment *adjust,
463                              gdouble *vel,
464                              gdouble inc,
465                              gint *overshooting,
466                              gint *overshot_dist,
467                              gdouble *scroll_to,
468                              gint overshoot_max,
469                              gboolean *s)
470 {
471   gdouble dist;
472   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
473
474   dist = gtk_adjustment_get_value (adjust) - inc;
475
476   /** Overshooting
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
479    */
480   if (!(*overshooting)) {
481
482     /* Initiation of the overshoot happens when the finger is released
483      * and the current position of the pannable contents are out of range
484      */
485     if (dist < adjust->lower) {
486       if (s) *s = FALSE;
487
488       dist = adjust->lower;
489
490       if (overshoot_max!=0) {
491         *overshooting = 1;
492         *scroll_to = -1;
493         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
494         gtk_widget_queue_resize (GTK_WIDGET (area));
495       } else {
496         *vel = 0.0;
497       }
498     } else if (dist > adjust->upper - adjust->page_size) {
499       if (s) *s = FALSE;
500
501       dist = adjust->upper - adjust->page_size;
502
503       if (overshoot_max!=0) {
504         *overshooting = 1;
505         *scroll_to = -1;
506         *overshot_dist = CLAMP (*overshot_dist + *vel, -1*overshoot_max, 0);
507         gtk_widget_queue_resize (GTK_WIDGET (area));
508       } else {
509         *vel = 0.0;
510       }
511     } else {
512       if ((*scroll_to) != -1) {
513         if (((inc < 0)&&(*scroll_to <= dist))||
514             ((inc > 0)&&(*scroll_to >= dist))) {
515           dist = *scroll_to;
516           *scroll_to = -1;
517           *vel = 0;
518         }
519       }
520     }
521
522     gtk_adjustment_set_value (adjust, dist);
523   } else {
524     if (!priv->clicked) {
525
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
530        */
531       if (*overshot_dist > 0) {
532
533         if ((*overshooting < BOUNCE_STEPS) && (*vel > 0)) {
534           (*overshooting)++;
535           *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel);
536         } else if ((*overshooting >= BOUNCE_STEPS) && (*vel > 0)) {
537           *vel *= -1;
538           (*overshooting)--;
539         } else if ((*overshooting > 1) && (*vel < 0)) {
540           (*overshooting)--;
541           /* we add the MAX in order to avoid very small speeds */
542           *vel = MIN ((((gdouble)*overshot_dist)/overshoot_max) * (*vel), -10.0);
543         }
544
545         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
546
547         gtk_widget_queue_resize (GTK_WIDGET (area));
548
549       } else if (*overshot_dist < 0) {
550
551         if ((*overshooting < BOUNCE_STEPS) && (*vel < 0)) {
552           (*overshooting)++;
553           *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel) * -1;
554         } else if ((*overshooting >= BOUNCE_STEPS) && (*vel < 0)) {
555           *vel *= -1;
556           (*overshooting)--;
557         } else if ((*overshooting > 1) && (*vel > 0)) {
558           (*overshooting)--;
559           /* we add the MIN in order to avoid very small speeds */
560           *vel = MAX ((((gdouble)*overshot_dist)/overshoot_max) * (*vel) * -1, 10.0);
561         }
562
563         *overshot_dist = CLAMP (*overshot_dist + (*vel), -1*overshoot_max, 0);
564
565         gtk_widget_queue_resize (GTK_WIDGET (area));
566
567       } else {
568         *overshooting = 0;
569         *vel = 0;
570         gtk_widget_queue_resize (GTK_WIDGET (area));
571       }
572     } else {
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);
577       } else {
578         *overshooting = 0;
579         gtk_adjustment_set_value (adjust, dist);
580       }
581       gtk_widget_queue_resize (GTK_WIDGET (area));
582     }
583   }
584 }
585
586 static void
587 hildon_pannable_area_scroll (HildonPannableArea *area,
588                              gdouble x, gdouble y)
589 {
590   gboolean sx, sy;
591   HildonPannableAreaPrivate *priv;
592   gboolean hscroll, vscroll;
593
594   priv = PANNABLE_AREA_PRIVATE (area);
595
596   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
597     return;
598
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;
603
604   sx = TRUE;
605   sy = TRUE;
606
607   if (vscroll) {
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);
611   }
612
613   if (hscroll) {
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);
617   }
618
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.
622    */
623   if (!sx) {
624     priv->x = priv->ex;
625   }
626
627   if (!sy) {
628     priv->y = priv->ey;
629   }
630
631 }
632
633 static gboolean
634 hildon_pannable_area_timeout (HildonPannableArea * area)
635 {
636   HildonPannableAreaPrivate *priv;
637
638   GDK_THREADS_ENTER ();
639
640   priv = PANNABLE_AREA_PRIVATE (area);
641
642   if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
643     priv->idle_id = 0;
644     return FALSE;
645   }
646
647   if (!priv->clicked) {
648     /* Decelerate gradually when pointer is raised */
649     if ((!priv->overshot_dist_y) &&
650         (!priv->overshot_dist_x)) {
651
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)) {
654
655         if (ABS (priv->vel_x) >= 1.5) {
656           priv->vel_x *= priv->decel;
657         }
658
659         if (ABS (priv->vel_y) >= 1.5) {
660           priv->vel_y *= priv->decel;
661         }
662
663       } else {
664         priv->vel_x *= priv->decel;
665         priv->vel_y *= priv->decel;
666
667         if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
668           priv->vel_x = 0;
669           priv->vel_y = 0;
670           priv->idle_id = 0;
671           return FALSE;
672         }
673       }
674     }
675   } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
676     priv->idle_id = 0;
677     return FALSE;
678   }
679
680   hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
681
682   GDK_THREADS_LEAVE ();
683
684   return TRUE;
685 }
686
687 static gboolean
688 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
689                                        GdkEventMotion * event)
690 {
691   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
692   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
693   gint dnd_threshold;
694   gdouble x, y;
695   gdouble delta, rawvel_x, rawvel_y;
696   gint direction_x, direction_y;
697
698   if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
699     return TRUE;
700
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);
704     return TRUE;
705   }
706
707   if (priv->last_type == 1) {
708     priv->first_drag = TRUE;
709   }
710
711   /* Only start the scroll if the mouse cursor passes beyond the
712    * DnD threshold for dragging.
713    */
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;
718
719   if (priv->first_drag && (!priv->moved) &&
720       ((ABS (x) > (dnd_threshold+DND_THRESHOLD_INC))
721        || (ABS (y) > (dnd_threshold+DND_THRESHOLD_INC)))) {
722     priv->moved = TRUE;
723
724     if (priv->first_drag) {
725
726       if (ABS (priv->click_y - event->y) >=
727           ABS (priv->click_x - event->x)) {
728         gboolean vscroll;
729
730         g_signal_emit (area,
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);
736
737         vscroll = (priv->vadjust->upper - priv->vadjust->lower >
738                    priv->vadjust->page_size) ? TRUE : FALSE;
739
740         if (!((vscroll)&&
741               (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_VERT)))
742           priv->moved = FALSE;
743
744       } else {
745         gboolean hscroll;
746
747         g_signal_emit (area,
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);
753
754         hscroll = (priv->hadjust->upper - priv->hadjust->lower >
755                    priv->hadjust->page_size) ? TRUE : FALSE;
756
757         if (!((hscroll)&&
758               (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_HORI)))
759           priv->moved = FALSE;
760       }
761     }
762
763     priv->first_drag = FALSE;
764
765     if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
766         (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
767
768       if (priv->idle_id)
769         g_source_remove (priv->idle_id);
770
771       priv->idle_id = g_timeout_add ((gint)
772                                      (1000.0 / (gdouble) priv->sps),
773                                      (GSourceFunc)
774                                      hildon_pannable_area_timeout, area);
775     }
776   }
777
778   if (priv->moved) {
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.
783        */
784       hildon_pannable_area_scroll (area, x, y);
785       priv->x = event->x;
786       priv->y = event->y;
787       break;
788     case HILDON_PANNABLE_AREA_MODE_ACCEL:
789       /* Set acceleration relative to the initial click */
790       priv->ex = event->x;
791       priv->ey = event->y;
792       priv->vel_x = ((x > 0) ? 1 : -1) *
793         (((ABS (x) /
794            (gdouble) widget->allocation.width) *
795           (priv->vmax - priv->vmin)) + priv->vmin);
796       priv->vel_y = ((y > 0) ? 1 : -1) *
797         (((ABS (y) /
798            (gdouble) widget->allocation.height) *
799           (priv->vmax - priv->vmin)) + priv->vmin);
800       break;
801     case HILDON_PANNABLE_AREA_MODE_AUTO:
802
803       delta = event->time - priv->last_time;
804
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);
816       } else {
817         x = 0;
818         priv->vel_x = 0;
819       }
820
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);
830       } else {
831         y = 0;
832         priv->vel_y = 0;
833       }
834
835       hildon_pannable_area_scroll (area, x, y);
836
837       if (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_HORI)
838         priv->x = event->x;
839       if (priv->mov_mode&HILDON_PANNABLE_AREA_MOV_MODE_VERT)
840         priv->y = event->y;
841
842       break;
843
844     default:
845       break;
846     }
847   }
848
849   if (priv->child) {
850     /* Send motion notify to child */
851     priv->last_time = event->time;
852     priv->last_type = 2;
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);
859   }
860
861   gdk_window_get_pointer (widget->window, NULL, NULL, 0);
862
863   return TRUE;
864 }
865
866 static gboolean
867 hildon_pannable_area_button_release_cb (GtkWidget * widget,
868                                         GdkEventButton * event)
869 {
870   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
871   gint x, y;
872   GdkWindow *child;
873
874   if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
875     return TRUE;
876
877   priv->scroll_indicator_event_interrupt = 0;
878   priv->scroll_delay_counter = 0;
879
880   if (priv->scroll_indicator_timeout) {
881     g_source_remove (priv->scroll_indicator_timeout);
882   }
883
884   if ((ABS (priv->vel_y) > 1.0)||
885       (ABS (priv->vel_x) > 1.0)) {
886     priv->scroll_indicator_alpha = 1.0;
887   }
888
889   priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
890                      (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
891
892   if ((!priv->clicked) || (!priv->enabled) || (event->button != 1) ||
893       ((event->time == priv->last_time) && (priv->last_type == 3)))
894     return TRUE;
895
896   priv->clicked = FALSE;
897
898   if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO ||
899       priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
900     if (priv->idle_id)
901       g_source_remove (priv->idle_id);
902
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;
907     }
908
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;
912     }
913
914     priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
915                                    (GSourceFunc)
916                                    hildon_pannable_area_timeout, widget);
917   }
918
919   priv->last_time = event->time;
920   priv->last_type = 3;
921
922   if (!priv->child) {
923     priv->moved = FALSE;
924     return TRUE;
925   }
926
927   child =
928     hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
929                                       event->x, event->y, &x, &y);
930
931   event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
932   event->x = x;
933   event->y = y;
934
935   /* Leave the widget if we've moved - This doesn't break selection,
936    * but stops buttons from being clicked.
937    */
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);
945   } else {
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);
952   }
953   g_object_remove_weak_pointer ((GObject *) priv->child,
954                                 (gpointer *) & priv->child);
955
956   priv->moved = FALSE;
957   gdk_event_free ((GdkEvent *) event);
958
959   return TRUE;
960 }
961
962 static void
963 rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b)
964 {
965   *r = (color->red >> 8) / 255.0;
966   *g = (color->green >> 8) / 255.0;
967   *b = (color->blue >> 8) / 255.0;
968 }
969
970 static void
971 hildon_pannable_draw_vscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
972 {
973   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
974   gfloat y, height;
975   cairo_t *cr;
976   cairo_pattern_t *pattern;
977   gdouble r, g, b;
978   gint radius = (priv->vscroll_rect.width/2) - 1;
979
980   cr = gdk_cairo_create(widget->window);
981
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);
989   cairo_clip (cr);
990
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;
1002
1003   /* Set a minimum height */
1004   height = MAX (SCROLL_BAR_MIN_SIZE, height);
1005
1006   /* Check the max y position */
1007   y = MIN (y, widget->allocation.height -
1008            (priv->hscroll ? priv->hscroll_rect.height : 0) -
1009            height);
1010
1011   /* Draw the scrollbar */
1012   rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1013
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);
1018   cairo_fill(cr);
1019   cairo_pattern_destroy(pattern);
1020
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);
1025   cairo_clip (cr);
1026
1027   cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1028
1029   cairo_destroy(cr);
1030 }
1031
1032 static void
1033 hildon_pannable_draw_hscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
1034 {
1035   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1036   gfloat x, width;
1037   cairo_t *cr;
1038   cairo_pattern_t *pattern;
1039   gdouble r, g, b;
1040   gint radius = (priv->hscroll_rect.height/2) - 1;
1041
1042   cr = gdk_cairo_create(widget->window);
1043
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);
1051   cairo_clip (cr);
1052
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)));
1057   width =
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;
1063
1064   /* Set a minimum width */
1065   width = MAX (SCROLL_BAR_MIN_SIZE, width);
1066
1067   /* Check the max x position */
1068   x = MIN (x, widget->allocation.width -
1069            (priv->vscroll ? priv->vscroll_rect.width : 0) -
1070            width);
1071
1072   /* Draw the scrollbar */
1073   rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1074
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);
1079   cairo_fill(cr);
1080   cairo_pattern_destroy(pattern);
1081
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);
1086   cairo_clip (cr);
1087
1088   cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1089
1090   cairo_destroy(cr);
1091 }
1092
1093 static gboolean
1094 hildon_pannable_area_expose_event (GtkWidget * widget, GdkEventExpose * event)
1095 {
1096
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];
1100
1101   if (gtk_bin_get_child (GTK_BIN (widget))) {
1102
1103     if (priv->scroll_indicator_alpha > 0) {
1104       if (priv->vscroll) {
1105         hildon_pannable_draw_vscroll (widget, &back_color, &scroll_color);
1106       }
1107       if (priv->hscroll) {
1108         hildon_pannable_draw_hscroll (widget, &back_color, &scroll_color);
1109       }
1110     }
1111
1112     /* draw overshooting rectangles */
1113     if (priv->overshot_dist_y > 0) {
1114       gint overshot_height;
1115
1116       overshot_height = MIN (priv->overshot_dist_y, widget->allocation.height -
1117                              (priv->hscroll ? priv->hscroll_rect.height : 0));
1118
1119       gdk_draw_rectangle (widget->window,
1120                           widget->style->bg_gc[GTK_STATE_NORMAL],
1121                           TRUE,
1122                           widget->allocation.x,
1123                           widget->allocation.y,
1124                           widget->allocation.width -
1125                           (priv->vscroll ? priv->vscroll_rect.width : 0),
1126                           overshot_height);
1127     } else if (priv->overshot_dist_y < 0) {
1128       gint overshot_height;
1129       gint overshot_y;
1130
1131       overshot_height =
1132         MAX (priv->overshot_dist_y,
1133              -1*(widget->allocation.height -
1134                  (priv->hscroll ? priv->hscroll_rect.height : 0)));
1135
1136       overshot_y = MAX (widget->allocation.y +
1137                         widget->allocation.height +
1138                         overshot_height -
1139                         (priv->hscroll ? priv->hscroll_rect.height : 0), 0);
1140
1141       gdk_draw_rectangle (widget->window,
1142                           widget->style->bg_gc[GTK_STATE_NORMAL],
1143                           TRUE,
1144                           widget->allocation.x,
1145                           overshot_y,
1146                           widget->allocation.width -
1147                           priv->vscroll_rect.width,
1148                           -overshot_height);
1149     }
1150
1151     if (priv->overshot_dist_x > 0) {
1152       gint overshot_width;
1153
1154       overshot_width = MIN (priv->overshot_dist_x, widget->allocation.width -
1155                              (priv->vscroll ? priv->vscroll_rect.width : 0));
1156
1157       gdk_draw_rectangle (widget->window,
1158                           widget->style->bg_gc[GTK_STATE_NORMAL],
1159                           TRUE,
1160                           widget->allocation.x,
1161                           widget->allocation.y,
1162                           overshot_width,
1163                           widget->allocation.height -
1164                           (priv->hscroll ? priv->hscroll_rect.height : 0));
1165     } else if (priv->overshot_dist_x < 0) {
1166       gint overshot_width;
1167       gint overshot_x;
1168
1169       overshot_width =
1170         MAX (priv->overshot_dist_x,
1171              -1*(widget->allocation.width -
1172                  (priv->vscroll ? priv->vscroll_rect.width : 0)));
1173
1174       overshot_x = MAX (widget->allocation.x +
1175                         widget->allocation.width +
1176                         overshot_width -
1177                         (priv->vscroll ? priv->vscroll_rect.width : 0), 0);
1178
1179       gdk_draw_rectangle (widget->window,
1180                           widget->style->bg_gc[GTK_STATE_NORMAL],
1181                           TRUE,
1182                           overshot_x,
1183                           widget->allocation.y,
1184                           -overshot_width,
1185                           widget->allocation.height -
1186                           priv->hscroll_rect.height);
1187     }
1188
1189   }
1190
1191   return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
1192 }
1193
1194 static void
1195 hildon_pannable_area_destroy (GtkObject * object)
1196 {
1197   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1198
1199   if (priv->hadjust) {
1200     g_object_unref (G_OBJECT (priv->hadjust));
1201     priv->hadjust = NULL;
1202   }
1203
1204   if (priv->vadjust) {
1205     g_object_unref (G_OBJECT (priv->vadjust));
1206     priv->vadjust = NULL;
1207   }
1208
1209   GTK_OBJECT_CLASS (hildon_pannable_area_parent_class)->destroy (object);
1210 }
1211
1212 static void
1213 hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
1214 {
1215   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (container);
1216   GtkBin *bin;
1217
1218   bin = GTK_BIN (container);
1219   g_return_if_fail (bin->child == NULL);
1220
1221   bin->child = child;
1222   gtk_widget_set_parent (child, GTK_WIDGET (bin));
1223
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__);
1227   }
1228 }
1229
1230 static void
1231 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
1232 {
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);
1236
1237   gtk_widget_set_scroll_adjustments (child, NULL, NULL);
1238
1239   /* chain parent class handler to remove child */
1240   GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->remove (container, child);
1241 }
1242
1243 static void
1244 hildon_pannable_calculate_vel_factor (HildonPannableArea * self)
1245 {
1246   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1247   gfloat fct = 0;
1248   gfloat fct_i = 1;
1249   gint i, n;
1250
1251   n = ceil (priv->sps * priv->scroll_time);
1252
1253   for (i = 0; i < n && fct_i >= RATIO_TOLERANCE; i++) {
1254     fct_i *= priv->decel;
1255     fct += fct_i;
1256   }
1257
1258     priv->vel_factor = fct;
1259 }
1260
1261 static void
1262 hildon_pannable_area_get_property (GObject * object, guint property_id,
1263                                    GValue * value, GParamSpec * pspec)
1264 {
1265   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1266
1267   switch (property_id) {
1268   case PROP_ENABLED:
1269     g_value_set_boolean (value, priv->enabled);
1270     break;
1271   case PROP_MODE:
1272     g_value_set_enum (value, priv->mode);
1273     break;
1274   case PROP_MOV_MODE:
1275     g_value_set_flags (value, priv->mov_mode);
1276     break;
1277   case PROP_VELOCITY_MIN:
1278     g_value_set_double (value, priv->vmin);
1279     break;
1280   case PROP_VELOCITY_MAX:
1281     g_value_set_double (value, priv->vmax);
1282     break;
1283   case PROP_VELOCITY_FAST_FACTOR:
1284     g_value_set_double (value, priv->vfast_factor);
1285     break;
1286   case PROP_DECELERATION:
1287     g_value_set_double (value, priv->decel);
1288     break;
1289   case PROP_SPS:
1290     g_value_set_uint (value, priv->sps);
1291     break;
1292   case PROP_VINDICATOR:
1293     g_value_set_enum (value, priv->vindicator_mode);
1294     break;
1295   case PROP_HINDICATOR:
1296     g_value_set_enum (value, priv->hindicator_mode);
1297     break;
1298   case PROP_VOVERSHOOT_MAX:
1299     g_value_set_int (value, priv->vovershoot_max);
1300     break;
1301   case PROP_HOVERSHOOT_MAX:
1302     g_value_set_int (value, priv->hovershoot_max);
1303     break;
1304   case PROP_SCROLL_TIME:
1305     g_value_set_double (value, priv->scroll_time);
1306     break;
1307   case PROP_INITIAL_HINT:
1308     g_value_set_boolean (value, priv->initial_hint);
1309     break;
1310
1311   default:
1312     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1313   }
1314 }
1315
1316 static void
1317 hildon_pannable_area_set_property (GObject * object, guint property_id,
1318                                    const GValue * value, GParamSpec * pspec)
1319 {
1320   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1321   gboolean enabled;
1322
1323   switch (property_id) {
1324   case PROP_ENABLED:
1325     enabled = g_value_get_boolean (value);
1326
1327     if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
1328       if (enabled)
1329         gdk_window_raise (priv->event_window);
1330       else
1331         gdk_window_lower (priv->event_window);
1332     }
1333
1334     priv->enabled = enabled;
1335     break;
1336   case PROP_MODE:
1337     priv->mode = g_value_get_enum (value);
1338     break;
1339   case PROP_MOV_MODE:
1340     priv->mov_mode = g_value_get_flags (value);
1341     break;
1342   case PROP_VELOCITY_MIN:
1343     priv->vmin = g_value_get_double (value);
1344     break;
1345   case PROP_VELOCITY_MAX:
1346     priv->vmax = g_value_get_double (value);
1347     break;
1348   case PROP_VELOCITY_FAST_FACTOR:
1349     priv->vfast_factor = g_value_get_double (value);
1350     break;
1351   case PROP_DECELERATION:
1352     hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1353
1354     priv->decel = g_value_get_double (value);
1355     break;
1356   case PROP_SPS:
1357     priv->sps = g_value_get_uint (value);
1358     break;
1359   case PROP_VINDICATOR:
1360     priv->vindicator_mode = g_value_get_enum (value);
1361     break;
1362   case PROP_HINDICATOR:
1363     priv->hindicator_mode = g_value_get_enum (value);
1364     break;
1365   case PROP_VOVERSHOOT_MAX:
1366     priv->vovershoot_max = g_value_get_int (value);
1367     break;
1368   case PROP_HOVERSHOOT_MAX:
1369     priv->hovershoot_max = g_value_get_int (value);
1370     break;
1371   case PROP_SCROLL_TIME:
1372     priv->scroll_time = g_value_get_double (value);
1373
1374     hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1375     break;
1376   case PROP_INITIAL_HINT:
1377     priv->initial_hint = g_value_get_boolean (value);
1378     break;
1379
1380   default:
1381     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1382   }
1383 }
1384
1385 static void
1386 hildon_pannable_area_dispose (GObject * object)
1387 {
1388   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1389
1390   if (priv->idle_id) {
1391     g_source_remove (priv->idle_id);
1392     priv->idle_id = 0;
1393   }
1394
1395   if (priv->scroll_indicator_timeout){
1396     g_source_remove (priv->scroll_indicator_timeout);
1397     priv->scroll_indicator_timeout = 0;
1398   }
1399
1400   if (priv->hadjust) {
1401     g_object_unref (priv->hadjust);
1402     priv->hadjust = NULL;
1403   }
1404   if (priv->vadjust) {
1405     g_object_unref (priv->vadjust);
1406     priv->vadjust = NULL;
1407   }
1408
1409   if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
1410     G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
1411 }
1412
1413 static void
1414 hildon_pannable_area_finalize (GObject * object)
1415 {
1416   G_OBJECT_CLASS (hildon_pannable_area_parent_class)->finalize (object);
1417 }
1418
1419 static void
1420 hildon_pannable_area_realize (GtkWidget * widget)
1421 {
1422   GdkWindowAttr attributes;
1423   gint attributes_mask;
1424   gint border_width;
1425   HildonPannableAreaPrivate *priv;
1426
1427   priv = PANNABLE_AREA_PRIVATE (widget);
1428
1429   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1430
1431   border_width = GTK_CONTAINER (widget)->border_width;
1432
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;
1445
1446   widget->window = gtk_widget_get_parent_window (widget);
1447   g_object_ref (widget->window);
1448
1449   attributes.wclass = GDK_INPUT_ONLY;
1450   attributes_mask = GDK_WA_X | GDK_WA_Y;
1451
1452   priv->event_window = gdk_window_new (widget->window,
1453                                        &attributes, attributes_mask);
1454   gdk_window_set_user_data (priv->event_window, widget);
1455
1456   widget->style = gtk_style_attach (widget->style, widget->window);
1457 }
1458
1459 static void
1460 hildon_pannable_area_unrealize (GtkWidget * widget)
1461 {
1462   HildonPannableAreaPrivate *priv;
1463
1464   priv = PANNABLE_AREA_PRIVATE (widget);
1465
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;
1470   }
1471
1472   if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1473     (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1474      unrealize) (widget);
1475 }
1476
1477 static void
1478 hildon_pannable_area_map (GtkWidget * widget)
1479 {
1480   HildonPannableAreaPrivate *priv;
1481   gboolean hscroll, vscroll;
1482
1483   priv = PANNABLE_AREA_PRIVATE (widget);
1484
1485   if (priv->event_window != NULL && !priv->enabled)
1486     gdk_window_show (priv->event_window);
1487
1488   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1489
1490   if (priv->event_window != NULL && priv->enabled)
1491     gdk_window_show (priv->event_window);
1492
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;
1509       }
1510
1511       if (vscroll || hscroll) {
1512         priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1513                                        (GSourceFunc)
1514                                        hildon_pannable_area_timeout, widget);
1515       }
1516     }
1517
1518     if (priv->vscroll || priv->hscroll) {
1519       priv->scroll_indicator_alpha = 1;
1520
1521       priv->scroll_indicator_timeout =
1522         g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1523                        (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
1524                        widget);
1525     }
1526   }
1527 }
1528
1529 static void
1530 hildon_pannable_area_unmap (GtkWidget * widget)
1531 {
1532   HildonPannableAreaPrivate *priv;
1533
1534   priv = PANNABLE_AREA_PRIVATE (widget);
1535
1536   if (priv->event_window != NULL)
1537     gdk_window_hide (priv->event_window);
1538
1539   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1540 }
1541
1542 static void
1543 hildon_pannable_area_size_request (GtkWidget * widget,
1544                                    GtkRequisition * requisition)
1545 {
1546   /* Request tiny size, seeing as we have no decoration of our own. */
1547   requisition->width = 32;
1548   requisition->height = 32;
1549 }
1550
1551 static void
1552 hildon_pannable_area_size_allocate (GtkWidget * widget,
1553                                     GtkAllocation * allocation)
1554 {
1555   GtkBin *bin;
1556   GtkAllocation child_allocation;
1557   HildonPannableAreaPrivate *priv;
1558
1559   widget->allocation = *allocation;
1560   bin = GTK_BIN (widget);
1561
1562   priv = PANNABLE_AREA_PRIVATE (widget);
1563
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);
1570
1571   if (GTK_WIDGET_REALIZED (widget)) {
1572     if (priv->event_window != NULL)
1573       gdk_window_move_resize (priv->event_window,
1574                               child_allocation.x,
1575                               child_allocation.y,
1576                               child_allocation.width,
1577                               child_allocation.height);
1578   }
1579
1580   hildon_pannable_area_refresh (HILDON_PANNABLE_AREA (widget));
1581
1582   child_allocation.width = MAX (child_allocation.width - (priv->vscroll ?
1583                                                           priv->vscroll_rect.width : 0),
1584                                 0);
1585   child_allocation.height = MAX (child_allocation.height - (priv->hscroll ?
1586                                                             priv->hscroll_rect.height : 0),
1587                                  0);
1588
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);
1595   }
1596
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);
1603   }
1604
1605   if (bin->child)
1606     gtk_widget_size_allocate (bin->child, &child_allocation);
1607
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);
1613   }
1614
1615   if (priv->overshot_dist_x < 0) {
1616     gtk_adjustment_set_value (priv->hadjust, priv->hadjust->upper -
1617                               priv->hadjust->page_size);
1618   }
1619 }
1620
1621 static void
1622 hildon_pannable_area_style_set (GtkWidget * widget, GtkStyle * previous_style)
1623 {
1624   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1625
1626   GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1627     style_set (widget, previous_style);
1628
1629   gtk_widget_style_get (widget, "indicator-width", &priv->area_width, NULL);
1630 }
1631
1632 static void
1633 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
1634 {
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);
1639
1640
1641   g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
1642
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;
1647
1648   gtkobject_class->destroy = hildon_pannable_area_destroy;
1649
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;
1661
1662   container_class->add = hildon_pannable_area_add;
1663   container_class->remove = hildon_pannable_area_remove;
1664
1665   klass->horizontal_movement = NULL;
1666   klass->vertical_movement = NULL;
1667
1668   g_object_class_install_property (object_class,
1669                                    PROP_ENABLED,
1670                                    g_param_spec_boolean ("enabled",
1671                                                          "Enabled",
1672                                                          "Enable or disable finger-scroll.",
1673                                                          TRUE,
1674                                                          G_PARAM_READWRITE |
1675                                                          G_PARAM_CONSTRUCT));
1676
1677   g_object_class_install_property (object_class,
1678                                    PROP_VINDICATOR,
1679                                    g_param_spec_enum ("vindicator_mode",
1680                                                       "vindicator mode",
1681                                                       "Mode of the vertical scrolling indicator",
1682                                                       HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1683                                                       HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1684                                                       G_PARAM_READWRITE |
1685                                                       G_PARAM_CONSTRUCT));
1686
1687   g_object_class_install_property (object_class,
1688                                    PROP_HINDICATOR,
1689                                    g_param_spec_enum ("hindicator_mode",
1690                                                       "hindicator mode",
1691                                                       "Mode of the horizontal scrolling indicator",
1692                                                       HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1693                                                       HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1694                                                       G_PARAM_READWRITE |
1695                                                       G_PARAM_CONSTRUCT));
1696
1697   g_object_class_install_property (object_class,
1698                                    PROP_MODE,
1699                                    g_param_spec_enum ("mode",
1700                                                       "Scroll mode",
1701                                                       "Change the finger-scrolling mode.",
1702                                                       HILDON_TYPE_PANNABLE_AREA_MODE,
1703                                                       HILDON_PANNABLE_AREA_MODE_AUTO,
1704                                                       G_PARAM_READWRITE |
1705                                                       G_PARAM_CONSTRUCT));
1706
1707   g_object_class_install_property (object_class,
1708                                    PROP_MOV_MODE,
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,
1714                                                        G_PARAM_READWRITE |
1715                                                        G_PARAM_CONSTRUCT));
1716
1717   g_object_class_install_property (object_class,
1718                                    PROP_VELOCITY_MIN,
1719                                    g_param_spec_double ("velocity_min",
1720                                                         "Minimum scroll velocity",
1721                                                         "Minimum distance the child widget should scroll "
1722                                                         "per 'frame', in pixels.",
1723                                                         0, G_MAXDOUBLE, 0,
1724                                                         G_PARAM_READWRITE |
1725                                                         G_PARAM_CONSTRUCT));
1726
1727   g_object_class_install_property (object_class,
1728                                    PROP_VELOCITY_MAX,
1729                                    g_param_spec_double ("velocity_max",
1730                                                         "Maximum scroll velocity",
1731                                                         "Maximum distance the child widget should scroll "
1732                                                         "per 'frame', in pixels.",
1733                                                         0, G_MAXDOUBLE, 80,
1734                                                         G_PARAM_READWRITE |
1735                                                         G_PARAM_CONSTRUCT));
1736
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.",
1744                                                         0, 1, 0.02,
1745                                                         G_PARAM_READWRITE |
1746                                                         G_PARAM_CONSTRUCT));
1747
1748   g_object_class_install_property (object_class,
1749                                    PROP_DECELERATION,
1750                                    g_param_spec_double ("deceleration",
1751                                                         "Deceleration multiplier",
1752                                                         "The multiplier used when decelerating when in "
1753                                                         "acceleration scrolling mode.",
1754                                                         0, 1.0, 0.85,
1755                                                         G_PARAM_READWRITE |
1756                                                         G_PARAM_CONSTRUCT));
1757
1758   g_object_class_install_property (object_class,
1759                                    PROP_SPS,
1760                                    g_param_spec_uint ("sps",
1761                                                       "Scrolls per second",
1762                                                       "Amount of scroll events to generate per second.",
1763                                                       0, G_MAXUINT, 25,
1764                                                       G_PARAM_READWRITE |
1765                                                       G_PARAM_CONSTRUCT));
1766
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.",
1772                                                      0, G_MAXINT, 150,
1773                                                      G_PARAM_READWRITE |
1774                                                      G_PARAM_CONSTRUCT));
1775
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.",
1781                                                      0, G_MAXINT, 150,
1782                                                      G_PARAM_READWRITE |
1783                                                      G_PARAM_CONSTRUCT));
1784
1785   g_object_class_install_property (object_class,
1786                                    PROP_SCROLL_TIME,
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.",
1791                                                         1.0, 20.0, 10.0,
1792                                                         G_PARAM_READWRITE |
1793                                                         G_PARAM_CONSTRUCT));
1794
1795   g_object_class_install_property (object_class,
1796                                    PROP_INITIAL_HINT,
1797                                    g_param_spec_boolean ("initial-hint",
1798                                                          "Initial hint",
1799                                                          "Whether to hint the user about the pannability of the container.",
1800                                                          TRUE,
1801                                                          G_PARAM_READWRITE |
1802                                                          G_PARAM_CONSTRUCT));
1803
1804   gtk_widget_class_install_style_property (widget_class,
1805                                            g_param_spec_uint
1806                                            ("indicator-width",
1807                                             "Width of the scroll indicators",
1808                                             "Pixel width used to draw the scroll indicators.",
1809                                             0, G_MAXUINT, 8,
1810                                             G_PARAM_READWRITE));
1811
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),
1817                   NULL, NULL,
1818                   _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
1819                   G_TYPE_NONE, 3,
1820                   G_TYPE_INT,
1821                   G_TYPE_DOUBLE,
1822                   G_TYPE_DOUBLE);
1823
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),
1829                   NULL, NULL,
1830                   _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
1831                   G_TYPE_NONE, 3,
1832                   G_TYPE_INT,
1833                   G_TYPE_DOUBLE,
1834                   G_TYPE_DOUBLE);
1835
1836
1837 }
1838
1839 static void
1840 hildon_pannable_area_init (HildonPannableArea * self)
1841 {
1842   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1843
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;
1855   priv->idle_id = 0;
1856   priv->vel_x = 0;
1857   priv->vel_y = 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;
1865
1866   hildon_pannable_calculate_vel_factor (self);
1867
1868   gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);
1869
1870   priv->hadjust =
1871     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1872   priv->vadjust =
1873     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1874
1875   g_object_ref_sink (G_OBJECT (priv->hadjust));
1876   g_object_ref_sink (G_OBJECT (priv->vadjust));
1877
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);
1886 }
1887
1888 /**
1889  * hildon_pannable_area_new:
1890  *
1891  * Create a new pannable area widget
1892  *
1893  * Returns: the newly created #HildonPannableArea
1894  */
1895
1896 GtkWidget *
1897 hildon_pannable_area_new (void)
1898 {
1899   return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
1900 }
1901
1902 /**
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
1910  *
1911  * Create a new #HildonPannableArea widget and set various properties
1912  *
1913  * returns: the newly create #HildonPannableArea
1914  */
1915
1916 GtkWidget *
1917 hildon_pannable_area_new_full (gint mode, gboolean enabled,
1918                                gdouble vel_min, gdouble vel_max,
1919                                gdouble decel, guint sps)
1920 {
1921   return g_object_new (HILDON_TYPE_PANNABLE_AREA,
1922                        "mode", mode,
1923                        "enabled", enabled,
1924                        "velocity_min", vel_min,
1925                        "velocity_max", vel_max,
1926                        "deceleration", decel, "sps", sps, NULL);
1927 }
1928
1929 /**
1930  * hildon_pannable_area_add_with_viewport:
1931  * @area: A #HildonPannableArea
1932  * @child: Child widget to add to the viewport
1933  *
1934  * Convenience function used to add a child to a #GtkViewport, and add the
1935  * viewport to the scrolled window.
1936  */
1937
1938 void
1939 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
1940                                         GtkWidget * child)
1941 {
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);
1947 }
1948
1949 /**
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.
1954  *
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().
1959  *
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.
1964  *
1965  * <informalexample><programlisting>
1966  *  GtkTreePath *path;
1967  *  GdkRectangle *rect;
1968  *
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>
1977  *
1978  * If you want to present a child widget in simpler scenarios,
1979  * use hildon_pannable_area_scroll_to_child() instead.
1980  *
1981  **/
1982 void
1983 hildon_pannable_area_scroll_to (HildonPannableArea *area,
1984                                 const gint x, const gint y)
1985 {
1986   HildonPannableAreaPrivate *priv;
1987   gint width, height;
1988   gint dist_x, dist_y;
1989
1990   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
1991
1992   priv = PANNABLE_AREA_PRIVATE (area);
1993
1994   if (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)
1995     hildon_pannable_area_jump_to (area, x, y);
1996
1997   g_return_if_fail (x >= -1 && y >= -1);
1998
1999   if (x == -1 && y == -1) {
2000     return;
2001   }
2002
2003   width = priv->hadjust->upper - priv->hadjust->lower;
2004   height = priv->vadjust->upper - priv->vadjust->lower;
2005
2006   g_return_if_fail (x < width || y < height);
2007
2008   if (x > -1) {
2009     priv->scroll_to_x = x - priv->hadjust->page_size/2;
2010     dist_x = priv->scroll_to_x - priv->hadjust->value;
2011     if (dist_x == 0) {
2012       priv->scroll_to_x = -1;
2013     } else {
2014       priv->vel_x = - dist_x/priv->vel_factor;
2015     }
2016   } else {
2017     priv->scroll_to_x = -1;
2018   }
2019
2020   if (y > -1) {
2021     priv->scroll_to_y = y - priv->vadjust->page_size/2;
2022     dist_y = priv->scroll_to_y - priv->vadjust->value;
2023     if (dist_y == 0) {
2024       priv->scroll_to_y = -1;
2025     } else {
2026       priv->vel_y = - dist_y/priv->vel_factor;
2027     }
2028   } else {
2029     priv->scroll_to_y = y;
2030   }
2031
2032   if ((priv->scroll_to_y == -1) && (priv->scroll_to_y == -1)) {
2033     return;
2034   }
2035
2036   priv->scroll_indicator_alpha = 1.0;
2037
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);
2042
2043   if (priv->idle_id)
2044     g_source_remove (priv->idle_id);
2045   priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
2046                                  (GSourceFunc)
2047                                  hildon_pannable_area_timeout, area);
2048 }
2049
2050 /**
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.
2055  *
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.
2061  *
2062  **/
2063 void
2064 hildon_pannable_area_jump_to (HildonPannableArea *area,
2065                               const gint x, const gint y)
2066 {
2067   HildonPannableAreaPrivate *priv;
2068   gint width, height;
2069
2070   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2071   g_return_if_fail (x >= -1 && y >= -1);
2072
2073   if (x == -1 && y == -1) {
2074     return;
2075   }
2076
2077   priv = PANNABLE_AREA_PRIVATE (area);
2078
2079   width = priv->hadjust->upper - priv->hadjust->lower;
2080   height = priv->vadjust->upper - priv->vadjust->lower;
2081
2082   g_return_if_fail (x < width || y < height);
2083
2084   if (x != -1) {
2085     gdouble jump_to = x - priv->hadjust->page_size/2;
2086
2087     if (jump_to > priv->hadjust->upper - priv->hadjust->page_size) {
2088       jump_to = priv->hadjust->upper - priv->hadjust->page_size;
2089     }
2090
2091     gtk_adjustment_set_value (priv->hadjust, jump_to);
2092   }
2093
2094   if (y != -1) {
2095     gdouble jump_to =  y - priv->vadjust->page_size/2;
2096
2097     if (jump_to > priv->vadjust->upper - priv->vadjust->page_size) {
2098       jump_to = priv->vadjust->upper - priv->vadjust->page_size;
2099     }
2100
2101     gtk_adjustment_set_value (priv->vadjust, jump_to);
2102   }
2103
2104   priv->scroll_indicator_alpha = 1.0;
2105
2106   if (priv->scroll_indicator_timeout) {
2107
2108     priv->vel_x = 0.0;
2109     priv->vel_y = 0.0;
2110     priv->overshooting_x = 0;
2111     priv->overshooting_y = 0;
2112
2113     if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
2114       priv->overshot_dist_x = 0;
2115       priv->overshot_dist_y = 0;
2116
2117       gtk_widget_queue_resize (GTK_WIDGET (area));
2118     }
2119     g_source_remove (priv->scroll_indicator_timeout);
2120     priv->scroll_indicator_timeout = 0;
2121   }
2122
2123   if (priv->idle_id)
2124     g_source_remove (priv->idle_id);
2125   priv->idle_id = 0;
2126 }
2127
2128 /**
2129  * hildon_pannable_area_scroll_to_child:
2130  * @area: A #HildonPannableArea.
2131  * @child: A #GtkWidget, descendant of @area.
2132  *
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().
2136  *
2137  **/
2138 void
2139 hildon_pannable_area_scroll_to_child (HildonPannableArea *area, GtkWidget *child)
2140 {
2141   GtkWidget *bin_child;
2142   gint x, y;
2143
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)));
2147
2148   if (GTK_BIN (area)->child == NULL)
2149     return;
2150
2151   /* We need to get to check the child of the inside the area */
2152   bin_child = GTK_BIN (area)->child;
2153
2154   /* we check if we added a viewport */
2155   if (GTK_IS_VIEWPORT (bin_child)) {
2156     bin_child = GTK_BIN (bin_child)->child;
2157   }
2158
2159   if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2160     hildon_pannable_area_scroll_to (area, x, y);
2161 }
2162
2163 /**
2164  * hildon_pannable_area_jump_to_child:
2165  * @area: A #HildonPannableArea.
2166  * @child: A #GtkWidget, descendant of @area.
2167  *
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().
2171  *
2172  **/
2173 void
2174 hildon_pannable_area_jump_to_child (HildonPannableArea *area, GtkWidget *child)
2175 {
2176   GtkWidget *bin_child;
2177   gint x, y;
2178
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)));
2182
2183   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
2184     return;
2185
2186   /* We need to get to check the child of the inside the area */
2187   bin_child = gtk_bin_get_child (GTK_BIN (area));
2188
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));
2192   }
2193
2194   if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2195     hildon_pannable_area_jump_to (area, x, y);
2196 }
2197
2198 /**
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
2203  *
2204  * Get the widget at the point (x, y) inside the pannable area. In
2205  * case no widget found it returns NULL.
2206  *
2207  * returns: the #GtkWidget if we find a widget, NULL in any other case
2208  **/
2209 GtkWidget*
2210 hildon_pannable_get_child_widget_at (HildonPannableArea *area,
2211                                      gdouble x, gdouble y)
2212 {
2213   GdkWindow *window = NULL;
2214   GtkWidget *child_widget = NULL;
2215
2216   window = hildon_pannable_area_get_topmost
2217     (gtk_bin_get_child (GTK_BIN (area))->window,
2218      x, y, NULL, NULL);
2219
2220   gdk_window_get_user_data (window, (void**) &child_widget);
2221
2222   return child_widget;
2223 }