2 * This file is a part of hildon
4 * Copyright (C) 2008 Nokia Corporation, all rights reserved.
6 * Contact: Rodrigo Novo <rodrigo.novo@nokia.com>
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.
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.
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.
25 * SECTION: hildon-pannable-area
26 * @short_description: A scrolling widget designed for touch screens
27 * @see_also: #GtkScrolledWindow
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.
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
41 #undef HILDON_DISABLE_DEPRECATED
44 #if USE_CAIRO_SCROLLBARS == 1
49 #include "hildon-pannable-area.h"
50 #include "hildon-marshalers.h"
51 #include "hildon-enum-types.h"
53 #define USE_CAIRO_SCROLLBARS 0
55 #define SCROLL_BAR_MIN_SIZE 5
56 #define RATIO_TOLERANCE 0.000001
57 #define SCROLL_FADE_TIMEOUT 100
58 #define MOTION_EVENTS_PER_SECOND 25
59 #define CURSOR_STOPPED_TIMEOUT 200
60 #define MAX_SPEED_THRESHOLD 280
61 #define PANNABLE_MAX_WIDTH 788
62 #define PANNABLE_MAX_HEIGHT 378
63 #define ACCEL_FACTOR 27
64 #define MIN_ACCEL_THRESHOLD 40
65 #define FAST_CLICK 125
67 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
69 #define PANNABLE_AREA_PRIVATE(o) \
70 (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA, \
71 HildonPannableAreaPrivate))
73 struct _HildonPannableAreaPrivate {
74 HildonPannableAreaMode mode;
75 HildonMovementMode mov_mode;
76 GdkWindow *event_window;
77 gdouble x; /* Used to store mouse co-ordinates of the first or */
78 gdouble y; /* previous events in a press-motion pair */
79 gdouble ex; /* Used to store mouse co-ordinates of the last */
80 gdouble ey; /* motion event in acceleration mode */
82 gboolean button_pressed;
83 guint32 last_time; /* Last event time, to stop infinite loops */
84 guint32 last_press_time;
90 gdouble vmax_overshooting;
99 guint panning_threshold;
100 guint scrollbar_fade_delay;
103 guint direction_error_margin;
111 gint ix; /* Initial click mouse co-ordinates */
113 gint cx; /* Initial click child window mouse co-ordinates */
120 gint overshot_dist_x;
121 gint overshot_dist_y;
124 gdouble scroll_indicator_alpha;
125 gint motion_event_scroll_timeout;
126 gint scroll_indicator_timeout;
127 gint scroll_indicator_event_interrupt;
128 gint scroll_delay_counter;
131 gboolean initial_hint;
132 gboolean initial_effect;
133 gboolean low_friction_mode;
136 gboolean size_request_policy;
137 gboolean hscroll_visible;
138 gboolean vscroll_visible;
139 GdkRectangle hscroll_rect;
140 GdkRectangle vscroll_rect;
141 guint indicator_width;
143 GtkAdjustment *hadjust;
144 GtkAdjustment *vadjust;
148 GtkPolicyType vscrollbar_policy;
149 GtkPolicyType hscrollbar_policy;
151 GdkGC *scrollbars_gc;
152 GdkColor scroll_color;
154 gboolean center_on_child_focus;
155 gboolean center_on_child_focus_pending;
167 static guint pannable_area_signals [LAST_SIGNAL] = { 0 };
175 PROP_VEL_MAX_OVERSHOOTING,
176 PROP_VELOCITY_FAST_FACTOR,
180 PROP_PANNING_THRESHOLD,
181 PROP_SCROLLBAR_FADE_DELAY,
184 PROP_DIRECTION_ERROR_MARGIN,
185 PROP_VSCROLLBAR_POLICY,
186 PROP_HSCROLLBAR_POLICY,
191 PROP_LOW_FRICTION_MODE,
192 PROP_SIZE_REQUEST_POLICY,
195 PROP_CENTER_ON_CHILD_FOCUS,
199 static void hildon_pannable_area_class_init (HildonPannableAreaClass * klass);
200 static void hildon_pannable_area_init (HildonPannableArea * area);
201 static void hildon_pannable_area_get_property (GObject * object,
205 static void hildon_pannable_area_set_property (GObject * object,
207 const GValue * value,
209 static void hildon_pannable_area_remove_timeouts (GtkWidget * widget);
210 static void hildon_pannable_area_dispose (GObject * object);
211 static void hildon_pannable_area_realize (GtkWidget * widget);
212 static void hildon_pannable_area_unrealize (GtkWidget * widget);
213 static void hildon_pannable_area_size_request (GtkWidget * widget,
214 GtkRequisition * requisition);
215 static void hildon_pannable_area_size_allocate (GtkWidget * widget,
216 GtkAllocation * allocation);
217 static void hildon_pannable_area_child_allocate_calculate (GtkWidget * widget,
218 GtkAllocation * allocation,
219 GtkAllocation * child_allocation);
220 static void hildon_pannable_area_style_set (GtkWidget * widget,
221 GtkStyle * previous_style);
222 static void hildon_pannable_area_map (GtkWidget * widget);
223 static void hildon_pannable_area_unmap (GtkWidget * widget);
224 static void hildon_pannable_area_grab_notify (GtkWidget *widget,
225 gboolean was_grabbed,
227 #if USE_CAIRO_SCROLLBARS == 1
228 static void rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b);
229 #else /* USE_CAIRO_SCROLLBARS */
230 static void tranparency_color (GdkColor *color,
233 gdouble transparency);
234 #endif /* USE_CAIRO_SCROLLBARS */
235 static void hildon_pannable_draw_vscroll (GtkWidget * widget,
236 GdkColor *back_color,
237 GdkColor *scroll_color);
238 static void hildon_pannable_draw_hscroll (GtkWidget * widget,
239 GdkColor *back_color,
240 GdkColor *scroll_color);
241 static void hildon_pannable_area_initial_effect (GtkWidget * widget);
242 static void hildon_pannable_area_redraw (HildonPannableArea * area);
243 static void hildon_pannable_area_launch_fade_timeout (HildonPannableArea * area,
245 static void hildon_pannable_area_adjust_value_changed (HildonPannableArea * area,
247 static void hildon_pannable_area_adjust_changed (HildonPannableArea * area,
249 static gboolean hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area);
250 static gboolean hildon_pannable_area_expose_event (GtkWidget * widget,
251 GdkEventExpose * event);
252 static GdkWindow * hildon_pannable_area_get_topmost (GdkWindow * window,
254 gint * tx, gint * ty,
256 static void synth_crossing (GdkWindow * child,
258 gint x_root, gint y_root,
259 guint32 time, gboolean in);
260 static gboolean hildon_pannable_area_button_press_cb (GtkWidget * widget,
261 GdkEventButton * event);
262 static void hildon_pannable_area_refresh (HildonPannableArea * area);
263 static gboolean hildon_pannable_area_check_scrollbars (HildonPannableArea * area);
264 static void hildon_pannable_axis_scroll (HildonPannableArea *area,
265 GtkAdjustment *adjust,
273 static void hildon_pannable_area_scroll (HildonPannableArea *area,
274 gdouble x, gdouble y);
275 static gboolean hildon_pannable_area_timeout (HildonPannableArea * area);
276 static void hildon_pannable_area_calculate_velocity (gdouble *vel,
280 gdouble drag_inertia,
283 static gboolean hildon_pannable_area_motion_event_scroll_timeout (HildonPannableArea *area);
284 static void hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
285 gdouble x, gdouble y);
286 static void hildon_pannable_area_check_move (HildonPannableArea *area,
287 GdkEventMotion * event,
290 static void hildon_pannable_area_handle_move (HildonPannableArea *area,
291 GdkEventMotion * event,
294 static gboolean hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
295 GdkEventMotion * event);
296 static gboolean hildon_pannable_leave_notify_event (GtkWidget *widget,
297 GdkEventCrossing *event);
298 static gboolean hildon_pannable_area_button_release_cb (GtkWidget * widget,
299 GdkEventButton * event);
300 static gboolean hildon_pannable_area_scroll_cb (GtkWidget *widget,
301 GdkEventScroll *event);
302 static void hildon_pannable_area_child_mapped (GtkWidget *widget,
305 static void hildon_pannable_area_add (GtkContainer *container, GtkWidget *child);
306 static void hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child);
307 static void hildon_pannable_calculate_vel_factor (HildonPannableArea * self);
308 static void hildon_pannable_area_set_focus_child (GtkContainer *container,
310 static void hildon_pannable_area_center_on_child_focus (HildonPannableArea *area);
314 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
316 GObjectClass *object_class = G_OBJECT_CLASS (klass);
317 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
318 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
321 g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
323 object_class->get_property = hildon_pannable_area_get_property;
324 object_class->set_property = hildon_pannable_area_set_property;
325 object_class->dispose = hildon_pannable_area_dispose;
327 widget_class->realize = hildon_pannable_area_realize;
328 widget_class->unrealize = hildon_pannable_area_unrealize;
329 widget_class->map = hildon_pannable_area_map;
330 widget_class->unmap = hildon_pannable_area_unmap;
331 widget_class->size_request = hildon_pannable_area_size_request;
332 widget_class->size_allocate = hildon_pannable_area_size_allocate;
333 widget_class->expose_event = hildon_pannable_area_expose_event;
334 widget_class->style_set = hildon_pannable_area_style_set;
335 widget_class->button_press_event = hildon_pannable_area_button_press_cb;
336 widget_class->button_release_event = hildon_pannable_area_button_release_cb;
337 widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
338 widget_class->leave_notify_event = hildon_pannable_leave_notify_event;
339 widget_class->scroll_event = hildon_pannable_area_scroll_cb;
341 container_class->add = hildon_pannable_area_add;
342 container_class->remove = hildon_pannable_area_remove;
343 container_class->set_focus_child = hildon_pannable_area_set_focus_child;
345 klass->horizontal_movement = NULL;
346 klass->vertical_movement = NULL;
348 g_object_class_install_property (object_class,
350 g_param_spec_boolean ("enabled",
352 "Enable or disable finger-scroll.",
357 g_object_class_install_property (object_class,
358 PROP_VSCROLLBAR_POLICY,
359 g_param_spec_enum ("vscrollbar_policy",
361 "Visual policy of the vertical scrollbar",
362 GTK_TYPE_POLICY_TYPE,
363 GTK_POLICY_AUTOMATIC,
367 g_object_class_install_property (object_class,
368 PROP_HSCROLLBAR_POLICY,
369 g_param_spec_enum ("hscrollbar_policy",
371 "Visual policy of the horizontal scrollbar",
372 GTK_TYPE_POLICY_TYPE,
373 GTK_POLICY_AUTOMATIC,
377 g_object_class_install_property (object_class,
379 g_param_spec_enum ("mode",
381 "Change the finger-scrolling mode.",
382 HILDON_TYPE_PANNABLE_AREA_MODE,
383 HILDON_PANNABLE_AREA_MODE_AUTO,
387 g_object_class_install_property (object_class,
389 g_param_spec_flags ("mov_mode",
390 "Scroll movement mode",
391 "Controls if the widget can scroll vertically, horizontally or both",
392 HILDON_TYPE_MOVEMENT_MODE,
393 HILDON_MOVEMENT_MODE_VERT,
397 g_object_class_install_property (object_class,
399 g_param_spec_double ("velocity_min",
400 "Minimum scroll velocity",
401 "Minimum distance the child widget should scroll "
402 "per 'frame', in pixels per frame.",
407 g_object_class_install_property (object_class,
409 g_param_spec_double ("velocity_max",
410 "Maximum scroll velocity",
411 "Maximum distance the child widget should scroll "
412 "per 'frame', in pixels per frame.",
413 0, G_MAXDOUBLE, 3500,
417 g_object_class_install_property (object_class,
418 PROP_VEL_MAX_OVERSHOOTING,
419 g_param_spec_double ("velocity_overshooting_max",
420 "Maximum scroll velocity when overshooting",
421 "Maximum distance the child widget should scroll "
422 "per 'frame', in pixels per frame when it overshoots after hitting the edge.",
427 g_object_class_install_property (object_class,
428 PROP_VELOCITY_FAST_FACTOR,
429 g_param_spec_double ("velocity_fast_factor",
430 "Fast velocity factor",
431 "Minimum velocity that is considered 'fast': "
432 "children widgets won't receive button presses. "
433 "Expressed as a fraction of the maximum velocity.",
438 g_object_class_install_property (object_class,
440 g_param_spec_double ("deceleration",
441 "Deceleration multiplier",
442 "The multiplier used when decelerating when in "
443 "acceleration scrolling mode.",
448 g_object_class_install_property (object_class,
450 g_param_spec_double ("drag_inertia",
451 "Inertia of the cursor dragging",
452 "Percentage of the calculated speed in each moment we are are going to use"
453 "to calculate the launch speed, the other part would be the speed"
454 "calculated previously",
459 g_object_class_install_property (object_class,
461 g_param_spec_uint ("sps",
462 "Scrolls per second",
463 "Amount of scroll events to generate per second.",
468 g_object_class_install_property (object_class,
469 PROP_PANNING_THRESHOLD,
470 g_param_spec_uint ("panning_threshold",
471 "Threshold to consider a motion event an scroll",
472 "Amount of pixels to consider a motion event an scroll, if it is less"
473 "it is a click detected incorrectly by the touch screen.",
478 g_object_class_install_property (object_class,
479 PROP_SCROLLBAR_FADE_DELAY,
480 g_param_spec_uint ("scrollbar_fade_delay",
481 "Time before starting to fade the scrollbar",
482 "Time the scrollbar is going to be visible if the widget is not in"
483 "action in miliseconds",
488 g_object_class_install_property (object_class,
490 g_param_spec_uint ("bounce_steps",
492 "Number of steps that is going to be used to bounce when hitting the"
493 "edge, the rubberband effect depends on it",
498 g_object_class_install_property (object_class,
500 g_param_spec_uint ("force",
501 "Multiplier of the calculated speed",
502 "Force applied to the movement, multiplies the calculated speed of the"
503 "user movement the cursor in the screen",
508 g_object_class_install_property (object_class,
509 PROP_DIRECTION_ERROR_MARGIN,
510 g_param_spec_uint ("direction_error_margin",
511 "Margin in the direction detection",
512 "After detecting the direction of the movement (horizontal or"
513 "vertical), we can add this margin of error to allow the movement in"
514 "the other direction even apparently it is not",
519 g_object_class_install_property (object_class,
521 g_param_spec_int ("vovershoot_max",
522 "Vertical overshoot distance",
523 "Space we allow the widget to pass over its vertical limits when"
524 "hitting the edges, set 0 in order to deactivate overshooting.",
529 g_object_class_install_property (object_class,
531 g_param_spec_int ("hovershoot_max",
532 "Horizontal overshoot distance",
533 "Space we allow the widget to pass over its horizontal limits when"
534 "hitting the edges, set 0 in order to deactivate overshooting.",
539 g_object_class_install_property (object_class,
541 g_param_spec_double ("scroll_time",
542 "Time to scroll to a position",
543 "The time to scroll to a position when calling the hildon_pannable_scroll_to function",
548 g_object_class_install_property (object_class,
550 g_param_spec_boolean ("initial-hint",
552 "Whether to hint the user about the pannability of the container.",
557 g_object_class_install_property (object_class,
558 PROP_LOW_FRICTION_MODE,
559 g_param_spec_boolean ("low-friction-mode",
560 "Do not decelerate the initial velocity",
561 "Avoid decelerating the panning movement, like no friction, the widget"
562 "will stop in the edges or if the user clicks.",
567 g_object_class_install_property (object_class,
568 PROP_SIZE_REQUEST_POLICY,
569 g_param_spec_enum ("size-request-policy",
570 "Size Requisition policy",
571 "Controls the size request policy of the widget",
572 HILDON_TYPE_SIZE_REQUEST_POLICY,
573 HILDON_SIZE_REQUEST_MINIMUM,
577 g_object_class_install_property (object_class,
579 g_param_spec_object ("hadjustment",
580 "Horizontal Adjustment",
581 "The GtkAdjustment for the horizontal position",
584 g_object_class_install_property (object_class,
586 g_param_spec_object ("vadjustment",
587 "Vertical Adjustment",
588 "The GtkAdjustment for the vertical position",
592 g_object_class_install_property (object_class,
593 PROP_CENTER_ON_CHILD_FOCUS,
594 g_param_spec_boolean ("center-on-child-focus",
595 "Center on the child with the focus",
596 "Whether to center the pannable on the child that receives the focus.",
602 gtk_widget_class_install_style_property (widget_class,
605 "Width of the scroll indicators",
606 "Pixel width used to draw the scroll indicators.",
611 * HildonPannableArea::horizontal-movement:
612 * @hildonpannable: the object which received the signal
613 * @direction: the direction of the movement #HILDON_MOVEMENT_LEFT or #HILDON_MOVEMENT_RIGHT
614 * @initial_x: the x coordinate of the point where the user clicked to start the movement
615 * @initial_y: the y coordinate of the point where the user clicked to start the movement
617 * The horizontal-movement signal is emitted when the pannable area
618 * detects a horizontal movement. The detection does not mean the
619 * widget is going to move (i.e. maybe the children are smaller
620 * horizontally than the screen).
624 pannable_area_signals[HORIZONTAL_MOVEMENT] =
625 g_signal_new ("horizontal_movement",
626 G_TYPE_FROM_CLASS (object_class),
627 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
628 G_STRUCT_OFFSET (HildonPannableAreaClass, horizontal_movement),
630 _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
637 * HildonPannableArea::vertical-movement:
638 * @hildonpannable: the object which received the signal
639 * @direction: the direction of the movement #HILDON_MOVEMENT_UP or #HILDON_MOVEMENT_DOWN
640 * @initial_x: the x coordinate of the point where the user clicked to start the movement
641 * @initial_y: the y coordinate of the point where the user clicked to start the movement
643 * The vertical-movement signal is emitted when the pannable area
644 * detects a vertical movement. The detection does not mean the
645 * widget is going to move (i.e. maybe the children are smaller
646 * vertically than the screen).
650 pannable_area_signals[VERTICAL_MOVEMENT] =
651 g_signal_new ("vertical_movement",
652 G_TYPE_FROM_CLASS (object_class),
653 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
654 G_STRUCT_OFFSET (HildonPannableAreaClass, vertical_movement),
656 _hildon_marshal_VOID__INT_DOUBLE_DOUBLE,
663 * HildonPannableArea::panning-started:
664 * @hildonpannable: the pannable area object that is going to start
667 * This signal is emitted before the panning starts. Applications
668 * can return %TRUE to avoid the panning. The main difference with
669 * the vertical-movement and horizontal-movement signals is those
670 * gesture signals are launched no matter if the widget is going to
671 * move, this signal means the widget is going to start moving. It
672 * could even happen that the widget moves and there was no gesture
673 * (i.e. click meanwhile the pannable is overshooting).
675 * Returns: %TRUE to stop the panning launch. %FALSE to continue
680 pannable_area_signals[PANNING_STARTED] =
681 g_signal_new ("panning-started",
682 G_TYPE_FROM_CLASS (object_class),
686 _hildon_marshal_BOOLEAN__VOID,
690 * HildonPannableArea::panning-finished:
691 * @hildonpannable: the pannable area object that finished the
694 * This signal is emitted after the kinetic panning has
699 pannable_area_signals[PANNING_FINISHED] =
700 g_signal_new ("panning-finished",
701 G_TYPE_FROM_CLASS (object_class),
705 _hildon_marshal_VOID__VOID,
711 hildon_pannable_area_init (HildonPannableArea * area)
713 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
715 GTK_WIDGET_UNSET_FLAGS (area, GTK_NO_WINDOW);
720 priv->button_pressed = FALSE;
722 priv->last_press_time = 0;
724 priv->vscroll_visible = TRUE;
725 priv->hscroll_visible = TRUE;
726 priv->indicator_width = 6;
727 priv->overshot_dist_x = 0;
728 priv->overshot_dist_y = 0;
729 priv->overshooting_y = 0;
730 priv->overshooting_x = 0;
731 priv->accel_vel_x = 0;
732 priv->accel_vel_y = 0;
738 priv->scroll_indicator_alpha = 0.0;
739 priv->scroll_indicator_timeout = 0;
740 priv->motion_event_scroll_timeout = 0;
741 priv->scroll_indicator_event_interrupt = 0;
742 priv->scroll_delay_counter = 0;
743 priv->scrollbar_fade_delay = 0;
744 priv->scroll_to_x = -1;
745 priv->scroll_to_y = -1;
746 priv->first_drag = TRUE;
747 priv->initial_effect = TRUE;
748 priv->child_width = 0;
749 priv->child_height = 0;
750 priv->last_in = TRUE;
753 priv->center_on_child_focus_pending = FALSE;
755 gtk_style_lookup_color (GTK_WIDGET (area)->style,
756 "SecondaryTextColor", &priv->scroll_color);
759 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
761 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
763 g_object_ref_sink (G_OBJECT (priv->hadjust));
764 g_object_ref_sink (G_OBJECT (priv->vadjust));
766 g_signal_connect_swapped (priv->hadjust, "value-changed",
767 G_CALLBACK (hildon_pannable_area_adjust_value_changed), area);
768 g_signal_connect_swapped (priv->vadjust, "value-changed",
769 G_CALLBACK (hildon_pannable_area_adjust_value_changed), area);
770 g_signal_connect_swapped (priv->hadjust, "changed",
771 G_CALLBACK (hildon_pannable_area_adjust_changed), area);
772 g_signal_connect_swapped (priv->vadjust, "changed",
773 G_CALLBACK (hildon_pannable_area_adjust_changed), area);
774 g_signal_connect (area, "grab-notify",
775 G_CALLBACK (hildon_pannable_area_grab_notify), NULL);
779 hildon_pannable_area_get_property (GObject * object,
784 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
786 switch (property_id) {
788 g_value_set_boolean (value, priv->enabled);
791 g_value_set_enum (value, priv->mode);
793 case PROP_MOVEMENT_MODE:
794 g_value_set_flags (value, priv->mov_mode);
796 case PROP_VELOCITY_MIN:
797 g_value_set_double (value, priv->vmin);
799 case PROP_VELOCITY_MAX:
800 g_value_set_double (value, priv->vmax);
802 case PROP_VEL_MAX_OVERSHOOTING:
803 g_value_set_double (value, priv->vmax_overshooting);
805 case PROP_VELOCITY_FAST_FACTOR:
806 g_value_set_double (value, priv->vfast_factor);
808 case PROP_DECELERATION:
809 g_value_set_double (value, priv->decel);
811 case PROP_DRAG_INERTIA:
812 g_value_set_double (value, priv->drag_inertia);
815 g_value_set_uint (value, priv->sps);
817 case PROP_PANNING_THRESHOLD:
818 g_value_set_uint (value, priv->panning_threshold);
820 case PROP_SCROLLBAR_FADE_DELAY:
821 /* convert to miliseconds */
822 g_value_set_uint (value, priv->scrollbar_fade_delay * SCROLL_FADE_TIMEOUT);
824 case PROP_BOUNCE_STEPS:
825 g_value_set_uint (value, priv->bounce_steps);
828 g_value_set_uint (value, priv->force);
830 case PROP_DIRECTION_ERROR_MARGIN:
831 g_value_set_uint (value, priv->direction_error_margin);
833 case PROP_VSCROLLBAR_POLICY:
834 g_value_set_enum (value, priv->vscrollbar_policy);
836 case PROP_HSCROLLBAR_POLICY:
837 g_value_set_enum (value, priv->hscrollbar_policy);
839 case PROP_VOVERSHOOT_MAX:
840 g_value_set_int (value, priv->vovershoot_max);
842 case PROP_HOVERSHOOT_MAX:
843 g_value_set_int (value, priv->hovershoot_max);
845 case PROP_SCROLL_TIME:
846 g_value_set_double (value, priv->scroll_time);
848 case PROP_INITIAL_HINT:
849 g_value_set_boolean (value, priv->initial_hint);
851 case PROP_LOW_FRICTION_MODE:
852 g_value_set_boolean (value, priv->low_friction_mode);
854 case PROP_SIZE_REQUEST_POLICY:
855 g_value_set_enum (value, priv->size_request_policy);
857 case PROP_HADJUSTMENT:
858 g_value_set_object (value,
859 hildon_pannable_area_get_hadjustment
860 (HILDON_PANNABLE_AREA (object)));
862 case PROP_VADJUSTMENT:
863 g_value_set_object (value,
864 hildon_pannable_area_get_vadjustment
865 (HILDON_PANNABLE_AREA (object)));
867 case PROP_CENTER_ON_CHILD_FOCUS:
868 g_value_set_boolean (value, priv->center_on_child_focus);
871 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
876 hildon_pannable_area_set_property (GObject * object,
878 const GValue * value,
881 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
884 switch (property_id) {
886 enabled = g_value_get_boolean (value);
888 if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
890 gdk_window_raise (priv->event_window);
892 gdk_window_lower (priv->event_window);
895 priv->enabled = enabled;
898 priv->mode = g_value_get_enum (value);
900 case PROP_MOVEMENT_MODE:
901 priv->mov_mode = g_value_get_flags (value);
903 case PROP_VELOCITY_MIN:
904 priv->vmin = g_value_get_double (value);
906 case PROP_VELOCITY_MAX:
907 priv->vmax = g_value_get_double (value);
909 case PROP_VEL_MAX_OVERSHOOTING:
910 priv->vmax_overshooting = g_value_get_double (value);
912 case PROP_VELOCITY_FAST_FACTOR:
913 priv->vfast_factor = g_value_get_double (value);
915 case PROP_DECELERATION:
916 priv->decel = g_value_get_double (value);
917 hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
919 case PROP_DRAG_INERTIA:
920 priv->drag_inertia = g_value_get_double (value);
923 priv->sps = g_value_get_uint (value);
925 case PROP_PANNING_THRESHOLD:
927 GtkSettings *settings = gtk_settings_get_default ();
928 GtkSettingsValue svalue = { NULL, { 0, }, };
930 priv->panning_threshold = g_value_get_uint (value);
932 /* insure gtk dnd is the same we are using, not allowed
933 different thresholds in the same application */
934 svalue.origin = "panning_threshold";
935 g_value_init (&svalue.value, G_TYPE_LONG);
936 g_value_set_long (&svalue.value, priv->panning_threshold);
937 gtk_settings_set_property_value (settings, "gtk-dnd-drag-threshold", &svalue);
938 g_value_unset (&svalue.value);
941 case PROP_SCROLLBAR_FADE_DELAY:
942 /* convert to miliseconds */
943 priv->scrollbar_fade_delay = g_value_get_uint (value)/(SCROLL_FADE_TIMEOUT);
945 case PROP_BOUNCE_STEPS:
946 priv->bounce_steps = g_value_get_uint (value);
949 priv->force = g_value_get_uint (value);
951 case PROP_DIRECTION_ERROR_MARGIN:
952 priv->direction_error_margin = g_value_get_uint (value);
954 case PROP_VSCROLLBAR_POLICY:
955 priv->vscrollbar_policy = g_value_get_enum (value);
957 gtk_widget_queue_resize (GTK_WIDGET (object));
959 case PROP_HSCROLLBAR_POLICY:
960 priv->hscrollbar_policy = g_value_get_enum (value);
962 gtk_widget_queue_resize (GTK_WIDGET (object));
964 case PROP_VOVERSHOOT_MAX:
965 priv->vovershoot_max = g_value_get_int (value);
967 case PROP_HOVERSHOOT_MAX:
968 priv->hovershoot_max = g_value_get_int (value);
970 case PROP_SCROLL_TIME:
971 priv->scroll_time = g_value_get_double (value);
973 hildon_pannable_calculate_vel_factor (HILDON_PANNABLE_AREA (object));
975 case PROP_INITIAL_HINT:
976 priv->initial_hint = g_value_get_boolean (value);
978 case PROP_LOW_FRICTION_MODE:
979 priv->low_friction_mode = g_value_get_boolean (value);
981 case PROP_SIZE_REQUEST_POLICY:
982 hildon_pannable_area_set_size_request_policy (HILDON_PANNABLE_AREA (object),
983 g_value_get_enum (value));
985 case PROP_CENTER_ON_CHILD_FOCUS:
986 priv->center_on_child_focus = g_value_get_boolean (value);
990 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
995 hildon_pannable_area_dispose (GObject * object)
997 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
998 GtkWidget *child = gtk_bin_get_child (GTK_BIN (object));
1000 hildon_pannable_area_remove_timeouts (GTK_WIDGET (object));
1003 g_signal_handlers_disconnect_by_func (child,
1004 hildon_pannable_area_child_mapped,
1008 g_signal_handlers_disconnect_by_func (object,
1009 hildon_pannable_area_grab_notify,
1012 if (priv->hadjust) {
1013 g_signal_handlers_disconnect_by_func (priv->hadjust,
1014 hildon_pannable_area_adjust_value_changed,
1016 g_signal_handlers_disconnect_by_func (priv->hadjust,
1017 hildon_pannable_area_adjust_changed,
1019 g_object_unref (priv->hadjust);
1020 priv->hadjust = NULL;
1023 if (priv->vadjust) {
1024 g_signal_handlers_disconnect_by_func (priv->vadjust,
1025 hildon_pannable_area_adjust_value_changed,
1027 g_signal_handlers_disconnect_by_func (priv->vadjust,
1028 hildon_pannable_area_adjust_changed,
1030 g_object_unref (priv->vadjust);
1031 priv->vadjust = NULL;
1034 if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
1035 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
1039 hildon_pannable_area_realize (GtkWidget * widget)
1041 GdkWindowAttr attributes;
1042 gint attributes_mask;
1044 HildonPannableAreaPrivate *priv;
1046 priv = HILDON_PANNABLE_AREA (widget)->priv;
1048 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1050 border_width = GTK_CONTAINER (widget)->border_width;
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;
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;
1064 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
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);
1070 /* create the events window */
1073 attributes.event_mask = gtk_widget_get_events (widget)
1074 | GDK_BUTTON_MOTION_MASK
1075 | GDK_BUTTON_PRESS_MASK
1076 | GDK_BUTTON_RELEASE_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;
1082 attributes_mask = GDK_WA_X | GDK_WA_Y;
1084 priv->event_window = gdk_window_new (widget->window,
1085 &attributes, attributes_mask);
1086 gdk_window_set_user_data (priv->event_window, widget);
1088 widget->style = gtk_style_attach (widget->style, widget->window);
1089 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
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]);
1097 hildon_pannable_area_remove_timeouts (GtkWidget * widget)
1099 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1101 if (priv->idle_id) {
1102 g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
1103 g_source_remove (priv->idle_id);
1107 if (priv->scroll_indicator_timeout){
1108 g_source_remove (priv->scroll_indicator_timeout);
1109 priv->scroll_indicator_timeout = 0;
1112 if (priv->motion_event_scroll_timeout){
1113 g_source_remove (priv->motion_event_scroll_timeout);
1114 priv->motion_event_scroll_timeout = 0;
1119 hildon_pannable_area_unrealize (GtkWidget * widget)
1121 HildonPannableAreaPrivate *priv;
1123 priv = HILDON_PANNABLE_AREA (widget)->priv;
1125 hildon_pannable_area_remove_timeouts (widget);
1127 if (priv->event_window != NULL) {
1128 gdk_window_set_user_data (priv->event_window, NULL);
1129 gdk_window_destroy (priv->event_window);
1130 priv->event_window = NULL;
1133 gdk_gc_unref (priv->scrollbars_gc);
1135 if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1136 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)(widget);
1140 hildon_pannable_area_size_request (GtkWidget * widget,
1141 GtkRequisition * requisition)
1143 GtkRequisition child_requisition = {0};
1144 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1145 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
1147 if (child && GTK_WIDGET_VISIBLE (child))
1149 gtk_widget_size_request (child, &child_requisition);
1152 if (priv->hscrollbar_policy == GTK_POLICY_NEVER) {
1153 requisition->width = child_requisition.width;
1155 switch (priv->size_request_policy) {
1156 case HILDON_SIZE_REQUEST_CHILDREN:
1157 requisition->width = MIN (PANNABLE_MAX_WIDTH,
1158 child_requisition.width);
1160 case HILDON_SIZE_REQUEST_MINIMUM:
1162 requisition->width = priv->indicator_width;
1166 if (priv->vscrollbar_policy == GTK_POLICY_NEVER) {
1167 requisition->height = child_requisition.height;
1169 switch (priv->size_request_policy) {
1170 case HILDON_SIZE_REQUEST_CHILDREN:
1171 requisition->height = MIN (PANNABLE_MAX_HEIGHT,
1172 child_requisition.height);
1174 case HILDON_SIZE_REQUEST_MINIMUM:
1176 requisition->height = priv->indicator_width;
1180 requisition->width += 2 * GTK_CONTAINER (widget)->border_width;
1181 requisition->height += 2 * GTK_CONTAINER (widget)->border_width;
1185 hildon_pannable_area_child_allocate_calculate (GtkWidget * widget,
1186 GtkAllocation * allocation,
1187 GtkAllocation * child_allocation)
1190 HildonPannableAreaPrivate *priv;
1192 border_width = GTK_CONTAINER (widget)->border_width;
1194 priv = HILDON_PANNABLE_AREA (widget)->priv;
1196 child_allocation->x = 0;
1197 child_allocation->y = 0;
1198 child_allocation->width = MAX (allocation->width - 2 * border_width -
1199 (priv->vscroll_visible ? priv->vscroll_rect.width : 0), 0);
1200 child_allocation->height = MAX (allocation->height - 2 * border_width -
1201 (priv->hscroll_visible ? priv->hscroll_rect.height : 0), 0);
1203 if (priv->overshot_dist_y > 0) {
1204 child_allocation->y = MIN (child_allocation->y + priv->overshot_dist_y,
1205 child_allocation->height);
1206 child_allocation->height = MAX (child_allocation->height - priv->overshot_dist_y, 0);
1207 } else if (priv->overshot_dist_y < 0) {
1208 child_allocation->height = MAX (child_allocation->height + priv->overshot_dist_y, 0);
1211 if (priv->overshot_dist_x > 0) {
1212 child_allocation->x = MIN (child_allocation->x + priv->overshot_dist_x,
1213 child_allocation->width);
1214 child_allocation->width = MAX (child_allocation->width - priv->overshot_dist_x, 0);
1215 } else if (priv->overshot_dist_x < 0) {
1216 child_allocation->width = MAX (child_allocation->width + priv->overshot_dist_x, 0);
1221 hildon_pannable_area_size_allocate (GtkWidget * widget,
1222 GtkAllocation * allocation)
1224 GtkAllocation child_allocation;
1225 HildonPannableAreaPrivate *priv;
1226 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
1230 border_width = GTK_CONTAINER (widget)->border_width;
1232 widget->allocation = *allocation;
1234 priv = HILDON_PANNABLE_AREA (widget)->priv;
1236 if (GTK_WIDGET_REALIZED (widget)) {
1237 gdk_window_move_resize (widget->window,
1238 allocation->x + border_width,
1239 allocation->y + border_width,
1240 allocation->width - border_width * 2,
1241 allocation->height - border_width * 2);
1242 gdk_window_move_resize (priv->event_window,
1245 allocation->width - border_width * 2,
1246 allocation->height - border_width * 2);
1249 if (child && GTK_WIDGET_VISIBLE (child)) {
1251 hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget));
1253 hildon_pannable_area_child_allocate_calculate (widget,
1257 gtk_widget_size_allocate (child, &child_allocation);
1259 if (hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget))) {
1260 hildon_pannable_area_child_allocate_calculate (widget,
1264 gtk_widget_size_allocate (child, &child_allocation);
1267 if (priv->vadjust->page_size >= 0) {
1268 priv->accel_vel_y = MIN (priv->vmax,
1269 priv->vadjust->upper/priv->vadjust->page_size*ACCEL_FACTOR);
1270 priv->accel_vel_x = MIN (priv->vmax,
1271 priv->hadjust->upper/priv->hadjust->page_size*ACCEL_FACTOR);
1274 hv = priv->hadjust->value;
1275 vv = priv->vadjust->value;
1277 /* we have to do this after child size_allocate because page_size is
1278 * changed when we allocate the size of the children */
1279 if (priv->overshot_dist_y < 0) {
1280 priv->vadjust->value = priv->vadjust->upper - priv->vadjust->page_size;
1283 if (priv->overshot_dist_x < 0) {
1284 priv->hadjust->value = priv->hadjust->upper - priv->hadjust->page_size;
1287 if (hv != priv->hadjust->value)
1288 gtk_adjustment_value_changed (priv->hadjust);
1290 if (vv != priv->vadjust->value)
1291 gtk_adjustment_value_changed (priv->vadjust);
1294 hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget));
1299 hildon_pannable_area_style_set (GtkWidget * widget,
1300 GtkStyle * previous_style)
1302 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1304 GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1305 style_set (widget, previous_style);
1307 gtk_style_lookup_color (widget->style, "SecondaryTextColor", &priv->scroll_color);
1308 gtk_widget_style_get (widget, "indicator-width", &priv->indicator_width, NULL);
1312 hildon_pannable_area_map (GtkWidget * widget)
1314 HildonPannableAreaPrivate *priv;
1316 priv = HILDON_PANNABLE_AREA (widget)->priv;
1318 gdk_window_show (widget->window);
1320 if (priv->event_window != NULL && !priv->enabled)
1321 gdk_window_show (priv->event_window);
1323 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1325 if (priv->event_window != NULL && priv->enabled)
1326 gdk_window_show (priv->event_window);
1330 hildon_pannable_area_unmap (GtkWidget * widget)
1332 HildonPannableAreaPrivate *priv;
1334 priv = HILDON_PANNABLE_AREA (widget)->priv;
1336 if (priv->event_window != NULL)
1337 gdk_window_hide (priv->event_window);
1339 gdk_window_hide (widget->window);
1341 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1345 hildon_pannable_area_grab_notify (GtkWidget *widget,
1346 gboolean was_grabbed,
1349 /* an internal widget has grabbed the focus and now has returned it,
1350 we have to do some release actions */
1352 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1354 priv->scroll_indicator_event_interrupt = 0;
1356 if ((!priv->scroll_indicator_timeout)&&(priv->scroll_indicator_alpha)>0.1) {
1357 priv->scroll_delay_counter = priv->scrollbar_fade_delay;
1359 hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
1360 priv->scroll_indicator_alpha);
1363 priv->last_type = 3;
1364 priv->moved = FALSE;
1368 #if USE_CAIRO_SCROLLBARS == 1
1371 rgb_from_gdkcolor (GdkColor *color, gdouble *r, gdouble *g, gdouble *b)
1373 *r = (color->red >> 8) / 255.0;
1374 *g = (color->green >> 8) / 255.0;
1375 *b = (color->blue >> 8) / 255.0;
1379 hildon_pannable_draw_vscroll (GtkWidget * widget,
1380 GdkColor *back_color,
1381 GdkColor *scroll_color)
1383 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1386 cairo_pattern_t *pattern;
1388 gint radius = (priv->vscroll_rect.width/2) - 1;
1390 cr = gdk_cairo_create(widget->window);
1392 /* Draw the background */
1393 rgb_from_gdkcolor (back_color, &r, &g, &b);
1394 cairo_set_source_rgb (cr, r, g, b);
1395 cairo_rectangle(cr, priv->vscroll_rect.x, priv->vscroll_rect.y,
1396 priv->vscroll_rect.width,
1397 priv->vscroll_rect.height);
1398 cairo_fill_preserve (cr);
1401 /* Calculate the scroll bar height and position */
1402 y = ((priv->vadjust->value - priv->vadjust->lower) / (priv->vadjust->upper - priv->vadjust->lower)) *
1403 (widget->allocation.height -
1404 (priv->hscroll_visible ? priv->indicator_width : 0));
1405 height = ((((priv->vadjust->value - priv->vadjust->lower) +
1406 priv->vadjust->page_size) /
1407 (priv->vadjust->upper - priv->vadjust->lower)) *
1408 (widget->allocation.height -
1409 (priv->hscroll_visible ? priv->indicator_width : 0))) - y;
1411 /* Set a minimum height */
1412 height = MAX (SCROLL_BAR_MIN_SIZE, height);
1414 /* Check the max y position */
1415 y = MIN (y, widget->allocation.height -
1416 (priv->hscroll_visible ? priv->hscroll_rect.height : 0) -
1419 /* Draw the scrollbar */
1420 rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1422 pattern = cairo_pattern_create_linear(radius+1, y, radius+1,y + height);
1423 cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1424 cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1425 cairo_set_source(cr, pattern);
1427 cairo_pattern_destroy(pattern);
1429 cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + radius + 1, radius, G_PI, 0);
1430 cairo_line_to(cr, priv->vscroll_rect.x + (radius * 2) + 1, y + height - radius);
1431 cairo_arc(cr, priv->vscroll_rect.x + radius + 1, y + height - radius, radius, 0, G_PI);
1432 cairo_line_to(cr, priv->vscroll_rect.x + 1, y + height - radius);
1435 cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1441 hildon_pannable_draw_hscroll (GtkWidget * widget,
1442 GdkColor *back_color,
1443 GdkColor *scroll_color)
1445 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1448 cairo_pattern_t *pattern;
1450 gint radius = (priv->hscroll_rect.height/2) - 1;
1452 cr = gdk_cairo_create(widget->window);
1454 /* Draw the background */
1455 rgb_from_gdkcolor (back_color, &r, &g, &b);
1456 cairo_set_source_rgb (cr, r, g, b);
1457 cairo_rectangle(cr, priv->hscroll_rect.x, priv->hscroll_rect.y,
1458 priv->hscroll_rect.width,
1459 priv->hscroll_rect.height);
1460 cairo_fill_preserve (cr);
1463 /* calculate the scrollbar width and position */
1464 x = ((priv->hadjust->value - priv->hadjust->lower) / (priv->hadjust->upper - priv->hadjust->lower)) *
1465 (widget->allocation.width - (priv->vscroll_visible ? priv->indicator_width : 0));
1466 width =((((priv->hadjust->value - priv->hadjust->lower) +
1467 priv->hadjust->page_size) / (priv->hadjust->upper - priv->hadjust->lower)) *
1468 (widget->allocation.width -
1469 (priv->vscroll_visible ? priv->indicator_width : 0))) - x;
1471 /* Set a minimum width */
1472 width = MAX (SCROLL_BAR_MIN_SIZE, width);
1474 /* Check the max x position */
1475 x = MIN (x, widget->allocation.width -
1476 (priv->vscroll_visible ? priv->vscroll_rect.width : 0) -
1479 /* Draw the scrollbar */
1480 rgb_from_gdkcolor (scroll_color, &r, &g, &b);
1482 pattern = cairo_pattern_create_linear(x, radius+1, x+width, radius+1);
1483 cairo_pattern_add_color_stop_rgb(pattern, 0, r, g, b);
1484 cairo_pattern_add_color_stop_rgb(pattern, 1, r/2, g/2, b/2);
1485 cairo_set_source(cr, pattern);
1487 cairo_pattern_destroy(pattern);
1489 cairo_arc_negative(cr, x + radius + 1, priv->hscroll_rect.y + radius + 1, radius, 3*G_PI_2, G_PI_2);
1490 cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + (radius * 2) + 1);
1491 cairo_arc_negative(cr, x + width - radius, priv->hscroll_rect.y + radius + 1, radius, G_PI_2, 3*G_PI_2);
1492 cairo_line_to(cr, x + width - radius, priv->hscroll_rect.y + 1);
1495 cairo_paint_with_alpha(cr, priv->scroll_indicator_alpha);
1500 #else /* USE_CAIRO_SCROLLBARS */
1503 tranparency_color (GdkColor *color,
1506 gdouble transparency)
1510 diff = colora.red - colorb.red;
1511 color->red = colora.red-diff*transparency;
1513 diff = colora.green - colorb.green;
1514 color->green = colora.green-diff*transparency;
1516 diff = colora.blue - colorb.blue;
1517 color->blue = colora.blue-diff*transparency;
1521 hildon_pannable_draw_vscroll (GtkWidget *widget,
1522 GdkColor *back_color,
1523 GdkColor *scroll_color)
1525 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1527 GdkColor transp_color;
1528 GdkGC *gc = priv->scrollbars_gc;
1530 gdk_draw_rectangle (widget->window,
1531 widget->style->bg_gc[GTK_STATE_NORMAL],
1533 priv->vscroll_rect.x, priv->vscroll_rect.y,
1534 priv->vscroll_rect.width,
1535 priv->vscroll_rect.height);
1537 y = ((priv->vadjust->value - priv->vadjust->lower) / (priv->vadjust->upper - priv->vadjust->lower)) *
1538 (widget->allocation.height - (priv->hscroll_visible ? priv->indicator_width : 0));
1539 height = ((((priv->vadjust->value - priv->vadjust->lower) + priv->vadjust->page_size) /
1540 (priv->vadjust->upper - priv->vadjust->lower)) *
1541 (widget->allocation.height -
1542 (priv->hscroll_visible ? priv->indicator_width : 0))) - y;
1544 /* Set a minimum height */
1545 height = MAX (SCROLL_BAR_MIN_SIZE, height);
1547 /* Check the max y position */
1548 y = MIN (y, widget->allocation.height -
1549 (priv->hscroll_visible ? priv->hscroll_rect.height : 0) -
1552 if (priv->scroll_indicator_alpha == 1.0) {
1553 transp_color = priv->scroll_color;
1554 } else if (priv->scroll_indicator_alpha < 1.0) {
1555 tranparency_color (&transp_color, *back_color, *scroll_color,
1556 priv->scroll_indicator_alpha);
1558 gdk_gc_set_rgb_fg_color (gc, &transp_color);
1560 gdk_draw_rectangle (widget->window, gc,
1561 TRUE, priv->vscroll_rect.x, y,
1562 priv->vscroll_rect.width, height);
1566 hildon_pannable_draw_hscroll (GtkWidget *widget,
1567 GdkColor *back_color,
1568 GdkColor *scroll_color)
1570 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1572 GdkColor transp_color;
1573 GdkGC *gc = priv->scrollbars_gc;
1575 gdk_draw_rectangle (widget->window,
1576 widget->style->bg_gc[GTK_STATE_INSENSITIVE],
1578 priv->hscroll_rect.x, priv->hscroll_rect.y,
1579 priv->hscroll_rect.width,
1580 priv->hscroll_rect.height);
1582 /* calculate the scrollbar width and position */
1583 x = ((priv->hadjust->value - priv->hadjust->lower) / (priv->hadjust->upper - priv->hadjust->lower)) *
1584 (widget->allocation.width - (priv->vscroll_visible ? priv->indicator_width : 0));
1585 width =((((priv->hadjust->value - priv->hadjust->lower) +
1586 priv->hadjust->page_size) / (priv->hadjust->upper - priv->hadjust->lower)) *
1587 (widget->allocation.width -
1588 (priv->vscroll_visible ? priv->indicator_width : 0))) - x;
1590 /* Set a minimum width */
1591 width = MAX (SCROLL_BAR_MIN_SIZE, width);
1593 /* Check the max x position */
1594 x = MIN (x, widget->allocation.width -
1595 (priv->vscroll_visible ? priv->vscroll_rect.width : 0) -
1598 if (priv->scroll_indicator_alpha == 1.0) {
1599 transp_color = priv->scroll_color;
1600 } else if (priv->scroll_indicator_alpha < 1.0) {
1601 tranparency_color (&transp_color, *back_color, *scroll_color,
1602 priv->scroll_indicator_alpha);
1604 gdk_gc_set_rgb_fg_color (gc, &transp_color);
1606 gdk_draw_rectangle (widget->window, gc,
1607 TRUE, x, priv->hscroll_rect.y, width,
1608 priv->hscroll_rect.height);
1611 #endif /* USE_CAIRO_SCROLLBARS */
1614 hildon_pannable_area_initial_effect (GtkWidget * widget)
1616 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1618 if (priv->initial_hint) {
1619 if (priv->vscroll_visible || priv->hscroll_visible) {
1621 priv->scroll_indicator_event_interrupt = 0;
1622 priv->scroll_delay_counter = priv->scrollbar_fade_delay;
1624 hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget), 1.0);
1630 hildon_pannable_area_launch_fade_timeout (HildonPannableArea * area,
1633 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
1635 priv->scroll_indicator_alpha = alpha;
1637 if (!priv->scroll_indicator_timeout)
1638 priv->scroll_indicator_timeout =
1639 gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
1640 SCROLL_FADE_TIMEOUT,
1641 (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
1647 hildon_pannable_area_adjust_changed (HildonPannableArea * area,
1650 if (GTK_WIDGET_REALIZED (area))
1651 hildon_pannable_area_refresh (area);
1655 hildon_pannable_area_adjust_value_changed (HildonPannableArea * area,
1658 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
1660 gint x = priv->x_offset;
1661 gint y = priv->y_offset;
1663 priv->x_offset = priv->hadjust->value;
1664 xdiff = x - priv->x_offset;
1665 priv->y_offset = priv->vadjust->value;
1666 ydiff = y - priv->y_offset;
1668 if ((xdiff || ydiff) && GTK_WIDGET_DRAWABLE (area)) {
1669 hildon_pannable_area_redraw (area);
1671 if ((priv->vscroll_visible) || (priv->hscroll_visible)) {
1672 priv->scroll_indicator_event_interrupt = 0;
1673 priv->scroll_delay_counter = priv->scrollbar_fade_delay;
1675 hildon_pannable_area_launch_fade_timeout (area, 1.0);
1681 hildon_pannable_area_redraw (HildonPannableArea * area)
1683 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
1685 /* Redraw scroll indicators */
1686 if (GTK_WIDGET_DRAWABLE (area)) {
1687 if (priv->hscroll_visible) {
1688 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
1689 &priv->hscroll_rect, FALSE);
1692 if (priv->vscroll_visible) {
1693 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
1694 &priv->vscroll_rect, FALSE);
1700 hildon_pannable_area_scroll_indicator_fade(HildonPannableArea * area)
1702 HildonPannableAreaPrivate *priv = area->priv;
1704 /* if moving do not fade out */
1705 if (((ABS (priv->vel_y)>priv->vmin)||
1706 (ABS (priv->vel_x)>priv->vmin))&&(!priv->button_pressed)) {
1711 if (priv->scroll_indicator_event_interrupt) {
1712 /* Stop a fade out, and fade back in */
1713 if (priv->scroll_indicator_alpha > 0.9) {
1714 priv->scroll_indicator_alpha = 1.0;
1715 priv->scroll_indicator_timeout = 0;
1719 priv->scroll_indicator_alpha += 0.2;
1720 hildon_pannable_area_redraw (area);
1726 if ((priv->scroll_indicator_alpha > 0.9) &&
1727 (priv->scroll_delay_counter > 0)) {
1728 priv->scroll_delay_counter--;
1733 if (!priv->scroll_indicator_event_interrupt) {
1734 /* Continue fade out */
1735 if (priv->scroll_indicator_alpha < 0.1) {
1736 priv->scroll_indicator_timeout = 0;
1737 priv->scroll_indicator_alpha = 0.0;
1741 priv->scroll_indicator_alpha -= 0.2;
1742 hildon_pannable_area_redraw (area);
1752 hildon_pannable_area_expose_event (GtkWidget * widget,
1753 GdkEventExpose * event)
1756 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
1757 #if USE_CAIRO_SCROLLBARS == 1
1758 GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1759 GdkColor scroll_color = widget->style->base[GTK_STATE_SELECTED];
1760 #else /* USE_CAIRO_SCROLLBARS */
1761 GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
1762 GdkColor scroll_color = priv->scroll_color;
1765 if (G_UNLIKELY (priv->initial_effect)) {
1766 hildon_pannable_area_initial_effect (widget);
1768 priv->initial_effect = FALSE;
1771 if (gtk_bin_get_child (GTK_BIN (widget))) {
1773 if (priv->scroll_indicator_alpha > 0.1) {
1774 if (priv->vscroll_visible) {
1775 hildon_pannable_draw_vscroll (widget, &back_color, &scroll_color);
1777 if (priv->hscroll_visible) {
1778 hildon_pannable_draw_hscroll (widget, &back_color, &scroll_color);
1782 /* draw overshooting rectangles */
1783 if (priv->overshot_dist_y > 0) {
1784 gint overshot_height;
1786 overshot_height = MIN (priv->overshot_dist_y, widget->allocation.height -
1787 (priv->hscroll_visible ? priv->hscroll_rect.height : 0));
1789 gdk_draw_rectangle (widget->window,
1790 widget->style->bg_gc[GTK_STATE_NORMAL],
1794 widget->allocation.width -
1795 (priv->vscroll_visible ? priv->vscroll_rect.width : 0),
1797 } else if (priv->overshot_dist_y < 0) {
1798 gint overshot_height;
1802 MAX (priv->overshot_dist_y,
1803 -(widget->allocation.height -
1804 (priv->hscroll_visible ? priv->hscroll_rect.height : 0)));
1806 overshot_y = MAX (widget->allocation.height +
1808 (priv->hscroll_visible ? priv->hscroll_rect.height : 0), 0);
1810 gdk_draw_rectangle (widget->window,
1811 widget->style->bg_gc[GTK_STATE_NORMAL],
1815 widget->allocation.width -
1816 priv->vscroll_rect.width,
1820 if (priv->overshot_dist_x > 0) {
1821 gint overshot_width;
1823 overshot_width = MIN (priv->overshot_dist_x, widget->allocation.width -
1824 (priv->vscroll_visible ? priv->vscroll_rect.width : 0));
1826 gdk_draw_rectangle (widget->window,
1827 widget->style->bg_gc[GTK_STATE_NORMAL],
1832 widget->allocation.height -
1833 (priv->hscroll_visible ? priv->hscroll_rect.height : 0));
1834 } else if (priv->overshot_dist_x < 0) {
1835 gint overshot_width;
1839 MAX (priv->overshot_dist_x,
1840 -(widget->allocation.width -
1841 (priv->vscroll_visible ? priv->vscroll_rect.width : 0)));
1843 overshot_x = MAX (widget->allocation.width +
1845 (priv->vscroll_visible ? priv->vscroll_rect.width : 0), 0);
1847 gdk_draw_rectangle (widget->window,
1848 widget->style->bg_gc[GTK_STATE_NORMAL],
1853 widget->allocation.height -
1854 priv->hscroll_rect.height);
1859 return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
1863 hildon_pannable_area_get_topmost (GdkWindow * window,
1865 gint * tx, gint * ty,
1868 /* Find the GdkWindow at the given point, by recursing from a given
1869 * parent GdkWindow. Optionally return the co-ordinates transformed
1870 * relative to the child window.
1873 GList *c, *children;
1874 GdkWindow *selected_window = NULL;
1876 gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
1877 if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
1880 children = gdk_window_peek_children (window);
1887 selected_window = window;
1890 for (c = children; c; c = c->next) {
1891 GdkWindow *child = (GdkWindow *) c->data;
1894 gdk_drawable_get_size (GDK_DRAWABLE (child), &width, &height);
1895 gdk_window_get_position (child, &wx, &wy);
1897 if ((x >= wx) && (x < (wx + width)) && (y >= wy) && (y < (wy + height)) &&
1898 (gdk_window_is_visible (child))) {
1900 if (gdk_window_peek_children (child)) {
1901 selected_window = hildon_pannable_area_get_topmost (child, x-wx, y-wy,
1903 if (!selected_window) {
1908 selected_window = child;
1911 if ((gdk_window_get_events (child)&mask)) {
1916 selected_window = child;
1922 return selected_window;
1926 synth_crossing (GdkWindow * child,
1928 gint x_root, gint y_root,
1929 guint32 time, gboolean in)
1931 GdkEventCrossing *crossing_event;
1932 GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
1934 /* Send synthetic enter event */
1935 crossing_event = (GdkEventCrossing *) gdk_event_new (type);
1936 ((GdkEventAny *) crossing_event)->type = type;
1937 ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
1938 ((GdkEventAny *) crossing_event)->send_event = FALSE;
1939 crossing_event->subwindow = g_object_ref (child);
1940 crossing_event->time = time;
1941 crossing_event->x = x;
1942 crossing_event->y = y;
1943 crossing_event->x_root = x_root;
1944 crossing_event->y_root = y_root;
1945 crossing_event->mode = GDK_CROSSING_NORMAL;
1946 crossing_event->detail = GDK_NOTIFY_UNKNOWN;
1947 crossing_event->focus = FALSE;
1948 crossing_event->state = 0;
1949 gdk_event_put ((GdkEvent *) crossing_event);
1950 gdk_event_free ((GdkEvent *) crossing_event);
1954 hildon_pannable_area_button_press_cb (GtkWidget * widget,
1955 GdkEventButton * event)
1958 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
1959 HildonPannableAreaPrivate *priv = area->priv;
1961 if ((!priv->enabled) || (event->button != 1) ||
1962 ((event->time == priv->last_time) &&
1963 (priv->last_type == 1)) || (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
1966 priv->scroll_indicator_event_interrupt = 1;
1968 hildon_pannable_area_launch_fade_timeout (area,
1969 priv->scroll_indicator_alpha);
1971 priv->last_time = event->time;
1972 priv->last_press_time = event->time;
1973 priv->last_type = 1;
1975 priv->scroll_to_x = -1;
1976 priv->scroll_to_y = -1;
1978 if (priv->button_pressed && priv->child) {
1979 /* Widget stole focus on last click, send crossing-out event */
1980 synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
1981 event->time, FALSE);
1989 /* Don't allow a click if we're still moving fast */
1990 if ((ABS (priv->vel_x) <= (priv->vmax * priv->vfast_factor)) &&
1991 (ABS (priv->vel_y) <= (priv->vmax * priv->vfast_factor)))
1993 hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
1994 event->x, event->y, &x, &y, GDK_BUTTON_PRESS_MASK);
1998 priv->button_pressed = TRUE;
2000 /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
2001 priv->old_vel_x = priv->vel_x;
2002 priv->old_vel_y = priv->vel_y;
2005 if (priv->idle_id) {
2006 g_source_remove (priv->idle_id);
2008 g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
2013 gdk_drawable_get_size (priv->child, &priv->child_width,
2014 &priv->child_height);
2015 priv->last_in = TRUE;
2017 g_object_add_weak_pointer ((GObject *) priv->child,
2018 (gpointer) & priv->child);
2020 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
2021 /* remove the reference we added with the copy */
2022 g_object_unref (priv->event_window);
2028 synth_crossing (priv->child, x, y, event->x_root,
2029 event->y_root, event->time, TRUE);
2031 /* Send synthetic click (button press/release) event */
2032 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
2034 gdk_event_put ((GdkEvent *) event);
2035 gdk_event_free ((GdkEvent *) event);
2043 hildon_pannable_area_check_scrollbars (HildonPannableArea * area)
2045 HildonPannableAreaPrivate *priv = area->priv;
2046 gboolean prev_hscroll_visible, prev_vscroll_visible;
2048 prev_hscroll_visible = priv->hscroll_visible;
2049 prev_vscroll_visible = priv->vscroll_visible;
2051 if (!gtk_bin_get_child (GTK_BIN (area))) {
2052 priv->vscroll_visible = FALSE;
2053 priv->hscroll_visible = FALSE;
2055 switch (priv->hscrollbar_policy) {
2056 case GTK_POLICY_ALWAYS:
2057 priv->hscroll_visible = TRUE;
2059 case GTK_POLICY_NEVER:
2060 priv->hscroll_visible = FALSE;
2063 priv->hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2064 priv->hadjust->page_size);
2067 switch (priv->vscrollbar_policy) {
2068 case GTK_POLICY_ALWAYS:
2069 priv->vscroll_visible = TRUE;
2071 case GTK_POLICY_NEVER:
2072 priv->vscroll_visible = FALSE;
2075 priv->vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2076 priv->vadjust->page_size);
2079 /* Store the vscroll/hscroll areas for redrawing */
2080 if (priv->vscroll_visible) {
2081 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
2082 priv->vscroll_rect.x = allocation->width - priv->indicator_width;
2083 priv->vscroll_rect.y = 0;
2084 priv->vscroll_rect.width = priv->indicator_width;
2085 priv->vscroll_rect.height = allocation->height -
2086 (priv->hscroll_visible ? priv->indicator_width : 0);
2088 if (priv->hscroll_visible) {
2089 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
2090 priv->hscroll_rect.y = allocation->height - priv->indicator_width;
2091 priv->hscroll_rect.x = 0;
2092 priv->hscroll_rect.height = priv->indicator_width;
2093 priv->hscroll_rect.width = allocation->width -
2094 (priv->vscroll_visible ? priv->indicator_width : 0);
2098 return ((priv->hscroll_visible != prev_hscroll_visible) ||
2099 (priv->vscroll_visible != prev_vscroll_visible));
2103 hildon_pannable_area_refresh (HildonPannableArea * area)
2105 if (GTK_WIDGET_DRAWABLE (area) &&
2106 hildon_pannable_area_check_scrollbars (area)) {
2107 HildonPannableAreaPrivate *priv = area->priv;
2109 gtk_widget_queue_resize (GTK_WIDGET (area));
2111 if ((priv->vscroll_visible) || (priv->hscroll_visible)) {
2112 priv->scroll_indicator_event_interrupt = 0;
2113 priv->scroll_delay_counter = area->priv->scrollbar_fade_delay;
2115 hildon_pannable_area_launch_fade_timeout (area, 1.0);
2118 hildon_pannable_area_redraw (area);
2122 /* Scroll by a particular amount (in pixels). Optionally, return if
2123 * the scroll on a particular axis was successful.
2126 hildon_pannable_axis_scroll (HildonPannableArea *area,
2127 GtkAdjustment *adjust,
2131 gint *overshot_dist,
2137 HildonPannableAreaPrivate *priv = area->priv;
2139 dist = gtk_adjustment_get_value (adjust) - inc;
2142 * We use overshot_dist to define the distance of the current overshoot,
2143 * and overshooting to define the direction/whether or not we are overshot
2145 if (!(*overshooting)) {
2147 /* Initiation of the overshoot happens when the finger is released
2148 * and the current position of the pannable contents are out of range
2150 if (dist < adjust->lower) {
2153 dist = adjust->lower;
2155 if (overshoot_max!=0) {
2158 *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
2159 *vel = MIN (priv->vmax_overshooting, *vel);
2160 gtk_widget_queue_resize (GTK_WIDGET (area));
2164 } else if (dist > adjust->upper - adjust->page_size) {
2167 dist = adjust->upper - adjust->page_size;
2169 if (overshoot_max!=0) {
2172 *overshot_dist = CLAMP (*overshot_dist + *vel, -overshoot_max, 0);
2173 *vel = MAX (-priv->vmax_overshooting, *vel);
2174 gtk_widget_queue_resize (GTK_WIDGET (area));
2179 if ((*scroll_to) != -1) {
2180 if (((inc < 0)&&(*scroll_to <= dist))||
2181 ((inc > 0)&&(*scroll_to >= dist))) {
2189 adjust->value = dist;
2191 if (!priv->button_pressed) {
2193 /* When the overshoot has started we continue for
2194 * PROP_BOUNCE_STEPS more steps into the overshoot before we
2195 * reverse direction. The deceleration factor is calculated
2196 * based on the percentage distance from the first item with
2197 * each iteration, therefore always returning us to the
2198 * top/bottom most element
2200 if (*overshot_dist > 0) {
2202 if ((*overshooting < priv->bounce_steps) && (*vel > 0)) {
2204 *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel);
2205 } else if ((*overshooting >= priv->bounce_steps) && (*vel > 0)) {
2207 } else if ((*overshooting > 1) && (*vel < 0)) {
2208 /* we add the MIN in order to avoid very small speeds */
2209 *vel = MIN (((((gdouble)*overshot_dist)*0.8) * -1), -10.0);
2212 *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
2214 gtk_widget_queue_resize (GTK_WIDGET (area));
2216 } else if (*overshot_dist < 0) {
2218 if ((*overshooting < priv->bounce_steps) && (*vel < 0)) {
2220 *vel = (((gdouble)*overshot_dist)/overshoot_max) * (*vel) * -1;
2221 } else if ((*overshooting >= priv->bounce_steps) && (*vel < 0)) {
2223 } else if ((*overshooting > 1) && (*vel > 0)) {
2224 /* we add the MAX in order to avoid very small speeds */
2225 *vel = MAX (((((gdouble)*overshot_dist)*0.8) * -1), 10.0);
2228 *overshot_dist = CLAMP (*overshot_dist + (*vel), -overshoot_max, 0);
2230 gtk_widget_queue_resize (GTK_WIDGET (area));
2235 gtk_widget_queue_resize (GTK_WIDGET (area));
2239 gint overshot_dist_old = *overshot_dist;
2241 if (*overshot_dist > 0) {
2242 *overshot_dist = CLAMP ((*overshot_dist) + inc, 0, overshoot_max);
2243 } else if (*overshot_dist < 0) {
2244 *overshot_dist = CLAMP ((*overshot_dist) + inc, -1 * overshoot_max, 0);
2247 adjust->value = CLAMP (dist,
2253 if (*overshot_dist != overshot_dist_old)
2254 gtk_widget_queue_resize (GTK_WIDGET (area));
2260 hildon_pannable_area_scroll (HildonPannableArea *area,
2261 gdouble x, gdouble y)
2264 HildonPannableAreaPrivate *priv = area->priv;
2265 gboolean hscroll_visible, vscroll_visible;
2268 if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
2271 vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2272 priv->vadjust->page_size);
2273 hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2274 priv->hadjust->page_size);
2279 hv = priv->hadjust->value;
2280 vv = priv->vadjust->value;
2282 if (vscroll_visible) {
2283 hildon_pannable_axis_scroll (area, priv->vadjust, &priv->vel_y, y,
2284 &priv->overshooting_y, &priv->overshot_dist_y,
2285 &priv->scroll_to_y, priv->vovershoot_max, &sy);
2290 if (hscroll_visible) {
2291 hildon_pannable_axis_scroll (area, priv->hadjust, &priv->vel_x, x,
2292 &priv->overshooting_x, &priv->overshot_dist_x,
2293 &priv->scroll_to_x, priv->hovershoot_max, &sx);
2298 if (hv != priv->hadjust->value)
2299 gtk_adjustment_value_changed (priv->hadjust);
2301 if (vv != priv->vadjust->value)
2302 gtk_adjustment_value_changed (priv->vadjust);
2304 /* If the scroll on a particular axis wasn't succesful, reset the
2305 * initial scroll position to the new mouse co-ordinate. This means
2306 * when you get to the top of the page, dragging down works immediately.
2308 if (priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
2320 hildon_pannable_area_timeout (HildonPannableArea * area)
2322 HildonPannableAreaPrivate *priv = area->priv;
2324 if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
2326 g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
2331 hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
2333 gdk_window_process_updates (GTK_WIDGET (area)->window, FALSE);
2335 if (!priv->button_pressed) {
2336 /* Decelerate gradually when pointer is raised */
2337 if ((!priv->overshot_dist_y) &&
2338 (!priv->overshot_dist_x)) {
2340 /* in case we move to a specific point do not decelerate when arriving */
2341 if ((priv->scroll_to_x != -1)||(priv->scroll_to_y != -1)) {
2343 if (ABS (priv->vel_x) >= 1.5) {
2344 priv->vel_x *= priv->decel;
2347 if (ABS (priv->vel_y) >= 1.5) {
2348 priv->vel_y *= priv->decel;
2352 if ((!priv->low_friction_mode) ||
2353 ((priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) &&
2354 (ABS (priv->vel_x) < 0.8*priv->vmax)))
2355 priv->vel_x *= priv->decel;
2357 if ((!priv->low_friction_mode) ||
2358 ((priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) &&
2359 (ABS (priv->vel_y) < 0.8*priv->vmax)))
2360 priv->vel_y *= priv->decel;
2362 if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
2367 g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
2373 } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
2383 hildon_pannable_area_calculate_velocity (gdouble *vel,
2387 gdouble drag_inertia,
2393 if (ABS (dist) >= RATIO_TOLERANCE) {
2394 rawvel = (dist / ABS (delta)) * force;
2395 *vel = *vel * (1 - drag_inertia) +
2396 rawvel * drag_inertia;
2397 *vel = *vel > 0 ? MIN (*vel, vmax)
2398 : MAX (*vel, -1 * vmax);
2403 hildon_pannable_area_motion_event_scroll_timeout (HildonPannableArea *area)
2405 HildonPannableAreaPrivate *priv = area->priv;
2407 if ((priv->motion_x != 0)||(priv->motion_y != 0))
2408 hildon_pannable_area_scroll (area, priv->motion_x, priv->motion_y);
2410 priv->motion_event_scroll_timeout = 0;
2416 hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
2417 gdouble x, gdouble y)
2419 HildonPannableAreaPrivate *priv = area->priv;
2421 if (priv->motion_event_scroll_timeout) {
2423 priv->motion_x += x;
2424 priv->motion_y += y;
2428 /* we do not delay the first event but the next ones */
2429 hildon_pannable_area_scroll (area, x, y);
2434 priv->motion_event_scroll_timeout = gdk_threads_add_timeout_full
2435 (G_PRIORITY_HIGH_IDLE + 20,
2436 (gint) (1000.0 / (gdouble) MOTION_EVENTS_PER_SECOND),
2437 (GSourceFunc) hildon_pannable_area_motion_event_scroll_timeout, area, NULL);
2442 hildon_pannable_area_check_move (HildonPannableArea *area,
2443 GdkEventMotion * event,
2447 HildonPannableAreaPrivate *priv = area->priv;
2449 if (priv->first_drag && (!priv->moved) &&
2450 ((ABS (*x) > (priv->panning_threshold))
2451 || (ABS (*y) > (priv->panning_threshold)))) {
2456 if (priv->first_drag) {
2457 gboolean vscroll_visible;
2458 gboolean hscroll_visible;
2460 if (ABS (priv->iy - event->y) >=
2461 ABS (priv->ix - event->x)) {
2463 g_signal_emit (area,
2464 pannable_area_signals[VERTICAL_MOVEMENT],
2465 0, (priv->iy > event->y) ?
2466 HILDON_MOVEMENT_UP :
2467 HILDON_MOVEMENT_DOWN,
2468 (gdouble)priv->ix, (gdouble)priv->iy);
2470 vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2471 priv->vadjust->page_size);
2473 if (!((vscroll_visible)&&
2474 (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT))) {
2476 hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2477 priv->hadjust->page_size);
2479 /* even in case we do not have to move we check if this
2480 could be a fake horizontal movement */
2481 if (!((hscroll_visible)&&
2482 (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ)) ||
2483 (ABS (priv->iy - event->y) -
2484 ABS (priv->ix - event->x) >= priv->direction_error_margin))
2485 priv->moved = FALSE;
2489 g_signal_emit (area,
2490 pannable_area_signals[HORIZONTAL_MOVEMENT],
2491 0, (priv->ix > event->x) ?
2492 HILDON_MOVEMENT_LEFT :
2493 HILDON_MOVEMENT_RIGHT,
2494 (gdouble)priv->ix, (gdouble)priv->iy);
2496 hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
2497 priv->hadjust->page_size);
2499 if (!((hscroll_visible)&&
2500 (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ))) {
2502 vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
2503 priv->vadjust->page_size);
2505 /* even in case we do not have to move we check if this
2506 could be a fake vertical movement */
2507 if (!((vscroll_visible) &&
2508 (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT)) ||
2509 (ABS (priv->ix - event->x) -
2510 ABS (priv->iy - event->y) >= priv->direction_error_margin))
2511 priv->moved = FALSE;
2515 if ((priv->moved)&&(priv->child)) {
2518 pos_x = priv->cx + (event->x - priv->ix);
2519 pos_y = priv->cy + (event->y - priv->iy);
2521 synth_crossing (priv->child, pos_x, pos_y, event->x_root,
2522 event->y_root, event->time, FALSE);
2526 gboolean result_val;
2528 g_signal_emit (area,
2529 pannable_area_signals[PANNING_STARTED],
2532 priv->moved = !result_val;
2536 priv->first_drag = FALSE;
2538 if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
2539 (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
2542 priv->idle_id = gdk_threads_add_timeout_full
2543 (G_PRIORITY_HIGH_IDLE + 20,
2544 (gint)(1000.0 / (gdouble) priv->sps),
2546 hildon_pannable_area_timeout, area, NULL);
2552 hildon_pannable_area_handle_move (HildonPannableArea *area,
2553 GdkEventMotion * event,
2557 HildonPannableAreaPrivate *priv = area->priv;
2560 switch (priv->mode) {
2561 case HILDON_PANNABLE_AREA_MODE_PUSH:
2562 /* Scroll by the amount of pixels the cursor has moved
2563 * since the last motion event.
2565 hildon_pannable_area_motion_event_scroll (area, *x, *y);
2569 case HILDON_PANNABLE_AREA_MODE_ACCEL:
2570 /* Set acceleration relative to the initial click */
2571 priv->ex = event->x;
2572 priv->ey = event->y;
2573 priv->vel_x = ((*x > 0) ? 1 : -1) *
2575 (gdouble) GTK_WIDGET (area)->allocation.width) *
2576 (priv->vmax - priv->vmin)) + priv->vmin);
2577 priv->vel_y = ((*y > 0) ? 1 : -1) *
2579 (gdouble) GTK_WIDGET (area)->allocation.height) *
2580 (priv->vmax - priv->vmin)) + priv->vmin);
2582 case HILDON_PANNABLE_AREA_MODE_AUTO:
2584 delta = event->time - priv->last_time;
2586 if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) {
2587 gdouble dist = event->y - priv->y;
2589 hildon_pannable_area_calculate_velocity (&priv->vel_y,
2601 if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) {
2602 gdouble dist = event->x - priv->x;
2604 hildon_pannable_area_calculate_velocity (&priv->vel_x,
2616 hildon_pannable_area_motion_event_scroll (area, *x, *y);
2618 if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ)
2620 if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT)
2630 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
2631 GdkEventMotion * event)
2633 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
2634 HildonPannableAreaPrivate *priv = area->priv;
2637 if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
2640 if ((!priv->enabled) || (!priv->button_pressed) ||
2641 ((event->time == priv->last_time) && (priv->last_type == 2))) {
2642 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
2646 if (priv->last_type == 1) {
2647 priv->first_drag = TRUE;
2650 x = event->x - priv->x;
2651 y = event->y - priv->y;
2654 hildon_pannable_area_check_move (area, event, &x, &y);
2658 hildon_pannable_area_handle_move (area, event, &x, &y);
2659 } else if (priv->child) {
2663 pos_x = priv->cx + (event->x - priv->ix);
2664 pos_y = priv->cy + (event->y - priv->iy);
2666 in = (((0 <= pos_x)&&(priv->child_width >= pos_x)) &&
2667 ((0 <= pos_y)&&(priv->child_height >= pos_y)));
2669 if (((!priv->last_in)&&in)||((priv->last_in)&&(!in))) {
2671 synth_crossing (priv->child, pos_x, pos_y, event->x_root,
2672 event->y_root, event->time, in);
2678 priv->last_time = event->time;
2679 priv->last_type = 2;
2682 /* Send motion notify to child */
2683 event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
2684 /* remove the reference we added with the copy */
2685 g_object_unref (priv->event_window);
2686 event->x = priv->cx + (event->x - priv->ix);
2687 event->y = priv->cy + (event->y - priv->iy);
2688 event->window = g_object_ref (priv->child);
2689 gdk_event_put ((GdkEvent *) event);
2690 gdk_event_free ((GdkEvent *) event);
2693 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
2699 hildon_pannable_leave_notify_event (GtkWidget *widget,
2700 GdkEventCrossing *event)
2702 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
2703 HildonPannableAreaPrivate *priv = area->priv;
2705 if ((priv->child)&&(priv->last_in)) {
2706 priv->last_in = FALSE;
2708 synth_crossing (priv->child, 0, 0, event->x_root,
2709 event->y_root, event->time, FALSE);
2716 hildon_pannable_area_button_release_cb (GtkWidget * widget,
2717 GdkEventButton * event)
2719 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
2720 HildonPannableAreaPrivate *priv = area->priv;
2724 gboolean force_fast = TRUE;
2726 if (((event->time == priv->last_time) && (priv->last_type == 3))
2727 || (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
2728 || (!priv->button_pressed) || (!priv->enabled) || (event->button != 1))
2731 /* if last event was a motion-notify we have to check the movement
2732 and launch the animation */
2733 if (priv->last_type == 2) {
2735 dx = event->x - priv->x;
2736 dy = event->y - priv->y;
2738 hildon_pannable_area_check_move (area, (GdkEventMotion *) event, &dx, &dy);
2741 gdouble delta = event->time - priv->last_time;
2743 hildon_pannable_area_handle_move (area, (GdkEventMotion *) event, &dx, &dy);
2745 /* move all the way to the last position now */
2746 if (priv->motion_event_scroll_timeout) {
2747 g_source_remove (priv->motion_event_scroll_timeout);
2748 hildon_pannable_area_motion_event_scroll_timeout (HILDON_PANNABLE_AREA (widget));
2753 if ((ABS (dx) < 4.0) && (delta >= CURSOR_STOPPED_TIMEOUT))
2756 if ((ABS (dy) < 4.0) && (delta >= CURSOR_STOPPED_TIMEOUT))
2761 /* If overshoot has been initiated with a finger down, on release set max speed */
2762 if (priv->overshot_dist_y != 0) {
2763 priv->overshooting_y = priv->bounce_steps; /* Hack to stop a bounce in the finger down case */
2764 priv->vel_y = priv->overshot_dist_y * 0.9;
2767 if (priv->overshot_dist_x != 0) {
2768 priv->overshooting_x = priv->bounce_steps; /* Hack to stop a bounce in the finger down case */
2769 priv->vel_x = priv->overshot_dist_x * 0.9;
2772 priv->button_pressed = FALSE;
2774 /* if widget was moving fast in the panning, increase speed even more */
2775 if ((event->time - priv->last_press_time < FAST_CLICK) &&
2776 ((ABS (priv->old_vel_x) > priv->vmin) ||
2777 (ABS (priv->old_vel_y) > priv->vmin)) &&
2778 ((ABS (priv->old_vel_x) > MIN_ACCEL_THRESHOLD) ||
2779 (ABS (priv->old_vel_y) > MIN_ACCEL_THRESHOLD)))
2783 if (priv->vel_x != 0)
2784 symbol = ((priv->vel_x * priv->old_vel_x) > 0) ? 1 : -1;
2786 priv->vel_x = symbol *
2787 (priv->old_vel_x + ((priv->old_vel_x > 0) ? priv->accel_vel_x
2788 : -priv->accel_vel_x));
2792 if (priv->vel_y != 0)
2793 symbol = ((priv->vel_y * priv->old_vel_y) > 0) ? 1 : -1;
2795 priv->vel_y = symbol *
2796 (priv->old_vel_y + ((priv->old_vel_y > 0) ? priv->accel_vel_y
2797 : -priv->accel_vel_y));
2802 if ((ABS (priv->vel_y) >= priv->vmin) ||
2803 (ABS (priv->vel_x) >= priv->vmin)) {
2805 /* we have to move because we are in overshooting position*/
2807 gboolean result_val;
2809 g_signal_emit (area,
2810 pannable_area_signals[PANNING_STARTED],
2814 priv->scroll_indicator_alpha = 1.0;
2817 if ((ABS (priv->vel_x) > MAX_SPEED_THRESHOLD) &&
2818 (priv->accel_vel_x > MAX_SPEED_THRESHOLD))
2819 priv->vel_x = (priv->vel_x > 0) ? priv->accel_vel_x : -priv->accel_vel_x;
2821 if ((ABS (priv->vel_y) > MAX_SPEED_THRESHOLD) &&
2822 (priv->accel_vel_y > MAX_SPEED_THRESHOLD))
2823 priv->vel_y = (priv->vel_y > 0) ? priv->accel_vel_y : -priv->accel_vel_y;
2827 priv->idle_id = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
2828 (gint) (1000.0 / (gdouble) priv->sps),
2829 (GSourceFunc) hildon_pannable_area_timeout,
2832 if (priv->center_on_child_focus_pending) {
2833 hildon_pannable_area_center_on_child_focus (area);
2837 g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
2840 area->priv->center_on_child_focus_pending = FALSE;
2842 priv->scroll_indicator_event_interrupt = 0;
2843 priv->scroll_delay_counter = priv->scrollbar_fade_delay;
2845 hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
2846 priv->scroll_indicator_alpha);
2848 priv->last_time = event->time;
2849 priv->last_type = 3;
2852 priv->moved = FALSE;
2857 hildon_pannable_area_get_topmost (gtk_bin_get_child (GTK_BIN (widget))->window,
2858 event->x, event->y, &x, &y, GDK_BUTTON_RELEASE_MASK);
2860 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
2861 /* remove the reference we added with the copy */
2862 g_object_unref (priv->event_window);
2866 /* Leave the widget if we've moved - This doesn't break selection,
2867 * but stops buttons from being clicked.
2869 if ((child != priv->child) || (priv->moved)) {
2870 /* Send synthetic leave event */
2871 synth_crossing (priv->child, x, y, event->x_root,
2872 event->y_root, event->time, FALSE);
2873 /* insure no click will happen for widgets that do not handle
2877 /* Send synthetic button release event */
2878 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
2879 gdk_event_put ((GdkEvent *) event);
2881 /* Send synthetic button release event */
2882 ((GdkEventAny *) event)->window = g_object_ref (child);
2883 gdk_event_put ((GdkEvent *) event);
2884 /* Send synthetic leave event */
2885 synth_crossing (priv->child, x, y, event->x_root,
2886 event->y_root, event->time, FALSE);
2888 g_object_remove_weak_pointer ((GObject *) priv->child,
2889 (gpointer) & priv->child);
2891 priv->moved = FALSE;
2892 gdk_event_free ((GdkEvent *) event);
2897 /* utility event handler */
2899 hildon_pannable_area_scroll_cb (GtkWidget *widget,
2900 GdkEventScroll *event)
2902 GtkAdjustment *adj = NULL;
2903 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
2905 if ((!priv->enabled) ||
2906 (gtk_bin_get_child (GTK_BIN (widget)) == NULL))
2909 priv->scroll_indicator_event_interrupt = 0;
2910 priv->scroll_delay_counter = priv->scrollbar_fade_delay + 20;
2912 hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget), 1.0);
2914 /* Stop inertial scrolling */
2915 if (priv->idle_id) {
2918 priv->overshooting_x = 0;
2919 priv->overshooting_y = 0;
2921 if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
2922 priv->overshot_dist_x = 0;
2923 priv->overshot_dist_y = 0;
2925 gtk_widget_queue_resize (GTK_WIDGET (widget));
2928 g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
2930 g_source_remove (priv->idle_id);
2934 if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN)
2935 adj = priv->vadjust;
2937 adj = priv->hadjust;
2941 gdouble delta, new_value;
2943 /* from gtkrange.c calculate delta*/
2944 delta = pow (adj->page_size, 2.0 / 3.0);
2946 if (event->direction == GDK_SCROLL_UP ||
2947 event->direction == GDK_SCROLL_LEFT)
2950 new_value = CLAMP (adj->value + delta, adj->lower, adj->upper - adj->page_size);
2952 gtk_adjustment_set_value (adj, new_value);
2959 hildon_pannable_area_child_mapped (GtkWidget *widget,
2963 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (user_data)->priv;
2965 if (priv->event_window != NULL && priv->enabled)
2966 gdk_window_raise (priv->event_window);
2970 hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
2972 HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (container)->priv;
2974 g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == NULL);
2976 gtk_widget_set_parent (child, GTK_WIDGET (container));
2977 GTK_BIN (container)->child = child;
2979 g_signal_connect_after (child, "map-event",
2980 G_CALLBACK (hildon_pannable_area_child_mapped),
2983 if (!gtk_widget_set_scroll_adjustments (child, priv->hadjust, priv->vadjust)) {
2984 g_warning ("%s: cannot add non scrollable widget, "
2985 "wrap it in a viewport", __FUNCTION__);
2989 /* call this function if you are not panning */
2991 hildon_pannable_area_center_on_child_focus (HildonPannableArea *area)
2993 GtkWidget *focused_child = NULL;
2994 GtkWidget *window = NULL;
2996 window = gtk_widget_get_toplevel (GTK_WIDGET (area));
2998 if (GTK_WIDGET_TOPLEVEL (window)) {
2999 focused_child = gtk_window_get_focus (GTK_WINDOW (window));
3002 if (focused_child) {
3003 hildon_pannable_area_scroll_to_child (area, focused_child);
3008 hildon_pannable_area_set_focus_child (GtkContainer *container,
3011 HildonPannableArea *area = HILDON_PANNABLE_AREA (container);
3013 if (!area->priv->center_on_child_focus) {
3017 if (GTK_IS_WIDGET (child)) {
3018 area->priv->center_on_child_focus_pending = TRUE;
3023 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
3025 g_return_if_fail (HILDON_IS_PANNABLE_AREA (container));
3026 g_return_if_fail (child != NULL);
3027 g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == child);
3029 gtk_widget_set_scroll_adjustments (child, NULL, NULL);
3031 g_signal_handlers_disconnect_by_func (child,
3032 hildon_pannable_area_child_mapped,
3035 /* chain parent class handler to remove child */
3036 GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->remove (container, child);
3040 * This method calculates a factor necessary to determine the initial distance
3041 * to jump in hildon_pannable_area_scroll_to(). For fixed time and frames per
3042 * second, we know in how many frames 'n' we need to reach the destination
3043 * point. We know that, for a distance d,
3045 * d = d_0 + d_1 + ... + d_n
3047 * where d_i is the distance travelled in the i-th frame and decel_factor is
3048 * the deceleration factor. This can be rewritten as
3050 * d = d_0 + (d_0 * decel_factor) + ... + (d_n-1 * decel_factor),
3052 * since the distance travelled on each frame is the distance travelled in the
3053 * previous frame reduced by the deceleration factor. Reducing this and
3054 * factoring d_0 out, we get
3056 * d = d_0 (1 + decel_factor + ... + decel_factor^(n-1)).
3058 * Since the sum is independent of the distance to be travelled, we can define
3061 * vel_factor = 1 + decel_factor + ... + decel_factor^(n-1).
3063 * That's the gem we calculate in this method.
3066 hildon_pannable_calculate_vel_factor (HildonPannableArea * self)
3068 HildonPannableAreaPrivate *priv = self->priv;
3073 n = ceil (priv->sps * priv->scroll_time);
3075 for (i = 1; i < n && fct_i >= RATIO_TOLERANCE; i++) {
3076 fct_i *= priv->decel;
3080 priv->vel_factor = fct;
3084 * hildon_pannable_area_new:
3086 * Create a new pannable area widget
3088 * Returns: the newly created #HildonPannableArea
3094 hildon_pannable_area_new (void)
3096 return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
3100 * hildon_pannable_area_new_full:
3101 * @mode: #HildonPannableAreaMode
3102 * @enabled: Value for the enabled property
3103 * @vel_min: Value for the velocity-min property
3104 * @vel_max: Value for the velocity-max property
3105 * @decel: Value for the deceleration property
3106 * @sps: Value for the sps property
3108 * Create a new #HildonPannableArea widget and set various properties
3110 * returns: the newly create #HildonPannableArea
3116 hildon_pannable_area_new_full (gint mode, gboolean enabled,
3117 gdouble vel_min, gdouble vel_max,
3118 gdouble decel, guint sps)
3120 return g_object_new (HILDON_TYPE_PANNABLE_AREA,
3123 "velocity_min", vel_min,
3124 "velocity_max", vel_max,
3125 "deceleration", decel, "sps", sps, NULL);
3129 * hildon_pannable_area_add_with_viewport:
3130 * @area: A #HildonPannableArea
3131 * @child: Child widget to add to the viewport
3133 * Convenience function used to add a child to a #GtkViewport, and add the
3134 * viewport to the scrolled window.
3140 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
3144 GtkWidget *viewport;
3146 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3147 g_return_if_fail (GTK_IS_WIDGET (child));
3148 g_return_if_fail (child->parent == NULL);
3150 bin = GTK_BIN (area);
3152 if (bin->child != NULL)
3154 g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
3155 g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
3157 viewport = bin->child;
3161 HildonPannableAreaPrivate *priv = area->priv;
3163 viewport = gtk_viewport_new (priv->hadjust,
3165 gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
3166 gtk_container_add (GTK_CONTAINER (area), viewport);
3169 gtk_widget_show (viewport);
3170 gtk_container_add (GTK_CONTAINER (viewport), child);
3174 * hildon_pannable_area_scroll_to:
3175 * @area: A #HildonPannableArea.
3176 * @x: The x coordinate of the destination point or -1 to ignore this axis.
3177 * @y: The y coordinate of the destination point or -1 to ignore this axis.
3179 * Smoothly scrolls @area to ensure that (@x, @y) is a visible point
3180 * on the widget. To move in only one coordinate, you must set the other one
3181 * to -1. Notice that, in %HILDON_PANNABLE_AREA_MODE_PUSH mode, this function
3182 * works just like hildon_pannable_area_jump_to().
3184 * This function is useful if you need to present the user with a particular
3185 * element inside a scrollable widget, like #GtkTreeView. For instance,
3186 * the following example shows how to scroll inside a #GtkTreeView to
3187 * make visible an item, indicated by the #GtkTreeIter @iter.
3191 * GtkTreePath *path;
3192 * GdkRectangle *rect;
3194 * path = gtk_tree_model_get_path (model, &iter);
3195 * gtk_tree_view_get_background_area (GTK_TREE_VIEW (treeview),
3196 * path, NULL, &rect);
3197 * gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (treeview),
3198 * 0, rect.y, NULL, &y);
3199 * hildon_pannable_area_scroll_to (panarea, -1, y);
3200 * gtk_tree_path_free (path);
3204 * If you want to present a child widget in simpler scenarios,
3205 * use hildon_pannable_area_scroll_to_child() instead.
3207 * There is a precondition to this function: the widget must be
3208 * already realized. Check the hildon_pannable_area_jump_to_child() for
3209 * more tips regarding how to call this function during
3215 hildon_pannable_area_scroll_to (HildonPannableArea *area,
3216 const gint x, const gint y)
3218 HildonPannableAreaPrivate *priv;
3220 gint dist_x, dist_y;
3221 gboolean hscroll_visible, vscroll_visible;
3223 g_return_if_fail (GTK_WIDGET_REALIZED (area));
3224 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3228 vscroll_visible = (priv->vadjust->upper - priv->vadjust->lower >
3229 priv->vadjust->page_size);
3230 hscroll_visible = (priv->hadjust->upper - priv->hadjust->lower >
3231 priv->hadjust->page_size);
3233 if (((!vscroll_visible)&&(!hscroll_visible)) ||
3234 (x == -1 && y == -1)) {
3238 if (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)
3239 hildon_pannable_area_jump_to (area, x, y);
3241 width = priv->hadjust->upper - priv->hadjust->lower;
3242 height = priv->vadjust->upper - priv->vadjust->lower;
3244 g_return_if_fail (x < width || y < height);
3246 if ((x > -1)&&(hscroll_visible)) {
3247 priv->scroll_to_x = CLAMP (x - priv->hadjust->page_size/2,
3248 priv->hadjust->lower,
3249 priv->hadjust->upper - priv->hadjust->page_size);
3250 dist_x = priv->scroll_to_x - priv->hadjust->value;
3252 priv->scroll_to_x = -1;
3254 priv->vel_x = - dist_x/priv->vel_factor;
3257 priv->scroll_to_x = -1;
3260 if ((y > -1)&&(vscroll_visible)) {
3261 priv->scroll_to_y = CLAMP (y - priv->vadjust->page_size/2,
3262 priv->vadjust->lower,
3263 priv->vadjust->upper - priv->vadjust->page_size);
3264 dist_y = priv->scroll_to_y - priv->vadjust->value;
3266 priv->scroll_to_y = -1;
3268 priv->vel_y = - dist_y/priv->vel_factor;
3271 priv->scroll_to_y = y;
3274 if ((priv->scroll_to_y == -1) && (priv->scroll_to_x == -1)) {
3278 hildon_pannable_area_launch_fade_timeout (area, 1.0);
3281 priv->idle_id = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
3282 (gint) (1000.0 / (gdouble) priv->sps),
3283 (GSourceFunc) hildon_pannable_area_timeout,
3288 * hildon_pannable_area_jump_to:
3289 * @area: A #HildonPannableArea.
3290 * @x: The x coordinate of the destination point or -1 to ignore this axis.
3291 * @y: The y coordinate of the destination point or -1 to ignore this axis.
3293 * Jumps the position of @area to ensure that (@x, @y) is a visible
3294 * point in the widget. In order to move in only one coordinate, you
3295 * must set the other one to -1. See hildon_pannable_area_scroll_to()
3296 * function for an example of how to calculate the position of
3297 * children in scrollable widgets like #GtkTreeview.
3299 * There is a precondition to this function: the widget must be
3300 * already realized. Check the hildon_pannable_area_jump_to_child() for
3301 * more tips regarding how to call this function during
3307 hildon_pannable_area_jump_to (HildonPannableArea *area,
3308 const gint x, const gint y)
3310 HildonPannableAreaPrivate *priv;
3314 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3315 g_return_if_fail (GTK_WIDGET_REALIZED (area));
3316 g_return_if_fail (x >= -1 && y >= -1);
3318 if (x == -1 && y == -1) {
3324 width = priv->hadjust->upper - priv->hadjust->lower;
3325 height = priv->vadjust->upper - priv->vadjust->lower;
3327 g_return_if_fail (x < width || y < height);
3329 hv = priv->hadjust->value;
3330 vv = priv->vadjust->value;
3333 gdouble jump_to = x - priv->hadjust->page_size/2;
3335 priv->hadjust->value = CLAMP (jump_to,
3336 priv->hadjust->lower,
3337 priv->hadjust->upper -
3338 priv->hadjust->page_size);
3342 gdouble jump_to = y - priv->vadjust->page_size/2;
3344 priv->vadjust->value = CLAMP (jump_to,
3345 priv->vadjust->lower,
3346 priv->vadjust->upper -
3347 priv->vadjust->page_size);
3350 if (hv != priv->hadjust->value)
3351 gtk_adjustment_value_changed (priv->hadjust);
3353 if (vv != priv->vadjust->value)
3354 gtk_adjustment_value_changed (priv->vadjust);
3356 priv->scroll_indicator_alpha = 1.0;
3358 if (priv->scroll_indicator_timeout) {
3359 g_source_remove (priv->scroll_indicator_timeout);
3360 priv->scroll_indicator_timeout = 0;
3363 if (priv->idle_id) {
3366 priv->overshooting_x = 0;
3367 priv->overshooting_y = 0;
3369 if ((priv->overshot_dist_x>0)||(priv->overshot_dist_y>0)) {
3370 priv->overshot_dist_x = 0;
3371 priv->overshot_dist_y = 0;
3373 gtk_widget_queue_resize (GTK_WIDGET (area));
3376 g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
3377 g_source_remove (priv->idle_id);
3383 * hildon_pannable_area_scroll_to_child:
3384 * @area: A #HildonPannableArea.
3385 * @child: A #GtkWidget, descendant of @area.
3387 * Smoothly scrolls until @child is visible inside @area. @child must
3388 * be a descendant of @area. If you need to scroll inside a scrollable
3389 * widget, e.g., #GtkTreeview, see hildon_pannable_area_scroll_to().
3391 * There is a precondition to this function: the widget must be
3392 * already realized. Check the hildon_pannable_area_jump_to_child() for
3393 * more tips regarding how to call this function during
3399 hildon_pannable_area_scroll_to_child (HildonPannableArea *area, GtkWidget *child)
3401 GtkWidget *bin_child;
3404 g_return_if_fail (GTK_WIDGET_REALIZED (area));
3405 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3406 g_return_if_fail (GTK_IS_WIDGET (child));
3407 g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
3409 if (GTK_BIN (area)->child == NULL)
3412 /* We need to get to check the child of the inside the area */
3413 bin_child = GTK_BIN (area)->child;
3415 /* we check if we added a viewport */
3416 if (GTK_IS_VIEWPORT (bin_child)) {
3417 bin_child = GTK_BIN (bin_child)->child;
3420 if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
3421 hildon_pannable_area_scroll_to (area, x, y);
3425 * hildon_pannable_area_jump_to_child:
3426 * @area: A #HildonPannableArea.
3427 * @child: A #GtkWidget, descendant of @area.
3429 * Jumps to make sure @child is visible inside @area. @child must
3430 * be a descendant of @area. If you want to move inside a scrollable
3431 * widget, like, #GtkTreeview, see hildon_pannable_area_scroll_to().
3433 * There is a precondition to this function: the widget must be
3434 * already realized. You can control if the widget is ready with the
3435 * GTK_WIDGET_REALIZED macro. If you want to call this function during
3436 * the initialization process of the widget do it inside a callback to
3437 * the ::realize signal, using g_signal_connect_after() function.
3442 hildon_pannable_area_jump_to_child (HildonPannableArea *area, GtkWidget *child)
3444 GtkWidget *bin_child;
3447 g_return_if_fail (GTK_WIDGET_REALIZED (area));
3448 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3449 g_return_if_fail (GTK_IS_WIDGET (child));
3450 g_return_if_fail (gtk_widget_is_ancestor (child, GTK_WIDGET (area)));
3452 if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
3455 /* We need to get to check the child of the inside the area */
3456 bin_child = gtk_bin_get_child (GTK_BIN (area));
3458 /* we check if we added a viewport */
3459 if (GTK_IS_VIEWPORT (bin_child)) {
3460 bin_child = gtk_bin_get_child (GTK_BIN (bin_child));
3463 if (gtk_widget_translate_coordinates (child, bin_child, 0, 0, &x, &y))
3464 hildon_pannable_area_jump_to (area, x, y);
3468 * hildon_pannable_get_child_widget_at:
3469 * @area: A #HildonPannableArea.
3470 * @x: horizontal coordinate of the point
3471 * @y: vertical coordinate of the point
3473 * Get the widget at the point (x, y) inside the pannable area. In
3474 * case no widget found it returns NULL.
3476 * returns: the #GtkWidget if we find a widget, NULL in any other case
3481 hildon_pannable_get_child_widget_at (HildonPannableArea *area,
3482 gdouble x, gdouble y)
3484 GdkWindow *window = NULL;
3485 GtkWidget *child_widget = NULL;
3487 window = hildon_pannable_area_get_topmost
3488 (gtk_bin_get_child (GTK_BIN (area))->window,
3489 x, y, NULL, NULL, GDK_ALL_EVENTS_MASK);
3491 gdk_window_get_user_data (window, (gpointer) &child_widget);
3493 return child_widget;
3498 * hildon_pannable_area_get_hadjustment:
3499 * @area: A #HildonPannableArea.
3501 * Returns the horizontal adjustment. This adjustment is the internal
3502 * widget adjustment used to control the animations. Do not modify it
3503 * directly to change the position of the pannable, to do that use the
3504 * pannable API. If you modify the object directly it could cause
3505 * artifacts in the animations.
3507 * returns: The horizontal #GtkAdjustment
3512 hildon_pannable_area_get_hadjustment (HildonPannableArea *area)
3515 g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), NULL);
3517 return area->priv->hadjust;
3521 * hildon_pannable_area_get_vadjustment:
3522 * @area: A #HildonPannableArea.
3524 * Returns the vertical adjustment. This adjustment is the internal
3525 * widget adjustment used to control the animations. Do not modify it
3526 * directly to change the position of the pannable, to do that use the
3527 * pannable API. If you modify the object directly it could cause
3528 * artifacts in the animations.
3530 * returns: The vertical #GtkAdjustment
3535 hildon_pannable_area_get_vadjustment (HildonPannableArea *area)
3537 g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), NULL);
3539 return area->priv->vadjust;
3544 * hildon_pannable_area_get_size_request_policy:
3545 * @area: A #HildonPannableArea.
3547 * This function returns the current size request policy of the
3548 * widget. That policy controls the way the size_request is done in
3549 * the pannable area. Check
3550 * hildon_pannable_area_set_size_request_policy() for a more detailed
3553 * returns: the policy is currently being used in the widget
3554 * #HildonSizeRequestPolicy.
3558 HildonSizeRequestPolicy
3559 hildon_pannable_area_get_size_request_policy (HildonPannableArea *area)
3561 HildonPannableAreaPrivate *priv;
3563 g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), FALSE);
3567 return priv->size_request_policy;
3571 * hildon_pannable_area_set_size_request_policy:
3572 * @area: A #HildonPannableArea.
3573 * @size_request_policy: One of the allowed #HildonSizeRequestPolicy
3575 * This function sets the pannable area size request policy. That
3576 * policy controls the way the size_request is done in the pannable
3577 * area. Pannable can use the size request of its children
3578 * (#HILDON_SIZE_REQUEST_CHILDREN) or the minimum size required for
3579 * the area itself (#HILDON_SIZE_REQUEST_MINIMUM), the latter is the
3580 * default. Recall this size depends on the scrolling policy you are
3581 * requesting to the pannable area, if you set #GTK_POLICY_NEVER this
3582 * parameter will not have any effect with
3583 * #HILDON_SIZE_REQUEST_MINIMUM set.
3587 * Deprecated: This method and the policy request is deprecated, DO
3588 * NOT use it in future code, the only policy properly supported in
3589 * gtk+ nowadays is the minimum size. Use #gtk_window_set_default_size
3590 * or #gtk_window_set_geometry_hints with the proper size in your case
3591 * to define the height of your dialogs.
3594 hildon_pannable_area_set_size_request_policy (HildonPannableArea *area,
3595 HildonSizeRequestPolicy size_request_policy)
3597 HildonPannableAreaPrivate *priv;
3599 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3603 if (priv->size_request_policy == size_request_policy)
3606 priv->size_request_policy = size_request_policy;
3608 gtk_widget_queue_resize (GTK_WIDGET (area));
3610 g_object_notify (G_OBJECT (area), "size-request-policy");
3614 * hildon_pannable_area_get_center_on_child_focus
3615 * @area: A #HildonPannableArea
3617 * Gets the @area #HildonPannableArea:center-on-child-focus property
3620 * See #HildonPannableArea:center-on-child-focus for more information.
3622 * Returns: the @area #HildonPannableArea:center-on-child-focus value
3627 hildon_pannable_area_get_center_on_child_focus (HildonPannableArea *area)
3629 g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), FALSE);
3631 return area->priv->center_on_child_focus;
3635 * hildon_pannable_area_set_center_on_child_focus
3636 * @area: A #HildonPannableArea
3637 * @value: the new value
3639 * Sets the @area #HildonPannableArea:center-on-child-focus property
3642 * See #HildonPannableArea:center-on-child-focus for more information.
3647 hildon_pannable_area_set_center_on_child_focus (HildonPannableArea *area,
3650 g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
3652 area->priv->center_on_child_focus = value;