More tuning for the pannable large lists kinetics.
[hildon] / hildon / hildon-pannable-area.c
index 78f21f7..219810a 100644 (file)
@@ -56,8 +56,8 @@
 #define RATIO_TOLERANCE 0.000001
 #define SCROLL_FADE_TIMEOUT 100
 #define MOTION_EVENTS_PER_SECOND 25
-#define CURSOR_STOPPED_TIMEOUT 80
-#define MAX_SPEED_THRESHOLD 250
+#define CURSOR_STOPPED_TIMEOUT 200
+#define MAX_SPEED_THRESHOLD 290
 #define PANNABLE_MAX_WIDTH 788
 #define PANNABLE_MAX_HEIGHT 378
 
@@ -78,6 +78,7 @@ struct _HildonPannableAreaPrivate {
   gboolean enabled;
   gboolean button_pressed;
   guint32 last_time;   /* Last event time, to stop infinite loops */
+  guint32 last_press_time;
   gint last_type;
   gboolean last_in;
   gboolean moved;
@@ -97,6 +98,8 @@ struct _HildonPannableAreaPrivate {
   guint direction_error_margin;
   gdouble vel_x;
   gdouble vel_y;
+  gdouble old_vel_x;
+  gdouble old_vel_y;
   GdkWindow *child;
   gint child_width;
   gint child_height;
@@ -141,12 +144,18 @@ struct _HildonPannableAreaPrivate {
   GtkPolicyType hscrollbar_policy;
 
   GdkGC *scrollbars_gc;
+  GdkColor scroll_color;
+
+  gboolean center_on_child_focus;
+  gboolean center_on_child_focus_pending;
 };
 
 /*signals*/
 enum {
   HORIZONTAL_MOVEMENT,
   VERTICAL_MOVEMENT,
+  PANNING_STARTED,
+  PANNING_FINISHED,
   LAST_SIGNAL
 };
 
@@ -178,6 +187,7 @@ enum {
   PROP_SIZE_REQUEST_POLICY,
   PROP_HADJUSTMENT,
   PROP_VADJUSTMENT,
+  PROP_CENTER_ON_CHILD_FOCUS,
   PROP_LAST
 };
 
@@ -191,6 +201,7 @@ static void hildon_pannable_area_set_property (GObject * object,
                                                guint property_id,
                                                const GValue * value,
                                                GParamSpec * pspec);
+static void hildon_pannable_area_remove_timeouts (GtkWidget * widget);
 static void hildon_pannable_area_dispose (GObject * object);
 static void hildon_pannable_area_realize (GtkWidget * widget);
 static void hildon_pannable_area_unrealize (GtkWidget * widget);
@@ -289,6 +300,9 @@ static void hildon_pannable_area_child_mapped (GtkWidget *widget,
 static void hildon_pannable_area_add (GtkContainer *container, GtkWidget *child);
 static void hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child);
 static void hildon_pannable_calculate_vel_factor (HildonPannableArea * self);
+static void hildon_pannable_area_set_focus_child (GtkContainer *container,
+                                                 GtkWidget *child);
+static void hildon_pannable_area_center_on_child_focus (HildonPannableArea *area);
 
 
 static void
@@ -321,6 +335,7 @@ hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
 
   container_class->add = hildon_pannable_area_add;
   container_class->remove = hildon_pannable_area_remove;
+  container_class->set_focus_child = hildon_pannable_area_set_focus_child;
 
   klass->horizontal_movement = NULL;
   klass->vertical_movement = NULL;
@@ -400,7 +415,7 @@ hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
                                                        "Maximum scroll velocity when overshooting",
                                                        "Maximum distance the child widget should scroll "
                                                        "per 'frame', in pixels per frame when it overshoots after hitting the edge.",
-                                                       0, G_MAXDOUBLE, 20,
+                                                       0, G_MAXDOUBLE, 130,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));
 
@@ -421,7 +436,7 @@ hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
                                                        "Deceleration multiplier",
                                                        "The multiplier used when decelerating when in "
                                                        "acceleration scrolling mode.",
-                                                       0, 1.0, 0.93,
+                                                       0, 1.0, 0.85,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));
 
