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