* src/hildon-pannable-area.c, (hildon_pannable_area_get_topmost): replaced gdk_window...
[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  x Make OVERSHOOT a property, the default of this property is fixed
39  * 2  x Add method of scrolling to the selected element of a treeview and have it align to half way point
40  * 3  x Add a property for initial scrolling of the widget, so it scrolls on realize
41  * 4  X Add a method of removing children from the alignment, gtk-container-remove override?
42  * 5  x Remove elasticity if it is no longer required?
43  * 6  x Reduce calls to queue_resize to improve performance
44  * 7  x Make 'fast' factor a property
45  * 8  - Strip out g_print/g_debug calls
46  * 9  - Scroll policies (horizontal/vertical/both) suggest 1,2,3 for different policies (binary OR'd)
47  * 10 x Delay click mode (only send synthetic clicks on mouse-up, as in previous
48  *      versions.
49  * 11 - 'Physical' mode for acceleration scrolling
50  * 12 X Add a property to disable overshoot entirely
51  * 13 x Fix problem with kinetics when pannable has been in overshoot (a flick stops rather than continues) (Search TODO 13)
52  * 14 x Review the other modes, probably they are not working well after overshooting patches
53  * 15 x Very small scrollbars when overshooting are not correctly rendered (fixed adding a minimum size)
54  */
55
56 #include <gdk/gdkx.h>
57 #include <cairo.h>
58 #include <math.h>
59 #include "hildon-pannable-area.h"
60
61 #define SMOOTH_FACTOR 0.85
62 #define FORCE 5
63 #define BOUNCE_STEPS 6
64 #define SCROLL_BAR_MIN_SIZE 5
65 #define RATIO_TOLERANCE 0.000001
66
67 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
68 #define PANNABLE_AREA_PRIVATE(o)                                \
69   (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA, \
70                                 HildonPannableAreaPrivate))
71 typedef struct _HildonPannableAreaPrivate HildonPannableAreaPrivate;
72
73 struct _HildonPannableAreaPrivate {
74   HildonPannableAreaMode mode;
75   GdkWindow *event_window;
76   gdouble x;            /* Used to store mouse co-ordinates of the first or */
77   gdouble y;            /* previous events in a press-motion pair */
78   gdouble ex;           /* Used to store mouse co-ordinates of the last */
79   gdouble ey;           /* motion event in acceleration mode */
80   gboolean enabled;
81   gboolean clicked;
82   guint32 last_time;    /* Last event time, to stop infinite loops */
83   gint last_type;
84   gboolean moved;
85   gdouble vmin;
86   gdouble vmax;
87   gdouble vfast_factor;
88   gdouble decel;
89   gdouble scroll_time;
90   gdouble vel_factor;
91   guint sps;
92   gdouble vel_x;
93   gdouble vel_y;
94   GdkWindow *child;
95   gint ix;                      /* Initial click mouse co-ordinates */
96   gint iy;
97   gint cx;                      /* Initial click child window mouse co-ordinates */
98   gint cy;
99   guint idle_id;
100   gdouble scroll_to_x;
101   gdouble scroll_to_y;
102   gint overshot_dist_x;
103   gint overshot_dist_y;
104   gint overshooting_y;
105   gint overshooting_x;
106   gdouble scroll_indicator_alpha;
107   gint scroll_indicator_timeout;
108   gint scroll_indicator_event_interrupt;
109   gint scroll_delay_counter;
110   gint overshoot_max;
111   gboolean initial_hint;
112
113   gboolean hscroll;
114   gboolean vscroll;
115   GdkRectangle hscroll_rect;
116   GdkRectangle vscroll_rect;
117   guint area_width;
118
119   GtkAdjustment *hadjust;
120   GtkAdjustment *vadjust;
121
122   gdouble click_x;
123   gdouble click_y;
124
125   guint event_mode;
126
127   HildonPannableAreaIndicatorMode vindicator_mode;
128   HildonPannableAreaIndicatorMode hindicator_mode;
129
130 };
131
132 enum {
133   PROP_ENABLED = 1,
134   PROP_MODE,
135   PROP_VELOCITY_MIN,
136   PROP_VELOCITY_MAX,
137   PROP_VELOCITY_FAST_FACTOR,
138   PROP_DECELERATION,
139   PROP_SPS,
140   PROP_VINDICATOR,
141   PROP_HINDICATOR,
142   PROP_OVERSHOOT_MAX,
143   PROP_SCROLL_TIME,
144   PROP_INITIAL_HINT
145 };
146
147 static GdkWindow *hildon_pannable_area_get_topmost (GdkWindow * window,
148                                                     gint x, gint y,
149                                                     gint * tx, gint * ty)
150 {
151   /* Find the GdkWindow at the given point, by recursing from a given
152    * parent GdkWindow. Optionally return the co-ordinates transformed
153    * relative to the child window.
154    */
155   gint width, height;
156
157   gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
158   if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
159     return NULL;
160
161   /*g_debug ("Finding window at (%d, %d) in %p", x, y, window); */
162
163   while (window) {
164     gint child_x = 0, child_y = 0;
165     GList *c, *children = gdk_window_peek_children (window);
166     GdkWindow *old_window = window;
167
168     for (c = children; c; c = c->next) {
169       GdkWindow *child = (GdkWindow *) c->data;
170       gint wx, wy;
171
172       gdk_window_get_geometry (child, &wx, &wy, &width, &height, NULL);
173       /*g_debug ("Child: %p, (%dx%d+%d,%d)", child,
174          width, height, wx, wy); */
175
176       if ((x >= wx) && (x < (wx + width)) && (y >= wy)
177           && (y < (wy + height))) {
178         child_x = x - wx;
179         child_y = y - wy;
180         window = child;
181       }
182     }
183
184     /*g_debug ("\\|/"); */
185     if (window == old_window)
186       break;
187
188     x = child_x;
189     y = child_y;
190   }
191
192   if (tx)
193     *tx = x;
194   if (ty)
195     *ty = y;
196
197   /*g_debug ("Returning: %p", window); */
198
199   return window;
200 }
201
202 static void
203 synth_crossing (GdkWindow * child,
204                 gint x, gint y,
205                 gint x_root, gint y_root, guint32 time, gboolean in)
206 {
207   GdkEventCrossing *crossing_event;
208   GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
209
210   /* Send synthetic enter event */
211   crossing_event = (GdkEventCrossing *) gdk_event_new (type);
212   ((GdkEventAny *) crossing_event)->type = type;
213   ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
214   ((GdkEventAny *) crossing_event)->send_event = FALSE;
215   crossing_event->subwindow = g_object_ref (child);
216   crossing_event->time = time;
217   crossing_event->x = x;
218   crossing_event->y = y;
219   crossing_event->x_root = x_root;
220   crossing_event->y_root = y_root;
221   crossing_event->mode = GDK_CROSSING_NORMAL;
222   crossing_event->detail = GDK_NOTIFY_UNKNOWN;
223   crossing_event->focus = FALSE;
224   crossing_event->state = 0;
225   gdk_event_put ((GdkEvent *) crossing_event);
226   gdk_event_free ((GdkEvent *) crossing_event);
227 }
228
229 static gboolean
230 hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area)
231 {
232   gint retval = TRUE;
233   HildonPannableAreaPrivate *priv;
234
235   GDK_THREADS_ENTER ();
236
237   priv = PANNABLE_AREA_PRIVATE (area);
238
239   /* if moving do not fade out */
240   if (((ABS (priv->vel_y)>1.0)||
241        (ABS (priv->vel_x)>1.0))&&(!priv->clicked)) {
242     return TRUE;
243   }
244
245   if (!priv->scroll_indicator_timeout) {
246     return FALSE;
247   }
248
249   if (priv->scroll_indicator_event_interrupt) {
250     /* Stop a fade out, and fade back in */
251     if (priv->scroll_indicator_alpha >= 0.9) {
252       priv->scroll_indicator_timeout = 0;
253       priv->scroll_indicator_alpha = 1;
254       retval = FALSE;
255     } else {
256       priv->scroll_indicator_alpha += 0.2;
257     }
258     gtk_widget_queue_draw_area (GTK_WIDGET(area),
259                                 priv->vscroll_rect.x,
260                                 priv->vscroll_rect.y,
261                                 priv->vscroll_rect.width,
262                                 priv->vscroll_rect.height);
263
264     gtk_widget_queue_draw_area (GTK_WIDGET(area),
265                                 priv->hscroll_rect.x,
266                                 priv->hscroll_rect.y,
267                                 priv->hscroll_rect.width,
268                                 priv->hscroll_rect.height);
269
270   }
271
272   if ((priv->scroll_indicator_alpha > 0.9) &&
273       (priv->scroll_delay_counter < 20)) {
274     priv->scroll_delay_counter++;
275     return TRUE;
276   }
277
278   if (!priv->scroll_indicator_event_interrupt) {
279     /* Continue fade out */
280     if (priv->scroll_indicator_alpha <= 0.1) {
281       priv->scroll_indicator_timeout = 0;
282       priv->scroll_delay_counter = 0;
283       priv->scroll_indicator_alpha = 0;
284       retval = FALSE;
285     } else {
286       priv->scroll_indicator_alpha -= 0.2;
287     }
288     gtk_widget_queue_draw_area (GTK_WIDGET(area),
289                                 priv->vscroll_rect.x,
290                                 priv->vscroll_rect.y,
291                                 priv->vscroll_rect.width,
292                                 priv->vscroll_rect.height);
293
294     gtk_widget_queue_draw_area (GTK_WIDGET(area),
295                                 priv->hscroll_rect.x,
296                                 priv->hscroll_rect.y,
297                                 priv->hscroll_rect.width,
298                                 priv->hscroll_rect.height);
299   }
300
301   GDK_THREADS_LEAVE ();
302
303   return retval;
304 }
305
306 static gboolean
307 hildon_pannable_area_button_press_cb (GtkWidget * widget,
308                                       GdkEventButton * event)
309 {
310   gint x, y;
311   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
312
313   if ((!priv->enabled) || (event->button != 1) ||
314       ((event->time == priv->last_time) &&
315        (priv->last_type == 1)) || (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
316     return TRUE;
317
318   priv->scroll_indicator_event_interrupt = 1;
319   if (priv->scroll_indicator_timeout){
320     g_source_remove (priv->scroll_indicator_timeout);
321   }
322   priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) (priv->sps*2)),
323                      (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
324   priv->last_time = event->time;
325   priv->last_type = 1;
326
327   priv->click_x = event->x;
328   priv->click_y = event->y;
329
330   priv->scroll_to_x = -1;
331   priv->scroll_to_y = -1;
332
333   if (priv->clicked && priv->child) {
334     /* Widget stole focus on last click, send crossing-out event */
335     synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
336                     event->time, FALSE);
337   }
338
339   priv->x = event->x;
340   priv->y = event->y;
341   priv->ix = priv->x;
342   priv->iy = priv->y;
343   /* Don't allow a click if we're still moving fast, where fast is
344    * defined as a quarter of our top possible speed.
345    */
346   if ((ABS (priv->vel_x) < (priv->vmax * priv->vfast_factor)) &&
347       (ABS (priv->vel_y) < (priv->vmax * priv->vfast_factor)))
348     priv->child =
349       hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
350                                         event->x, event->y, &x, &y);
351   else
352     priv->child = NULL;
353
354   priv->clicked = TRUE;
355   /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
356   priv->vel_x = 0;
357   priv->vel_y = 0;
358
359   if ((priv->child) && (priv->child != gtk_bin_get_child (GTK_BIN (widget))->window)) {
360
361     g_object_add_weak_pointer ((GObject *) priv->child,
362                                (gpointer *) & priv->child);
363
364     event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
365     event->x = x;
366     event->y = y;
367     priv->cx = x;
368     priv->cy = y;
369
370     synth_crossing (priv->child, x, y, event->x_root,
371                     event->y_root, event->time, TRUE);
372
373     /* Send synthetic click (button press/release) event */
374     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
375
376     gdk_event_put ((GdkEvent *) event);
377     gdk_event_free ((GdkEvent *) event);
378   } else
379     priv->child = NULL;
380
381   return TRUE;
382 }
383
384 static void
385 hildon_pannable_area_redraw (HildonPannableArea * area)
386 {
387   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
388
389   /* Redraw scroll indicators */
390   if (priv->hscroll) {
391     if (GTK_WIDGET (area)->window) {
392       gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
393                                   &priv->hscroll_rect, FALSE);
394     }
395   }
396   if (priv->vscroll) {
397     if (GTK_WIDGET (area)->window) {
398       gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
399                                   &priv->vscroll_rect, FALSE);
400     }
401   }
402 }
403
404 static void
405 hildon_pannable_area_refresh (HildonPannableArea * area)
406 {
407   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
408   GtkWidget *widget = gtk_bin_get_child (GTK_BIN (area));
409   gboolean vscroll, hscroll;
410
411   if (!widget) {
412     priv->vscroll = FALSE;
413     priv->hscroll = FALSE;
414     return;
415   }
416
417   /* Calculate if we need scroll indicators */
418    gtk_widget_size_request (widget, NULL);
419
420   switch (priv->hindicator_mode) {
421   case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
422     hscroll = TRUE;
423     break;
424   case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
425     hscroll = FALSE;
426     break;
427   default:
428     hscroll = (priv->hadjust->upper - priv->hadjust->lower >
429                priv->hadjust->page_size) ? TRUE : FALSE;
430   }
431
432   switch (priv->vindicator_mode) {
433   case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
434     vscroll = TRUE;
435     break;
436   case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
437     vscroll = FALSE;
438     break;
439   default:
440     vscroll = (priv->vadjust->upper - priv->vadjust->lower >
441                priv->vadjust->page_size) ? TRUE : FALSE;
442   }
443
444   /* Store the vscroll/hscroll areas for redrawing */
445   if (vscroll) {
446     GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
447     priv->vscroll_rect.x = allocation->x + allocation->width -
448       priv->area_width;
449     priv->vscroll_rect.y = allocation->y;
450     priv->vscroll_rect.width = priv->area_width;
451     priv->vscroll_rect.height = allocation->height -
452       (hscroll ? priv->area_width : 0);
453   }
454   if (hscroll) {
455     GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
456     priv->hscroll_rect.y = allocation->y + allocation->height -
457       priv->area_width;
458     priv->hscroll_rect.x = allocation->x;
459     priv->hscroll_rect.height = priv->area_width;
460     priv->hscroll_rect.width = allocation->width -
461       (vscroll ? priv->area_width : 0);
462   }
463
464   priv->vscroll = vscroll;
465   priv->hscroll = hscroll;
466 }
467
468 /* Scroll by a particular amount (in pixels). Optionally, return if
469  * the scroll on a particular axis was successful.
470  */
471 static void
472 hildon_pannable_axis_scroll (HildonPannableArea *area,
473                              GtkAdjustment *adjust,
474                              gdouble *vel,
475                              gdouble inc,
476                              gint *overshooting,
477                              gint *overshot_dist,
478                              gdouble *scroll_to,
479                              gboolean *s)
480 {
481   gdouble dist;
482   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
483
484   dist = gtk_adjustment_get_value (adjust) - inc;
485
486   /** Overshooting
487    * We use overshot_dist to define the distance of the current overshoot,
488    * and overshooting to define the direction/whether or not we are overshot
489    */
490   if (!(*overshooting)) {
491
492     /* Initiation of the overshoot happens when the finger is released
493      * and the current position of the pannable contents are out of range
494      */
495     if (dist < adjust->lower) {
496       if (s) *s = FALSE;
497
498       dist = adjust->lower;
499
500       if (priv->overshoot_max!=0) {
501         *overshooting = 1;
502         *scroll_to = -1;
503         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, priv->overshoot_max);
504         gtk_widget_queue_resize (GTK_WIDGET (area));
505       }
506     } else if (dist > adjust->upper - adjust->page_size) {
507       if (s) *s = FALSE;
508
509       dist = adjust->upper - adjust->page_size;
510
511       if (priv->overshoot_max!=0) {
512         *overshooting = 1;
513         *scroll_to = -1;
514         *overshot_dist = CLAMP (*overshot_dist + *vel, -1*priv->overshoot_max, 0);
515         gtk_widget_queue_resize (GTK_WIDGET (area));
516       }
517     } else {
518       if ((*scroll_to) != -1) {
519         if (((inc < 0)&&(*scroll_to <= dist))||
520             ((inc > 0)&&(*scroll_to >= dist))) {
521           dist = *scroll_to;
522           *scroll_to = -1;
523           *vel = 0;
524         }
525       }
526     }
527
528     gtk_adjustment_set_value (adjust, dist);
529   } else {
530     if (!priv->clicked) {
531
532       /* When the overshoot has started we continue for BOUNCE_STEPS more steps into the overshoot
533        * before we reverse direction. The deceleration factor is calculated based on
534        * the percentage distance from the first item with each iteration, therefore always
535        * returning us to the top/bottom most element
536        */
537       if (*overshot_dist > 0) {
538
539         if ((*overshooting < BOUNCE_STEPS) && (*vel > 0)) {
540           (*overshooting)++;
541           *vel = (((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel);
542         } else if ((*overshooting >= BOUNCE_STEPS) && (*vel > 0)) {
543           *vel *= -1;
544           (*overshooting)--;
545         } else if ((*overshooting > 1) && (*vel < 0)) {
546           (*overshooting)--;
547           /* we add the MAX in order to avoid very small speeds */
548           *vel = MIN ((((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel), -10.0);
549         }
550
551         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, priv->overshoot_max);
552
553         gtk_widget_queue_resize (GTK_WIDGET (area));
554
555       } else if (*overshot_dist < 0) {
556
557         if ((*overshooting < BOUNCE_STEPS) && (*vel < 0)) {
558           (*overshooting)++;
559           *vel = (((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel) * -1;
560         } else if ((*overshooting >= BOUNCE_STEPS) && (*vel < 0)) {
561           *vel *= -1;
562           (*overshooting)--;
563         } else if ((*overshooting > 1) && (*vel > 0)) {
564           (*overshooting)--;
565           /* we add the MIN in order to avoid very small speeds */
566           *vel = MAX ((((gdouble)*overshot_dist)/priv->overshoot_max) * (*vel) * -1, 10.0);
567         }
568
569         *overshot_dist = CLAMP (*overshot_dist + (*vel), -1*priv->overshoot_max, 0);
570
571         gtk_widget_queue_resize (GTK_WIDGET (area));
572
573       } else {
574         *overshooting = 0;
575         *vel = 0;
576         gtk_widget_queue_resize (GTK_WIDGET (area));
577       }
578     } else {
579       if (*overshot_dist > 0) {
580         *overshot_dist = CLAMP ((*overshot_dist) + inc, 0, priv->overshoot_max);
581       } else if (*overshot_dist < 0) {
582         *overshot_dist = CLAMP ((*overshot_dist) + inc, -1 * priv->overshoot_max, 0);
583       } else {
584         *overshooting = 0;
585         gtk_adjustment_set_value (adjust, dist);
586       }
587       gtk_widget_queue_resize (GTK_WIDGET (area));
588     }
589   }
590 }
591
592 static void
593 hildon_pannable_area_scroll (HildonPannableArea *area,
594                              gdouble x, gdouble y)
595 {
596   gboolean sx, sy;
597   HildonPannableAreaPrivate *priv;
598   gboolean hscroll, vscroll;
599
600   priv = PANNABLE_AREA_PRIVATE (area);
601
602   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
603     return;
604
605   vscroll = (priv->vadjust->upper - priv->vadjust->lower >
606              priv->vadjust->page_size) ? TRUE : FALSE;
607   hscroll = (priv->hadjust->upper - priv->hadjust->lower >
608              priv->hadjust->page_size) ? TRUE : FALSE;
609
610   sx = TRUE;
611   sy = TRUE;
612
613   if (vscroll) {
614     hildon_pannable_axis_scroll (area, priv->vadjust, &priv->vel_y, y,
615                                  &priv->overshooting_y, &priv->overshot_dist_y,
616                                  &priv->scroll_to_y, &sy);
617   }
618
619   if (hscroll) {
620     hildon_pannable_axis_scroll (area, priv->hadjust, &priv->vel_x, x,
621                                  &priv->overshooting_x, &priv->overshot_dist_x,
622                                  &priv->scroll_to_x, &sx);
623   }
624
625   /* If the scroll on a particular axis wasn't succesful, reset the
626    * initial scroll position to the new mouse co-ordinate. This means
627    * when you get to the top of the page, dragging down works immediately.
628    */
629   if (!sx) {
630     priv->x = priv->ex;
631   }
632
633   if (!sy) {
634     priv->y = priv->ey;
635   }
636
637 }
638
639 static gboolean
640 hildon_pannable_area_timeout (HildonPannableArea * area)
641 {
642   HildonPannableAreaPrivate *priv;
643
644   GDK_THREADS_ENTER ();
645
646   priv = PANNABLE_AREA_PRIVATE (area);
647
648   if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
649     priv->idle_id = 0;
650     return FALSE;
651   }
652
653   if (!priv->clicked) {
654     /* Decelerate gradually when pointer is raised */
655     if ((!priv->overshot_dist_y) &&
656         (!priv->overshot_dist_x)) {
657
658       /* in case we move to a specific point do not decelerate when arriving */
659       if ((priv->scroll_to_x != -1)||(priv->scroll_to_y != -1)) {
660
661         if (ABS (priv->vel_x) >= 1.5) {
662           priv->vel_x *= priv->decel;
663         }
664
665         if (ABS (priv->vel_y) >= 1.5) {
666           priv->vel_y *= priv->decel;
667         }
668
669       } else {
670         priv->vel_x *= priv->decel;
671         priv->vel_y *= priv->decel;
672
673         if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
674           priv->idle_id = 0;
675           return FALSE;
676         }
677       }
678     }
679   } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
680     priv->idle_id = 0;
681     return FALSE;
682   }
683
684   hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
685
686   GDK_THREADS_LEAVE ();
687
688   return TRUE;
689 }
690
691 static gboolean
692 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
693                                        GdkEventMotion * event)
694 {
695   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
696   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
697   gint dnd_threshold;
698   gdouble x, y;
699   gdouble delta, rawvel_x, rawvel_y;
700   gint direction_x, direction_y;
701
702   if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
703     return TRUE;
704
705   if ((!priv->enabled) || (!priv->clicked) ||
706       ((event->time == priv->last_time) && (priv->last_type == 2))) {
707     gdk_window_get_pointer (widget->window, NULL, NULL, 0);
708     return 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->moved) && ((ABS (x) > dnd_threshold)
720                          || (ABS (y) > dnd_threshold))) {
721     priv->moved = TRUE;
722
723     if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
724         (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
725
726       if (priv->idle_id)
727         g_source_remove (priv->idle_id);
728
729       priv->idle_id = g_timeout_add ((gint)
730                                      (1000.0 / (gdouble) priv->sps),
731                                      (GSourceFunc)
732                                      hildon_pannable_area_timeout, area);
733     }
734   }
735
736   if (priv->moved) {
737     switch (priv->mode) {
738     case HILDON_PANNABLE_AREA_MODE_PUSH:
739       /* Scroll by the amount of pixels the cursor has moved
740        * since the last motion event.
741        */
742       hildon_pannable_area_scroll (area, x, y);
743       priv->x = event->x;
744       priv->y = event->y;
745       break;
746     case HILDON_PANNABLE_AREA_MODE_ACCEL:
747       /* Set acceleration relative to the initial click */
748       priv->ex = event->x;
749       priv->ey = event->y;
750       priv->vel_x = ((x > 0) ? 1 : -1) *
751         (((ABS (x) /
752            (gdouble) widget->allocation.width) *
753           (priv->vmax - priv->vmin)) + priv->vmin);
754       priv->vel_y = ((y > 0) ? 1 : -1) *
755         (((ABS (y) /
756            (gdouble) widget->allocation.height) *
757           (priv->vmax - priv->vmin)) + priv->vmin);
758       break;
759     case HILDON_PANNABLE_AREA_MODE_AUTO:
760
761       delta = event->time - priv->last_time;
762
763       rawvel_x = (((event->x - priv->x) / ABS (delta)) *
764                   (gdouble) priv->sps) * FORCE;
765       rawvel_y = (((event->y - priv->y) / ABS (delta)) *
766                   (gdouble) priv->sps) * FORCE;
767
768       /* we store the direction and after the calculation we
769          change it, this reduces the ifs for the calculation */
770       direction_x = rawvel_x < 0 ? -1 : 1;
771       direction_y = rawvel_y < 0 ? -1 : 1;
772
773       rawvel_y = ABS (rawvel_y);
774       rawvel_x = ABS (rawvel_x);
775
776       priv->vel_x = priv->vel_x * (1 - SMOOTH_FACTOR) +
777         direction_x * rawvel_x * SMOOTH_FACTOR;
778       priv->vel_y = priv->vel_y * (1 - SMOOTH_FACTOR) +
779         direction_y * rawvel_y * SMOOTH_FACTOR;
780
781       priv->vel_x = priv->vel_x > 0 ? MIN (priv->vel_x, priv->vmax)
782         : MAX (priv->vel_x, -1 * priv->vmax);
783       priv->vel_y = priv->vel_y > 0 ? MIN (priv->vel_y, priv->vmax)
784         : MAX (priv->vel_y, -1 * priv->vmax);
785
786       hildon_pannable_area_scroll (area, x, y);
787
788       priv->x = event->x;
789       priv->y = event->y;
790
791       break;
792
793     default:
794       break;
795     }
796   }
797
798   if (priv->child) {
799     /* Send motion notify to child */
800     priv->last_time = event->time;
801     priv->last_type = 2;
802     event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
803     event->x = priv->cx + (event->x - priv->ix);
804     event->y = priv->cy + (event->y - priv->iy);
805     event->window = g_object_ref (priv->child);
806     gdk_event_put ((GdkEvent *) event);
807     gdk_event_free ((GdkEvent *) event);
808   }
809
810   gdk_window_get_pointer (widget->window, NULL, NULL, 0);
811
812   return TRUE;
813 }
814
815 static gboolean
816 hildon_pannable_area_button_release_cb (GtkWidget * widget,
817                                         GdkEventButton * event)
818 {
819   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
820   gint x, y;
821   GdkWindow *child;
822
823   if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
824     return TRUE;
825
826   priv->scroll_indicator_event_interrupt = 0;
827   priv->scroll_delay_counter = 0;
828
829   if (priv->scroll_indicator_timeout) {
830     g_source_remove (priv->scroll_indicator_timeout);
831   }
832
833   if ((ABS (priv->vel_y) > 1.0)||
834       (ABS (priv->vel_x) > 1.0)) {
835     priv->scroll_indicator_alpha = 1.0;
836   }
837
838   priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
839                      (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, widget);
840
841   if ((!priv->clicked) || (!priv->enabled) || (event->button != 1) ||
842       ((event->time == priv->last_time) && (priv->last_type == 3)))
843     return TRUE;
844
845   priv->clicked = FALSE;
846
847   if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO ||
848       priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
849     if (priv->idle_id)
850       g_source_remove (priv->idle_id);
851
852     /* If overshoot has been initiated with a finger down, on release set max speed */
853     if (priv->overshot_dist_y != 0) {
854       priv->overshooting_y = BOUNCE_STEPS; /* Hack to stop a bounce in the finger down case */
855       priv->vel_y = priv->vmax;
856     }
857
858     if (priv->overshot_dist_x != 0) {
859       priv->overshooting_x = BOUNCE_STEPS; /* Hack to stop a bounce in the finger down case */
860       priv->vel_x = priv->vmax;
861     }
862
863     priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
864                                    (GSourceFunc)
865                                    hildon_pannable_area_timeout, widget);
866   }
867
868   priv->last_time = event->time;
869   priv->last_type = 3;
870
871   if (!priv->child) {
872     priv->moved = FALSE;
873     return TRUE;
874   }
875
876   child =
877     hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
878                                       event->x, event->y, &x, &y);
879
880   event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
881   event->x = x;
882   event->y = y;
883
884   /* Leave the widget if we've moved - This doesn't break selection,
885    * but stops buttons from being clicked.
886    */
887   if ((child != priv->child) || (priv->moved)) {
888     /* Send synthetic leave event */
889     synth_crossing (priv->child, x, y, event->x_root,
890                     event->y_root, event->time, FALSE);
891     /* Send synthetic button release event */
892     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
893     gdk_event_put ((GdkEvent *) event);
894   } else {
895     /* Send synthetic button release event */
896     ((GdkEventAny *) event)->window = g_object_ref (child);
897     gdk_event_put ((GdkEvent *) event);
898     /* Send synthetic leave event */
899     synth_crossing (priv->child, x, y, event->x_root,
900                     event->y_root, event->time, FALSE);
901   }
902   g_object_remove_weak_pointer ((GObject *) priv->child,
903                                 (gpointer *) & priv->child);
904
905   priv->moved = FALSE;
906   gdk_event_free ((GdkEvent *) event);
907
908   return TRUE;
909 }
910
911 static void
912 rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b)
913 {
914   *r = (color->red >> 8) / 255.0;
915   *g = (color->green >> 8) / 255.0;
916   *b = (color->blue >> 8) / 255.0;
917 }
918
919 static void
920 hildon_pannable_draw_vscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
921 {
922   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
923   gfloat y, height;
924   cairo_t *cr;
925   cairo_pattern_t *pattern;
926   gdouble r, g, b;
927   gint radius = (priv->vscroll_rect.width/2) - 1;
928
929   cr = gdk_cairo_create(widget->window);
930
931   /* Draw the background */
932   rgb_from_gdkcolor (back_color, &r, &g, &b);
933   cairo_set_source_rgb (cr, r, g, b);
934   cairo_rectangle(cr, priv->vscroll_rect.x, priv->vscroll_rect.y,
935                   priv->vscroll_rect.width,
936                   priv->vscroll_rect.height);
937   cairo_fill_preserve (cr);
938   cairo_clip (cr);
939
940   /* Calculate the scroll bar height and position */
941   y = widget->allocation.y +
942     ((priv->vadjust->value / priv->vadjust->upper) *
943      (widget->allocation.height -
944       (priv->hscroll ? priv->area_width : 0)));
945   height = (widget->allocation.y +
946             (((priv->vadjust->value +
947                priv->vadjust->page_size) /
948               priv->vadjust->upper) *
949              (widget->allocation.height -
950               (priv->hscroll ? priv->area_width : 0)))) - y;
951
952   /* Set a minimum height */
953   height = MAX (SCROLL_BAR_MIN_SIZE, height);
954
955   /* Check the max y position */
956   y = MIN (y, widget->allocation.height -
957            (priv->hscroll ? priv->hscroll_rect.height : 0) -
958            height);
959
960   /* Draw the scrollbar */
961   rgb_from_gdkcolor (scroll_color, &r, &g, &b);
962
963   pattern = cairo_pattern_create_linear(radius+1, y, radius+1,y + height);
964   cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
965   cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
966   cairo_set_source(cr, pattern);
967   cairo_fill(cr);
968   cairo_pattern_destroy(pattern);
969
970   cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + radius + 1, radius, G_PI, 0);
971   cairo_line_to(cr, priv->vscroll_rect.x + (radius * 2) + 1, y + height - radius);
972   cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + height - radius, radius, 0, G_PI);
973   cairo_line_to(cr, priv->vscroll_rect.x + 1, y + height - radius);
974   cairo_clip (cr);
975
976   cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
977
978   cairo_destroy(cr);
979 }
980
981 static void
982 hildon_pannable_draw_hscroll (GtkWidget * widget, GdkColor *back_color, GdkColor *scroll_color)
983 {
984   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
985   gfloat x, width;
986   cairo_t *cr;
987   cairo_pattern_t *pattern;
988   gdouble r, g, b;
989   gint radius = (priv->hscroll_rect.height/2) - 1;
990
991   cr = gdk_cairo_create(widget->window);
992
993   /* Draw the background */
994   rgb_from_gdkcolor (back_color, &r, &g, &b);
995   cairo_set_source_rgb (cr, r, g, b);
996   cairo_rectangle(cr, priv->hscroll_rect.x, priv->hscroll_rect.y,
997                   priv->hscroll_rect.width,
998                   priv->hscroll_rect.height);
999   cairo_fill_preserve (cr);
1000   cairo_clip (cr);
1001
1002   /* calculate the scrollbar width and position */
1003   x = widget->allocation.x +
1004     ((priv->hadjust->value / priv->hadjust->upper) *
1005      (widget->allocation.width - (priv->vscroll ? priv->area_width : 0)));
1006   width =
1007     (widget->allocation.x +
1008      (((priv->hadjust->value +
1009         priv->hadjust->page_size) / priv->hadjust->upper) *
1010       (widget->allocation.width -
1011        (priv->vscroll ? priv->area_width : 0)))) - x;
1012
1013   /* Set a minimum width */
1014   width = MAX (SCROLL_BAR_MIN_SIZE, width);
1015
1016   /* Check the max x position */
1017   x = MIN (x, widget->allocation.width -
1018            (priv->vscroll ? priv->vscroll_rect.width : 0) -
1019            width);
1020
1021   /* Draw the scrollbar */
1022   rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1023
1024   pattern = cairo_pattern_create_linear(x, radius+1, x+width, radius+1);
1025   cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1026   cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1027   cairo_set_source(cr, pattern);
1028   cairo_fill(cr);
1029   cairo_pattern_destroy(pattern);
1030
1031   cairo_arc_negative(cr, x + radius + 1, priv->hscroll_rect.y + radius + 1, radius, 3*G_PI_2, G_PI_2);
1032   cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + (radius * 2) + 1);
1033   cairo_arc_negative(cr, x + width - radius, priv->hscroll_rect.y + radius + 1, radius, G_PI_2, 3*G_PI_2);
1034   cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + 1);
1035   cairo_clip (cr);
1036
1037   cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1038
1039   cairo_destroy(cr);
1040 }
1041
1042 static gboolean
1043 hildon_pannable_area_expose_event (GtkWidget * widget, GdkEventExpose * event)
1044 {
1045
1046   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1047   GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1048   GdkColor scroll_color = widget->style->base[GTK_STATE_SELECTED];
1049
1050   if (gtk_bin_get_child (GTK_BIN (widget))) {
1051
1052     if (priv->scroll_indicator_alpha > 0) {
1053       if (priv->vscroll) {
1054         hildon_pannable_draw_vscroll (widget, &back_color, &scroll_color);
1055       }
1056       if (priv->hscroll) {
1057         hildon_pannable_draw_hscroll (widget, &back_color, &scroll_color);
1058       }
1059     }
1060
1061     /* draw overshooting rectangles */
1062     if (priv->overshot_dist_y > 0) {
1063       gint overshot_height;
1064
1065       overshot_height = MIN (priv->overshot_dist_y, widget->allocation.height -
1066                              (priv->hscroll ? priv->hscroll_rect.height : 0));
1067
1068       gdk_draw_rectangle (widget->window,
1069                           widget->style->bg_gc[GTK_STATE_NORMAL],
1070                           TRUE,
1071                           widget->allocation.x,
1072                           widget->allocation.y,
1073                           widget->allocation.width -
1074                           (priv->vscroll ? priv->vscroll_rect.width : 0),
1075                           overshot_height);
1076     } else if (priv->overshot_dist_y < 0) {
1077       gint overshot_height;
1078       gint overshot_y;
1079
1080       overshot_height =
1081         MAX (priv->overshot_dist_y,
1082              -1*(widget->allocation.height -
1083                  (priv->hscroll ? priv->hscroll_rect.height : 0)));
1084
1085       overshot_y = MAX (widget->allocation.y +
1086                         widget->allocation.height +
1087                         overshot_height -
1088                         (priv->hscroll ? priv->hscroll_rect.height : 0), 0);
1089
1090       gdk_draw_rectangle (widget->window,
1091                           widget->style->bg_gc[GTK_STATE_NORMAL],
1092                           TRUE,
1093                           widget->allocation.x,
1094                           overshot_y,
1095                           widget->allocation.width -
1096                           priv->vscroll_rect.width,
1097                           -overshot_height);
1098     }
1099
1100     if (priv->overshot_dist_x > 0) {
1101       gint overshot_width;
1102
1103       overshot_width = MIN (priv->overshot_dist_x, widget->allocation.width -
1104                              (priv->vscroll ? priv->vscroll_rect.width : 0));
1105
1106       gdk_draw_rectangle (widget->window,
1107                           widget->style->bg_gc[GTK_STATE_NORMAL],
1108                           TRUE,
1109                           widget->allocation.x,
1110                           widget->allocation.y,
1111                           overshot_width,
1112                           widget->allocation.height -
1113                           (priv->hscroll ? priv->hscroll_rect.height : 0));
1114     } else if (priv->overshot_dist_x < 0) {
1115       gint overshot_width;
1116       gint overshot_x;
1117
1118       overshot_width =
1119         MAX (priv->overshot_dist_x,
1120              -1*(widget->allocation.width -
1121                  (priv->vscroll ? priv->vscroll_rect.width : 0)));
1122
1123       overshot_x = MAX (widget->allocation.x +
1124                         widget->allocation.width +
1125                         overshot_width -
1126                         (priv->vscroll ? priv->vscroll_rect.width : 0), 0);
1127
1128       gdk_draw_rectangle (widget->window,
1129                           widget->style->bg_gc[GTK_STATE_NORMAL],
1130                           TRUE,
1131                           overshot_x,
1132                           widget->allocation.y,
1133                           -overshot_width,
1134                           widget->allocation.height -
1135                           priv->hscroll_rect.height);
1136     }
1137
1138   }
1139
1140   return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
1141 }
1142
1143 static void
1144 hildon_pannable_area_destroy (GtkObject * object)
1145 {
1146   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1147
1148   if (priv->hadjust) {
1149     g_object_unref (G_OBJECT (priv->hadjust));
1150     priv->hadjust = NULL;
1151   }
1152
1153   if (priv->vadjust) {
1154     g_object_unref (G_OBJECT (priv->vadjust));
1155     priv->vadjust = NULL;
1156   }
1157
1158   GTK_OBJECT_CLASS (hildon_pannable_area_parent_class)->destroy (object);
1159 }
1160
1161 static void
1162 hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
1163 {
1164   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (container);
1165   GtkBin *bin;
1166
1167   bin = GTK_BIN (container);
1168   g_return_if_fail (bin->child == NULL);
1169
1170   bin->child = child;
1171   gtk_widget_set_parent (child, GTK_WIDGET (bin));
1172
1173   if (!gtk_widget_set_scroll_adjustments (child, priv->hadjust, priv->vadjust)) {
1174     g_warning ("%s: cannot add non scrollable widget, "
1175                "wrap it in a viewport", __FUNCTION__);
1176   }
1177 }
1178
1179 static void
1180 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
1181 {
1182   g_return_if_fail (HILDON_IS_PANNABLE_AREA (container));
1183   g_return_if_fail (child != NULL);
1184   g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == child);
1185
1186   gtk_widget_set_scroll_adjustments (child, NULL, NULL);
1187
1188   /* chain parent class handler to remove child */
1189   GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->remove (container, child);
1190 }
1191
1192 static void
1193 hildon_pannable_calculate_vel_factor (HildonPannableArea * self)
1194 {
1195   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1196   gfloat fct = 0;
1197   gfloat fct_i = 1;
1198   gint i, n;
1199
1200   n = ceil (priv->sps * priv->scroll_time);
1201
1202   for (i = 0; i < n && fct_i >= RATIO_TOLERANCE; i++) {
1203     fct_i *= priv->decel;
1204     fct += fct_i;
1205   }
1206
1207     priv->vel_factor = fct;
1208 }
1209
1210 static void
1211 hildon_pannable_area_get_property (GObject * object, guint property_id,
1212                                    GValue * value, GParamSpec * pspec)
1213 {
1214   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1215
1216   switch (property_id) {
1217   case PROP_ENABLED:
1218     g_value_set_boolean (value, priv->enabled);
1219     break;
1220   case PROP_MODE:
1221     g_value_set_enum (value, priv->mode);
1222     break;
1223   case PROP_VELOCITY_MIN:
1224     g_value_set_double (value, priv->vmin);
1225     break;
1226   case PROP_VELOCITY_MAX:
1227     g_value_set_double (value, priv->vmax);
1228     break;
1229   case PROP_VELOCITY_FAST_FACTOR:
1230     g_value_set_double (value, priv->vfast_factor);
1231     break;
1232   case PROP_DECELERATION:
1233     g_value_set_double (value, priv->decel);
1234     break;
1235   case PROP_SPS:
1236     g_value_set_uint (value, priv->sps);
1237     break;
1238   case PROP_VINDICATOR:
1239     g_value_set_enum (value, priv->vindicator_mode);
1240     break;
1241   case PROP_HINDICATOR:
1242     g_value_set_enum (value, priv->hindicator_mode);
1243     break;
1244   case PROP_OVERSHOOT_MAX:
1245     g_value_set_int (value, priv->overshoot_max);
1246     break;
1247   case PROP_SCROLL_TIME:
1248     g_value_set_double (value, priv->scroll_time);
1249     break;
1250   case PROP_INITIAL_HINT:
1251     g_value_set_boolean (value, priv->initial_hint);
1252     break;
1253
1254   default:
1255     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1256   }
1257 }
1258
1259 static void
1260 hildon_pannable_area_set_property (GObject * object, guint property_id,
1261                                    const GValue * value, GParamSpec * pspec)
1262 {
1263   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1264   gboolean enabled;
1265
1266   switch (property_id) {
1267   case PROP_ENABLED:
1268     enabled = g_value_get_boolean (value);
1269
1270     if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
1271       if (enabled)
1272         gdk_window_raise (priv->event_window);
1273       else
1274         gdk_window_lower (priv->event_window);
1275     }
1276
1277     priv->enabled = enabled;
1278     break;
1279   case PROP_MODE:
1280     priv->mode = g_value_get_enum (value);
1281     break;
1282   case PROP_VELOCITY_MIN:
1283     priv->vmin = g_value_get_double (value);
1284     break;
1285   case PROP_VELOCITY_MAX:
1286     priv->vmax = g_value_get_double (value);
1287     break;
1288   case PROP_VELOCITY_FAST_FACTOR:
1289     priv->vfast_factor = g_value_get_double (value);
1290     break;
1291   case PROP_DECELERATION:
1292     hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1293
1294     priv->decel = g_value_get_double (value);
1295     break;
1296   case PROP_SPS:
1297     priv->sps = g_value_get_uint (value);
1298     break;
1299   case PROP_VINDICATOR:
1300     priv->vindicator_mode = g_value_get_enum (value);
1301     break;
1302   case PROP_HINDICATOR:
1303     priv->hindicator_mode = g_value_get_enum (value);
1304     break;
1305   case PROP_OVERSHOOT_MAX:
1306     priv->overshoot_max = g_value_get_int (value);
1307     break;
1308   case PROP_SCROLL_TIME:
1309     priv->scroll_time = g_value_get_double (value);
1310
1311     hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
1312     break;
1313   case PROP_INITIAL_HINT:
1314     priv->initial_hint = g_value_get_boolean (value);
1315     break;
1316
1317   default:
1318     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1319   }
1320 }
1321
1322 static void
1323 hildon_pannable_area_dispose (GObject * object)
1324 {
1325   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
1326
1327   if (priv->idle_id) {
1328     g_source_remove (priv->idle_id);
1329     priv->idle_id = 0;
1330   }
1331
1332   if (priv->scroll_indicator_timeout){
1333     g_source_remove (priv->scroll_indicator_timeout);
1334     priv->scroll_indicator_timeout = 0;
1335   }
1336
1337   if (priv->hadjust) {
1338     g_object_unref (priv->hadjust);
1339     priv->hadjust = NULL;
1340   }
1341   if (priv->vadjust) {
1342     g_object_unref (priv->vadjust);
1343     priv->vadjust = NULL;
1344   }
1345
1346   if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
1347     G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
1348 }
1349
1350 static void
1351 hildon_pannable_area_finalize (GObject * object)
1352 {
1353   G_OBJECT_CLASS (hildon_pannable_area_parent_class)->finalize (object);
1354 }
1355
1356 static void
1357 hildon_pannable_area_realize (GtkWidget * widget)
1358 {
1359   GdkWindowAttr attributes;
1360   gint attributes_mask;
1361   gint border_width;
1362   HildonPannableAreaPrivate *priv;
1363
1364   priv = PANNABLE_AREA_PRIVATE (widget);
1365
1366   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1367
1368   border_width = GTK_CONTAINER (widget)->border_width;
1369
1370   attributes.x = widget->allocation.x + border_width;
1371   attributes.y = widget->allocation.y + border_width;
1372   attributes.width = widget->allocation.width - 2 * border_width -
1373     (priv->vscroll ? priv->vscroll_rect.width : 0);
1374   attributes.height = widget->allocation.height - 2 * border_width -
1375     (priv->hscroll ? priv->hscroll_rect.height : 0);
1376   attributes.window_type = GDK_WINDOW_CHILD;
1377   attributes.event_mask = gtk_widget_get_events (widget)
1378     | GDK_BUTTON_MOTION_MASK
1379     | GDK_BUTTON_PRESS_MASK
1380     | GDK_BUTTON_RELEASE_MASK
1381     | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
1382
1383   widget->window = gtk_widget_get_parent_window (widget);
1384   g_object_ref (widget->window);
1385
1386   attributes.wclass = GDK_INPUT_ONLY;
1387   attributes_mask = GDK_WA_X | GDK_WA_Y;
1388
1389   priv->event_window = gdk_window_new (widget->window,
1390                                        &attributes, attributes_mask);
1391   gdk_window_set_user_data (priv->event_window, widget);
1392
1393   widget->style = gtk_style_attach (widget->style, widget->window);
1394 }
1395
1396 static void
1397 hildon_pannable_area_unrealize (GtkWidget * widget)
1398 {
1399   HildonPannableAreaPrivate *priv;
1400
1401   priv = PANNABLE_AREA_PRIVATE (widget);
1402
1403   if (priv->event_window != NULL) {
1404     gdk_window_set_user_data (priv->event_window, NULL);
1405     gdk_window_destroy (priv->event_window);
1406     priv->event_window = NULL;
1407   }
1408
1409   if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1410     (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1411      unrealize) (widget);
1412 }
1413
1414 static void
1415 hildon_pannable_area_map (GtkWidget * widget)
1416 {
1417   HildonPannableAreaPrivate *priv;
1418   gboolean hscroll, vscroll;
1419
1420   priv = PANNABLE_AREA_PRIVATE (widget);
1421
1422   if (priv->event_window != NULL && !priv->enabled)
1423     gdk_window_show (priv->event_window);
1424
1425   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1426
1427   if (priv->event_window != NULL && priv->enabled)
1428     gdk_window_show (priv->event_window);
1429
1430   if (priv->initial_hint) {
1431     if ((priv->overshoot_max != 0) &&
1432         ((priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) ||
1433          (priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL))) {
1434       vscroll = (priv->vadjust->upper - priv->vadjust->lower >
1435                  priv->vadjust->page_size) ? TRUE : FALSE;
1436       hscroll = (priv->hadjust->upper - priv->hadjust->lower >
1437                  priv->hadjust->page_size) ? TRUE : FALSE;
1438       /* If scrolling is possible in both axes, only hint about scrolling in
1439          the vertical one. */
1440       if (vscroll) {
1441         priv->overshot_dist_y = priv->overshoot_max;
1442         priv->vel_y = priv->vmax * 0.1;
1443       } else if (hscroll) {
1444         priv->overshot_dist_x = priv->overshoot_max;
1445         priv->vel_x = priv->vmax * 0.1;
1446       }
1447
1448       if (vscroll || hscroll) {
1449         priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1450                                        (GSourceFunc)
1451                                        hildon_pannable_area_timeout, widget);
1452       }
1453     }
1454
1455     if (priv->vscroll || priv->hscroll) {
1456       priv->scroll_indicator_alpha = 1;
1457
1458       priv->scroll_indicator_timeout =
1459         g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1460                        (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
1461                        widget);
1462     }
1463   }
1464 }
1465
1466 static void
1467 hildon_pannable_area_unmap (GtkWidget * widget)
1468 {
1469   HildonPannableAreaPrivate *priv;
1470
1471   priv = PANNABLE_AREA_PRIVATE (widget);
1472
1473   if (priv->event_window != NULL)
1474     gdk_window_hide (priv->event_window);
1475
1476   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1477 }
1478
1479 static void
1480 hildon_pannable_area_size_request (GtkWidget * widget,
1481                                    GtkRequisition * requisition)
1482 {
1483   /* Request tiny size, seeing as we have no decoration of our own. */
1484   requisition->width = 32;
1485   requisition->height = 32;
1486 }
1487
1488 static void
1489 hildon_pannable_area_size_allocate (GtkWidget * widget,
1490                                     GtkAllocation * allocation)
1491 {
1492   GtkBin *bin;
1493   GtkAllocation child_allocation;
1494   HildonPannableAreaPrivate *priv;
1495
1496   widget->allocation = *allocation;
1497   bin = GTK_BIN (widget);
1498
1499   priv = PANNABLE_AREA_PRIVATE (widget);
1500
1501   child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
1502   child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
1503   child_allocation.width = MAX (allocation->width -
1504                                 GTK_CONTAINER (widget)->border_width * 2, 0);
1505   child_allocation.height = MAX (allocation->height -
1506                                  GTK_CONTAINER (widget)->border_width * 2, 0);
1507
1508   if (GTK_WIDGET_REALIZED (widget)) {
1509     if (priv->event_window != NULL)
1510       gdk_window_move_resize (priv->event_window,
1511                               child_allocation.x,
1512                               child_allocation.y,
1513                               child_allocation.width,
1514                               child_allocation.height);
1515   }
1516
1517   hildon_pannable_area_refresh (HILDON_PANNABLE_AREA (widget));
1518
1519   child_allocation.width = MAX (child_allocation.width - (priv->vscroll ?
1520                                                           priv->vscroll_rect.width : 0),
1521                                 0);
1522   child_allocation.height = MAX (child_allocation.height - (priv->hscroll ?
1523                                                             priv->hscroll_rect.height : 0),
1524                                  0);
1525
1526   if (priv->overshot_dist_y > 0) {
1527     child_allocation.y = MIN (child_allocation.y + priv->overshot_dist_y,
1528                               allocation->y + child_allocation.height);
1529     child_allocation.height = MAX (child_allocation.height - priv->overshot_dist_y, 0);
1530   } else if (priv->overshot_dist_y < 0) {
1531     child_allocation.height = MAX (child_allocation.height + priv->overshot_dist_y, 0);
1532   }
1533
1534   if (priv->overshot_dist_x > 0) {
1535     child_allocation.x = MIN (child_allocation.x + priv->overshot_dist_x,
1536                               allocation->x + child_allocation.width);
1537     child_allocation.width = MAX (child_allocation.width - priv->overshot_dist_x, 0);
1538   } else if (priv->overshot_dist_x < 0) {
1539     child_allocation.width = MAX (child_allocation.width + priv->overshot_dist_x, 0);
1540   }
1541
1542   if (bin->child)
1543     gtk_widget_size_allocate (bin->child, &child_allocation);
1544
1545   /* we have to this after child size_allocate because page_size is
1546    * changed when we allocate the size of the children */
1547   if (priv->overshot_dist_y < 0) {
1548     gtk_adjustment_set_value (priv->vadjust, priv->vadjust->upper -
1549                               priv->vadjust->page_size);
1550   }
1551
1552   if (priv->overshot_dist_x < 0) {
1553     gtk_adjustment_set_value (priv->hadjust, priv->hadjust->upper -
1554                               priv->hadjust->page_size);
1555   }
1556 }
1557
1558 static void
1559 hildon_pannable_area_style_set (GtkWidget * widget, GtkStyle * previous_style)
1560 {
1561   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1562
1563   GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1564     style_set (widget, previous_style);
1565
1566   gtk_widget_style_get (widget, "indicator-width", &priv->area_width, NULL);
1567 }
1568
1569 static void
1570 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
1571 {
1572   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1573   GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
1574   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1575   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1576
1577
1578   g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
1579
1580   object_class->get_property = hildon_pannable_area_get_property;
1581   object_class->set_property = hildon_pannable_area_set_property;
1582   object_class->dispose = hildon_pannable_area_dispose;
1583   object_class->finalize = hildon_pannable_area_finalize;
1584
1585   gtkobject_class->destroy = hildon_pannable_area_destroy;
1586
1587   widget_class->realize = hildon_pannable_area_realize;
1588   widget_class->unrealize = hildon_pannable_area_unrealize;
1589   widget_class->map = hildon_pannable_area_map;
1590   widget_class->unmap = hildon_pannable_area_unmap;
1591   widget_class->size_request = hildon_pannable_area_size_request;
1592   widget_class->size_allocate = hildon_pannable_area_size_allocate;
1593   widget_class->expose_event = hildon_pannable_area_expose_event;
1594   widget_class->style_set = hildon_pannable_area_style_set;
1595   widget_class->button_press_event = hildon_pannable_area_button_press_cb;
1596   widget_class->button_release_event = hildon_pannable_area_button_release_cb;
1597   widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
1598
1599   container_class->add = hildon_pannable_area_add;
1600   container_class->remove = hildon_pannable_area_remove;
1601
1602   g_object_class_install_property (object_class,
1603                                    PROP_ENABLED,
1604                                    g_param_spec_boolean ("enabled",
1605                                                          "Enabled",
1606                                                          "Enable or disable finger-scroll.",
1607                                                          TRUE,
1608                                                          G_PARAM_READWRITE |
1609                                                          G_PARAM_CONSTRUCT));
1610
1611   g_object_class_install_property (object_class,
1612                                    PROP_VINDICATOR,
1613                                    g_param_spec_enum ("vindicator_mode",
1614                                                       "vindicator mode",
1615                                                       "Mode of the vertical scrolling indicator",
1616                                                       HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1617                                                       HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1618                                                       G_PARAM_READWRITE |
1619                                                       G_PARAM_CONSTRUCT));
1620
1621   g_object_class_install_property (object_class,
1622                                    PROP_HINDICATOR,
1623                                    g_param_spec_enum ("hindicator_mode",
1624                                                       "hindicator mode",
1625                                                       "Mode of the horizontal scrolling indicator",
1626                                                       HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1627                                                       HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1628                                                       G_PARAM_READWRITE |
1629                                                       G_PARAM_CONSTRUCT));
1630
1631   g_object_class_install_property (object_class,
1632                                    PROP_MODE,
1633                                    g_param_spec_enum ("mode",
1634                                                       "Scroll mode",
1635                                                       "Change the finger-scrolling mode.",
1636                                                       HILDON_TYPE_PANNABLE_AREA_MODE,
1637                                                       HILDON_PANNABLE_AREA_MODE_AUTO,
1638                                                       G_PARAM_READWRITE |
1639                                                       G_PARAM_CONSTRUCT));
1640
1641   g_object_class_install_property (object_class,
1642                                    PROP_VELOCITY_MIN,
1643                                    g_param_spec_double ("velocity_min",
1644                                                         "Minimum scroll velocity",
1645                                                         "Minimum distance the child widget should scroll "
1646                                                         "per 'frame', in pixels.",
1647                                                         0, G_MAXDOUBLE, 0,
1648                                                         G_PARAM_READWRITE |
1649                                                         G_PARAM_CONSTRUCT));
1650
1651   g_object_class_install_property (object_class,
1652                                    PROP_VELOCITY_MAX,
1653                                    g_param_spec_double ("velocity_max",
1654                                                         "Maximum scroll velocity",
1655                                                         "Maximum distance the child widget should scroll "
1656                                                         "per 'frame', in pixels.",
1657                                                         0, G_MAXDOUBLE, 80,
1658                                                         G_PARAM_READWRITE |
1659                                                         G_PARAM_CONSTRUCT));
1660
1661   g_object_class_install_property (object_class,
1662                                    PROP_VELOCITY_FAST_FACTOR,
1663                                    g_param_spec_double ("velocity_fast_factor",
1664                                                         "Fast velocity factor",
1665                                                         "Minimum velocity that is considered 'fast': "
1666                                                         "children widgets won't receive button presses. "
1667                                                         "Expressed as a fraction of the maximum velocity.",
1668                                                         0, 1, 0.25,
1669                                                         G_PARAM_READWRITE |
1670                                                         G_PARAM_CONSTRUCT));
1671
1672   g_object_class_install_property (object_class,
1673                                    PROP_DECELERATION,
1674                                    g_param_spec_double ("deceleration",
1675                                                         "Deceleration multiplier",
1676                                                         "The multiplier used when decelerating when in "
1677                                                         "acceleration scrolling mode.",
1678                                                         0, 1.0, 0.85,
1679                                                         G_PARAM_READWRITE |
1680                                                         G_PARAM_CONSTRUCT));
1681
1682   g_object_class_install_property (object_class,
1683                                    PROP_SPS,
1684                                    g_param_spec_uint ("sps",
1685                                                       "Scrolls per second",
1686                                                       "Amount of scroll events to generate per second.",
1687                                                       0, G_MAXUINT, 25,
1688                                                       G_PARAM_READWRITE |
1689                                                       G_PARAM_CONSTRUCT));
1690
1691   g_object_class_install_property (object_class,
1692                                    PROP_OVERSHOOT_MAX,
1693                                    g_param_spec_int ("overshoot",
1694                                                      "Overshoot distance",
1695                                                      "Space we allow the widget to pass over its limits when hitting the edges, set 0 in order to deactivate overshooting.",
1696                                                      G_MININT, G_MAXINT, 150,
1697                                                      G_PARAM_READWRITE |
1698                                                      G_PARAM_CONSTRUCT));
1699
1700   g_object_class_install_property (object_class,
1701                                    PROP_SCROLL_TIME,
1702                                    g_param_spec_double ("scroll_time",
1703                                                         "Time to scroll to a position",
1704                                                         "The time to scroll to a position when calling the hildon_pannable_scroll_to function"
1705                                                         "acceleration scrolling mode.",
1706                                                         1.0, 20.0, 10.0,
1707                                                         G_PARAM_READWRITE |
1708                                                         G_PARAM_CONSTRUCT));
1709
1710   g_object_class_install_property (object_class,
1711                                    PROP_INITIAL_HINT,
1712                                    g_param_spec_boolean ("initial-hint",
1713                                                          "Initial hint",
1714                                                          "Whether to hint the user about the pannability of the container.",
1715                                                          TRUE,
1716                                                          G_PARAM_READWRITE |
1717                                                          G_PARAM_CONSTRUCT));
1718
1719   gtk_widget_class_install_style_property (widget_class,
1720                                            g_param_spec_uint
1721                                            ("indicator-width",
1722                                             "Width of the scroll indicators",
1723                                             "Pixel width used to draw the scroll indicators.",
1724                                             0, G_MAXUINT, 8,
1725                                             G_PARAM_READWRITE));
1726 }
1727
1728 static void
1729 hildon_pannable_area_init (HildonPannableArea * self)
1730 {
1731   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1732
1733   priv->moved = FALSE;
1734   priv->clicked = FALSE;
1735   priv->last_time = 0;
1736   priv->last_type = 0;
1737   priv->vscroll = TRUE;
1738   priv->hscroll = TRUE;
1739   priv->area_width = 6;
1740   priv->overshot_dist_x = 0;
1741   priv->overshot_dist_y = 0;
1742   priv->overshooting_y = 0;
1743   priv->overshooting_x = 0;
1744   priv->idle_id = 0;
1745   priv->vel_x = 0.0;
1746   priv->vel_y = 0.0;
1747   priv->scroll_indicator_alpha = 0;
1748   priv->scroll_indicator_timeout = 0;
1749   priv->scroll_indicator_event_interrupt = 0;
1750   priv->scroll_delay_counter = 0;
1751   priv->scroll_to_x = -1;
1752   priv->scroll_to_y = -1;
1753
1754   hildon_pannable_calculate_vel_factor (self);
1755
1756   gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);
1757
1758   priv->hadjust =
1759     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1760   priv->vadjust =
1761     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1762
1763   g_object_ref_sink (G_OBJECT (priv->hadjust));
1764   g_object_ref_sink (G_OBJECT (priv->vadjust));
1765
1766   g_signal_connect_swapped (G_OBJECT (priv->hadjust), "changed",
1767                             G_CALLBACK (hildon_pannable_area_refresh), self);
1768   g_signal_connect_swapped (G_OBJECT (priv->vadjust), "changed",
1769                             G_CALLBACK (hildon_pannable_area_refresh), self);
1770   g_signal_connect_swapped (G_OBJECT (priv->hadjust), "value-changed",
1771                             G_CALLBACK (hildon_pannable_area_redraw), self);
1772   g_signal_connect_swapped (G_OBJECT (priv->vadjust), "value-changed",
1773                             G_CALLBACK (hildon_pannable_area_redraw), self);
1774 }
1775
1776 /**
1777  * hildon_pannable_area_new:
1778  *
1779  * Create a new pannable area widget
1780  *
1781  * Returns: the newly created #HildonPannableArea
1782  */
1783
1784 GtkWidget *
1785 hildon_pannable_area_new (void)
1786 {
1787   return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
1788 }
1789
1790 /**
1791  * hildon_pannable_area_new_full:
1792  * @mode: #HildonPannableAreaMode
1793  * @enabled: Value for the enabled property
1794  * @vel_min: Value for the velocity-min property
1795  * @vel_max: Value for the velocity-max property
1796  * @decel: Value for the deceleration property
1797  * @sps: Value for the sps property
1798  *
1799  * Create a new #HildonPannableArea widget and set various properties
1800  *
1801  * returns: the newly create #HildonPannableArea
1802  */
1803
1804 GtkWidget *
1805 hildon_pannable_area_new_full (gint mode, gboolean enabled,
1806                                gdouble vel_min, gdouble vel_max,
1807                                gdouble decel, guint sps)
1808 {
1809   return g_object_new (HILDON_TYPE_PANNABLE_AREA,
1810                        "mode", mode,
1811                        "enabled", enabled,
1812                        "velocity_min", vel_min,
1813                        "velocity_max", vel_max,
1814                        "deceleration", decel, "sps", sps, NULL);
1815 }
1816
1817 /**
1818  * hildon_pannable_area_add_with_viewport:
1819  * @area: A #HildonPannableArea
1820  * @child: Child widget to add to the viewport
1821  *
1822  * Convenience function used to add a child to a #GtkViewport, and add the
1823  * viewport to the scrolled window.
1824  */
1825
1826 void
1827 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
1828                                         GtkWidget * child)
1829 {
1830   GtkWidget *viewport = gtk_viewport_new (NULL, NULL);
1831   gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1832   gtk_container_add (GTK_CONTAINER (viewport), child);
1833   gtk_widget_show (viewport);
1834   gtk_container_add (GTK_CONTAINER (area), viewport);
1835 }
1836
1837 /**
1838  * hildon_pannable_area_scroll_to:
1839  * @area: A #HildonPannableArea.
1840  * @x: The x coordinate of the destination point or -1 to ignore this axis.
1841  * @y: The y coordinate of the destination point or -1 to ignore this axis.
1842  *
1843  * Smoothly scrolls @area to ensure that (@x, @y) is a visible point
1844  * on the widget. To move in only one coordinate, you must set the other one
1845  * to -1. Notice that, in %HILDON_PANNABLE_AREA_MODE_PUSH mode, this function
1846  * works just like hildon_pannable_area_jump_to().
1847  *
1848  * This function is useful if you need to present the user with a particular
1849  * element inside a scrollable widget, like #GtkTreeView. For instance,
1850  * the following example shows how to scroll inside a #GtkTreeView to
1851  * make visible an item, indicated by the #GtkTreeIter @iter.
1852  *
1853  * <informalexample><programlisting>
1854  *  GtkTreePath *path;
1855  *  GdkRectangle *rect;
1856  *
1857  *  path = gtk_tree_model_get_path (model, &iter);
1858  *  gtk_tree_view_get_background_area (GTK_TREE_VIEW (treeview),
1859  *                                     path, NULL, &rect);
1860  *  gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (treeview),
1861  *                                                   0, rect.y, NULL, &y);
1862  *  hildon_pannable_area_scroll_to (panarea, -1, y);
1863  *  gtk_tree_path_free (path);
1864  * </programlisting></informalexample>
1865  *
1866  * If you want to present a child widget in simpler scenarios,
1867  * use hildon_pannable_area_scroll_to_child() instead.
1868  *
1869  **/
1870 void
1871 hildon_pannable_area_scroll_to (HildonPannableArea *area,
1872                                 const gint x, const gint y)
1873 {
1874   HildonPannableAreaPrivate *priv;
1875   gint width, height;
1876   gint dist_x, dist_y;
1877
1878   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
1879
1880   priv = PANNABLE_AREA_PRIVATE (area);
1881
1882   if (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)
1883     hildon_pannable_area_jump_to (area, x, y);
1884
1885   g_return_if_fail (x >= -1 && y >= -1);
1886
1887   if (x == -1 && y == -1) {
1888     return;
1889   }
1890
1891   width = priv->hadjust->upper - priv->hadjust->lower;
1892   height = priv->vadjust->upper - priv->vadjust->lower;
1893
1894   g_return_if_fail (x < width || y < height);
1895
1896   if (x > -1) {
1897     priv->scroll_to_x = x - priv->hadjust->page_size/2;
1898     dist_x = priv->scroll_to_x - priv->hadjust->value;
1899     if (dist_x == 0) {
1900       priv->scroll_to_x = -1;
1901     } else {
1902       priv->vel_x = - dist_x/priv->vel_factor;
1903     }
1904   } else {
1905     priv->scroll_to_x = -1;
1906   }
1907
1908   if (y > -1) {
1909     priv->scroll_to_y = y - priv->vadjust->page_size/2;
1910     dist_y = priv->scroll_to_y - priv->vadjust->value;
1911     if (dist_y == 0) {
1912       priv->scroll_to_y = -1;
1913     } else {
1914       priv->vel_y = - dist_y/priv->vel_factor;
1915     }
1916   } else {
1917     priv->scroll_to_y = y;
1918   }
1919
1920   if ((priv->scroll_to_y == -1) && (priv->scroll_to_y == -1)) {
1921     return;
1922   }
1923
1924   priv->scroll_indicator_alpha = 1.0;
1925
1926   if (priv->scroll_indicator_timeout)
1927     g_source_remove (priv->scroll_indicator_timeout);
1928   priv->scroll_indicator_timeout = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1929                                                   (GSourceFunc) hildon_pannable_area_scroll_indicator_fade, area);
1930
1931   if (priv->idle_id)
1932     g_source_remove (priv->idle_id);
1933   priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1934                                  (GSourceFunc)
1935                                  hildon_pannable_area_timeout, area);
1936 }
1937
1938 /**
1939  * hildon_pannable_area_jump_to:
1940  * @area: A #HildonPannableArea.
1941  * @x: The x coordinate of the destination point or -1 to ignore this axis.
1942  * @y: The y coordinate of the destination point or -1 to ignore this axis.
1943  *
1944  * Jumps the position of @area to ensure that (@x, @y) is a visible
1945  * point in the widget. In order to move in only one coordinate, you
1946  * must set the other one to -1. See hildon_pannable_area_scroll_to()
1947  * function for an example of how to calculate the position of
1948  * children in scrollable widgets like #GtkTreeview.
1949  *
1950  **/
1951 void
1952 hildon_pannable_area_jump_to (HildonPannableArea *area,
1953                               const gint x, const gint y)
1954 {
1955   HildonPannableAreaPrivate *priv;
1956   gint width, height;
1957
1958   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
1959   g_return_if_fail (x >= -1 && y >= -1);
1960
1961   if (x == -1 && y == -1) {
1962     return;
1963   }
1964
1965   priv = PANNABLE_AREA_PRIVATE (area);
1966
1967   width = priv->hadjust->upper - priv->hadjust->lower;
1968   height = priv->vadjust->upper - priv->vadjust->lower;
1969
1970   g_return_if_fail (x < width || y < height);
1971
1972   if (x != -1) {
1973     gdouble jump_to = x - priv->hadjust->page_size/2;
1974
1975     if (jump_to > priv->hadjust->upper - priv->hadjust->page_size) {
1976       jump_to = priv->hadjust->upper - priv->hadjust->page_size;
1977     }
1978
1979     gtk_adjustment_set_value (priv->hadjust, jump_to);
1980   }
1981
1982   if (y != -1) {
1983     gdouble jump_to =  y - priv->vadjust->page_size/2;
1984
1985     if (jump_to > priv->vadjust->upper - priv->vadjust->page_size) {
1986       jump_to = priv->vadjust->upper - priv->vadjust->page_size;
1987     }
1988
1989     gtk_adjustment_set_value (priv->vadjust, jump_to);
1990   }
1991
1992   priv->scroll_indicator_alpha = 1.0;
1993
1994   if (priv->scroll_indicator_timeout) {
1995
1996     priv->vel_x = 0.0;
1997     priv->vel_y = 0.0;
1998     priv->overshooting_x = 0;
1999     priv->overshooting_y = 0;
2000
2001     if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
2002       priv->overshot_dist_x = 0;
2003       priv->overshot_dist_y = 0;
2004
2005       gtk_widget_queue_resize (GTK_WIDGET (area));
2006     }
2007     g_source_remove (priv->scroll_indicator_timeout);
2008   }
2009
2010   if (priv->idle_id)
2011     g_source_remove (priv->idle_id);
2012 }
2013
2014 /**
2015  * hildon_pannable_area_scroll_to_child:
2016  * @area: A #HildonPannableArea.
2017  * @child: A #GtkWidget, descendant of @area.
2018  *
2019  * Smoothly scrolls until @child is visible inside @area. @child must
2020  * be a descendant of @area. If you need to scroll inside a scrollable
2021  * widget, e.g., #GtkTreeview, see hildon_pannable_area_scroll_to().
2022  *
2023  **/
2024 void
2025 hildon_pannable_area_scroll_to_child (HildonPannableArea *area, GtkWidget *child)
2026 {
2027   GtkWidget *bin_child;
2028   gint x, y;
2029
2030   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2031   g_return_if_fail (GTK_IS_WIDGET (child));
2032   g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
2033
2034   if (GTK_BIN (area)->child == NULL)
2035     return;
2036
2037   /* We need to get to check the child of the inside the area */
2038   bin_child = GTK_BIN (area)->child;
2039
2040   /* we check if we added a viewport */
2041   if (GTK_IS_VIEWPORT (bin_child)) {
2042     bin_child = GTK_BIN (bin_child)->child;
2043   }
2044
2045   if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2046     hildon_pannable_area_scroll_to (area, x, y);
2047 }
2048
2049 /**
2050  * hildon_pannable_area_jump_to_child:
2051  * @area: A #HildonPannableArea.
2052  * @child: A #GtkWidget, descendant of @area.
2053  *
2054  * Jumps to make sure @child is visible inside @area. @child must
2055  * be a descendant of @area. If you want to move inside a scrollable
2056  * widget, like, #GtkTreeview, see hildon_pannable_area_scroll_to().
2057  *
2058  **/
2059 void
2060 hildon_pannable_area_jump_to_child (HildonPannableArea *area, GtkWidget *child)
2061 {
2062   GtkWidget *bin_child;
2063   gint x, y;
2064
2065   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
2066   g_return_if_fail (GTK_IS_WIDGET (child));
2067   g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
2068
2069   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
2070     return;
2071
2072   /* We need to get to check the child of the inside the area */
2073   bin_child = gtk_bin_get_child (GTK_BIN (area));
2074
2075   /* we check if we added a viewport */
2076   if (GTK_IS_VIEWPORT (bin_child)) {
2077     bin_child = gtk_bin_get_child (GTK_BIN (bin_child));
2078   }
2079
2080   if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
2081     hildon_pannable_area_jump_to (area, x, y);
2082 }