@@ -461,7 +476,7 @@ hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
                                                      "Time before starting to fade the scrollbar",
                                                      "Time the scrollbar is going to be visible if the widget is not in"
                                                       "action in miliseconds",
-                                                     0, G_MAXUINT, 3000,
+                                                     0, G_MAXUINT, 1000,
                                                      G_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));
 
@@ -569,6 +584,16 @@ hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
                                                        GTK_TYPE_ADJUSTMENT,
                                                        G_PARAM_READABLE));
 
+  g_object_class_install_property (object_class,
+                                   PROP_CENTER_ON_CHILD_FOCUS,
+                                   g_param_spec_boolean ("center-on-child-focus",
+                                                         "Center on the child with the focus",
+                                                         "Whether to center the pannable on the child that receives the focus.",
+                                                         FALSE,
+                                                         G_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT));
+
+
   gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_uint
                                           ("indicator-width",
@@ -629,6 +654,52 @@ hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
                  G_TYPE_DOUBLE,
                  G_TYPE_DOUBLE);
 
+ /**
+   * HildonPannableArea::panning-started:
+   * @hildonpannable: the pannable area object that is going to start
+   * the panning
+   *
+   * This signal is emitted before the panning starts. Applications
+   * can return %TRUE to avoid the panning. The main difference with
+   * the vertical-movement and horizontal-movement signals is those
+   * gesture signals are launched no matter if the widget is going to
+   * move, this signal means the widget is going to start moving. It
+   * could even happen that the widget moves and there was no gesture
+   * (i.e. click meanwhile the pannable is overshooting).
+   *
+   * Returns: %TRUE to stop the panning launch. %FALSE to continue
+   * with it.
+   *
+   * Since: 2.2
+   */
+  pannable_area_signals[PANNING_STARTED] =
+    g_signal_new ("panning-started",
+                  G_TYPE_FROM_CLASS (object_class),
+                  0,
+                  0,
+                  NULL, NULL,
+                  _hildon_marshal_BOOLEAN__VOID,
+                  G_TYPE_BOOLEAN, 0);
+
+ /**
+   * HildonPannableArea::panning-finished:
+   * @hildonpannable: the pannable area object that finished the
+   * panning
+   *
+   * This signal is emitted after the kinetic panning has
+   * finished.
+   *
+   * Since: 2.2
+   */
+  pannable_area_signals[PANNING_FINISHED] =
+    g_signal_new ("panning-finished",
+                  G_TYPE_FROM_CLASS (object_class),
+                  0,
+                  0,
+                  NULL, NULL,
+                  _hildon_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
 }
 
 static void
@@ -643,6 +714,7 @@ hildon_pannable_area_init (HildonPannableArea * area)
   priv->moved = FALSE;
   priv->button_pressed = FALSE;
   priv->last_time = 0;
+  priv->last_press_time = 0;
   priv->last_type = 0;
   priv->vscroll_visible = TRUE;
   priv->hscroll_visible = TRUE;
@@ -654,6 +726,8 @@ hildon_pannable_area_init (HildonPannableArea * area)
   priv->idle_id = 0;
   priv->vel_x = 0;
   priv->vel_y = 0;
+  priv->old_vel_x = 0;
+  priv->old_vel_y = 0;
   priv->scroll_indicator_alpha = 0.0;
   priv->scroll_indicator_timeout = 0;
   priv->motion_event_scroll_timeout = 0;
@@ -669,8 +743,10 @@ hildon_pannable_area_init (HildonPannableArea * area)
   priv->last_in = TRUE;
   priv->x_offset = 0;
   priv->y_offset = 0;
+  priv->center_on_child_focus_pending = FALSE;
 
-  gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_HINT_MASK);
+  gtk_style_lookup_color (GTK_WIDGET (area)->style,
+                         "SecondaryTextColor", &priv->scroll_color);
 
   priv->hadjust =
     GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
@@ -781,6 +857,9 @@ hildon_pannable_area_get_property (GObject * object,
                         hildon_pannable_area_get_vadjustment
                         (HILDON_PANNABLE_AREA (object)));
     break;
