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