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