+  case PROP_CENTER_ON_CHILD_FOCUS:
+    g_value_set_boolean (value, priv->center_on_child_focus);
+    break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -896,6 +975,9 @@ hildon_pannable_area_set_property (GObject * object,
     hildon_pannable_area_set_size_request_policy (HILDON_PANNABLE_AREA (object),
                                                   g_value_get_enum (value));
     break;
+  case PROP_CENTER_ON_CHILD_FOCUS:
+    priv->center_on_child_focus = g_value_get_boolean (value);
+    break;
 
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -908,20 +990,7 @@ hildon_pannable_area_dispose (GObject * object)
   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (object)->priv;
   GtkWidget *child = gtk_bin_get_child (GTK_BIN (object));
 
-  if (priv->idle_id) {
-    g_source_remove (priv->idle_id);
-    priv->idle_id = 0;
-  }
-
-  if (priv->scroll_indicator_timeout){
-    g_source_remove (priv->scroll_indicator_timeout);
-    priv->scroll_indicator_timeout = 0;
-  }
-
-  if (priv->motion_event_scroll_timeout){
-    g_source_remove (priv->motion_event_scroll_timeout);
-    priv->motion_event_scroll_timeout = 0;
-  }
+  hildon_pannable_area_remove_timeouts (GTK_WIDGET (object));
 
   if (child) {
     g_signal_handlers_disconnect_by_func (child,
@@ -999,6 +1068,7 @@ hildon_pannable_area_realize (GtkWidget * widget)
     | GDK_BUTTON_PRESS_MASK
     | GDK_BUTTON_RELEASE_MASK
     | GDK_SCROLL_MASK
+    | GDK_POINTER_MOTION_HINT_MASK
     | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
   attributes.wclass = GDK_INPUT_ONLY;
 
@@ -1015,6 +1085,29 @@ hildon_pannable_area_realize (GtkWidget * widget)
   gdk_gc_copy (priv->scrollbars_gc, widget->style->fg_gc[GTK_STATE_INSENSITIVE]);
 }
 
+
+static void
+hildon_pannable_area_remove_timeouts (GtkWidget * widget)
+{
+  HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
+
+  if (priv->idle_id) {
+    g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
+    g_source_remove (priv->idle_id);
+    priv->idle_id = 0;
+  }
+
+  if (priv->scroll_indicator_timeout){
+    g_source_remove (priv->scroll_indicator_timeout);
+    priv->scroll_indicator_timeout = 0;
+  }
+
+  if (priv->motion_event_scroll_timeout){
+    g_source_remove (priv->motion_event_scroll_timeout);
+    priv->motion_event_scroll_timeout = 0;
+  }
+}
+
 static void
 hildon_pannable_area_unrealize (GtkWidget * widget)
 {
@@ -1022,6 +1115,8 @@ hildon_pannable_area_unrealize (GtkWidget * widget)
 
   priv = HILDON_PANNABLE_AREA (widget)->priv;
 
+  hildon_pannable_area_remove_timeouts (widget);
+
   if (priv->event_window != NULL) {
     gdk_window_set_user_data (priv->event_window, NULL);
     gdk_window_destroy (priv->event_window);
@@ -1146,6 +1241,8 @@ hildon_pannable_area_size_allocate (GtkWidget * widget,
 
   if (child && GTK_WIDGET_VISIBLE (child)) {
 
+    hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget));
+
     hildon_pannable_area_child_allocate_calculate (widget,
                                                    allocation,
                                                    &child_allocation);
@@ -1193,6 +1290,7 @@ hildon_pannable_area_style_set (GtkWidget * widget,
   GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
     style_set (widget, previous_style);
 
+  gtk_style_lookup_color (widget->style, "SecondaryTextColor", &priv->scroll_color);
   gtk_widget_style_get (widget, "indicator-width", &priv->indicator_width, NULL);
 }
 
@@ -1413,7 +1511,7 @@ hildon_pannable_draw_vscroll (GtkWidget *widget,
   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
   gfloat y, height;
   GdkColor transp_color;
-  GdkGC *gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
+  GdkGC *gc = priv->scrollbars_gc;
 
   gdk_draw_rectangle (widget->window,
                       widget->style->bg_gc[GTK_STATE_NORMAL],
@@ -1437,14 +1535,13 @@ hildon_pannable_draw_vscroll (GtkWidget *widget,
            (priv->hscroll_visible ? priv->hscroll_rect.height : 0) -
            height);
 
-  if (priv->scroll_indicator_alpha < 1.0) {
+  if (priv->scroll_indicator_alpha == 1.0) {
+    transp_color = priv->scroll_color;
+  } else if (priv->scroll_indicator_alpha < 1.0) {
     tranparency_color (&transp_color, *back_color, *scroll_color,
                        priv->scroll_indicator_alpha);
-
-    gdk_gc_set_rgb_fg_color (priv->scrollbars_gc, &transp_color);
-
-    gc = priv->scrollbars_gc;
   }
+  gdk_gc_set_rgb_fg_color (gc, &transp_color);
 
   gdk_draw_rectangle (widget->window, gc,
                       TRUE, priv->vscroll_rect.x, y,
@@ -1459,7 +1556,7 @@ hildon_pannable_draw_hscroll (GtkWidget *widget,
   HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
   gfloat x, width;
   GdkColor transp_color;
-  GdkGC *gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
+  GdkGC *gc = priv->scrollbars_gc;
 
   gdk_draw_rectangle (widget->window,
                       widget->style->bg_gc[GTK_STATE_INSENSITIVE],
@@ -1484,14 +1581,13 @@ hildon_pannable_draw_hscroll (GtkWidget *widget,
            (priv->vscroll_visible ? priv->vscroll_rect.width : 0) -
            width);
 
-  if (priv->scroll_indicator_alpha < 1.0) {
+  if (priv->scroll_indicator_alpha == 1.0) {
+    transp_color = priv->scroll_color;
+  } else if (priv->scroll_indicator_alpha < 1.0) {
     tranparency_color (&transp_color, *back_color, *scroll_color,
                        priv->scroll_indicator_alpha);
-
-    gdk_gc_set_rgb_fg_color (priv->scrollbars_gc, &transp_color);
-
-    gc = priv->scrollbars_gc;
   }
+  gdk_gc_set_rgb_fg_color (gc, &transp_color);
 
   gdk_draw_rectangle (widget->window, gc,
                       TRUE, x, priv->hscroll_rect.y, width,
@@ -1526,9 +1622,11 @@ hildon_pannable_area_launch_fade_timeout (HildonPannableArea * area,
 
   if (!priv->scroll_indicator_timeout)
     priv->scroll_indicator_timeout =
-      gdk_threads_add_timeout (SCROLL_FADE_TIMEOUT,
-                               (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
-                               area);
+      gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
+                                   SCROLL_FADE_TIMEOUT,
+                                   (GSourceFunc) hildon_pannable_area_scroll_indicator_fade,
+                                   area,
+                                   NULL);
 }
 
 static void
@@ -1647,7 +1745,7 @@ hildon_pannable_area_expose_event (GtkWidget * widget,
   GdkColor scroll_color = widget->style->base[GTK_STATE_SELECTED];
 #else /* USE_CAIRO_SCROLLBARS */
   GdkColor back_color = widget->style->bg[GTK_STATE_NORMAL];
-  GdkColor scroll_color = widget->style->fg[GTK_STATE_INSENSITIVE];
+  GdkColor scroll_color = priv->scroll_color;
 #endif
 
   if (G_UNLIKELY (priv->initial_effect)) {
@@ -1790,9 +1888,9 @@ hildon_pannable_area_get_topmost (GdkWindow * window,
                                                             tx, ty, mask);
         if (!selected_window) {
           if (tx)
-            *tx = x;
+            *tx = x-wx;
           if (ty)
-            *ty = y;
+            *ty = y-wy;
           selected_window = child;
         }
       } else {
@@ -1843,7 +1941,8 @@ hildon_pannable_area_button_press_cb (GtkWidget * widget,
                                      GdkEventButton * event)
 {
   gint x, y;
-  HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
+  HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
+  HildonPannableAreaPrivate *priv = area->priv;
 
   if ((!priv->enabled) || (event->button != 1) ||
       ((event->time == priv->last_time) &&
@@ -1852,10 +1951,11 @@ hildon_pannable_area_button_press_cb (GtkWidget * widget,
 
   priv->scroll_indicator_event_interrupt = 1;
 
-  hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
+  hildon_pannable_area_launch_fade_timeout (area,
                                             priv->scroll_indicator_alpha);
 
   priv->last_time = event->time;
+  priv->last_press_time = event->time;
   priv->last_type = 1;
 
   priv->scroll_to_x = -1;
@@ -1884,8 +1984,15 @@ hildon_pannable_area_button_press_cb (GtkWidget * widget,
   priv->button_pressed = TRUE;
 
   /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
+  priv->old_vel_x = priv->vel_x;
+  priv->old_vel_y = priv->vel_y;
   priv->vel_x = 0;
   priv->vel_y = 0;
+  if (priv->idle_id) {
+    g_source_remove (priv->idle_id);
+    priv->idle_id = 0;
+    g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
+  }
 
   if (priv->child) {
 
@@ -1897,6 +2004,8 @@ hildon_pannable_area_button_press_cb (GtkWidget * widget,
                               (gpointer) & priv->child);
 
     event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
+    /* remove the reference we added with the copy */
+    g_object_unref (priv->event_window);
     event->x = x;
     event->y = y;
     priv->cx = x;
@@ -2083,7 +2192,7 @@ hildon_pannable_axis_scroll (HildonPannableArea *area,
           *vel *= -1;
         } else if ((*overshooting > 1) && (*vel < 0)) {
           /* we add the MIN in order to avoid very small speeds */
-          *vel = MIN ((((gdouble)*overshot_dist)*0.4) * -1, -2.0);
+          *vel = MIN (((((gdouble)*overshot_dist)*0.8) * -1), -10.0);
         }
 
         *overshot_dist = CLAMP (*overshot_dist + *vel, 0, overshoot_max);
@@ -2099,7 +2208,7 @@ hildon_pannable_axis_scroll (HildonPannableArea *area,
           *vel *= -1;
         } else if ((*overshooting > 1) && (*vel > 0)) {
           /* we add the MAX in order to avoid very small speeds */
-          *vel = MAX ((((gdouble)*overshot_dist)*0.4) * -1, 2.0);
+          *vel = MAX (((((gdouble)*overshot_dist)*0.8) * -1), 10.0);
         }
 
         *overshot_dist = CLAMP (*overshot_dist + (*vel), -overshoot_max, 0);
@@ -2200,6 +2309,7 @@ hildon_pannable_area_timeout (HildonPannableArea * area)
 
   if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
     priv->idle_id = 0;
+    g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
 
     return FALSE;
   }
@@ -2236,6 +2346,8 @@ hildon_pannable_area_timeout (HildonPannableArea * area)
           priv->vel_y = 0;
           priv->idle_id = 0;
 
+          g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
+
           return FALSE;
         }
       }
@@ -2248,6 +2360,8 @@ hildon_pannable_area_timeout (HildonPannableArea * area)
 
   hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y);
 
+  gdk_window_process_updates (GTK_WIDGET (area)->window, FALSE);
+
   return TRUE;
 }
 
@@ -2303,9 +2417,10 @@ hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
     priv->motion_x = 0;
     priv->motion_y = 0;
 
-    priv->motion_event_scroll_timeout = gdk_threads_add_timeout
-      ((gint) (1000.0 / (gdouble) MOTION_EVENTS_PER_SECOND),
-       (GSourceFunc) hildon_pannable_area_motion_event_scroll_timeout, area);
+    priv->motion_event_scroll_timeout = gdk_threads_add_timeout_full
+      (G_PRIORITY_HIGH_IDLE + 20,
+       (gint) (1000.0 / (gdouble) MOTION_EVENTS_PER_SECOND),
+       (GSourceFunc) hildon_pannable_area_motion_event_scroll_timeout, area, NULL);
   }
 }
 
@@ -2392,6 +2507,16 @@ hildon_pannable_area_check_move (HildonPannableArea *area,
         synth_crossing (priv->child, pos_x, pos_y, event->x_root,
                         event->y_root, event->time, FALSE);
       }
+
+      if (priv->moved) {
+        gboolean result_val;
+
+        g_signal_emit (area,
+                       pannable_area_signals[PANNING_STARTED],
+                       0, &result_val);
+
+        priv->moved = !result_val;
+      }
     }
 
     priv->first_drag = FALSE;
@@ -2400,10 +2525,11 @@ hildon_pannable_area_check_move (HildonPannableArea *area,
        (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
 
       if (!priv->idle_id)
-        priv->idle_id = gdk_threads_add_timeout ((gint)
-                                                 (1000.0 / (gdouble) priv->sps),
-                                                 (GSourceFunc)
-                                                 hildon_pannable_area_timeout, area);
+        priv->idle_id = gdk_threads_add_timeout_full
+          (G_PRIORITY_HIGH_IDLE + 20,
+          (gint)(1000.0 / (gdouble) priv->sps),
+          (GSourceFunc)
+          hildon_pannable_area_timeout, area, NULL);
     }
   }
 }
@@ -2541,6 +2667,8 @@ hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
   if (priv->child) {
     /* Send motion notify to child */
     event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
+    /* remove the reference we added with the copy */
+    g_object_unref (priv->event_window);
     event->x = priv->cx + (event->x - priv->ix);
     event->y = priv->cy + (event->y - priv->iy);
     event->window = g_object_ref (priv->child);
@@ -2579,6 +2707,7 @@ hildon_pannable_area_button_release_cb (GtkWidget * widget,
   gint x, y;
   gdouble dx, dy;
   GdkWindow *child;
+  gboolean force_fast = TRUE;
 
   if  (((event->time == priv->last_time) && (priv->last_type == 3))
        || (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
@@ -2618,33 +2747,80 @@ hildon_pannable_area_button_release_cb (GtkWidget * widget,
   /* If overshoot has been initiated with a finger down, on release set max speed */
   if (priv->overshot_dist_y != 0) {
     priv->overshooting_y = priv->bounce_steps; /* Hack to stop a bounce in the finger down case */
-    priv->vel_y = priv->vmax_overshooting;
+    priv->vel_y = priv->overshot_dist_y * 0.9;
   }
 
   if (priv->overshot_dist_x != 0) {
     priv->overshooting_x = priv->bounce_steps; /* Hack to stop a bounce in the finger down case */
-    priv->vel_x = priv->vmax_overshooting;
+    priv->vel_x = priv->overshot_dist_x * 0.9;
   }
 
   priv->button_pressed = FALSE;
 
+  /* if widget was moving fast in the panning, increase speed even more */
+  if ((event->time - priv->last_press_time < 125) &&
+      ((ABS (priv->old_vel_x) > priv->vmin) ||
+       (ABS (priv->old_vel_y) > priv->vmin)) &&
+      ((ABS (priv->old_vel_x) > 40) ||
+       (ABS (priv->old_vel_y) > 40)))
+    {
+      gint symbol = 0;
+
+      if (priv->vel_x != 0)
+        symbol = ((priv->vel_x * priv->old_vel_x) > 0) ? 1 : -1;
+
+      priv->vel_x = symbol *
+        (priv->old_vel_x + ((priv->old_vel_x > 0) ? priv->vmax : -priv->vmax));
+
+      symbol = 0;
+
+      if (priv->vel_y != 0)
+        symbol = ((priv->vel_y * priv->old_vel_y) > 0) ? 1 : -1;
+
+      priv->vel_y = symbol *
+        (priv->old_vel_y + ((priv->old_vel_y > 0) ? priv->vmax : -priv->vmax));
+
+      force_fast = FALSE;
+    }
+
   if  ((ABS (priv->vel_y) >= priv->vmin) ||
        (ABS (priv->vel_x) >= priv->vmin)) {
 
+    /* we have to move because we are in overshooting position*/
+    if (!priv->moved) {
+      gboolean result_val;
+
+      g_signal_emit (area,
+                     pannable_area_signals[PANNING_STARTED],
+                     0, &result_val);
+    }
+
     priv->scroll_indicator_alpha = 1.0;
 
-    if (ABS (priv->vel_x) > MAX_SPEED_THRESHOLD)
-      priv->vel_x = (priv->vel_x > 0) ? priv->vmax : -priv->vmax;
+    if (force_fast) {
+      if (ABS (priv->vel_x) > MAX_SPEED_THRESHOLD)
+        priv->vel_x = (priv->vel_x > 0) ? priv->vmax : -priv->vmax;
 
-    if (ABS (priv->vel_y) > MAX_SPEED_THRESHOLD)
-      priv->vel_y = (priv->vel_y > 0) ? priv->vmax : -priv->vmax;
+      if (ABS (priv->vel_y) > MAX_SPEED_THRESHOLD)
+        priv->vel_y = (priv->vel_y > 0) ? priv->vmax : -priv->vmax;
+    }
 
     if (!priv->idle_id)
-      priv->idle_id = gdk_threads_add_timeout ((gint) (1000.0 / (gdouble) priv->sps),
-                                               (GSourceFunc)
-                                               hildon_pannable_area_timeout, widget);
+      priv->idle_id = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
+                                                   (gint) (1000.0 / (gdouble) priv->sps),
+                                                   (GSourceFunc) hildon_pannable_area_timeout,
+                                                   widget, NULL);
+  } else {
+    if (priv->center_on_child_focus_pending) {
+      hildon_pannable_area_center_on_child_focus (area);
+    }
+
+    if (priv->moved)
+      g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
   }
 
+  area->priv->center_on_child_focus_pending = FALSE;
+
   priv->scroll_indicator_event_interrupt = 0;
   priv->scroll_delay_counter = priv->scrollbar_fade_delay;
 
@@ -2664,6 +2840,8 @@ hildon_pannable_area_button_release_cb (GtkWidget * widget,
                                      event->x, event->y, &x, &y, GDK_BUTTON_RELEASE_MASK);
 
   event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
+  /* remove the reference we added with the copy */
+  g_object_unref (priv->event_window);
   event->x = x;
   event->y = y;
 
@@ -2674,6 +2852,10 @@ hildon_pannable_area_button_release_cb (GtkWidget * widget,
     /* Send synthetic leave event */
     synth_crossing (priv->child, x, y, event->x_root,
                    event->y_root, event->time, FALSE);
+    /* insure no click will happen for widgets that do not handle
+       leave-notify */
+    event->x = -16384;
+    event->y = -16384;
     /* Send synthetic button release event */
     ((GdkEventAny *) event)->window = g_object_ref (priv->child);
     gdk_event_put ((GdkEvent *) event);
@@ -2725,6 +2907,8 @@ hildon_pannable_area_scroll_cb (GtkWidget *widget,
       gtk_widget_queue_resize (GTK_WIDGET (widget));
     }
 
+    g_signal_emit (widget, pannable_area_signals[PANNING_FINISHED], 0);
+
     g_source_remove (priv->idle_id);
     priv->idle_id = 0;
   }
@@ -2784,6 +2968,39 @@ hildon_pannable_area_add (GtkContainer *container, GtkWidget *child)
   }
 }
 
+/* call this function if you are not panning */
+static void
+hildon_pannable_area_center_on_child_focus      (HildonPannableArea *area)
+{
+  GtkWidget *focused_child = NULL;
+  GtkWidget *window = NULL;
+
+  window = gtk_widget_get_toplevel (GTK_WIDGET (area));
+
+  if (GTK_WIDGET_TOPLEVEL (window)) {
+    focused_child = gtk_window_get_focus (GTK_WINDOW (window));
+  }
+
+  if (focused_child) {
+    hildon_pannable_area_scroll_to_child (area, focused_child);
+  }
+}
+
+static void
+hildon_pannable_area_set_focus_child            (GtkContainer     *container,
+                                                 GtkWidget        *child)
+{
+  HildonPannableArea *area = HILDON_PANNABLE_AREA (container);
+
+  if (!area->priv->center_on_child_focus) {
+    return;
+  }
+
+  if (GTK_IS_WIDGET (child)) {
+    area->priv->center_on_child_focus_pending = TRUE;
+  }
+}
+
 static void
 hildon_pannable_area_remove (GtkContainer *container, GtkWidget *child)
 {
@@ -3009,7 +3226,9 @@ hildon_pannable_area_scroll_to (HildonPannableArea *area,
   g_return_if_fail (x < width || y < height);
 
   if ((x > -1)&&(hscroll_visible)) {
-    priv->scroll_to_x = x - priv->hadjust->page_size/2;
+    priv->scroll_to_x = CLAMP (x - priv->hadjust->page_size/2,
+                               priv->hadjust->lower,
+                               priv->hadjust->upper - priv->hadjust->page_size);
     dist_x = priv->scroll_to_x - priv->hadjust->value;
     if (dist_x == 0) {
       priv->scroll_to_x = -1;
@@ -3021,7 +3240,9 @@ hildon_pannable_area_scroll_to (HildonPannableArea *area,
   }
 
   if ((y > -1)&&(vscroll_visible)) {
-    priv->scroll_to_y = y - priv->vadjust->page_size/2;
+    priv->scroll_to_y = CLAMP (y - priv->vadjust->page_size/2,
+                               priv->vadjust->lower,
+                               priv->vadjust->upper - priv->vadjust->page_size);
     dist_y = priv->scroll_to_y - priv->vadjust->value;
     if (dist_y == 0) {
       priv->scroll_to_y = -1;
@@ -3032,16 +3253,17 @@ hildon_pannable_area_scroll_to (HildonPannableArea *area,
     priv->scroll_to_y = y;
   }
 
-  if ((priv->scroll_to_y == -1) && (priv->scroll_to_y == -1)) {
+  if ((priv->scroll_to_y == -1) && (priv->scroll_to_x == -1)) {
     return;
   }
 
   hildon_pannable_area_launch_fade_timeout (area, 1.0);
 
   if (!priv->idle_id)
-    priv->idle_id = gdk_threads_add_timeout ((gint) (1000.0 / (gdouble) priv->sps),
-                                             (GSourceFunc)
-                                             hildon_pannable_area_timeout, area);
+    priv->idle_id = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE + 20,
+                                                 (gint) (1000.0 / (gdouble) priv->sps),
+                                                 (GSourceFunc) hildon_pannable_area_timeout,
+                                                 area, NULL);
 }
 
 /**
@@ -3133,6 +3355,7 @@ hildon_pannable_area_jump_to (HildonPannableArea *area,
       gtk_widget_queue_resize (GTK_WIDGET (area));
     }
 
+    g_signal_emit (area, pannable_area_signals[PANNING_FINISHED], 0);
     g_source_remove (priv->idle_id);
     priv->idle_id = 0;
   }
@@ -3369,3 +3592,44 @@ hildon_pannable_area_set_size_request_policy (HildonPannableArea *area,
   g_object_notify (G_OBJECT (area), "size-request-policy");
 }
 
+/**
+ * hildon_pannable_area_get_center_on_child_focus
+ * @area: A #HildonPannableArea
+ *
+ * Gets the @area #HildonPannableArea:center-on-child-focus property
+ * value.
+ *
+ * See #HildonPannableArea:center-on-child-focus for more information.
+ *
+ * Returns: the @area #HildonPannableArea:center-on-child-focus value
+ *
+ * Since: 2.2
+ **/
+gboolean
+hildon_pannable_area_get_center_on_child_focus  (HildonPannableArea *area)
+{
+  g_return_val_if_fail (HILDON_IS_PANNABLE_AREA (area), FALSE);
+
+  return area->priv->center_on_child_focus;
+}
+
+/**
+ * hildon_pannable_area_set_center_on_child_focus
+ * @area: A #HildonPannableArea
+ * @value: the new value
+ *
+ * Sets the @area #HildonPannableArea:center-on-child-focus property
+ * to @value.
+ *
+ * See #HildonPannableArea:center-on-child-focus for more information.
+ *
+ * Since: 2.2
+ **/
+void
+hildon_pannable_area_set_center_on_child_focus  (HildonPannableArea *area,
+                                                 gboolean value)
+{
+  g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
+
+  area->priv->center_on_child_focus = value;
+}