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