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