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