Fixed problem with the timeout deceleration in pannable.
[hildon] / hildon / 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: Rodrigo Novo <rodrigo.novo@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 is a container widget that can be "panned" (scrolled)
30  * up and down using the touchscreen with fingers. The widget has no scrollbars,
31  * but it rather shows small scroll indicators to give an idea of the part of the
32  * content that is visible at a time. The scroll indicators appear when a dragging
33  * motion is started on the pannable area.
34  *
35  * The scrolling is "kinetic", meaning the motion can be "flicked" and it will
36  * continue from the initial motion by gradually slowing down to an eventual stop.
37  * The motion can also be stopped immediately by pressing the touchscreen over the
38  * pannable area.
39  */
40
41 #undef HILDON_DISABLE_DEPRECATED
42
43 #include <math.h>
44 #if USE_CAIRO_SCROLLBARS == 1
45 #include <cairo.h>
46 #endif
47 #include <gdk/gdkx.h>
48
49 #include "hildon-pannable-area.h"
50 #include "hildon-marshalers.h"
51 #include "hildon-enum-types.h"
52
53 #define USE_CAIRO_SCROLLBARS 0
54
55 #define SCROLL_BAR_MIN_SIZE 5
56 #define RATIO_TOLERANCE 0.000001
57 #define SCROLL_FADE_TIMEOUT 100
58 #define MOTION_EVENTS_PER_SECOND 25
59 #define CURSOR_STOPPED_TIMEOUT 200
60 #define MAX_SPEED_THRESHOLD 290
61 #define PANNABLE_MAX_WIDTH 788
62 #define PANNABLE_MAX_HEIGHT 378
63
64 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
65
66 #define PANNABLE_AREA_PRIVATE(o)                                \
67   (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA, \
68                                 HildonPannableAreaPrivate))
69
70 struct _HildonPannableAreaPrivate {
71   HildonPannableAreaMode mode;
72   HildonMovementMode mov_mode;
73   GdkWindow *event_window;
74   gdouble x;            /* Used to store mouse co-ordinates of the first or */
75   gdouble y;            /* previous events in a press-motion pair */
76   gdouble ex;           /* Used to store mouse co-ordinates of the last */
77   gdouble ey;           /* motion event in acceleration mode */
78   gboolean enabled;
79   gboolean button_pressed;
80   guint32 last_time;    /* Last event time, to stop infinite loops */
81   guint32 last_press_time;
82   gint last_type;
83   gboolean last_in;
84   gboolean moved;
85   gdouble vmin;
86   gdouble vmax;
87   gdouble vmax_overshooting;
88   gdouble vfast_factor;
89   gdouble decel;
90   gdouble drag_inertia;
91   gdouble scroll_time;
92   gdouble vel_factor;
93   guint sps;
94   guint panning_threshold;
95   guint scrollbar_fade_delay;
96   guint bounce_steps;
97   guint force;
98   guint direction_error_margin;
99   gdouble vel_x;
100   gdouble vel_y;
101   gdouble old_vel_x;
102   gdouble old_vel_y;
103   GdkWindow *child;
104   gint child_width;
105   gint child_height;
106   gint ix;                      /* Initial click mouse co-ordinates */
107   gint iy;
108   gint cx;                      /* Initial click child window mouse co-ordinates */
109   gint cy;
110   guint idle_id;
111   gdouble scroll_to_x;
112   gdouble scroll_to_y;
113   gdouble motion_x;
114   gdouble motion_y;
115   gint overshot_dist_x;
116   gint overshot_dist_y;
117   gint overshooting_y;
118   gint overshooting_x;
119   gdouble scroll_indicator_alpha;
120   gint motion_event_scroll_timeout;
121   gint scroll_indicator_timeout;
122   gint scroll_indicator_event_interrupt;
123   gint scroll_delay_counter;
124   gint vovershoot_max;
125   gint hovershoot_max;
126   gboolean initial_hint;
127   gboolean initial_effect;
128   gboolean low_friction_mode;
129   gboolean first_drag;
130
131   gboolean size_request_policy;
132   gboolean hscroll_visible;
133   gboolean vscroll_visible;
134   GdkRectangle hscroll_rect;
135   GdkRectangle vscroll_rect;
136   guint indicator_width;
137
138   GtkAdjustment *hadjust;
139   GtkAdjustment *vadjust;
140   gint x_offset;
141   gint y_offset;
142
143   GtkPolicyType vscrollbar_policy;
144   GtkPolicyType hscrollbar_policy;
145
146   GdkGC *scrollbars_gc;
147   GdkColor scroll_color;
148
149   gboolean center_on_child_focus;
150   gboolean center_on_child_focus_pending;
151 };
152
153 /*signals*/
154 enum {
155   HORIZONTAL_MOVEMENT,
156   VERTICAL_MOVEMENT,
157   PANNING_STARTED,
158   PANNING_FINISHED,
159   LAST_SIGNAL
160 };
161
162 static guint pannable_area_signals [LAST_SIGNAL] = { 0 };
163
164 enum {
165   PROP_ENABLED = 1,
166   PROP_MODE,
167   PROP_MOVEMENT_MODE,
168   PROP_VELOCITY_MIN,
169   PROP_VELOCITY_MAX,
170   PROP_VEL_MAX_OVERSHOOTING,
171   PROP_VELOCITY_FAST_FACTOR,
172   PROP_DECELERATION,
173   PROP_DRAG_INERTIA,
174   PROP_SPS,
175   PROP_PANNING_THRESHOLD,
176   PROP_SCROLLBAR_FADE_DELAY,
177   PROP_BOUNCE_STEPS,
178   PROP_FORCE,
179   PROP_DIRECTION_ERROR_MARGIN,
180   PROP_VSCROLLBAR_POLICY,
181   PROP_HSCROLLBAR_POLICY,
182   PROP_VOVERSHOOT_MAX,
183   PROP_HOVERSHOOT_MAX,
184   PROP_SCROLL_TIME,
185   PROP_INITIAL_HINT,
186   PROP_LOW_FRICTION_MODE,
187   PROP_SIZE_REQUEST_POLICY,
188   PROP_HADJUSTMENT,
189   PROP_VADJUSTMENT,
190   PROP_CENTER_ON_CHILD_FOCUS,
191   PROP_LAST
192 };
193
194 static void hildon_pannable_area_class_init (HildonPannableAreaClass * klass);
195 static void hildon_pannable_area_init (HildonPannableArea * area);
196 static void hildon_pannable_area_get_property (GObject * object,
197                                                guint property_id,
198                                                GValue * value,
199                                                GParamSpec * pspec);
200 static void hildon_pannable_area_set_property (GObject * object,
201                                                guint property_id,
202                                                const GValue * value,
203                                                GParamSpec * pspec);
204 static void hildon_pannable_area_remove_timeouts (GtkWidget * widget);
205 static void hildon_pannable_area_dispose (GObject * object);
206 static void hildon_pannable_area_realize (GtkWidget * widget);
207 static void hildon_pannable_area_unrealize (GtkWidget * widget);
208 static void hildon_pannable_area_size_request (GtkWidget * widget,
209                                                GtkRequisition * requisition);
210 static void hildon_pannable_area_size_allocate (GtkWidget * widget,
211                                                 GtkAllocation * allocation);
212 static void hildon_pannable_area_child_allocate_calculate (GtkWidget * widget,
213                                                            GtkAllocation * allocation,
214                                                            GtkAllocation * child_allocation);
215 static void hildon_pannable_area_style_set (GtkWidget * widget,
216                                             GtkStyle * previous_style);
217 static void hildon_pannable_area_map (GtkWidget * widget);
218 static void hildon_pannable_area_unmap (GtkWidget * widget);
219 static void hildon_pannable_area_grab_notify (GtkWidget *widget,
220                                               gboolean was_grabbed,
221                                               gpointer user_data);
222 #if USE_CAIRO_SCROLLBARS == 1
223 static void rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b);
224 #else /* USE_CAIRO_SCROLLBARS */
225 static void tranparency_color (GdkColor *color,
226                                GdkColor colora,
227                                GdkColor colorb,
228                                gdouble transparency);
229 #endif /* USE_CAIRO_SCROLLBARS */
230 static void hildon_pannable_draw_vscroll (GtkWidget * widget,
231                                           GdkColor *back_color,
232                                           GdkColor *scroll_color);
233 static void hildon_pannable_draw_hscroll (GtkWidget * widget,
234                                           GdkColor *back_color,
235                                           GdkColor *scroll_color);
236 static void hildon_pannable_area_initial_effect (GtkWidget * widget);
237 static void hildon_pannable_area_redraw (HildonPannableArea * area);
238 static void hildon_pannable_area_launch_fade_timeout (HildonPannableArea * area,
239                                                       gdouble alpha);
240 static void hildon_pannable_area_adjust_value_changed (HildonPannableArea * area,
241                                                        gpointer data);
242 static void hildon_pannable_area_adjust_changed (HildonPannableArea * area,
243                                                  gpointer data);
244 static gboolean hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area);
245 static gboolean hildon_pannable_area_expose_event (GtkWidget * widget,
246                                                    GdkEventExpose * event);
247 static GdkWindow * hildon_pannable_area_get_topmost (GdkWindow * window,
248                                                      gint x, gint y,
249                                                      gint * tx, gint * ty,
250                                                      GdkEventMask mask);
251 static void synth_crossing (GdkWindow * child,
252                             gint x, gint y,
253                             gint x_root, gint y_root,
254                             guint32 time, gboolean in);
255 static gboolean hildon_pannable_area_button_press_cb (GtkWidget * widget,
256                                                       GdkEventButton * event);
257 static void hildon_pannable_area_refresh (HildonPannableArea * area);
258 static gboolean hildon_pannable_area_check_scrollbars (HildonPannableArea * area);
259 static void hildon_pannable_axis_scroll (HildonPannableArea *area,
260                                          GtkAdjustment *adjust,
261                                          gdouble *vel,
262                                          gdouble inc,
263                                          gint *overshooting,
264                                          gint *overshot_dist,
265                                          gdouble *scroll_to,
266                                          gint overshoot_max,
267                                          gboolean *s);
268 static void hildon_pannable_area_scroll (HildonPannableArea *area,
269                                          gdouble x, gdouble y);
270 static gboolean hildon_pannable_area_timeout (HildonPannableArea * area);
271 static void hildon_pannable_area_calculate_velocity (gdouble *vel,
272                                                      gdouble delta,
273                                                      gdouble dist,
274                                                      gdouble vmax,
275                                                      gdouble drag_inertia,
276                                                      gdouble force,
277                                                      guint sps);
278 static gboolean hildon_pannable_area_motion_event_scroll_timeout (HildonPannableArea *area);
279 static void hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
280                                                       gdouble x, gdouble y);
281 static void hildon_pannable_area_check_move (HildonPannableArea *area,
282                                              GdkEventMotion * event,
283                                              gdouble *x,
284                                              gdouble *y);
285 static void hildon_pannable_area_handle_move (HildonPannableArea *area,
286                                               GdkEventMotion * event,
287                                               gdouble *x,
288                                               gdouble *y);
289 static gboolean hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
290                                                        GdkEventMotion * event);
291 static gboolean hildon_pannable_leave_notify_event (GtkWidget *widget,
292                                                     GdkEventCrossing *event);
293 static gboolean hildon_pannable_area_button_release_cb (GtkWidget * widget,
294                                                         GdkEventButton * event);
295 static gboolean hildon_pannable_area_scroll_cb (GtkWidget *widget,
296                                                 GdkEventScroll *event);
297 static void hildon_pannable_area_child_mapped (GtkWidget *widget,
298                                                GdkEvent  *event,
299                                                gpointer user_data);
300 static void hildon_pannable_area_add (GtkContainer *container, GtkWidget *child);
301 static void hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child);
302 static void hildon_pannable_calculate_vel_factor (HildonPannableArea * self);
303 static void hildon_pannable_area_set_focus_child (GtkContainer *container,
304                                                  GtkWidget *child);
305 static void hildon_pannable_area_center_on_child_focus (HildonPannableArea *area);
306
307
308 static void
309 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
310 {
311   GObjectClass *object_class = G_OBJECT_CLASS (klass);
312   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
313   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
314
315
316   g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
317
318   object_class->get_property = hildon_pannable_area_get_property;
319   object_class->set_property = hildon_pannable_area_set_property;
320   object_class->dispose = hildon_pannable_area_dispose;
321
322   widget_class->realize = hildon_pannable_area_realize;
323   widget_class->unrealize = hildon_pannable_area_unrealize;
324   widget_class->map = hildon_pannable_area_map;
325   widget_class->unmap = hildon_pannable_area_unmap;
326   widget_class->size_request = hildon_pannable_area_size_request;
327   widget_class->size_allocate = hildon_pannable_area_size_allocate;
328   widget_class->expose_event = hildon_pannable_area_expose_event;
329   widget_class->style_set = hildon_pannable_area_style_set;
330   widget_class->button_press_event = hildon_pannable_area_button_press_cb;
331   widget_class->button_release_event = hildon_pannable_area_button_release_cb;
332   widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
333   widget_class->leave_notify_event = hildon_pannable_leave_notify_event;
334   widget_class->scroll_event = hildon_pannable_area_scroll_cb;
335
336   container_class->add = hildon_pannable_area_add;
337   container_class->remove = hildon_pannable_area_remove;
338   container_class->set_focus_child = hildon_pannable_area_set_focus_child;
339
340   klass->horizontal_movement = NULL;
341   klass->vertical_movement = NULL;
342
343   g_object_class_install_property (object_class,
344                                    PROP_ENABLED,
345                                    g_param_spec_boolean ("enabled",
346                                                          "Enabled",
347                                                          "Enable or disable finger-scroll.",
348                                                          TRUE,
349                                                          G_PARAM_READWRITE |
350                                                          G_PARAM_CONSTRUCT));
351
352   g_object_class_install_property (object_class,
353                                    PROP_VSCROLLBAR_POLICY,
354                                    g_param_spec_enum ("vscrollbar_policy",
355                                                       "vscrollbar policy",
356                                                       "Visual policy of the vertical scrollbar",
357                                                       GTK_TYPE_POLICY_TYPE,
358                                                       GTK_POLICY_AUTOMATIC,
359                                                       G_PARAM_READWRITE |
360                                                       G_PARAM_CONSTRUCT));
361
362   g_object_class_install_property (object_class,
363                                    PROP_HSCROLLBAR_POLICY,
364                                    g_param_spec_enum ("hscrollbar_policy",
365                                                       "hscrollbar policy",
366                                                       "Visual policy of the horizontal scrollbar",
367                                                       GTK_TYPE_POLICY_TYPE,
368                                                       GTK_POLICY_AUTOMATIC,
369                                                       G_PARAM_READWRITE |
370                                                       G_PARAM_CONSTRUCT));
371
372   g_object_class_install_property (object_class,
373                                    PROP_MODE,
374                                    g_param_spec_enum ("mode",
375                                                       "Scroll mode",
376                                                       "Change the finger-scrolling mode.",
377                                                       HILDON_TYPE_PANNABLE_AREA_MODE,
378                                                       HILDON_PANNABLE_AREA_MODE_AUTO,
379                                                       G_PARAM_READWRITE |
380                                                       G_PARAM_CONSTRUCT));
381
382   g_object_class_install_property (object_class,
383                                    PROP_MOVEMENT_MODE,
384                                    g_param_spec_flags ("mov_mode",
385                                                        "Scroll movement mode",
386                                                        "Controls if the widget can scroll vertically, horizontally or both",
387                                                        HILDON_TYPE_MOVEMENT_MODE,
388                                                        HILDON_MOVEMENT_MODE_VERT,
389                                                        G_PARAM_READWRITE |
390                                                        G_PARAM_CONSTRUCT));
391
392   g_object_class_install_property (object_class,
393                                    PROP_VELOCITY_MIN,
394                                    g_param_spec_double ("velocity_min",
395                                                         "Minimum scroll velocity",
396                                                         "Minimum distance the child widget should scroll "
397                                                         "per 'frame', in pixels per frame.",
398                                                         0, G_MAXDOUBLE, 10,
399                                                         G_PARAM_READWRITE |
400                                                         G_PARAM_CONSTRUCT));
401
402   g_object_class_install_property (object_class,
403                                    PROP_VELOCITY_MAX,
404                                    g_param_spec_double ("velocity_max",
405                                                         "Maximum scroll velocity",
406                                                         "Maximum distance the child widget should scroll "
407                                                         "per 'frame', in pixels per frame.",
408                                                         0, G_MAXDOUBLE, 3500,
409                                                         G_PARAM_READWRITE |
410                                                         G_PARAM_CONSTRUCT));
411
412   g_object_class_install_property (object_class,
413                                    PROP_VEL_MAX_OVERSHOOTING,
414                                    g_param_spec_double ("velocity_overshooting_max",
415                                                         "Maximum scroll velocity when overshooting",
416                                                         "Maximum distance the child widget should scroll "
417                                                         "per 'frame', in pixels per frame when it overshoots after hitting the edge.",
418                                                         0, G_MAXDOUBLE, 130,
419                                                         G_PARAM_READWRITE |
420                                                         G_PARAM_CONSTRUCT));
421
422   g_object_class_install_property (object_class,
423                                    PROP_VELOCITY_FAST_FACTOR,
424                                    g_param_spec_double ("velocity_fast_factor",
425                                                         "Fast velocity factor",
426                                                         "Minimum velocity that is considered 'fast': "
427                                                         "children widgets won't receive button presses. "
428                                                         "Expressed as a fraction of the maximum velocity.",
429                                                         0, 1, 0.01,
430                                                         G_PARAM_READWRITE |
431                                                         G_PARAM_CONSTRUCT));
432
433   g_object_class_install_property (object_class,
434                                    PROP_DECELERATION,
435                                    g_param_spec_double ("deceleration",
436                                                         "Deceleration multiplier",
437                                                         "The multiplier used when decelerating when in "
438                                                         "acceleration scrolling mode.",
439                                                         0, 1.0, 0.85,
440                                                         G_PARAM_READWRITE |
441                                                         G_PARAM_CONSTRUCT));
442
443   g_object_class_install_property (object_class,
444                                    PROP_DRAG_INERTIA,
445                                    g_param_spec_double ("drag_inertia",
446                                                         "Inertia of the cursor dragging",
447                                                         "Percentage of the calculated speed in each moment we are are going to use"
448                                                         "to calculate the launch speed, the other part would be the speed"
449                                                         "calculated previously",
450                                                         0, 1.0, 0.85,
451                                                         G_PARAM_READWRITE |
452                                                         G_PARAM_CONSTRUCT));
453
454   g_object_class_install_property (object_class,
455                                    PROP_SPS,
456                                    g_param_spec_uint ("sps",
457                                                       "Scrolls per second",
458                                                       "Amount of scroll events to generate per second.",
459                                                       0, G_MAXUINT, 20,
460                                                       G_PARAM_READWRITE |
461                                                       G_PARAM_CONSTRUCT));
462
463   g_object_class_install_property (object_class,
464                                    PROP_PANNING_THRESHOLD,
465                                    g_param_spec_uint ("panning_threshold",
466                                                       "Threshold to consider a motion event an scroll",
467                                                       "Amount of pixels to consider a motion event an scroll, if it is less"
468                                                       "it is a click detected incorrectly by the touch screen.",
469                                                       0, G_MAXUINT, 25,
470                                                       G_PARAM_READWRITE |
471                                                       G_PARAM_CONSTRUCT));
472
473   g_object_class_install_property (object_class,
474                                    PROP_SCROLLBAR_FADE_DELAY,
475                                    g_param_spec_uint ("scrollbar_fade_delay",
476                                                       "Time before starting to fade the scrollbar",
477                                                       "Time the scrollbar is going to be visible if the widget is not in"
478                                                       "action in miliseconds",
479                                                       0, G_MAXUINT, 1000,
480                                                       G_PARAM_READWRITE |
481                                                       G_PARAM_CONSTRUCT));
482
483   g_object_class_install_property (object_class,
484                                    PROP_BOUNCE_STEPS,
485                                    g_param_spec_uint ("bounce_steps",
486                                                       "Bounce steps",
487                                                       "Number of steps that is going to be used to bounce when hitting the"
488                                                       "edge, the rubberband effect depends on it",
489                                                       0, G_MAXUINT, 3,
490                                                       G_PARAM_READWRITE |
491                                                       G_PARAM_CONSTRUCT));
492
493   g_object_class_install_property (object_class,
494                                    PROP_FORCE,
495                                    g_param_spec_uint ("force",
496                                                       "Multiplier of the calculated speed",
497                                                       "Force applied to the movement, multiplies the calculated speed of the"
498                                                       "user movement the cursor in the screen",
499                                                       0, G_MAXUINT, 50,
500                                                       G_PARAM_READWRITE |
501                                                       G_PARAM_CONSTRUCT));
502
503   g_object_class_install_property (object_class,
504                                    PROP_DIRECTION_ERROR_MARGIN,
505                                    g_param_spec_uint ("direction_error_margin",
506                                                       "Margin in the direction detection",
507                                                       "After detecting the direction of the movement (horizontal or"
508                                                       "vertical), we can add this margin of error to allow the movement in"
509                                                       "the other direction even apparently it is not",
510                                                       0, G_MAXUINT, 10,
511                                                       G_PARAM_READWRITE |
512                                                       G_PARAM_CONSTRUCT));
513
514   g_object_class_install_property (object_class,
515                                    PROP_VOVERSHOOT_MAX,
516                                    g_param_spec_int ("vovershoot_max",
517                                                      "Vertical overshoot distance",
518                                                      "Space we allow the widget to pass over its vertical limits when"
519                                                      "hitting the edges, set 0 in order to deactivate overshooting.",
520                                                      0, G_MAXINT, 150,
521                                                      G_PARAM_READWRITE |
522                                                      G_PARAM_CONSTRUCT));
523
524   g_object_class_install_property (object_class,
525                                    PROP_HOVERSHOOT_MAX,
526                                    g_param_spec_int ("hovershoot_max",
527                                                      "Horizontal overshoot distance",
528                                                      "Space we allow the widget to pass over its horizontal limits when"
529                                                      "hitting the edges, set 0 in order to deactivate overshooting.",
530                                                      0, G_MAXINT, 150,
531                                                      G_PARAM_READWRITE |
532                                                      G_PARAM_CONSTRUCT));
533
534   g_object_class_install_property (object_class,
535                                    PROP_SCROLL_TIME,
536                                    g_param_spec_double ("scroll_time",
537                                                         "Time to scroll to a position",
538                                                         "The time to scroll to a position when calling the hildon_pannable_scroll_to function",
539                                                         0.0, 20.0, 1.0,
540                                                         G_PARAM_READWRITE |
541                                                         G_PARAM_CONSTRUCT));
542
543   g_object_class_install_property (object_class,
544                                    PROP_INITIAL_HINT,
545                                    g_param_spec_boolean ("initial-hint",
546                                                          "Initial hint",
547                                                          "Whether to hint the user about the pannability of the container.",
548                                                          TRUE,
549                                                          G_PARAM_READWRITE |
550                                                          G_PARAM_CONSTRUCT));
551
552   g_object_class_install_property (object_class,
553                                    PROP_LOW_FRICTION_MODE,
554                                    g_param_spec_boolean ("low-friction-mode",
555                                                          "Do not decelerate the initial velocity",
556                                                          "Avoid decelerating the panning movement, like no friction, the widget"
557                                                          "will stop in the edges or if the user clicks.",
558                                                          FALSE,
559                                                          G_PARAM_READWRITE |
560                                                          G_PARAM_CONSTRUCT));
561
562   g_object_class_install_property (object_class,
563                                    PROP_SIZE_REQUEST_POLICY,
564                                    g_param_spec_enum ("size-request-policy",
565                                                       "Size Requisition policy",
566                                                       "Controls the size request policy of the widget",
567                                                       HILDON_TYPE_SIZE_REQUEST_POLICY,
568                                                       HILDON_SIZE_REQUEST_MINIMUM,
569                                                       G_PARAM_READWRITE|
570                                                       G_PARAM_CONSTRUCT));
571
572   g_object_class_install_property (object_class,
573                                    PROP_HADJUSTMENT,
574                                    g_param_spec_object ("hadjustment",
575                                                         "Horizontal Adjustment",
576                                                         "The GtkAdjustment for the horizontal position",
577                                                         GTK_TYPE_ADJUSTMENT,
578                                                         G_PARAM_READABLE));
579   g_object_class_install_property (object_class,
580                                    PROP_VADJUSTMENT,
581                                    g_param_spec_object ("vadjustment",
582                                                         "Vertical Adjustment",
583                                                         "The GtkAdjustment for the vertical position",
584                                                         GTK_TYPE_ADJUSTMENT,
585                                                         G_PARAM_READABLE));
586
587   g_object_class_install_property (object_class,
588                                    PROP_CENTER_ON_CHILD_FOCUS,
589                                    g_param_spec_boolean ("center-on-child-focus",
590                                                          "Center on the child with the focus",
591                                                          "Whether to center the pannable on the child that receives the focus.",
592                                                          FALSE,
593                                                          G_PARAM_READWRITE |
594                                                          G_PARAM_CONSTRUCT));
595
596
597   gtk_widget_class_install_style_property (widget_class,
598                                            g_param_spec_uint
599                                            ("indicator-width",
600                                             "Width of the scroll indicators",
601                                             "Pixel width used to draw the scroll indicators.",
602                                             0, G_MAXUINT, 8,
603                                             G_PARAM_READWRITE));
604
605  /**
606    * HildonPannableArea::horizontal-movement:
607    * @hildonpannable: the object which received the signal
608    * @direction: the direction of the movement #HILDON_MOVEMENT_LEFT or #HILDON_MOVEMENT_RIGHT
609    * @initial_x: the x coordinate of the point where the user clicked to start the movement
610    * @initial_y: the y coordinate of the point where the user clicked to start the movement
611    *
612    * The horizontal-movement signal is emitted when the pannable area
613    * detects a horizontal movement. The detection does not mean the
614    * widget is going to move (i.e. maybe the children are smaller
615    * horizontally than the screen).
616    *
617    * Since: 2.2
618    */
619   pannable_area_signals[HORIZONTAL_MOVEMENT] =
620     g_signal_new ("horizontal_movement",
621                   G_TYPE_FROM_CLASS (object_class),
622                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
623                   G_STRUCT_OFFSET (HildonPannableAreaClass, horizontal_movement),
624                   NULL, NULL,
625                   _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
626                   G_TYPE_NONE, 3,
627                   G_TYPE_INT,
628                   G_TYPE_DOUBLE,
629                   G_TYPE_DOUBLE);
630
631   /**
632    * HildonPannableArea::vertical-movement:
633    * @hildonpannable: the object which received the signal
634    * @direction: the direction of the movement #HILDON_MOVEMENT_UP or #HILDON_MOVEMENT_DOWN
635    * @initial_x: the x coordinate of the point where the user clicked to start the movement
636    * @initial_y: the y coordinate of the point where the user clicked to start the movement
637    *
638    * The vertical-movement signal is emitted when the pannable area
639    * detects a vertical movement. The detection does not mean the
640    * widget is going to move (i.e. maybe the children are smaller
641    * vertically than the screen).
642    *
643    * Since: 2.2
644    */
645   pannable_area_signals[VERTICAL_MOVEMENT] =
646     g_signal_new ("vertical_movement",
647                   G_TYPE_FROM_CLASS (object_class),
648                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
649                   G_STRUCT_OFFSET (HildonPannableAreaClass, vertical_movement),
650                   NULL, NULL,
651                   _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
652                   G_TYPE_NONE, 3,
653                   G_TYPE_INT,
654                   G_TYPE_DOUBLE,
655                   G_TYPE_DOUBLE);
656
657  /**
658    * HildonPannableArea::panning-started:
659    * @hildonpannable: the pannable area object that is going to start
660    * the panning
661    *
662    * This signal is emitted before the panning starts. Applications
663    * can return %TRUE to avoid the panning. The main difference with
664    * the vertical-movement and horizontal-movement signals is those
665    * gesture signals are launched no matter if the widget is going to
666    * move, this signal means the widget is going to start moving. It
667    * could even happen that the widget moves and there was no gesture
668    * (i.e. click meanwhile the pannable is overshooting).
669    *
670    * Returns: %TRUE to stop the panning launch. %FALSE to continue
671    * with it.
672    *
673    * Since: 2.2
674    */
675   pannable_area_signals[PANNING_STARTED] =
676     g_signal_new ("panning-started",
677                   G_TYPE_FROM_CLASS (object_class),
678                   0,
679                   0,
680                   NULL, NULL,
681                   _hildon_marshal_BOOLEAN__VOID,
682                   G_TYPE_BOOLEAN, 0);
683
684  /**
685    * HildonPannableArea::panning-finished:
686    * @hildonpannable: the pannable area object that finished the
687    * panning
688    *
689    * This signal is emitted after the kinetic panning has
690    * finished.
691    *
692    * Since: 2.2
693    */
694   pannable_area_signals[PANNING_FINISHED] =
695     g_signal_new ("panning-finished",
696                   G_TYPE_FROM_CLASS (object_class),
697                   0,
698                   0,
699                   NULL, NULL,
700                   _hildon_marshal_VOID__VOID,
701                   G_TYPE_NONE, 0);
702
703 }
704
705 static void
706 hildon_pannable_area_init (HildonPannableArea * area)
707 {
708   HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
709
710   GTK_WIDGET_UNSET_FLAGS (area, GTK_NO_WINDOW);
711
712   area->priv = priv;
713
714   priv->moved = FALSE;
715   priv->button_pressed = FALSE;
716   priv->last_time = 0;
717   priv->last_press_time = 0;
718   priv->last_type = 0;
719   priv->vscroll_visible = TRUE;
720   priv->hscroll_visible = TRUE;
721   priv->indicator_width = 6;
722   priv->overshot_dist_x = 0;
723   priv->overshot_dist_y = 0;
724   priv->overshooting_y = 0;
725   priv->overshooting_x = 0;
726   priv->idle_id = 0;
727   priv->vel_x = 0;
728   priv->vel_y = 0;
729   priv->old_vel_x = 0;
730   priv->old_vel_y = 0;
731   priv->scroll_indicator_alpha = 0.0;
732   priv->scroll_indicator_timeout = 0;
733   priv->motion_event_scroll_timeout = 0;
734   priv->scroll_indicator_event_interrupt = 0;
735   priv->scroll_delay_counter = 0;
736   priv->scrollbar_fade_delay = 0;
737   priv->scroll_to_x = -1;
738   priv->scroll_to_y = -1;
739   priv->first_drag = TRUE;
740   priv->initial_effect = TRUE;
741   priv->child_width = 0;
742   priv->child_height = 0;
743   priv->last_in = TRUE;
744   priv->x_offset = 0;
745   priv->y_offset = 0;
746   priv->center_on_child_focus_pending = FALSE;
747
748   gtk_style_lookup_color (GTK_WIDGET (area)->style,
749                           "SecondaryTextColor", &priv->scroll_color);
750
751   priv->hadjust =
752     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
753   priv->vadjust =
754     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
755
756   g_object_ref_sink (G_OBJECT (priv->hadjust));
757   g_object_ref_sink (G_OBJECT (priv->vadjust));
758
759   g_signal_connect_swapped (priv->hadjust, "value-changed",
760                             G_CALLBACK (hildon_pannable_area_adjust_value_changed), area);
761   g_signal_connect_swapped (priv->vadjust, "value-changed",
762                             G_CALLBACK (hildon_pannable_area_adjust_value_changed), area);
763   g_signal_connect_swapped (priv->hadjust, "changed",
764                             G_CALLBACK (hildon_pannable_area_adjust_changed), area);
765   g_signal_connect_swapped (priv->vadjust, "changed",
766                             G_CALLBACK (hildon_pannable_area_adjust_changed), area);
767   g_signal_connect (area, "grab-notify",
768                     G_CALLBACK (hildon_pannable_area_grab_notify), NULL);
769 }
770
771 static void
772 hildon_pannable_area_get_property (GObject * object,
773                                    guint property_id,
774                                    GValue * value,
775                                    GParamSpec * pspec)
776 {
777   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
778
779   switch (property_id) {
780   case PROP_ENABLED:
781     g_value_set_boolean (value, priv->enabled);
782     break;
783   case PROP_MODE:
784     g_value_set_enum (value, priv->mode);
785     break;
786   case PROP_MOVEMENT_MODE:
787     g_value_set_flags (value, priv->mov_mode);
788     break;
789   case PROP_VELOCITY_MIN:
790     g_value_set_double (value, priv->vmin);
791     break;
792   case PROP_VELOCITY_MAX:
793     g_value_set_double (value, priv->vmax);
794     break;
795   case PROP_VEL_MAX_OVERSHOOTING:
796     g_value_set_double (value, priv->vmax_overshooting);
797     break;
798   case PROP_VELOCITY_FAST_FACTOR:
799     g_value_set_double (value, priv->vfast_factor);
800     break;
801   case PROP_DECELERATION:
802     g_value_set_double (value, priv->decel);
803     break;
804   case PROP_DRAG_INERTIA:
805     g_value_set_double (value, priv->drag_inertia);
806     break;
807   case PROP_SPS:
808     g_value_set_uint (value, priv->sps);
809     break;
810   case PROP_PANNING_THRESHOLD:
811     g_value_set_uint (value, priv->panning_threshold);
812     break;
813   case PROP_SCROLLBAR_FADE_DELAY:
814     /* convert to miliseconds */
815     g_value_set_uint (value, priv->scrollbar_fade_delay * SCROLL_FADE_TIMEOUT);
816     break;
817   case PROP_BOUNCE_STEPS:
818     g_value_set_uint (value, priv->bounce_steps);
819     break;
820   case PROP_FORCE:
821     g_value_set_uint (value, priv->force);
822     break;
823   case PROP_DIRECTION_ERROR_MARGIN:
824     g_value_set_uint (value, priv->direction_error_margin);
825     break;
826   case PROP_VSCROLLBAR_POLICY:
827     g_value_set_enum (value, priv->vscrollbar_policy);
828     break;
829   case PROP_HSCROLLBAR_POLICY:
830     g_value_set_enum (value, priv->hscrollbar_policy);
831     break;
832   case PROP_VOVERSHOOT_MAX:
833     g_value_set_int (value, priv->vovershoot_max);
834     break;
835   case PROP_HOVERSHOOT_MAX:
836     g_value_set_int (value, priv->hovershoot_max);
837     break;
838   case PROP_SCROLL_TIME:
839     g_value_set_double (value, priv->scroll_time);
840     break;
841   case PROP_INITIAL_HINT:
842     g_value_set_boolean (value, priv->initial_hint);
843     break;
844   case PROP_LOW_FRICTION_MODE:
845     g_value_set_boolean (value, priv->low_friction_mode);
846     break;
847   case PROP_SIZE_REQUEST_POLICY:
848     g_value_set_enum (value, priv->size_request_policy);
849     break;
850   case PROP_HADJUSTMENT:
851     g_value_set_object (value,
852                         hildon_pannable_area_get_hadjustment
853                         (HILDON_PANNABLE_AREA (object)));
854     break;
855   case PROP_VADJUSTMENT:
856     g_value_set_object (value,
857                         hildon_pannable_area_get_vadjustment
858                         (HILDON_PANNABLE_AREA (object)));
859     break;
860   case PROP_CENTER_ON_CHILD_FOCUS:
861     g_value_set_boolean (value, priv->center_on_child_focus);
862     break;
863   default:
864     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
865   }
866 }
867
868 static void
869 hildon_pannable_area_set_property (GObject * object,
870                                    guint property_id,
871                                    const GValue * value,
872                                    GParamSpec * pspec)
873 {
874   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
875   gboolean enabled;
876
877   switch (property_id) {
878   case PROP_ENABLED:
879     enabled = g_value_get_boolean (value);
880
881     if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
882       if (enabled)
883         gdk_window_raise (priv->event_window);
884       else
885         gdk_window_lower (priv->event_window);
886     }
887
888     priv->enabled = enabled;
889     break;
890   case PROP_MODE:
891     priv->mode = g_value_get_enum (value);
892     break;
893   case PROP_MOVEMENT_MODE:
894     priv->mov_mode = g_value_get_flags (value);
895     break;
896   case PROP_VELOCITY_MIN:
897     priv->vmin = g_value_get_double (value);
898     break;
899   case PROP_VELOCITY_MAX:
900     priv->vmax = g_value_get_double (value);
901     break;
902   case PROP_VEL_MAX_OVERSHOOTING:
903     priv->vmax_overshooting = g_value_get_double (value);
904     break;
905   case PROP_VELOCITY_FAST_FACTOR:
906     priv->vfast_factor = g_value_get_double (value);
907     break;
908   case PROP_DECELERATION:
909     priv->decel = g_value_get_double (value);
910     hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
911     break;
912   case PROP_DRAG_INERTIA:
913     priv->drag_inertia = g_value_get_double (value);
914     break;
915   case PROP_SPS:
916     priv->sps = g_value_get_uint (value);
917     break;
918   case PROP_PANNING_THRESHOLD:
919     {
920       GtkSettings *settings = gtk_settings_get_default ();
921       GtkSettingsValue svalue = { NULL, { 0, }, };
922
923       priv->panning_threshold = g_value_get_uint (value);
924
925       /* insure gtk dnd is the same we are using, not allowed
926          different thresholds in the same application */
927       svalue.origin = "panning_threshold";
928       g_value_init (&svalue.value, G_TYPE_LONG);
929       g_value_set_long (&svalue.value, priv->panning_threshold);
930       gtk_settings_set_property_value (settings, "gtk-dnd-drag-threshold", &svalue);
931       g_value_unset (&svalue.value);
932     }
933     break;
934   case PROP_SCROLLBAR_FADE_DELAY:
935     /* convert to miliseconds */
936     priv->scrollbar_fade_delay = g_value_get_uint (value)/(SCROLL_FADE_TIMEOUT);
937     break;
938   case PROP_BOUNCE_STEPS:
939     priv->bounce_steps = g_value_get_uint (value);
940     break;
941   case PROP_FORCE:
942     priv->force = g_value_get_uint (value);
943     break;
944   case PROP_DIRECTION_ERROR_MARGIN:
945     priv->direction_error_margin = g_value_get_uint (value);
946     break;
947   case PROP_VSCROLLBAR_POLICY:
948     priv->vscrollbar_policy = g_value_get_enum (value);
949
950     gtk_widget_queue_resize (GTK_WIDGET (object));
951     break;
952   case PROP_HSCROLLBAR_POLICY:
953     priv->hscrollbar_policy = g_value_get_enum (value);
954
955     gtk_widget_queue_resize (GTK_WIDGET (object));
956     break;
957   case PROP_VOVERSHOOT_MAX:
958     priv->vovershoot_max = g_value_get_int (value);
959     break;
960   case PROP_HOVERSHOOT_MAX:
961     priv->hovershoot_max = g_value_get_int (value);
962     break;
963   case PROP_SCROLL_TIME:
964     priv->scroll_time = g_value_get_double (value);
965
966     hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
967     break;
968   case PROP_INITIAL_HINT:
969     priv->initial_hint = g_value_get_boolean (value);
970     break;
971   case PROP_LOW_FRICTION_MODE:
972     priv->low_friction_mode = g_value_get_boolean (value);
973     break;
974   case PROP_SIZE_REQUEST_POLICY:
975     hildon_pannable_area_set_size_request_policy (HILDON_PANNABLE_AREA (object),
976                                                   g_value_get_enum (value));
977     break;
978   case PROP_CENTER_ON_CHILD_FOCUS:
979     priv->center_on_child_focus = g_value_get_boolean (value);
980     break;
981
982   default:
983     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
984   }
985 }
986
987 static void
988 hildon_pannable_area_dispose (GObject * object)
989 {
990   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
991   GtkWidget *child = gtk_bin_get_child (GTK_BIN (object));
992
993   hildon_pannable_area_remove_timeouts (GTK_WIDGET (object));
994
995   if (child) {
996     g_signal_handlers_disconnect_by_func (child,
997                                           hildon_pannable_area_child_mapped,
998                                           object);
999   }
1000
1001   g_signal_handlers_disconnect_by_func (object,
1002                                         hildon_pannable_area_grab_notify,
1003                                         NULL);
1004
1005   if (priv->hadjust) {
1006     g_signal_handlers_disconnect_by_func (priv->hadjust,
1007                                           hildon_pannable_area_adjust_value_changed,
1008                                           object);
1009     g_signal_handlers_disconnect_by_func (priv->hadjust,
1010                                           hildon_pannable_area_adjust_changed,
1011                                           object);
1012     g_object_unref (priv->hadjust);
1013     priv->hadjust = NULL;
1014   }
1015
1016   if (priv->vadjust) {
1017     g_signal_handlers_disconnect_by_func (priv->vadjust,
1018                                           hildon_pannable_area_adjust_value_changed,
1019                                           object);
1020     g_signal_handlers_disconnect_by_func (priv->vadjust,
1021                                           hildon_pannable_area_adjust_changed,
1022                                           object);
1023     g_object_unref (priv->vadjust);
1024     priv->vadjust = NULL;
1025   }
1026
1027   if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
1028     G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
1029 }
1030
1031 static void
1032 hildon_pannable_area_realize (GtkWidget * widget)
1033 {
1034   GdkWindowAttr attributes;
1035   gint attributes_mask;
1036   gint border_width;
1037   HildonPannableAreaPrivate *priv;
1038
1039   priv = HILDON_PANNABLE_AREA (widget)->priv;
1040
1041   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1042
1043   border_width = GTK_CONTAINER (widget)->border_width;
1044
1045   attributes.x = widget->allocation.x + border_width;
1046   attributes.y = widget->allocation.y + border_width;
1047   attributes.width = MAX (widget->allocation.width - 2 * border_width, 0);
1048   attributes.height = MAX (widget->allocation.height - 2 * border_width, 0);
1049   attributes.window_type = GDK_WINDOW_CHILD;
1050
1051   /* avoid using the hildon_window */
1052   attributes.visual = gtk_widget_get_visual (widget);
1053   attributes.colormap = gtk_widget_get_colormap (widget);
1054   attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
1055   attributes.wclass = GDK_INPUT_OUTPUT;
1056
1057   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1058
1059   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
1060                                    &attributes, attributes_mask);
1061   gdk_window_set_user_data (widget->window, widget);
1062
1063   /* create the events window */
1064   attributes.x = 0;
1065   attributes.y = 0;
1066   attributes.event_mask = gtk_widget_get_events (widget)
1067     | GDK_BUTTON_MOTION_MASK
1068     | GDK_BUTTON_PRESS_MASK
1069     | GDK_BUTTON_RELEASE_MASK
1070     | GDK_SCROLL_MASK
1071     | GDK_POINTER_MOTION_HINT_MASK
1072     | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
1073   attributes.wclass = GDK_INPUT_ONLY;
1074
1075   attributes_mask = GDK_WA_X | GDK_WA_Y;
1076
1077   priv->event_window = gdk_window_new (widget->window,
1078                                        &attributes, attributes_mask);
1079   gdk_window_set_user_data (priv->event_window, widget);
1080
1081   widget->style = gtk_style_attach (widget->style, widget->window);
1082   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1083
1084   priv->scrollbars_gc = gdk_gc_new (GDK_DRAWABLE (widget->window));
1085   gdk_gc_copy (priv->scrollbars_gc, widget->style->fg_gc[GTK_STATE_INSENSITIVE]);
1086 }
1087
1088
1089 static void
1090 hildon_pannable_area_remove_timeouts (GtkWidget * widget)
1091 {
1092   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1093
1094   if (priv->idle_id) {
1095     g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
1096     g_source_remove (priv->idle_id);
1097     priv->idle_id = 0;
1098   }
1099
1100   if (priv->scroll_indicator_timeout){
1101     g_source_remove (priv->scroll_indicator_timeout);
1102     priv->scroll_indicator_timeout = 0;
1103   }
1104
1105   if (priv->motion_event_scroll_timeout){
1106     g_source_remove (priv->motion_event_scroll_timeout);
1107     priv->motion_event_scroll_timeout = 0;
1108   }
1109 }
1110
1111 static void
1112 hildon_pannable_area_unrealize (GtkWidget * widget)
1113 {
1114   HildonPannableAreaPrivate *priv;
1115
1116   priv = HILDON_PANNABLE_AREA (widget)->priv;
1117
1118   hildon_pannable_area_remove_timeouts (widget);
1119
1120   if (priv->event_window != NULL) {
1121     gdk_window_set_user_data (priv->event_window, NULL);
1122     gdk_window_destroy (priv->event_window);
1123     priv->event_window = NULL;
1124   }
1125
1126   gdk_gc_unref (priv->scrollbars_gc);
1127
1128   if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1129     (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)(widget);
1130 }
1131
1132 static void
1133 hildon_pannable_area_size_request (GtkWidget * widget,
1134                                    GtkRequisition * requisition)
1135 {
1136   GtkRequisition child_requisition = {0};
1137   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1138   GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
1139
1140   if (child && GTK_WIDGET_VISIBLE (child))
1141     {
1142       gtk_widget_size_request (child, &child_requisition);
1143     }
1144
1145   if (priv->hscrollbar_policy == GTK_POLICY_NEVER) {
1146     requisition->width = child_requisition.width;
1147   } else {
1148     switch (priv->size_request_policy) {
1149       case HILDON_SIZE_REQUEST_CHILDREN:
1150         requisition->width = MIN (PANNABLE_MAX_WIDTH,
1151                                   child_requisition.width);
1152         break;
1153       case HILDON_SIZE_REQUEST_MINIMUM:
1154       default:
1155         requisition->width = priv->indicator_width;
1156       }
1157   }
1158
1159   if (priv->vscrollbar_policy == GTK_POLICY_NEVER) {
1160     requisition->height = child_requisition.height;
1161   } else {
1162     switch (priv->size_request_policy) {
1163       case HILDON_SIZE_REQUEST_CHILDREN:
1164         requisition->height = MIN (PANNABLE_MAX_HEIGHT,
1165                                    child_requisition.height);
1166         break;
1167       case HILDON_SIZE_REQUEST_MINIMUM:
1168       default:
1169         requisition->height = priv->indicator_width;
1170       }
1171   }
1172
1173   requisition->width += 2 * GTK_CONTAINER (widget)->border_width;
1174   requisition->height += 2 * GTK_CONTAINER (widget)->border_width;
1175 }
1176
1177 static void
1178 hildon_pannable_area_child_allocate_calculate (GtkWidget * widget,
1179                                                GtkAllocation * allocation,
1180                                                GtkAllocation * child_allocation)
1181 {
1182   gint border_width;
1183   HildonPannableAreaPrivate *priv;
1184
1185   border_width = GTK_CONTAINER (widget)->border_width;
1186
1187   priv = HILDON_PANNABLE_AREA (widget)->priv;
1188
1189   child_allocation->x = 0;
1190   child_allocation->y = 0;
1191   child_allocation->width = MAX (allocation->width - 2 * border_width -
1192                                  (priv->vscroll_visible ? priv->vscroll_rect.width : 0), 0);
1193   child_allocation->height = MAX (allocation->height - 2 * border_width -
1194                                   (priv->hscroll_visible ? priv->hscroll_rect.height : 0), 0);
1195
1196   if (priv->overshot_dist_y > 0) {
1197     child_allocation->y = MIN (child_allocation->y + priv->overshot_dist_y,
1198                                child_allocation->height);
1199     child_allocation->height = MAX (child_allocation->height - priv->overshot_dist_y, 0);
1200   } else if (priv->overshot_dist_y < 0) {
1201     child_allocation->height = MAX (child_allocation->height + priv->overshot_dist_y, 0);
1202   }
1203
1204   if (priv->overshot_dist_x > 0) {
1205     child_allocation->x = MIN (child_allocation->x + priv->overshot_dist_x,
1206                                child_allocation->width);
1207     child_allocation->width = MAX (child_allocation->width - priv->overshot_dist_x, 0);
1208   } else if (priv->overshot_dist_x < 0) {
1209     child_allocation->width = MAX (child_allocation->width + priv->overshot_dist_x, 0);
1210   }
1211 }
1212
1213 static void
1214 hildon_pannable_area_size_allocate (GtkWidget * widget,
1215                                     GtkAllocation * allocation)
1216 {
1217   GtkAllocation child_allocation;
1218   HildonPannableAreaPrivate *priv;
1219   GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
1220   gint border_width;
1221   gdouble hv, vv;
1222
1223   border_width = GTK_CONTAINER (widget)->border_width;
1224
1225   widget->allocation = *allocation;
1226
1227   priv = HILDON_PANNABLE_AREA (widget)->priv;
1228
1229   if (GTK_WIDGET_REALIZED (widget)) {
1230       gdk_window_move_resize (widget->window,
1231                               allocation->x + border_width,
1232                               allocation->y  + border_width,
1233                               allocation->width  - border_width * 2,
1234                               allocation->height - border_width * 2);
1235       gdk_window_move_resize (priv->event_window,
1236                               0,
1237                               0,
1238                               allocation->width  - border_width * 2,
1239                               allocation->height - border_width * 2);
1240   }
1241
1242   if (child && GTK_WIDGET_VISIBLE (child)) {
1243
1244     hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget));
1245
1246     hildon_pannable_area_child_allocate_calculate (widget,
1247                                                    allocation,
1248                                                    &child_allocation);
1249
1250     gtk_widget_size_allocate (child, &child_allocation);
1251
1252     if (hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget))) {
1253       hildon_pannable_area_child_allocate_calculate (widget,
1254                                                      allocation,
1255                                                      &child_allocation);
1256
1257       gtk_widget_size_allocate (child, &child_allocation);
1258     }
1259
1260     hv = priv->hadjust->value;
1261     vv = priv->vadjust->value;
1262
1263     /* we have to do this after child size_allocate because page_size is
1264      * changed when we allocate the size of the children */
1265     if (priv->overshot_dist_y < 0) {
1266       priv->vadjust->value = priv->vadjust->upper - priv->vadjust->page_size;
1267     }
1268
1269     if (priv->overshot_dist_x < 0) {
1270       priv->hadjust->value = priv->hadjust->upper - priv->hadjust->page_size;
1271     }
1272
1273     if (hv != priv->hadjust->value)
1274       gtk_adjustment_value_changed (priv->hadjust);
1275
1276     if (vv != priv->vadjust->value)
1277       gtk_adjustment_value_changed (priv->vadjust);
1278
1279   } else {
1280     hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget));
1281   }
1282 }
1283
1284 static void
1285 hildon_pannable_area_style_set (GtkWidget * widget,
1286                                 GtkStyle * previous_style)
1287 {
1288   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1289
1290   GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1291     style_set (widget, previous_style);
1292
1293   gtk_style_lookup_color (widget->style, "SecondaryTextColor", &priv->scroll_color);
1294   gtk_widget_style_get (widget, "indicator-width", &priv->indicator_width, NULL);
1295 }
1296
1297 static void
1298 hildon_pannable_area_map (GtkWidget * widget)
1299 {
1300   HildonPannableAreaPrivate *priv;
1301
1302   priv = HILDON_PANNABLE_AREA (widget)->priv;
1303
1304   gdk_window_show (widget->window);
1305
1306   if (priv->event_window != NULL && !priv->enabled)
1307     gdk_window_show (priv->event_window);
1308
1309   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1310
1311   if (priv->event_window != NULL && priv->enabled)
1312     gdk_window_show (priv->event_window);
1313 }
1314
1315 static void
1316 hildon_pannable_area_unmap (GtkWidget * widget)
1317 {
1318   HildonPannableAreaPrivate *priv;
1319
1320   priv = HILDON_PANNABLE_AREA (widget)->priv;
1321
1322   if (priv->event_window != NULL)
1323     gdk_window_hide (priv->event_window);
1324
1325   gdk_window_hide (widget->window);
1326
1327   (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1328 }
1329
1330 static void
1331 hildon_pannable_area_grab_notify (GtkWidget *widget,
1332                                   gboolean was_grabbed,
1333                                   gpointer user_data)
1334 {
1335   /* an internal widget has grabbed the focus and now has returned it,
1336      we have to do some release actions */
1337   if (was_grabbed) {
1338     HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1339
1340     priv->scroll_indicator_event_interrupt = 0;
1341
1342     if ((!priv->scroll_indicator_timeout)&&(priv->scroll_indicator_alpha)>0.1) {
1343       priv->scroll_delay_counter = priv->scrollbar_fade_delay;
1344
1345       hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
1346                                                 priv->scroll_indicator_alpha);
1347     }
1348
1349     priv->last_type = 3;
1350     priv->moved = FALSE;
1351   }
1352 }
1353
1354 #if USE_CAIRO_SCROLLBARS == 1
1355
1356 static void
1357 rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b)
1358 {
1359   *r = (color->red >> 8) / 255.0;
1360   *g = (color->green >> 8) / 255.0;
1361   *b = (color->blue >> 8) / 255.0;
1362 }
1363
1364 static void
1365 hildon_pannable_draw_vscroll (GtkWidget * widget,
1366                               GdkColor *back_color,
1367                               GdkColor *scroll_color)
1368 {
1369   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1370   gfloat y, height;
1371   cairo_t *cr;
1372   cairo_pattern_t *pattern;
1373   gdouble r, g, b;
1374   gint radius = (priv->vscroll_rect.width/2) - 1;
1375
1376   cr = gdk_cairo_create(widget->window);
1377
1378   /* Draw the background */
1379   rgb_from_gdkcolor (back_color, &r, &g, &b);
1380   cairo_set_source_rgb (cr, r, g, b);
1381   cairo_rectangle(cr, priv->vscroll_rect.x, priv->vscroll_rect.y,
1382                   priv->vscroll_rect.width,
1383                   priv->vscroll_rect.height);
1384   cairo_fill_preserve (cr);
1385   cairo_clip (cr);
1386
1387   /* Calculate the scroll bar height and position */
1388   y = ((priv->vadjust->value - priv->vadjust->lower) / (priv->vadjust->upper - priv->vadjust->lower)) *
1389     (widget->allocation.height -
1390      (priv->hscroll_visible ? priv->indicator_width : 0));
1391   height = ((((priv->vadjust->value - priv->vadjust->lower) +
1392               priv->vadjust->page_size) /
1393              (priv->vadjust->upper - priv->vadjust->lower)) *
1394             (widget->allocation.height -
1395              (priv->hscroll_visible ? priv->indicator_width : 0))) - y;
1396
1397   /* Set a minimum height */
1398   height = MAX (SCROLL_BAR_MIN_SIZE, height);
1399
1400   /* Check the max y position */
1401   y = MIN (y, widget->allocation.height -
1402            (priv->hscroll_visible ? priv->hscroll_rect.height : 0) -
1403            height);
1404
1405   /* Draw the scrollbar */
1406   rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1407
1408   pattern = cairo_pattern_create_linear(radius+1, y, radius+1,y + height);
1409   cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1410   cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1411   cairo_set_source(cr, pattern);
1412   cairo_fill(cr);
1413   cairo_pattern_destroy(pattern);
1414
1415   cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + radius + 1, radius, G_PI, 0);
1416   cairo_line_to(cr, priv->vscroll_rect.x + (radius * 2) + 1, y + height - radius);
1417   cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + height - radius, radius, 0, G_PI);
1418   cairo_line_to(cr, priv->vscroll_rect.x + 1, y + height - radius);
1419   cairo_clip (cr);
1420
1421   cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1422
1423   cairo_destroy(cr);
1424 }
1425
1426 static void
1427 hildon_pannable_draw_hscroll (GtkWidget * widget,
1428                               GdkColor *back_color,
1429                               GdkColor *scroll_color)
1430 {
1431   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1432   gfloat x, width;
1433   cairo_t *cr;
1434   cairo_pattern_t *pattern;
1435   gdouble r, g, b;
1436   gint radius = (priv->hscroll_rect.height/2) - 1;
1437
1438   cr = gdk_cairo_create(widget->window);
1439
1440   /* Draw the background */
1441   rgb_from_gdkcolor (back_color, &r, &g, &b);
1442   cairo_set_source_rgb (cr, r, g, b);
1443   cairo_rectangle(cr, priv->hscroll_rect.x, priv->hscroll_rect.y,
1444                   priv->hscroll_rect.width,
1445                   priv->hscroll_rect.height);
1446   cairo_fill_preserve (cr);
1447   cairo_clip (cr);
1448
1449   /* calculate the scrollbar width and position */
1450   x = ((priv->hadjust->value - priv->hadjust->lower) / (priv->hadjust->upper - priv->hadjust->lower)) *
1451     (widget->allocation.width - (priv->vscroll_visible ? priv->indicator_width : 0));
1452   width =((((priv->hadjust->value - priv->hadjust->lower) +
1453             priv->hadjust->page_size) / (priv->hadjust->upper - priv->hadjust->lower)) *
1454           (widget->allocation.width -
1455            (priv->vscroll_visible ? priv->indicator_width : 0))) - x;
1456
1457   /* Set a minimum width */
1458   width = MAX (SCROLL_BAR_MIN_SIZE, width);
1459
1460   /* Check the max x position */
1461   x = MIN (x, widget->allocation.width -
1462            (priv->vscroll_visible ? priv->vscroll_rect.width : 0) -
1463            width);
1464
1465   /* Draw the scrollbar */
1466   rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1467
1468   pattern = cairo_pattern_create_linear(x, radius+1, x+width, radius+1);
1469   cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1470   cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1471   cairo_set_source(cr, pattern);
1472   cairo_fill(cr);
1473   cairo_pattern_destroy(pattern);
1474
1475   cairo_arc_negative(cr, x + radius + 1, priv->hscroll_rect.y + radius + 1, radius, 3*G_PI_2, G_PI_2);
1476   cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + (radius * 2) + 1);
1477   cairo_arc_negative(cr, x + width - radius, priv->hscroll_rect.y + radius + 1, radius, G_PI_2, 3*G_PI_2);
1478   cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + 1);
1479   cairo_clip (cr);
1480
1481   cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1482
1483   cairo_destroy(cr);
1484 }
1485
1486 #else /* USE_CAIRO_SCROLLBARS */
1487
1488 static void
1489 tranparency_color (GdkColor *color,
1490                    GdkColor colora,
1491                    GdkColor colorb,
1492                    gdouble transparency)
1493 {
1494   gdouble diff;
1495
1496   diff = colora.red - colorb.red;
1497   color->red = colora.red-diff*transparency;
1498
1499   diff = colora.green - colorb.green;
1500   color->green = colora.green-diff*transparency;
1501
1502   diff = colora.blue - colorb.blue;
1503   color->blue = colora.blue-diff*transparency;
1504 }
1505
1506 static void
1507 hildon_pannable_draw_vscroll (GtkWidget *widget,
1508                               GdkColor *back_color,
1509                               GdkColor *scroll_color)
1510 {
1511   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1512   gfloat y, height;
1513   GdkColor transp_color;
1514   GdkGC *gc = priv->scrollbars_gc;
1515
1516   gdk_draw_rectangle (widget->window,
1517                       widget->style->bg_gc[GTK_STATE_NORMAL],
1518                       TRUE,
1519                        priv->vscroll_rect.x, priv->vscroll_rect.y,
1520                       priv->vscroll_rect.width,
1521                       priv->vscroll_rect.height);
1522
1523   y = ((priv->vadjust->value - priv->vadjust->lower) / (priv->vadjust->upper - priv->vadjust->lower)) *
1524     (widget->allocation.height - (priv->hscroll_visible ? priv->indicator_width : 0));
1525   height = ((((priv->vadjust->value - priv->vadjust->lower) + priv->vadjust->page_size) /
1526              (priv->vadjust->upper - priv->vadjust->lower)) *
1527             (widget->allocation.height -
1528              (priv->hscroll_visible ? priv->indicator_width : 0))) - y;
1529
1530   /* Set a minimum height */
1531   height = MAX (SCROLL_BAR_MIN_SIZE, height);
1532
1533   /* Check the max y position */
1534   y = MIN (y, widget->allocation.height -
1535            (priv->hscroll_visible ? priv->hscroll_rect.height : 0) -
1536            height);
1537
1538   if (priv->scroll_indicator_alpha == 1.0) {
1539     transp_color = priv->scroll_color;
1540   } else if (priv->scroll_indicator_alpha < 1.0) {
1541     tranparency_color (&transp_color, *back_color, *scroll_color,
1542                        priv->scroll_indicator_alpha);
1543   }
1544   gdk_gc_set_rgb_fg_color (gc, &transp_color);
1545
1546   gdk_draw_rectangle (widget->window, gc,
1547                       TRUE, priv->vscroll_rect.x, y,
1548                       priv->vscroll_rect.width, height);
1549 }
1550
1551 static void
1552 hildon_pannable_draw_hscroll (GtkWidget *widget,
1553                               GdkColor *back_color,
1554                               GdkColor *scroll_color)
1555 {
1556   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1557   gfloat x, width;
1558   GdkColor transp_color;
1559   GdkGC *gc = priv->scrollbars_gc;
1560
1561   gdk_draw_rectangle (widget->window,
1562                       widget->style->bg_gc[GTK_STATE_INSENSITIVE],
1563                       TRUE,
1564                       priv->hscroll_rect.x, priv->hscroll_rect.y,
1565                       priv->hscroll_rect.width,
1566                       priv->hscroll_rect.height);
1567
1568   /* calculate the scrollbar width and position */
1569   x = ((priv->hadjust->value - priv->hadjust->lower) / (priv->hadjust->upper - priv->hadjust->lower)) *
1570     (widget->allocation.width - (priv->vscroll_visible ? priv->indicator_width : 0));
1571   width =((((priv->hadjust->value - priv->hadjust->lower) +
1572             priv->hadjust->page_size) / (priv->hadjust->upper - priv->hadjust->lower)) *
1573           (widget->allocation.width -
1574            (priv->vscroll_visible ? priv->indicator_width : 0))) - x;
1575
1576   /* Set a minimum width */
1577   width = MAX (SCROLL_BAR_MIN_SIZE, width);
1578
1579   /* Check the max x position */
1580   x = MIN (x, widget->allocation.width -
1581            (priv->vscroll_visible ? priv->vscroll_rect.width : 0) -
1582            width);
1583
1584   if (priv->scroll_indicator_alpha == 1.0) {
1585     transp_color = priv->scroll_color;
1586   } else if (priv->scroll_indicator_alpha < 1.0) {
1587     tranparency_color (&transp_color, *back_color, *scroll_color,
1588                        priv->scroll_indicator_alpha);
1589   }
1590   gdk_gc_set_rgb_fg_color (gc, &transp_color);
1591
1592   gdk_draw_rectangle (widget->window, gc,
1593                       TRUE, x, priv->hscroll_rect.y, width,
1594                       priv->hscroll_rect.height);
1595 }
1596
1597 #endif /* USE_CAIRO_SCROLLBARS */
1598
1599 static void
1600 hildon_pannable_area_initial_effect (GtkWidget * widget)
1601 {
1602   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1603
1604   if (priv->initial_hint) {
1605     if (priv->vscroll_visible || priv->hscroll_visible) {
1606
1607       priv->scroll_indicator_event_interrupt = 0;
1608       priv->scroll_delay_counter = priv->scrollbar_fade_delay;
1609
1610       hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget), 1.0);
1611     }
1612   }
1613 }
1614
1615 static void
1616 hildon_pannable_area_launch_fade_timeout (HildonPannableArea * area,
1617                                           gdouble alpha)
1618 {
1619   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
1620
1621   priv->scroll_indicator_alpha = alpha;
1622
1623   if (!priv->scroll_indicator_timeout)
1624     priv->scroll_indicator_timeout =
1625       gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
1626                                     SCROLL_FADE_TIMEOUT,
1627                                     (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
1628                                     area,
1629                                     NULL);
1630 }
1631
1632 static void
1633 hildon_pannable_area_adjust_changed (HildonPannableArea * area,
1634                                      gpointer data)
1635 {
1636   if (GTK_WIDGET_REALIZED (area))
1637     hildon_pannable_area_refresh (area);
1638 }
1639
1640 static void
1641 hildon_pannable_area_adjust_value_changed (HildonPannableArea * area,
1642                                            gpointer data)
1643 {
1644   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
1645   gint xdiff, ydiff;
1646   gint x = priv->x_offset;
1647   gint y = priv->y_offset;
1648
1649   priv->x_offset = priv->hadjust->value;
1650   xdiff = x - priv->x_offset;
1651   priv->y_offset = priv->vadjust->value;
1652   ydiff = y - priv->y_offset;
1653
1654   if ((xdiff || ydiff) && GTK_WIDGET_DRAWABLE (area)) {
1655     hildon_pannable_area_redraw (area);
1656
1657     if ((priv->vscroll_visible) || (priv->hscroll_visible)) {
1658       priv->scroll_indicator_event_interrupt = 0;
1659       priv->scroll_delay_counter = priv->scrollbar_fade_delay;
1660
1661       hildon_pannable_area_launch_fade_timeout (area, 1.0);
1662     }
1663   }
1664 }
1665
1666 static void
1667 hildon_pannable_area_redraw (HildonPannableArea * area)
1668 {
1669   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
1670
1671   /* Redraw scroll indicators */
1672   if (GTK_WIDGET_DRAWABLE (area)) {
1673       if (priv->hscroll_visible) {
1674         gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
1675                                     &priv->hscroll_rect, FALSE);
1676       }
1677
1678       if (priv->vscroll_visible) {
1679         gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
1680                                     &priv->vscroll_rect, FALSE);
1681       }
1682   }
1683 }
1684
1685 static gboolean
1686 hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area)
1687 {
1688   HildonPannableAreaPrivate *priv = area->priv;
1689
1690   /* if moving do not fade out */
1691   if (((ABS (priv->vel_y)>priv->vmin)||
1692        (ABS (priv->vel_x)>priv->vmin))&&(!priv->button_pressed)) {
1693
1694     return TRUE;
1695   }
1696
1697   if (priv->scroll_indicator_event_interrupt) {
1698     /* Stop a fade out, and fade back in */
1699     if (priv->scroll_indicator_alpha > 0.9) {
1700       priv->scroll_indicator_alpha = 1.0;
1701       priv->scroll_indicator_timeout = 0;
1702
1703       return FALSE;
1704     } else {
1705       priv->scroll_indicator_alpha += 0.2;
1706       hildon_pannable_area_redraw (area);
1707
1708       return TRUE;
1709     }
1710   }
1711
1712   if ((priv->scroll_indicator_alpha > 0.9) &&
1713       (priv->scroll_delay_counter > 0)) {
1714     priv->scroll_delay_counter--;
1715
1716     return TRUE;
1717   }
1718
1719   if (!priv->scroll_indicator_event_interrupt) {
1720     /* Continue fade out */
1721     if (priv->scroll_indicator_alpha < 0.1) {
1722       priv->scroll_indicator_timeout = 0;
1723       priv->scroll_indicator_alpha = 0.0;
1724
1725       return FALSE;
1726     } else {
1727       priv->scroll_indicator_alpha -= 0.2;
1728       hildon_pannable_area_redraw (area);
1729
1730       return TRUE;
1731     }
1732   }
1733
1734   return TRUE;
1735 }
1736
1737 static gboolean
1738 hildon_pannable_area_expose_event (GtkWidget * widget,
1739                                    GdkEventExpose * event)
1740 {
1741
1742   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1743 #if USE_CAIRO_SCROLLBARS == 1
1744   GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1745   GdkColor scroll_color = widget->style->base[GTK_STATE_SELECTED];
1746 #else /* USE_CAIRO_SCROLLBARS */
1747   GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1748   GdkColor scroll_color = priv->scroll_color;
1749 #endif
1750
1751   if (G_UNLIKELY (priv->initial_effect)) {
1752     hildon_pannable_area_initial_effect (widget);
1753
1754     priv->initial_effect = FALSE;
1755   }
1756
1757   if (gtk_bin_get_child (GTK_BIN (widget))) {
1758
1759     if (priv->scroll_indicator_alpha > 0.1) {
1760       if (priv->vscroll_visible) {
1761         hildon_pannable_draw_vscroll (widget, &back_color, &scroll_color);
1762       }
1763       if (priv->hscroll_visible) {
1764         hildon_pannable_draw_hscroll (widget, &back_color, &scroll_color);
1765       }
1766     }
1767
1768     /* draw overshooting rectangles */
1769     if (priv->overshot_dist_y > 0) {
1770       gint overshot_height;
1771
1772       overshot_height = MIN (priv->overshot_dist_y, widget->allocation.height -
1773                              (priv->hscroll_visible ? priv->hscroll_rect.height : 0));
1774
1775       gdk_draw_rectangle (widget->window,
1776                           widget->style->bg_gc[GTK_STATE_NORMAL],
1777                           TRUE,
1778                           0,
1779                           0,
1780                           widget->allocation.width -
1781                           (priv->vscroll_visible ? priv->vscroll_rect.width : 0),
1782                           overshot_height);
1783     } else if (priv->overshot_dist_y < 0) {
1784       gint overshot_height;
1785       gint overshot_y;
1786
1787       overshot_height =
1788         MAX (priv->overshot_dist_y,
1789              -(widget->allocation.height -
1790                (priv->hscroll_visible ? priv->hscroll_rect.height : 0)));
1791
1792       overshot_y = MAX (widget->allocation.height +
1793                         overshot_height -
1794                         (priv->hscroll_visible ? priv->hscroll_rect.height : 0), 0);
1795
1796       gdk_draw_rectangle (widget->window,
1797                           widget->style->bg_gc[GTK_STATE_NORMAL],
1798                           TRUE,
1799                           0,
1800                           overshot_y,
1801                           widget->allocation.width -
1802                           priv->vscroll_rect.width,
1803                           -overshot_height);
1804     }
1805
1806     if (priv->overshot_dist_x > 0) {
1807       gint overshot_width;
1808
1809       overshot_width = MIN (priv->overshot_dist_x, widget->allocation.width -
1810                              (priv->vscroll_visible ? priv->vscroll_rect.width : 0));
1811
1812       gdk_draw_rectangle (widget->window,
1813                           widget->style->bg_gc[GTK_STATE_NORMAL],
1814                           TRUE,
1815                           0,
1816                           0,
1817                           overshot_width,
1818                           widget->allocation.height -
1819                           (priv->hscroll_visible ? priv->hscroll_rect.height : 0));
1820     } else if (priv->overshot_dist_x < 0) {
1821       gint overshot_width;
1822       gint overshot_x;
1823
1824       overshot_width =
1825         MAX (priv->overshot_dist_x,
1826              -(widget->allocation.width -
1827                (priv->vscroll_visible ? priv->vscroll_rect.width : 0)));
1828
1829       overshot_x = MAX (widget->allocation.width +
1830                         overshot_width -
1831                         (priv->vscroll_visible ? priv->vscroll_rect.width : 0), 0);
1832
1833       gdk_draw_rectangle (widget->window,
1834                           widget->style->bg_gc[GTK_STATE_NORMAL],
1835                           TRUE,
1836                           overshot_x,
1837                           0,
1838                           -overshot_width,
1839                           widget->allocation.height -
1840                           priv->hscroll_rect.height);
1841     }
1842
1843   }
1844
1845   return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
1846 }
1847
1848 static GdkWindow *
1849 hildon_pannable_area_get_topmost (GdkWindow * window,
1850                                   gint x, gint y,
1851                                   gint * tx, gint * ty,
1852                                   GdkEventMask mask)
1853 {
1854   /* Find the GdkWindow at the given point, by recursing from a given
1855    * parent GdkWindow. Optionally return the co-ordinates transformed
1856    * relative to the child window.
1857    */
1858   gint width, height;
1859   GList *c, *children;
1860   GdkWindow *selected_window = NULL;
1861
1862   gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
1863   if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
1864     return NULL;
1865
1866   children = gdk_window_peek_children (window);
1867
1868   if (!children) {
1869     if (tx)
1870       *tx = x;
1871     if (ty)
1872       *ty = y;
1873     selected_window = window;
1874   }
1875
1876   for (c = children; c; c = c->next) {
1877     GdkWindow *child = (GdkWindow *) c->data;
1878     gint wx, wy;
1879
1880     gdk_drawable_get_size (GDK_DRAWABLE (child), &width, &height);
1881     gdk_window_get_position (child, &wx, &wy);
1882
1883     if ((x >= wx) && (x < (wx + width)) && (y >= wy) && (y < (wy + height)) &&
1884         (gdk_window_is_visible (child))) {
1885
1886       if (gdk_window_peek_children (child)) {
1887         selected_window = hildon_pannable_area_get_topmost (child, x-wx, y-wy,
1888                                                             tx, ty, mask);
1889         if (!selected_window) {
1890           if (tx)
1891             *tx = x-wx;
1892           if (ty)
1893             *ty = y-wy;
1894           selected_window = child;
1895         }
1896       } else {
1897         if ((gdk_window_get_events (child)&mask)) {
1898           if (tx)
1899             *tx = x-wx;
1900           if (ty)
1901             *ty = y-wy;
1902           selected_window = child;
1903         }
1904       }
1905     }
1906   }
1907
1908   return selected_window;
1909 }
1910
1911 static void
1912 synth_crossing (GdkWindow * child,
1913                 gint x, gint y,
1914                 gint x_root, gint y_root,
1915                 guint32 time, gboolean in)
1916 {
1917   GdkEventCrossing *crossing_event;
1918   GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
1919
1920   /* Send synthetic enter event */
1921   crossing_event = (GdkEventCrossing *) gdk_event_new (type);
1922   ((GdkEventAny *) crossing_event)->type = type;
1923   ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
1924   ((GdkEventAny *) crossing_event)->send_event = FALSE;
1925   crossing_event->subwindow = g_object_ref (child);
1926   crossing_event->time = time;
1927   crossing_event->x = x;
1928   crossing_event->y = y;
1929   crossing_event->x_root = x_root;
1930   crossing_event->y_root = y_root;
1931   crossing_event->mode = GDK_CROSSING_NORMAL;
1932   crossing_event->detail = GDK_NOTIFY_UNKNOWN;
1933   crossing_event->focus = FALSE;
1934   crossing_event->state = 0;
1935   gdk_event_put ((GdkEvent *) crossing_event);
1936   gdk_event_free ((GdkEvent *) crossing_event);
1937 }
1938
1939 static gboolean
1940 hildon_pannable_area_button_press_cb (GtkWidget * widget,
1941                                       GdkEventButton * event)
1942 {
1943   gint x, y;
1944   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
1945   HildonPannableAreaPrivate *priv = area->priv;
1946
1947   if ((!priv->enabled) || (event->button != 1) ||
1948       ((event->time == priv->last_time) &&
1949        (priv->last_type == 1)) || (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
1950     return TRUE;
1951
1952   priv->scroll_indicator_event_interrupt = 1;
1953
1954   hildon_pannable_area_launch_fade_timeout (area,
1955                                             priv->scroll_indicator_alpha);
1956
1957   priv->last_time = event->time;
1958   priv->last_press_time = event->time;
1959   priv->last_type = 1;
1960
1961   priv->scroll_to_x = -1;
1962   priv->scroll_to_y = -1;
1963
1964   if (priv->button_pressed && priv->child) {
1965     /* Widget stole focus on last click, send crossing-out event */
1966     synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
1967                     event->time, FALSE);
1968   }
1969
1970   priv->x = event->x;
1971   priv->y = event->y;
1972   priv->ix = priv->x;
1973   priv->iy = priv->y;
1974
1975   /* Don't allow a click if we're still moving fast */
1976   if ((ABS (priv->vel_x) <= (priv->vmax * priv->vfast_factor)) &&
1977       (ABS (priv->vel_y) <= (priv->vmax * priv->vfast_factor)))
1978     priv->child =
1979       hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
1980                                         event->x, event->y, &x, &y, GDK_BUTTON_PRESS_MASK);
1981   else
1982     priv->child = NULL;
1983
1984   priv->button_pressed = TRUE;
1985
1986   /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
1987   priv->old_vel_x = priv->vel_x;
1988   priv->old_vel_y = priv->vel_y;
1989   priv->vel_x = 0;
1990   priv->vel_y = 0;
1991   if (priv->idle_id) {
1992     g_source_remove (priv->idle_id);
1993     priv->idle_id = 0;
1994     g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
1995   }
1996
1997   if (priv->child) {
1998
1999     gdk_drawable_get_size (priv->child, &priv->child_width,
2000                            &priv->child_height);
2001     priv->last_in = TRUE;
2002
2003     g_object_add_weak_pointer ((GObject *) priv->child,
2004                                (gpointer) & priv->child);
2005
2006     event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
2007     /* remove the reference we added with the copy */
2008     g_object_unref (priv->event_window);
2009     event->x = x;
2010     event->y = y;
2011     priv->cx = x;
2012     priv->cy = y;
2013
2014     synth_crossing (priv->child, x, y, event->x_root,
2015                     event->y_root, event->time, TRUE);
2016
2017     /* Send synthetic click (button press/release) event */
2018     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
2019
2020     gdk_event_put ((GdkEvent *) event);
2021     gdk_event_free ((GdkEvent *) event);
2022   } else
2023     priv->child = NULL;
2024
2025   return TRUE;
2026 }
2027
2028 static gboolean
2029 hildon_pannable_area_check_scrollbars (HildonPannableArea * area)
2030 {
2031   HildonPannableAreaPrivate *priv = area->priv;
2032   gboolean prev_hscroll_visible, prev_vscroll_visible;
2033
2034   prev_hscroll_visible = priv->hscroll_visible;
2035   prev_vscroll_visible = priv->vscroll_visible;
2036
2037   if (!gtk_bin_get_child (GTK_BIN (area))) {
2038     priv->vscroll_visible = FALSE;
2039     priv->hscroll_visible = FALSE;
2040   } else {
2041     switch (priv->hscrollbar_policy) {
2042     case GTK_POLICY_ALWAYS:
2043       priv->hscroll_visible = TRUE;
2044       break;
2045     case GTK_POLICY_NEVER:
2046       priv->hscroll_visible = FALSE;
2047       break;
2048     default:
2049       priv->hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2050                                priv->hadjust->page_size);
2051     }
2052
2053     switch (priv->vscrollbar_policy) {
2054     case GTK_POLICY_ALWAYS:
2055       priv->vscroll_visible = TRUE;
2056       break;
2057     case GTK_POLICY_NEVER:
2058       priv->vscroll_visible = FALSE;
2059       break;
2060     default:
2061       priv->vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2062                                priv->vadjust->page_size);
2063     }
2064
2065     /* Store the vscroll/hscroll areas for redrawing */
2066     if (priv->vscroll_visible) {
2067       GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
2068       priv->vscroll_rect.x = allocation->width - priv->indicator_width;
2069       priv->vscroll_rect.y = 0;
2070       priv->vscroll_rect.width = priv->indicator_width;
2071       priv->vscroll_rect.height = allocation->height -
2072         (priv->hscroll_visible ? priv->indicator_width : 0);
2073     }
2074     if (priv->hscroll_visible) {
2075       GtkAllocation *allocation = &GTK_WIDGET (area)->allocation;
2076       priv->hscroll_rect.y = allocation->height - priv->indicator_width;
2077       priv->hscroll_rect.x = 0;
2078       priv->hscroll_rect.height = priv->indicator_width;
2079       priv->hscroll_rect.width = allocation->width -
2080         (priv->vscroll_visible ? priv->indicator_width : 0);
2081     }
2082   }
2083
2084   return ((priv->hscroll_visible != prev_hscroll_visible) ||
2085           (priv->vscroll_visible != prev_vscroll_visible));
2086 }
2087
2088 static void
2089 hildon_pannable_area_refresh (HildonPannableArea * area)
2090 {
2091   if (GTK_WIDGET_DRAWABLE (area) &&
2092       hildon_pannable_area_check_scrollbars (area)) {
2093     HildonPannableAreaPrivate *priv = area->priv;
2094
2095     gtk_widget_queue_resize (GTK_WIDGET (area));
2096
2097     if ((priv->vscroll_visible) || (priv->hscroll_visible)) {
2098       priv->scroll_indicator_event_interrupt = 0;
2099       priv->scroll_delay_counter = area->priv->scrollbar_fade_delay;
2100
2101       hildon_pannable_area_launch_fade_timeout (area, 1.0);
2102     }
2103   } else {
2104     hildon_pannable_area_redraw (area);
2105   }
2106 }
2107
2108 /* Scroll by a particular amount (in pixels). Optionally, return if
2109  * the scroll on a particular axis was successful.
2110  */
2111 static void
2112 hildon_pannable_axis_scroll (HildonPannableArea *area,
2113                              GtkAdjustment *adjust,
2114                              gdouble *vel,
2115                              gdouble inc,
2116                              gint *overshooting,
2117                              gint *overshot_dist,
2118                              gdouble *scroll_to,
2119                              gint overshoot_max,
2120                              gboolean *s)
2121 {
2122   gdouble dist;
2123   HildonPannableAreaPrivate *priv = area->priv;
2124
2125   dist = gtk_adjustment_get_value (adjust) - inc;
2126
2127   /* Overshooting
2128    * We use overshot_dist to define the distance of the current overshoot,
2129    * and overshooting to define the direction/whether or not we are overshot
2130    */
2131   if (!(*overshooting)) {
2132
2133     /* Initiation of the overshoot happens when the finger is released
2134      * and the current position of the pannable contents are out of range
2135      */
2136     if (dist < adjust->lower) {
2137       if (s) *s = FALSE;
2138
2139       dist = adjust->lower;
2140
2141       if (overshoot_max!=0) {
2142         *overshooting = 1;
2143         *scroll_to = -1;
2144         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
2145         *vel = MIN (priv->vmax_overshooting, *vel);
2146         gtk_widget_queue_resize (GTK_WIDGET (area));
2147       } else {
2148         *vel = 0.0;
2149       }
2150     } else if (dist > adjust->upper - adjust->page_size) {
2151       if (s) *s = FALSE;
2152
2153       dist = adjust->upper - adjust->page_size;
2154
2155       if (overshoot_max!=0) {
2156         *overshooting = 1;
2157         *scroll_to = -1;
2158         *overshot_dist = CLAMP (*overshot_dist + *vel, -overshoot_max, 0);
2159         *vel = MAX (-priv->vmax_overshooting, *vel);
2160         gtk_widget_queue_resize (GTK_WIDGET (area));
2161       } else {
2162         *vel = 0.0;
2163       }
2164     } else {
2165       if ((*scroll_to) != -1) {
2166         if (((inc < 0)&&(*scroll_to <= dist))||
2167             ((inc > 0)&&(*scroll_to >= dist))) {
2168           dist = *scroll_to;
2169           *scroll_to = -1;
2170           *vel = 0;
2171         }
2172       }
2173     }
2174
2175     adjust->value = dist;
2176   } else {
2177     if (!priv->button_pressed) {
2178
2179       /* When the overshoot has started we continue for
2180        * PROP_BOUNCE_STEPS more steps into the overshoot before we
2181        * reverse direction. The deceleration factor is calculated
2182        * based on the percentage distance from the first item with
2183        * each iteration, therefore always returning us to the
2184        * top/bottom most element
2185        */
2186       if (*overshot_dist > 0) {
2187
2188         if ((*overshooting < priv->bounce_steps) && (*vel > 0)) {
2189           (*overshooting)++;
2190           *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel);
2191         } else if ((*overshooting >= priv->bounce_steps) && (*vel > 0)) {
2192           *vel *= -1;
2193         } else if ((*overshooting > 1) && (*vel < 0)) {
2194           /* we add the MIN in order to avoid very small speeds */
2195           *vel = MIN (((((gdouble)*overshot_dist)*0.8) * -1), -10.0);
2196         }
2197
2198         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
2199
2200         gtk_widget_queue_resize (GTK_WIDGET (area));
2201
2202       } else if (*overshot_dist < 0) {
2203
2204         if ((*overshooting < priv->bounce_steps) && (*vel < 0)) {
2205           (*overshooting)++;
2206           *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel) * -1;
2207         } else if ((*overshooting >= priv->bounce_steps) && (*vel < 0)) {
2208           *vel *= -1;
2209         } else if ((*overshooting > 1) && (*vel > 0)) {
2210           /* we add the MAX in order to avoid very small speeds */
2211           *vel = MAX (((((gdouble)*overshot_dist)*0.8) * -1), 10.0);
2212         }
2213
2214         *overshot_dist = CLAMP (*overshot_dist + (*vel), -overshoot_max, 0);
2215
2216         gtk_widget_queue_resize (GTK_WIDGET (area));
2217
2218       } else {
2219         *overshooting = 0;
2220         *vel = 0;
2221         gtk_widget_queue_resize (GTK_WIDGET (area));
2222       }
2223     } else {
2224
2225       gint overshot_dist_old = *overshot_dist;
2226
2227       if (*overshot_dist > 0) {
2228         *overshot_dist = CLAMP ((*overshot_dist) + inc, 0, overshoot_max);
2229       } else if (*overshot_dist < 0) {
2230         *overshot_dist = CLAMP ((*overshot_dist) + inc, -1 * overshoot_max, 0);
2231       } else {
2232         *overshooting = 0;
2233         adjust->value = CLAMP (dist,
2234                                adjust->lower,
2235                                adjust->upper -
2236                                adjust->page_size);
2237       }
2238
2239       if (*overshot_dist != overshot_dist_old)
2240         gtk_widget_queue_resize (GTK_WIDGET (area));
2241     }
2242   }
2243 }
2244
2245 static void
2246 hildon_pannable_area_scroll (HildonPannableArea *area,
2247                              gdouble x, gdouble y)
2248 {
2249   gboolean sx, sy;
2250   HildonPannableAreaPrivate *priv = area->priv;
2251   gboolean hscroll_visible, vscroll_visible;
2252   gdouble hv, vv;
2253
2254   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
2255     return;
2256
2257   vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2258              priv->vadjust->page_size);
2259   hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2260              priv->hadjust->page_size);
2261
2262   sx = TRUE;
2263   sy = TRUE;
2264
2265   hv = priv->hadjust->value;
2266   vv = priv->vadjust->value;
2267
2268   if (vscroll_visible) {
2269     hildon_pannable_axis_scroll (area, priv->vadjust, &priv->vel_y, y,
2270                                  &priv->overshooting_y, &priv->overshot_dist_y,
2271                                  &priv->scroll_to_y, priv->vovershoot_max, &sy);
2272   } else {
2273     priv->vel_y = 0;
2274   }
2275
2276   if (hscroll_visible) {
2277     hildon_pannable_axis_scroll (area, priv->hadjust, &priv->vel_x, x,
2278                                  &priv->overshooting_x, &priv->overshot_dist_x,
2279                                  &priv->scroll_to_x, priv->hovershoot_max, &sx);
2280   } else {
2281     priv->vel_x = 0;
2282   }
2283
2284   if (hv != priv->hadjust->value)
2285     gtk_adjustment_value_changed (priv->hadjust);
2286
2287   if (vv != priv->vadjust->value)
2288     gtk_adjustment_value_changed (priv->vadjust);
2289
2290   /* If the scroll on a particular axis wasn't succesful, reset the
2291    * initial scroll position to the new mouse co-ordinate. This means
2292    * when you get to the top of the page, dragging down works immediately.
2293    */
2294   if (priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
2295       if (!sx) {
2296         priv->x = priv->ex;
2297       }
2298
2299       if (!sy) {
2300         priv->y = priv->ey;
2301       }
2302     }
2303 }
2304
2305 static gboolean
2306 hildon_pannable_area_timeout (HildonPannableArea * area)
2307 {
2308   HildonPannableAreaPrivate *priv = area->priv;
2309
2310   if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
2311     priv->idle_id = 0;
2312     g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
2313
2314     return FALSE;
2315   }
2316
2317   hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
2318
2319   gdk_window_process_updates (GTK_WIDGET (area)->window, FALSE);
2320
2321   if (!priv->button_pressed) {
2322     /* Decelerate gradually when pointer is raised */
2323     if ((!priv->overshot_dist_y) &&
2324         (!priv->overshot_dist_x)) {
2325
2326       /* in case we move to a specific point do not decelerate when arriving */
2327       if ((priv->scroll_to_x != -1)||(priv->scroll_to_y != -1)) {
2328
2329         if (ABS (priv->vel_x) >= 1.5) {
2330           priv->vel_x *= priv->decel;
2331         }
2332
2333         if (ABS (priv->vel_y) >= 1.5) {
2334           priv->vel_y *= priv->decel;
2335         }
2336
2337       } else {
2338         if ((!priv->low_friction_mode) ||
2339             ((priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) &&
2340              (ABS (priv->vel_x) < 0.8*priv->vmax)))
2341           priv->vel_x *= priv->decel;
2342
2343         if ((!priv->low_friction_mode) ||
2344             ((priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) &&
2345              (ABS (priv->vel_y) < 0.8*priv->vmax)))
2346           priv->vel_y *= priv->decel;
2347
2348         if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
2349           priv->vel_x = 0;
2350           priv->vel_y = 0;
2351           priv->idle_id = 0;
2352
2353           g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
2354
2355           return FALSE;
2356         }
2357       }
2358     }
2359   } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
2360     priv->idle_id = 0;
2361
2362     return FALSE;
2363   }
2364
2365   return TRUE;
2366 }
2367
2368 static void
2369 hildon_pannable_area_calculate_velocity (gdouble *vel,
2370                                          gdouble delta,
2371                                          gdouble dist,
2372                                          gdouble vmax,
2373                                          gdouble drag_inertia,
2374                                          gdouble force,
2375                                          guint sps)
2376 {
2377   gdouble rawvel;
2378
2379   if (ABS (dist) >= RATIO_TOLERANCE) {
2380     rawvel = (dist / ABS (delta)) * force;
2381     *vel = *vel * (1 - drag_inertia) +
2382       rawvel * drag_inertia;
2383     *vel = *vel > 0 ? MIN (*vel, vmax)
2384       : MAX (*vel, -1 * vmax);
2385   }
2386 }
2387
2388 static gboolean
2389 hildon_pannable_area_motion_event_scroll_timeout (HildonPannableArea *area)
2390 {
2391   HildonPannableAreaPrivate *priv = area->priv;
2392
2393   if ((priv->motion_x != 0)||(priv->motion_y != 0))
2394     hildon_pannable_area_scroll (area, priv->motion_x, priv->motion_y);
2395
2396   priv->motion_event_scroll_timeout = 0;
2397
2398   return FALSE;
2399 }
2400
2401 static void
2402 hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
2403                                           gdouble x, gdouble y)
2404 {
2405   HildonPannableAreaPrivate *priv = area->priv;
2406
2407   if (priv->motion_event_scroll_timeout) {
2408
2409     priv->motion_x += x;
2410     priv->motion_y += y;
2411
2412   } else {
2413
2414   /* we do not delay the first event but the next ones */
2415     hildon_pannable_area_scroll (area, x, y);
2416
2417     priv->motion_x = 0;
2418     priv->motion_y = 0;
2419
2420     priv->motion_event_scroll_timeout = gdk_threads_add_timeout_full
2421       (G_PRIORITY_HIGH_IDLE + 20,
2422        (gint) (1000.0 / (gdouble) MOTION_EVENTS_PER_SECOND),
2423        (GSourceFunc) hildon_pannable_area_motion_event_scroll_timeout, area, NULL);
2424   }
2425 }
2426
2427 static void
2428 hildon_pannable_area_check_move (HildonPannableArea *area,
2429                                  GdkEventMotion * event,
2430                                  gdouble *x,
2431                                  gdouble *y)
2432 {
2433   HildonPannableAreaPrivate *priv = area->priv;
2434
2435   if (priv->first_drag && (!priv->moved) &&
2436       ((ABS (*x) > (priv->panning_threshold))
2437        || (ABS (*y) > (priv->panning_threshold)))) {
2438     priv->moved = TRUE;
2439     *x = 0;
2440     *y = 0;
2441
2442     if (priv->first_drag) {
2443         gboolean vscroll_visible;
2444         gboolean hscroll_visible;
2445
2446       if (ABS (priv->iy - event->y) >=
2447           ABS (priv->ix - event->x)) {
2448
2449         g_signal_emit (area,
2450                        pannable_area_signals[VERTICAL_MOVEMENT],
2451                        0, (priv->iy > event->y) ?
2452                        HILDON_MOVEMENT_UP :
2453                        HILDON_MOVEMENT_DOWN,
2454                        (gdouble)priv->ix, (gdouble)priv->iy);
2455
2456         vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2457                    priv->vadjust->page_size);
2458
2459         if (!((vscroll_visible)&&
2460               (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT))) {
2461
2462           hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2463                              priv->hadjust->page_size);
2464
2465           /* even in case we do not have to move we check if this
2466              could be a fake horizontal movement */
2467           if (!((hscroll_visible)&&
2468                 (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ)) ||
2469               (ABS (priv->iy - event->y) -
2470                ABS (priv->ix - event->x) >= priv->direction_error_margin))
2471             priv->moved = FALSE;
2472         }
2473       } else {
2474
2475         g_signal_emit (area,
2476                        pannable_area_signals[HORIZONTAL_MOVEMENT],
2477                        0, (priv->ix > event->x) ?
2478                        HILDON_MOVEMENT_LEFT :
2479                        HILDON_MOVEMENT_RIGHT,
2480                        (gdouble)priv->ix, (gdouble)priv->iy);
2481
2482         hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2483                            priv->hadjust->page_size);
2484
2485         if (!((hscroll_visible)&&
2486               (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ))) {
2487
2488           vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2489                              priv->vadjust->page_size);
2490
2491           /* even in case we do not have to move we check if this
2492              could be a fake vertical movement */
2493           if (!((vscroll_visible) &&
2494                 (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT)) ||
2495               (ABS (priv->ix - event->x) -
2496                ABS (priv->iy - event->y) >= priv->direction_error_margin))
2497             priv->moved = FALSE;
2498         }
2499       }
2500
2501       if ((priv->moved)&&(priv->child)) {
2502         gint pos_x, pos_y;
2503
2504         pos_x = priv->cx + (event->x - priv->ix);
2505         pos_y = priv->cy + (event->y - priv->iy);
2506
2507         synth_crossing (priv->child, pos_x, pos_y, event->x_root,
2508                         event->y_root, event->time, FALSE);
2509       }
2510
2511       if (priv->moved) {
2512         gboolean result_val;
2513
2514         g_signal_emit (area,
2515                        pannable_area_signals[PANNING_STARTED],
2516                        0, &result_val);
2517
2518         priv->moved = !result_val;
2519       }
2520     }
2521
2522     priv->first_drag = FALSE;
2523
2524     if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
2525         (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
2526
2527       if (!priv->idle_id)
2528         priv->idle_id = gdk_threads_add_timeout_full
2529           (G_PRIORITY_HIGH_IDLE + 20,
2530            (gint)(1000.0 / (gdouble) priv->sps),
2531            (GSourceFunc)
2532            hildon_pannable_area_timeout, area, NULL);
2533     }
2534   }
2535 }
2536
2537 static void
2538 hildon_pannable_area_handle_move (HildonPannableArea *area,
2539                                   GdkEventMotion * event,
2540                                   gdouble *x,
2541                                   gdouble *y)
2542 {
2543   HildonPannableAreaPrivate *priv = area->priv;
2544   gdouble delta;
2545
2546   switch (priv->mode) {
2547   case HILDON_PANNABLE_AREA_MODE_PUSH:
2548     /* Scroll by the amount of pixels the cursor has moved
2549      * since the last motion event.
2550      */
2551     hildon_pannable_area_motion_event_scroll (area, *x, *y);
2552     priv->x = event->x;
2553     priv->y = event->y;
2554     break;
2555   case HILDON_PANNABLE_AREA_MODE_ACCEL:
2556     /* Set acceleration relative to the initial click */
2557     priv->ex = event->x;
2558     priv->ey = event->y;
2559     priv->vel_x = ((*x > 0) ? 1 : -1) *
2560       (((ABS (*x) /
2561          (gdouble) GTK_WIDGET (area)->allocation.width) *
2562         (priv->vmax - priv->vmin)) + priv->vmin);
2563     priv->vel_y = ((*y > 0) ? 1 : -1) *
2564       (((ABS (*y) /
2565          (gdouble) GTK_WIDGET (area)->allocation.height) *
2566         (priv->vmax - priv->vmin)) + priv->vmin);
2567     break;
2568   case HILDON_PANNABLE_AREA_MODE_AUTO:
2569
2570     delta = event->time - priv->last_time;
2571
2572     if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) {
2573       gdouble dist = event->y - priv->y;
2574
2575       hildon_pannable_area_calculate_velocity (&priv->vel_y,
2576                                                delta,
2577                                                dist,
2578                                                priv->vmax,
2579                                                priv->drag_inertia,
2580                                                priv->force,
2581                                                priv->sps);
2582     } else {
2583       *y = 0;
2584       priv->vel_y = 0;
2585     }
2586
2587     if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) {
2588       gdouble dist = event->x - priv->x;
2589
2590       hildon_pannable_area_calculate_velocity (&priv->vel_x,
2591                                                delta,
2592                                                dist,
2593                                                priv->vmax,
2594                                                priv->drag_inertia,
2595                                                priv->force,
2596                                                priv->sps);
2597     } else {
2598       *x = 0;
2599       priv->vel_x = 0;
2600     }
2601
2602     hildon_pannable_area_motion_event_scroll (area, *x, *y);
2603
2604     if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ)
2605       priv->x = event->x;
2606     if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT)
2607       priv->y = event->y;
2608
2609     break;
2610   default:
2611     break;
2612   }
2613 }
2614
2615 static gboolean
2616 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
2617                                        GdkEventMotion * event)
2618 {
2619   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
2620   HildonPannableAreaPrivate *priv = area->priv;
2621   gdouble x, y;
2622
2623   if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
2624     return TRUE;
2625
2626   if ((!priv->enabled) || (!priv->button_pressed) ||
2627       ((event->time == priv->last_time) && (priv->last_type == 2))) {
2628     gdk_window_get_pointer (widget->window, NULL, NULL, 0);
2629     return TRUE;
2630   }
2631
2632   if (priv->last_type == 1) {
2633     priv->first_drag = TRUE;
2634   }
2635
2636   x = event->x - priv->x;
2637   y = event->y - priv->y;
2638
2639   if (!priv->moved) {
2640     hildon_pannable_area_check_move (area, event, &x, &y);
2641   }
2642
2643   if (priv->moved) {
2644     hildon_pannable_area_handle_move (area, event, &x, &y);
2645   } else if (priv->child) {
2646     gboolean in;
2647     gint pos_x, pos_y;
2648
2649     pos_x = priv->cx + (event->x - priv->ix);
2650     pos_y = priv->cy + (event->y - priv->iy);
2651
2652     in = (((0 <= pos_x)&&(priv->child_width >= pos_x)) &&
2653           ((0 <= pos_y)&&(priv->child_height >= pos_y)));
2654
2655     if (((!priv->last_in)&&in)||((priv->last_in)&&(!in))) {
2656
2657       synth_crossing (priv->child, pos_x, pos_y, event->x_root,
2658                       event->y_root, event->time, in);
2659
2660       priv->last_in = in;
2661     }
2662   }
2663
2664   priv->last_time = event->time;
2665   priv->last_type = 2;
2666
2667   if (priv->child) {
2668     /* Send motion notify to child */
2669     event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
2670     /* remove the reference we added with the copy */
2671     g_object_unref (priv->event_window);
2672     event->x = priv->cx + (event->x - priv->ix);
2673     event->y = priv->cy + (event->y - priv->iy);
2674     event->window = g_object_ref (priv->child);
2675     gdk_event_put ((GdkEvent *) event);
2676     gdk_event_free ((GdkEvent *) event);
2677   }
2678
2679   gdk_window_get_pointer (widget->window, NULL, NULL, 0);
2680
2681   return TRUE;
2682 }
2683
2684 static gboolean
2685 hildon_pannable_leave_notify_event (GtkWidget *widget,
2686                                     GdkEventCrossing *event)
2687 {
2688   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
2689   HildonPannableAreaPrivate *priv = area->priv;
2690
2691   if ((priv->child)&&(priv->last_in)) {
2692     priv->last_in = FALSE;
2693
2694     synth_crossing (priv->child, 0, 0, event->x_root,
2695                     event->y_root, event->time, FALSE);
2696   }
2697
2698   return FALSE;
2699 }
2700
2701 static gboolean
2702 hildon_pannable_area_button_release_cb (GtkWidget * widget,
2703                                         GdkEventButton * event)
2704 {
2705   HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
2706   HildonPannableAreaPrivate *priv = area->priv;
2707   gint x, y;
2708   gdouble dx, dy;
2709   GdkWindow *child;
2710   gboolean force_fast = TRUE;
2711
2712   if  (((event->time == priv->last_time) && (priv->last_type == 3))
2713        || (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
2714        || (!priv->button_pressed) || (!priv->enabled) || (event->button != 1))
2715     return TRUE;
2716
2717   /* if last event was a motion-notify we have to check the movement
2718      and launch the animation */
2719   if (priv->last_type == 2) {
2720
2721     dx = event->x - priv->x;
2722     dy = event->y - priv->y;
2723
2724     hildon_pannable_area_check_move (area, (GdkEventMotion *) event, &dx, &dy);
2725
2726     if (priv->moved) {
2727       gdouble delta = event->time - priv->last_time;
2728
2729       hildon_pannable_area_handle_move (area, (GdkEventMotion *) event, &dx, &dy);
2730
2731       /* move all the way to the last position now */
2732       if (priv->motion_event_scroll_timeout) {
2733         g_source_remove (priv->motion_event_scroll_timeout);
2734         hildon_pannable_area_motion_event_scroll_timeout (HILDON_PANNABLE_AREA (widget));
2735         priv->motion_x = 0;
2736         priv->motion_y = 0;
2737       }
2738
2739       if ((ABS (dx) < 4.0) && (delta >= CURSOR_STOPPED_TIMEOUT))
2740         priv->vel_x = 0;
2741
2742       if ((ABS (dy) < 4.0) && (delta >= CURSOR_STOPPED_TIMEOUT))
2743         priv->vel_y = 0;
2744     }
2745   }
2746
2747   /* If overshoot has been initiated with a finger down, on release set max speed */
2748   if (priv->overshot_dist_y != 0) {
2749     priv->overshooting_y = priv->bounce_steps; /* Hack to stop a bounce in the finger down case */
2750     priv->vel_y = priv->overshot_dist_y * 0.9;
2751   }
2752
2753   if (priv->overshot_dist_x != 0) {
2754     priv->overshooting_x = priv->bounce_steps; /* Hack to stop a bounce in the finger down case */
2755     priv->vel_x = priv->overshot_dist_x * 0.9;
2756   }
2757
2758   priv->button_pressed = FALSE;
2759
2760   /* if widget was moving fast in the panning, increase speed even more */
2761   if ((event->time - priv->last_press_time < 125) &&
2762       ((ABS (priv->old_vel_x) > priv->vmin) ||
2763        (ABS (priv->old_vel_y) > priv->vmin)) &&
2764       ((ABS (priv->old_vel_x) > 40) ||
2765        (ABS (priv->old_vel_y) > 40)))
2766     {
2767       gint symbol = 0;
2768
2769       if (priv->vel_x != 0)
2770         symbol = ((priv->vel_x * priv->old_vel_x) > 0) ? 1 : -1;
2771
2772       priv->vel_x = symbol *
2773         (priv->old_vel_x + ((priv->old_vel_x > 0) ? priv->vmax : -priv->vmax));
2774
2775       symbol = 0;
2776
2777       if (priv->vel_y != 0)
2778         symbol = ((priv->vel_y * priv->old_vel_y) > 0) ? 1 : -1;
2779
2780       priv->vel_y = symbol *
2781         (priv->old_vel_y + ((priv->old_vel_y > 0) ? priv->vmax : -priv->vmax));
2782
2783       force_fast = FALSE;
2784     }
2785
2786   if  ((ABS (priv->vel_y) >= priv->vmin) ||
2787        (ABS (priv->vel_x) >= priv->vmin)) {
2788
2789     /* we have to move because we are in overshooting position*/
2790     if (!priv->moved) {
2791       gboolean result_val;
2792
2793       g_signal_emit (area,
2794                      pannable_area_signals[PANNING_STARTED],
2795                      0, &result_val);
2796     }
2797
2798     priv->scroll_indicator_alpha = 1.0;
2799
2800     if (force_fast) {
2801       if (ABS (priv->vel_x) > MAX_SPEED_THRESHOLD)
2802         priv->vel_x = (priv->vel_x > 0) ? priv->vmax : -priv->vmax;
2803
2804       if (ABS (priv->vel_y) > MAX_SPEED_THRESHOLD)
2805         priv->vel_y = (priv->vel_y > 0) ? priv->vmax : -priv->vmax;
2806     }
2807
2808     if (!priv->idle_id)
2809       priv->idle_id = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
2810                                                     (gint) (1000.0 / (gdouble) priv->sps),
2811                                                     (GSourceFunc) hildon_pannable_area_timeout,
2812                                                     widget, NULL);
2813   } else {
2814     if (priv->center_on_child_focus_pending) {
2815       hildon_pannable_area_center_on_child_focus (area);
2816     }
2817
2818     if (priv->moved)
2819       g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
2820   }
2821
2822   area->priv->center_on_child_focus_pending = FALSE;
2823
2824   priv->scroll_indicator_event_interrupt = 0;
2825   priv->scroll_delay_counter = priv->scrollbar_fade_delay;
2826
2827   hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
2828                                             priv->scroll_indicator_alpha);
2829
2830   priv->last_time = event->time;
2831   priv->last_type = 3;
2832
2833   if (!priv->child) {
2834     priv->moved = FALSE;
2835     return TRUE;
2836   }
2837
2838   child =
2839     hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
2840                                       event->x, event->y, &x, &y, GDK_BUTTON_RELEASE_MASK);
2841
2842   event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
2843   /* remove the reference we added with the copy */
2844   g_object_unref (priv->event_window);
2845   event->x = x;
2846   event->y = y;
2847
2848   /* Leave the widget if we've moved - This doesn't break selection,
2849    * but stops buttons from being clicked.
2850    */
2851   if ((child != priv->child) || (priv->moved)) {
2852     /* Send synthetic leave event */
2853     synth_crossing (priv->child, x, y, event->x_root,
2854                     event->y_root, event->time, FALSE);
2855     /* insure no click will happen for widgets that do not handle
2856        leave-notify */
2857     event->x = -16384;
2858     event->y = -16384;
2859     /* Send synthetic button release event */
2860     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
2861     gdk_event_put ((GdkEvent *) event);
2862   } else {
2863     /* Send synthetic button release event */
2864     ((GdkEventAny *) event)->window = g_object_ref (child);
2865     gdk_event_put ((GdkEvent *) event);
2866     /* Send synthetic leave event */
2867     synth_crossing (priv->child, x, y, event->x_root,
2868                     event->y_root, event->time, FALSE);
2869   }
2870   g_object_remove_weak_pointer ((GObject *) priv->child,
2871                                 (gpointer) & priv->child);
2872
2873   priv->moved = FALSE;
2874   gdk_event_free ((GdkEvent *) event);
2875
2876   return TRUE;
2877 }
2878
2879 /* utility event handler */
2880 static gboolean
2881 hildon_pannable_area_scroll_cb (GtkWidget *widget,
2882                                 GdkEventScroll *event)
2883 {
2884   GtkAdjustment *adj = NULL;
2885   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
2886
2887   if ((!priv->enabled) ||
2888       (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
2889     return TRUE;
2890
2891   priv->scroll_indicator_event_interrupt = 0;
2892   priv->scroll_delay_counter = priv->scrollbar_fade_delay + 20;
2893
2894   hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget), 1.0);
2895
2896   /* Stop inertial scrolling */
2897   if (priv->idle_id) {
2898     priv->vel_x = 0.0;
2899     priv->vel_y = 0.0;
2900     priv->overshooting_x = 0;
2901     priv->overshooting_y = 0;
2902
2903     if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
2904       priv->overshot_dist_x = 0;
2905       priv->overshot_dist_y = 0;
2906
2907       gtk_widget_queue_resize (GTK_WIDGET (widget));
2908     }
2909
2910     g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
2911
2912     g_source_remove (priv->idle_id);
2913     priv->idle_id = 0;
2914   }
2915
2916   if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN)
2917     adj = priv->vadjust;
2918   else
2919     adj = priv->hadjust;
2920
2921   if (adj)
2922     {
2923       gdouble delta, new_value;
2924
2925       /* from gtkrange.c calculate delta*/
2926       delta = pow (adj->page_size, 2.0 / 3.0);
2927
2928       if (event->direction == GDK_SCROLL_UP ||
2929           event->direction == GDK_SCROLL_LEFT)
2930         delta = - delta;
2931
2932       new_value = CLAMP (adj->value + delta, adj->lower, adj->upper - adj->page_size);
2933
2934       gtk_adjustment_set_value (adj, new_value);
2935     }
2936
2937   return TRUE;
2938 }
2939
2940 static void
2941 hildon_pannable_area_child_mapped (GtkWidget *widget,
2942                                    GdkEvent  *event,
2943                                    gpointer user_data)
2944 {
2945   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (user_data)->priv;
2946
2947   if (priv->event_window != NULL && priv->enabled)
2948     gdk_window_raise (priv->event_window);
2949 }
2950
2951 static void
2952 hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
2953 {
2954   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (container)->priv;
2955
2956   g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == NULL);
2957
2958   gtk_widget_set_parent (child, GTK_WIDGET (container));
2959   GTK_BIN (container)->child = child;
2960
2961   g_signal_connect_after (child, "map-event",
2962                           G_CALLBACK (hildon_pannable_area_child_mapped),
2963                           container);
2964
2965   if (!gtk_widget_set_scroll_adjustments (child, priv->hadjust, priv->vadjust)) {
2966     g_warning ("%s: cannot add non scrollable widget, "
2967                "wrap it in a viewport", __FUNCTION__);
2968   }
2969 }
2970
2971 /* call this function if you are not panning */
2972 static void
2973 hildon_pannable_area_center_on_child_focus      (HildonPannableArea *area)
2974 {
2975   GtkWidget *focused_child = NULL;
2976   GtkWidget *window = NULL;
2977
2978   window = gtk_widget_get_toplevel (GTK_WIDGET (area));
2979
2980   if (GTK_WIDGET_TOPLEVEL (window)) {
2981     focused_child = gtk_window_get_focus (GTK_WINDOW (window));
2982   }
2983
2984   if (focused_child) {
2985     hildon_pannable_area_scroll_to_child (area, focused_child);
2986   }
2987 }
2988
2989 static void
2990 hildon_pannable_area_set_focus_child            (GtkContainer     *container,
2991                                                  GtkWidget        *child)
2992 {
2993   HildonPannableArea *area = HILDON_PANNABLE_AREA (container);
2994
2995   if (!area->priv->center_on_child_focus) {
2996     return;
2997   }
2998
2999   if (GTK_IS_WIDGET (child)) {
3000     area->priv->center_on_child_focus_pending = TRUE;
3001   }
3002 }
3003
3004 static void
3005 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
3006 {
3007   g_return_if_fail (HILDON_IS_PANNABLE_AREA (container));
3008   g_return_if_fail (child != NULL);
3009   g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == child);
3010
3011   gtk_widget_set_scroll_adjustments (child, NULL, NULL);
3012
3013   g_signal_handlers_disconnect_by_func (child,
3014                                         hildon_pannable_area_child_mapped,
3015                                         container);
3016
3017   /* chain parent class handler to remove child */
3018   GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->remove (container, child);
3019 }
3020
3021 /*
3022  * This method calculates a factor necessary to determine the initial distance
3023  * to jump in hildon_pannable_area_scroll_to(). For fixed time and frames per
3024  * second, we know in how many frames 'n' we need to reach the destination
3025  * point. We know that, for a distance d,
3026  *
3027  *   d = d_0 + d_1 + ... + d_n
3028  *
3029  * where d_i is the distance travelled in the i-th frame and decel_factor is
3030  * the deceleration factor. This can be rewritten as
3031  *
3032  *   d = d_0 + (d_0 * decel_factor) + ... + (d_n-1 * decel_factor),
3033  *
3034  * since the distance travelled on each frame is the distance travelled in the
3035  * previous frame reduced by the deceleration factor. Reducing this and
3036  * factoring d_0 out, we get
3037  *
3038  *   d = d_0 (1 + decel_factor + ... + decel_factor^(n-1)).
3039  *
3040  * Since the sum is independent of the distance to be travelled, we can define
3041  * vel_factor as
3042  *
3043  *   vel_factor = 1 + decel_factor + ... + decel_factor^(n-1).
3044  *
3045  * That's the gem we calculate in this method.
3046  */
3047 static void
3048 hildon_pannable_calculate_vel_factor (HildonPannableArea * self)
3049 {
3050   HildonPannableAreaPrivate *priv = self->priv;
3051   gfloat fct = 1;
3052   gfloat fct_i = 1;
3053   gint i, n;
3054
3055   n = ceil (priv->sps * priv->scroll_time);
3056
3057   for (i = 1; i < n && fct_i >= RATIO_TOLERANCE; i++) {
3058     fct_i *= priv->decel;
3059     fct += fct_i;
3060   }
3061
3062   priv->vel_factor = fct;
3063 }
3064
3065 /**
3066  * hildon_pannable_area_new:
3067  *
3068  * Create a new pannable area widget
3069  *
3070  * Returns: the newly created #HildonPannableArea
3071  *
3072  * Since: 2.2
3073  */
3074
3075 GtkWidget *
3076 hildon_pannable_area_new (void)
3077 {
3078   return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
3079 }
3080
3081 /**
3082  * hildon_pannable_area_new_full:
3083  * @mode: #HildonPannableAreaMode
3084  * @enabled: Value for the enabled property
3085  * @vel_min: Value for the velocity-min property
3086  * @vel_max: Value for the velocity-max property
3087  * @decel: Value for the deceleration property
3088  * @sps: Value for the sps property
3089  *
3090  * Create a new #HildonPannableArea widget and set various properties
3091  *
3092  * returns: the newly create #HildonPannableArea
3093  *
3094  * Since: 2.2
3095  */
3096
3097 GtkWidget *
3098 hildon_pannable_area_new_full (gint mode, gboolean enabled,
3099                                gdouble vel_min, gdouble vel_max,
3100                                gdouble decel, guint sps)
3101 {
3102   return g_object_new (HILDON_TYPE_PANNABLE_AREA,
3103                        "mode", mode,
3104                        "enabled", enabled,
3105                        "velocity_min", vel_min,
3106                        "velocity_max", vel_max,
3107                        "deceleration", decel, "sps", sps, NULL);
3108 }
3109
3110 /**
3111  * hildon_pannable_area_add_with_viewport:
3112  * @area: A #HildonPannableArea
3113  * @child: Child widget to add to the viewport
3114  *
3115  * Convenience function used to add a child to a #GtkViewport, and add the
3116  * viewport to the scrolled window.
3117  *
3118  * Since: 2.2
3119  */
3120
3121 void
3122 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
3123                                         GtkWidget * child)
3124 {
3125   GtkBin *bin;
3126   GtkWidget *viewport;
3127
3128   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3129   g_return_if_fail (GTK_IS_WIDGET (child));
3130   g_return_if_fail (child->parent == NULL);
3131
3132   bin = GTK_BIN (area);
3133
3134   if (bin->child != NULL)
3135     {
3136       g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
3137       g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
3138
3139       viewport = bin->child;
3140     }
3141   else
3142     {
3143       HildonPannableAreaPrivate *priv = area->priv;
3144
3145       viewport = gtk_viewport_new (priv->hadjust,
3146                                    priv->vadjust);
3147       gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
3148       gtk_container_add (GTK_CONTAINER (area), viewport);
3149     }
3150
3151   gtk_widget_show (viewport);
3152   gtk_container_add (GTK_CONTAINER (viewport), child);
3153 }
3154
3155 /**
3156  * hildon_pannable_area_scroll_to:
3157  * @area: A #HildonPannableArea.
3158  * @x: The x coordinate of the destination point or -1 to ignore this axis.
3159  * @y: The y coordinate of the destination point or -1 to ignore this axis.
3160  *
3161  * Smoothly scrolls @area to ensure that (@x, @y) is a visible point
3162  * on the widget. To move in only one coordinate, you must set the other one
3163  * to -1. Notice that, in %HILDON_PANNABLE_AREA_MODE_PUSH mode, this function
3164  * works just like hildon_pannable_area_jump_to().
3165  *
3166  * This function is useful if you need to present the user with a particular
3167  * element inside a scrollable widget, like #GtkTreeView. For instance,
3168  * the following example shows how to scroll inside a #GtkTreeView to
3169  * make visible an item, indicated by the #GtkTreeIter @iter.
3170  *
3171  * <example>
3172  * <programlisting>
3173  *  GtkTreePath *path;
3174  *  GdkRectangle *rect;
3175  *  <!-- -->
3176  *  path = gtk_tree_model_get_path (model, &amp;iter);
3177  *  gtk_tree_view_get_background_area (GTK_TREE_VIEW (treeview),
3178  *                                     path, NULL, &amp;rect);
3179  *  gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (treeview),
3180  *                                                   0, rect.y, NULL, &amp;y);
3181  *  hildon_pannable_area_scroll_to (panarea, -1, y);
3182  *  gtk_tree_path_free (path);
3183  * </programlisting>
3184  * </example>
3185  *
3186  * If you want to present a child widget in simpler scenarios,
3187  * use hildon_pannable_area_scroll_to_child() instead.
3188  *
3189  * There is a precondition to this function: the widget must be
3190  * already realized. Check the hildon_pannable_area_jump_to_child() for
3191  * more tips regarding how to call this function during
3192  * initialization.
3193  *
3194  * Since: 2.2
3195  **/
3196 void
3197 hildon_pannable_area_scroll_to (HildonPannableArea *area,
3198                                 const gint x, const gint y)
3199 {
3200   HildonPannableAreaPrivate *priv;
3201   gint width, height;
3202   gint dist_x, dist_y;
3203   gboolean hscroll_visible, vscroll_visible;
3204
3205   g_return_if_fail (GTK_WIDGET_REALIZED (area));
3206   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3207
3208   priv = area->priv;
3209
3210   vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
3211              priv->vadjust->page_size);
3212   hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
3213              priv->hadjust->page_size);
3214
3215   if (((!vscroll_visible)&&(!hscroll_visible)) ||
3216       (x == -1 && y == -1)) {
3217     return;
3218   }
3219
3220   if (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)
3221     hildon_pannable_area_jump_to (area, x, y);
3222
3223   width = priv->hadjust->upper - priv->hadjust->lower;
3224   height = priv->vadjust->upper - priv->vadjust->lower;
3225
3226   g_return_if_fail (x < width || y < height);
3227
3228   if ((x > -1)&&(hscroll_visible)) {
3229     priv->scroll_to_x = CLAMP (x - priv->hadjust->page_size/2,
3230                                priv->hadjust->lower,
3231                                priv->hadjust->upper - priv->hadjust->page_size);
3232     dist_x = priv->scroll_to_x - priv->hadjust->value;
3233     if (dist_x == 0) {
3234       priv->scroll_to_x = -1;
3235     } else {
3236       priv->vel_x = - dist_x/priv->vel_factor;
3237     }
3238   } else {
3239     priv->scroll_to_x = -1;
3240   }
3241
3242   if ((y > -1)&&(vscroll_visible)) {
3243     priv->scroll_to_y = CLAMP (y - priv->vadjust->page_size/2,
3244                                priv->vadjust->lower,
3245                                priv->vadjust->upper - priv->vadjust->page_size);
3246     dist_y = priv->scroll_to_y - priv->vadjust->value;
3247     if (dist_y == 0) {
3248       priv->scroll_to_y = -1;
3249     } else {
3250       priv->vel_y = - dist_y/priv->vel_factor;
3251     }
3252   } else {
3253     priv->scroll_to_y = y;
3254   }
3255
3256   if ((priv->scroll_to_y == -1) && (priv->scroll_to_x == -1)) {
3257     return;
3258   }
3259
3260   hildon_pannable_area_launch_fade_timeout (area, 1.0);
3261
3262   if (!priv->idle_id)
3263     priv->idle_id = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
3264                                                   (gint) (1000.0 / (gdouble) priv->sps),
3265                                                   (GSourceFunc) hildon_pannable_area_timeout,
3266                                                   area, NULL);
3267 }
3268
3269 /**
3270  * hildon_pannable_area_jump_to:
3271  * @area: A #HildonPannableArea.
3272  * @x: The x coordinate of the destination point or -1 to ignore this axis.
3273  * @y: The y coordinate of the destination point or -1 to ignore this axis.
3274  *
3275  * Jumps the position of @area to ensure that (@x, @y) is a visible
3276  * point in the widget. In order to move in only one coordinate, you
3277  * must set the other one to -1. See hildon_pannable_area_scroll_to()
3278  * function for an example of how to calculate the position of
3279  * children in scrollable widgets like #GtkTreeview.
3280  *
3281  * There is a precondition to this function: the widget must be
3282  * already realized. Check the hildon_pannable_area_jump_to_child() for
3283  * more tips regarding how to call this function during
3284  * initialization.
3285  *
3286  * Since: 2.2
3287  **/
3288 void
3289 hildon_pannable_area_jump_to (HildonPannableArea *area,
3290                               const gint x, const gint y)
3291 {
3292   HildonPannableAreaPrivate *priv;
3293   gint width, height;
3294   gdouble hv, vv;
3295
3296   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3297   g_return_if_fail (GTK_WIDGET_REALIZED (area));
3298   g_return_if_fail (x >= -1 && y >= -1);
3299
3300   if (x == -1 && y == -1) {
3301     return;
3302   }
3303
3304   priv = area->priv;
3305
3306   width = priv->hadjust->upper - priv->hadjust->lower;
3307   height = priv->vadjust->upper - priv->vadjust->lower;
3308
3309   g_return_if_fail (x < width || y < height);
3310
3311   hv = priv->hadjust->value;
3312   vv = priv->vadjust->value;
3313
3314   if (x != -1) {
3315     gdouble jump_to = x - priv->hadjust->page_size/2;
3316
3317     priv->hadjust->value = CLAMP (jump_to,
3318                                   priv->hadjust->lower,
3319                                   priv->hadjust->upper -
3320                                   priv->hadjust->page_size);
3321   }
3322
3323   if (y != -1) {
3324     gdouble jump_to =  y - priv->vadjust->page_size/2;
3325
3326     priv->vadjust->value = CLAMP (jump_to,
3327                                   priv->vadjust->lower,
3328                                   priv->vadjust->upper -
3329                                   priv->vadjust->page_size);
3330   }
3331
3332   if (hv != priv->hadjust->value)
3333     gtk_adjustment_value_changed (priv->hadjust);
3334
3335   if (vv != priv->vadjust->value)
3336     gtk_adjustment_value_changed (priv->vadjust);
3337
3338   priv->scroll_indicator_alpha = 1.0;
3339
3340   if (priv->scroll_indicator_timeout) {
3341     g_source_remove (priv->scroll_indicator_timeout);
3342     priv->scroll_indicator_timeout = 0;
3343   }
3344
3345   if (priv->idle_id) {
3346     priv->vel_x = 0.0;
3347     priv->vel_y = 0.0;
3348     priv->overshooting_x = 0;
3349     priv->overshooting_y = 0;
3350
3351     if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
3352       priv->overshot_dist_x = 0;
3353       priv->overshot_dist_y = 0;
3354
3355       gtk_widget_queue_resize (GTK_WIDGET (area));
3356     }
3357
3358     g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
3359     g_source_remove (priv->idle_id);
3360     priv->idle_id = 0;
3361   }
3362 }
3363
3364 /**
3365  * hildon_pannable_area_scroll_to_child:
3366  * @area: A #HildonPannableArea.
3367  * @child: A #GtkWidget, descendant of @area.
3368  *
3369  * Smoothly scrolls until @child is visible inside @area. @child must
3370  * be a descendant of @area. If you need to scroll inside a scrollable
3371  * widget, e.g., #GtkTreeview, see hildon_pannable_area_scroll_to().
3372  *
3373  * There is a precondition to this function: the widget must be
3374  * already realized. Check the hildon_pannable_area_jump_to_child() for
3375  * more tips regarding how to call this function during
3376  * initialization.
3377  *
3378  * Since: 2.2
3379  **/
3380 void
3381 hildon_pannable_area_scroll_to_child (HildonPannableArea *area, GtkWidget *child)
3382 {
3383   GtkWidget *bin_child;
3384   gint x, y;
3385
3386   g_return_if_fail (GTK_WIDGET_REALIZED (area));
3387   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3388   g_return_if_fail (GTK_IS_WIDGET (child));
3389   g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
3390
3391   if (GTK_BIN (area)->child == NULL)
3392     return;
3393
3394   /* We need to get to check the child of the inside the area */
3395   bin_child = GTK_BIN (area)->child;
3396
3397   /* we check if we added a viewport */
3398   if (GTK_IS_VIEWPORT (bin_child)) {
3399     bin_child = GTK_BIN (bin_child)->child;
3400   }
3401
3402   if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
3403     hildon_pannable_area_scroll_to (area, x, y);
3404 }
3405
3406 /**
3407  * hildon_pannable_area_jump_to_child:
3408  * @area: A #HildonPannableArea.
3409  * @child: A #GtkWidget, descendant of @area.
3410  *
3411  * Jumps to make sure @child is visible inside @area. @child must
3412  * be a descendant of @area. If you want to move inside a scrollable
3413  * widget, like, #GtkTreeview, see hildon_pannable_area_scroll_to().
3414  *
3415  * There is a precondition to this function: the widget must be
3416  * already realized. You can control if the widget is ready with the
3417  * GTK_WIDGET_REALIZED macro. If you want to call this function during
3418  * the initialization process of the widget do it inside a callback to
3419  * the ::realize signal, using g_signal_connect_after() function.
3420  *
3421  * Since: 2.2
3422  **/
3423 void
3424 hildon_pannable_area_jump_to_child (HildonPannableArea *area, GtkWidget *child)
3425 {
3426   GtkWidget *bin_child;
3427   gint x, y;
3428
3429   g_return_if_fail (GTK_WIDGET_REALIZED (area));
3430   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3431   g_return_if_fail (GTK_IS_WIDGET (child));
3432   g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
3433
3434   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
3435     return;
3436
3437   /* We need to get to check the child of the inside the area */
3438   bin_child = gtk_bin_get_child (GTK_BIN (area));
3439
3440   /* we check if we added a viewport */
3441   if (GTK_IS_VIEWPORT (bin_child)) {
3442     bin_child = gtk_bin_get_child (GTK_BIN (bin_child));
3443   }
3444
3445   if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
3446     hildon_pannable_area_jump_to (area, x, y);
3447 }
3448
3449 /**
3450  * hildon_pannable_get_child_widget_at:
3451  * @area: A #HildonPannableArea.
3452  * @x: horizontal coordinate of the point
3453  * @y: vertical coordinate of the point
3454  *
3455  * Get the widget at the point (x, y) inside the pannable area. In
3456  * case no widget found it returns NULL.
3457  *
3458  * returns: the #GtkWidget if we find a widget, NULL in any other case
3459  *
3460  * Since: 2.2
3461  **/
3462 GtkWidget*
3463 hildon_pannable_get_child_widget_at (HildonPannableArea *area,
3464                                      gdouble x, gdouble y)
3465 {
3466   GdkWindow *window = NULL;
3467   GtkWidget *child_widget = NULL;
3468
3469   window = hildon_pannable_area_get_topmost
3470     (gtk_bin_get_child (GTK_BIN (area))->window,
3471      x, y, NULL, NULL, GDK_ALL_EVENTS_MASK);
3472
3473   gdk_window_get_user_data (window, (gpointer) &child_widget);
3474
3475   return child_widget;
3476 }
3477
3478
3479 /**
3480  * hildon_pannable_area_get_hadjustment:
3481  * @area: A #HildonPannableArea.
3482  *
3483  * Returns the horizontal adjustment. This adjustment is the internal
3484  * widget adjustment used to control the animations. Do not modify it
3485  * directly to change the position of the pannable, to do that use the
3486  * pannable API. If you modify the object directly it could cause
3487  * artifacts in the animations.
3488  *
3489  * returns: The horizontal #GtkAdjustment
3490  *
3491  * Since: 2.2
3492  **/
3493 GtkAdjustment*
3494 hildon_pannable_area_get_hadjustment            (HildonPannableArea *area)
3495 {
3496
3497   g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), NULL);
3498
3499   return area->priv->hadjust;
3500 }
3501
3502 /**
3503  * hildon_pannable_area_get_vadjustment:
3504  * @area: A #HildonPannableArea.
3505  *
3506  * Returns the vertical adjustment. This adjustment is the internal
3507  * widget adjustment used to control the animations. Do not modify it
3508  * directly to change the position of the pannable, to do that use the
3509  * pannable API. If you modify the object directly it could cause
3510  * artifacts in the animations.
3511  *
3512  * returns: The vertical #GtkAdjustment
3513  *
3514  * Since: 2.2
3515  **/
3516 GtkAdjustment*
3517 hildon_pannable_area_get_vadjustment            (HildonPannableArea *area)
3518 {
3519   g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), NULL);
3520
3521   return area->priv->vadjust;
3522 }
3523
3524
3525 /**
3526  * hildon_pannable_area_get_size_request_policy:
3527  * @area: A #HildonPannableArea.
3528  *
3529  * This function returns the current size request policy of the
3530  * widget. That policy controls the way the size_request is done in
3531  * the pannable area. Check
3532  * hildon_pannable_area_set_size_request_policy() for a more detailed
3533  * explanation.
3534  *
3535  * returns: the policy is currently being used in the widget
3536  * #HildonSizeRequestPolicy.
3537  *
3538  * Since: 2.2
3539  **/
3540 HildonSizeRequestPolicy
3541 hildon_pannable_area_get_size_request_policy (HildonPannableArea *area)
3542 {
3543   HildonPannableAreaPrivate *priv;
3544
3545   g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), FALSE);
3546
3547   priv = area->priv;
3548
3549   return priv->size_request_policy;
3550 }
3551
3552 /**
3553  * hildon_pannable_area_set_size_request_policy:
3554  * @area: A #HildonPannableArea.
3555  * @size_request_policy: One of the allowed #HildonSizeRequestPolicy
3556  *
3557  * This function sets the pannable area size request policy. That
3558  * policy controls the way the size_request is done in the pannable
3559  * area. Pannable can use the size request of its children
3560  * (#HILDON_SIZE_REQUEST_CHILDREN) or the minimum size required for
3561  * the area itself (#HILDON_SIZE_REQUEST_MINIMUM), the latter is the
3562  * default. Recall this size depends on the scrolling policy you are
3563  * requesting to the pannable area, if you set #GTK_POLICY_NEVER this
3564  * parameter will not have any effect with
3565  * #HILDON_SIZE_REQUEST_MINIMUM set.
3566  *
3567  * Since: 2.2
3568  *
3569  * Deprecated: This method and the policy request is deprecated, DO
3570  * NOT use it in future code, the only policy properly supported in
3571  * gtk+ nowadays is the minimum size. Use #gtk_window_set_default_size
3572  * or #gtk_window_set_geometry_hints with the proper size in your case
3573  * to define the height of your dialogs.
3574  **/
3575 void
3576 hildon_pannable_area_set_size_request_policy (HildonPannableArea *area,
3577                                               HildonSizeRequestPolicy size_request_policy)
3578 {
3579   HildonPannableAreaPrivate *priv;
3580
3581   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3582
3583   priv = area->priv;
3584
3585   if (priv->size_request_policy == size_request_policy)
3586     return;
3587
3588   priv->size_request_policy = size_request_policy;
3589
3590   gtk_widget_queue_resize (GTK_WIDGET (area));
3591
3592   g_object_notify (G_OBJECT (area), "size-request-policy");
3593 }
3594
3595 /**
3596  * hildon_pannable_area_get_center_on_child_focus
3597  * @area: A #HildonPannableArea
3598  *
3599  * Gets the @area #HildonPannableArea:center-on-child-focus property
3600  * value.
3601  *
3602  * See #HildonPannableArea:center-on-child-focus for more information.
3603  *
3604  * Returns: the @area #HildonPannableArea:center-on-child-focus value
3605  *
3606  * Since: 2.2
3607  **/
3608 gboolean
3609 hildon_pannable_area_get_center_on_child_focus  (HildonPannableArea *area)
3610 {
3611   g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), FALSE);
3612
3613   return area->priv->center_on_child_focus;
3614 }
3615
3616 /**
3617  * hildon_pannable_area_set_center_on_child_focus
3618  * @area: A #HildonPannableArea
3619  * @value: the new value
3620  *
3621  * Sets the @area #HildonPannableArea:center-on-child-focus property
3622  * to @value.
3623  *
3624  * See #HildonPannableArea:center-on-child-focus for more information.
3625  *
3626  * Since: 2.2
3627  **/
3628 void
3629 hildon_pannable_area_set_center_on_child_focus  (HildonPannableArea *area,
3630                                                  gboolean value)
3631 {
3632   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3633
3634   area->priv->center_on_child_focus = value;
3635 }