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