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