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