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