Added a new check of scrollbars layout in the allocation
[hildon] / hildon / hildon-pannable-area.c
index e7332be..abf5223 100644 (file)
@@ -134,17 +134,25 @@ struct _HildonPannableAreaPrivate {
 
   GtkAdjustment *hadjust;
   GtkAdjustment *vadjust;
+  gint x_offset;
+  gint y_offset;
 
   GtkPolicyType vscrollbar_policy;
   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
 };
 
@@ -176,6 +184,7 @@ enum {
   PROP_SIZE_REQUEST_POLICY,
   PROP_HADJUSTMENT,
   PROP_VADJUSTMENT,
+  PROP_CENTER_ON_CHILD_FOCUS,
   PROP_LAST
 };
 
@@ -265,6 +274,14 @@ static void hildon_pannable_area_calculate_velocity (gdouble *vel,
 static gboolean hildon_pannable_area_motion_event_scroll_timeout (HildonPannableArea *area);
 static void hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
                                                       gdouble x, gdouble y);
+static void hildon_pannable_area_check_move (HildonPannableArea *area,
+                                             GdkEventMotion * event,
+                                             gdouble *x,
+                                             gdouble *y);
+static void hildon_pannable_area_handle_move (HildonPannableArea *area,
+                                              GdkEventMotion * event,
+                                              gdouble *x,
+                                              gdouble *y);
 static gboolean hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
                                                        GdkEventMotion * event);
 static gboolean hildon_pannable_leave_notify_event (GtkWidget *widget,
@@ -279,6 +296,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
@@ -311,6 +331,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;
@@ -390,7 +411,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));
 
@@ -559,6 +580,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",
@@ -619,6 +650,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
@@ -657,8 +734,12 @@ hildon_pannable_area_init (HildonPannableArea * area)
   priv->child_width = 0;
   priv->child_height = 0;
   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));
@@ -769,6 +850,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);
   }
@@ -884,6 +968,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);
@@ -897,6 +984,7 @@ hildon_pannable_area_dispose (GObject * object)
   GtkWidget *child = gtk_bin_get_child (GTK_BIN (object));
 
   if (priv->idle_id) {
+    g_signal_emit (object, pannable_area_signals[PANNING_FINISHED], 0);
     g_source_remove (priv->idle_id);
     priv->idle_id = 0;
   }
@@ -987,6 +1075,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;
 
@@ -1111,6 +1200,7 @@ hildon_pannable_area_size_allocate (GtkWidget * widget,
   HildonPannableAreaPrivate *priv;
   GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
   gint border_width;
+  gdouble hv, vv;
 
   border_width = GTK_CONTAINER (widget)->border_width;
 
@@ -1133,6 +1223,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);
@@ -1147,18 +1239,25 @@ hildon_pannable_area_size_allocate (GtkWidget * widget,
       gtk_widget_size_allocate (child, &child_allocation);
     }
 
+    hv = priv->hadjust->value;
+    vv = priv->vadjust->value;
+
     /* we have to do this after child size_allocate because page_size is
      * changed when we allocate the size of the children */
     if (priv->overshot_dist_y < 0) {
-      gtk_adjustment_set_value (priv->vadjust, priv->vadjust->upper -
-                                priv->vadjust->page_size);
+      priv->vadjust->value = priv->vadjust->upper - priv->vadjust->page_size;
     }
 
     if (priv->overshot_dist_x < 0) {
-      gtk_adjustment_set_value (priv->hadjust, priv->hadjust->upper -
-                                priv->hadjust->page_size);
+      priv->hadjust->value = priv->hadjust->upper - priv->hadjust->page_size;
     }
 
+    if (hv != priv->hadjust->value)
+      gtk_adjustment_value_changed (priv->hadjust);
+
+    if (vv != priv->vadjust->value)
+      gtk_adjustment_value_changed (priv->vadjust);
+
   } else {
     hildon_pannable_area_check_scrollbars (HILDON_PANNABLE_AREA (widget));
   }
@@ -1173,6 +1272,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);
 }
 
@@ -1393,7 +1493,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],
@@ -1417,14 +1517,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,
@@ -1439,7 +1538,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],
@@ -1464,14 +1563,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,
@@ -1523,9 +1621,17 @@ static void
 hildon_pannable_area_adjust_value_changed (HildonPannableArea * area,
                                            gpointer data)
 {
-  if (GTK_WIDGET_REALIZED (area)) {
-    HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
+  HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (area)->priv;
+  gint xdiff, ydiff;
+  gint x = priv->x_offset;
+  gint y = priv->y_offset;
 
+  priv->x_offset = priv->hadjust->value;
+  xdiff = x - priv->x_offset;
+  priv->y_offset = priv->vadjust->value;
+  ydiff = y - priv->y_offset;
+
+  if ((xdiff || ydiff) && GTK_WIDGET_DRAWABLE (area)) {
     hildon_pannable_area_redraw (area);
 
     if ((priv->vscroll_visible) || (priv->hscroll_visible)) {
@@ -1619,7 +1725,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)) {
@@ -1815,7 +1921,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) &&
@@ -1824,7 +1931,7 @@ 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;
@@ -1858,6 +1965,11 @@ hildon_pannable_area_button_press_cb (GtkWidget * widget,
   /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
   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) {
 
@@ -1869,6 +1981,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;
@@ -2035,7 +2149,7 @@ hildon_pannable_axis_scroll (HildonPannableArea *area,
       }
     }
 
-    gtk_adjustment_set_value (adjust, dist);
+    adjust->value = dist;
   } else {
     if (!priv->button_pressed) {
 
@@ -2055,7 +2169,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);
@@ -2071,7 +2185,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);
@@ -2093,7 +2207,10 @@ hildon_pannable_axis_scroll (HildonPannableArea *area,
         *overshot_dist = CLAMP ((*overshot_dist) + inc, -1 * overshoot_max, 0);
       } else {
         *overshooting = 0;
-        gtk_adjustment_set_value (adjust, dist);
+        adjust->value = CLAMP (dist,
+                               adjust->lower,
+                               adjust->upper -
+                               adjust->page_size);
       }
 
       if (*overshot_dist != overshot_dist_old)
@@ -2109,6 +2226,7 @@ hildon_pannable_area_scroll (HildonPannableArea *area,
   gboolean sx, sy;
   HildonPannableAreaPrivate *priv = area->priv;
   gboolean hscroll_visible, vscroll_visible;
+  gdouble hv, vv;
 
   if (gtk_bin_get_child (GTK_BIN (area)) == NULL)
     return;
@@ -2121,6 +2239,9 @@ hildon_pannable_area_scroll (HildonPannableArea *area,
   sx = TRUE;
   sy = TRUE;
 
+  hv = priv->hadjust->value;
+  vv = priv->vadjust->value;
+
   if (vscroll_visible) {
     hildon_pannable_axis_scroll (area, priv->vadjust, &priv->vel_y, y,
                                  &priv->overshooting_y, &priv->overshot_dist_y,
@@ -2137,6 +2258,12 @@ hildon_pannable_area_scroll (HildonPannableArea *area,
     priv->vel_x = 0;
   }
 
+  if (hv != priv->hadjust->value)
+    gtk_adjustment_value_changed (priv->hadjust);
+
+  if (vv != priv->vadjust->value)
+    gtk_adjustment_value_changed (priv->vadjust);
+
   /* If the scroll on a particular axis wasn't succesful, reset the
    * initial scroll position to the new mouse co-ordinate. This means
    * when you get to the top of the page, dragging down works immediately.
@@ -2159,6 +2286,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;
   }
@@ -2195,6 +2323,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;
         }
       }
@@ -2207,6 +2337,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;
 }
 
@@ -2268,37 +2400,20 @@ hildon_pannable_area_motion_event_scroll (HildonPannableArea *area,
   }
 }
 
-static gboolean
-hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
-                                      GdkEventMotion * event)
+static void
+hildon_pannable_area_check_move (HildonPannableArea *area,
+                                 GdkEventMotion * event,
+                                 gdouble *x,
+                                 gdouble *y)
 {
-  HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
   HildonPannableAreaPrivate *priv = area->priv;
-  gdouble x, y;
-  gdouble delta;
-
-  if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
-    return TRUE;
-
-  if ((!priv->enabled) || (!priv->button_pressed) ||
-      ((event->time == priv->last_time) && (priv->last_type == 2))) {
-    gdk_window_get_pointer (widget->window, NULL, NULL, 0);
-    return TRUE;
-  }
-
-  if (priv->last_type == 1) {
-    priv->first_drag = TRUE;
-  }
-
-  x = event->x - priv->x;
-  y = event->y - priv->y;
 
   if (priv->first_drag && (!priv->moved) &&
-      ((ABS (x) > (priv->panning_threshold))
-       || (ABS (y) > (priv->panning_threshold)))) {
+      ((ABS (*x) > (priv->panning_threshold))
+       || (ABS (*y) > (priv->panning_threshold)))) {
     priv->moved = TRUE;
-    x = 0;
-    y = 0;
+    *x = 0;
+    *y = 0;
 
     if (priv->first_drag) {
         gboolean vscroll_visible;
@@ -2368,6 +2483,16 @@ hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
         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;
@@ -2382,76 +2507,116 @@ hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
                                                  hildon_pannable_area_timeout, area);
     }
   }
+}
 
-  if (priv->moved) {
-    switch (priv->mode) {
-    case HILDON_PANNABLE_AREA_MODE_PUSH:
-      /* Scroll by the amount of pixels the cursor has moved
-       * since the last motion event.
-       */
-      hildon_pannable_area_motion_event_scroll (area, x, y);
+static void
+hildon_pannable_area_handle_move (HildonPannableArea *area,
+                                  GdkEventMotion * event,
+                                  gdouble *x,
+                                  gdouble *y)
+{
+  HildonPannableAreaPrivate *priv = area->priv;
+  gdouble delta;
+
+  switch (priv->mode) {
+  case HILDON_PANNABLE_AREA_MODE_PUSH:
+    /* Scroll by the amount of pixels the cursor has moved
+     * since the last motion event.
+     */
+    hildon_pannable_area_motion_event_scroll (area, *x, *y);
+    priv->x = event->x;
+    priv->y = event->y;
+    break;
+  case HILDON_PANNABLE_AREA_MODE_ACCEL:
+    /* Set acceleration relative to the initial click */
+    priv->ex = event->x;
+    priv->ey = event->y;
+    priv->vel_x = ((*x > 0) ? 1 : -1) *
+      (((ABS (*x) /
+         (gdouble) GTK_WIDGET (area)->allocation.width) *
+        (priv->vmax - priv->vmin)) + priv->vmin);
+    priv->vel_y = ((*y > 0) ? 1 : -1) *
+      (((ABS (*y) /
+         (gdouble) GTK_WIDGET (area)->allocation.height) *
+        (priv->vmax - priv->vmin)) + priv->vmin);
+    break;
+  case HILDON_PANNABLE_AREA_MODE_AUTO:
+
+    delta = event->time - priv->last_time;
+
+    if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) {
+      gdouble dist = event->y - priv->y;
+
+      hildon_pannable_area_calculate_velocity (&priv->vel_y,
+                                               delta,
+                                               dist,
+                                               priv->vmax,
+                                               priv->drag_inertia,
+                                               priv->force,
+                                               priv->sps);
+    } else {
+      *y = 0;
+      priv->vel_y = 0;
+    }
+
+    if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) {
+      gdouble dist = event->x - priv->x;
+
+      hildon_pannable_area_calculate_velocity (&priv->vel_x,
+                                               delta,
+                                               dist,
+                                               priv->vmax,
+                                               priv->drag_inertia,
+                                               priv->force,
+                                               priv->sps);
+    } else {
+      *x = 0;
+      priv->vel_x = 0;
+    }
+
+    hildon_pannable_area_motion_event_scroll (area, *x, *y);
+
+    if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ)
       priv->x = event->x;
+    if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT)
       priv->y = event->y;
-      break;
-    case HILDON_PANNABLE_AREA_MODE_ACCEL:
-      /* Set acceleration relative to the initial click */
-      priv->ex = event->x;
-      priv->ey = event->y;
-      priv->vel_x = ((x > 0) ? 1 : -1) *
-       (((ABS (x) /
-          (gdouble) widget->allocation.width) *
-         (priv->vmax - priv->vmin)) + priv->vmin);
-      priv->vel_y = ((y > 0) ? 1 : -1) *
-       (((ABS (y) /
-          (gdouble) widget->allocation.height) *
-         (priv->vmax - priv->vmin)) + priv->vmin);
-      break;
-    case HILDON_PANNABLE_AREA_MODE_AUTO:
-
-      delta = event->time - priv->last_time;
 
-      if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) {
-        gdouble dist = event->y - priv->y;
+    break;
+  default:
+    break;
+  }
+}
 
-        hildon_pannable_area_calculate_velocity (&priv->vel_y,
-                                                 delta,
-                                                 dist,
-                                                 priv->vmax,
-                                                 priv->drag_inertia,
-                                                 priv->force,
-                                                 priv->sps);
-      } else {
-        y = 0;
-        priv->vel_y = 0;
-      }
+static gboolean
+hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
+                                      GdkEventMotion * event)
+{
+  HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
+  HildonPannableAreaPrivate *priv = area->priv;
+  gdouble x, y;
 
-      if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) {
-        gdouble dist = event->x - priv->x;
+  if (gtk_bin_get_child (GTK_BIN (widget)) == NULL)
+    return TRUE;
 
-        hildon_pannable_area_calculate_velocity (&priv->vel_x,
-                                                 delta,
-                                                 dist,
-                                                 priv->vmax,
-                                                 priv->drag_inertia,
-                                                 priv->force,
-                                                 priv->sps);
-      } else {
-        x = 0;
-        priv->vel_x = 0;
-      }
+  if ((!priv->enabled) || (!priv->button_pressed) ||
+      ((event->time == priv->last_time) && (priv->last_type == 2))) {
+    gdk_window_get_pointer (widget->window, NULL, NULL, 0);
+    return TRUE;
+  }
 
-      hildon_pannable_area_motion_event_scroll (area, x, y);
+  if (priv->last_type == 1) {
+    priv->first_drag = TRUE;
+  }
 
-      if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ)
-        priv->x = event->x;
-      if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT)
-        priv->y = event->y;
+  x = event->x - priv->x;
+  y = event->y - priv->y;
 
-      break;
+  if (!priv->moved) {
+    hildon_pannable_area_check_move (area, event, &x, &y);
+  }
 
-    default:
-      break;
-    }
+  if (priv->moved) {
+    hildon_pannable_area_handle_move (area, event, &x, &y);
   } else if (priv->child) {
     gboolean in;
     gint pos_x, pos_y;
@@ -2477,6 +2642,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);
@@ -2510,8 +2677,10 @@ static gboolean
 hildon_pannable_area_button_release_cb (GtkWidget * widget,
                                        GdkEventButton * event)
 {
-  HildonPannableAreaPrivate *priv = HILDON_PANNABLE_AREA (widget)->priv;
+  HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
+  HildonPannableAreaPrivate *priv = area->priv;
   gint x, y;
+  gdouble dx, dy;
   GdkWindow *child;
 
   if  (((event->time == priv->last_time) && (priv->last_type == 3))
@@ -2519,102 +2688,90 @@ hildon_pannable_area_button_release_cb (GtkWidget * widget,
        || (!priv->button_pressed) || (!priv->enabled) || (event->button != 1))
     return TRUE;
 
-  priv->scroll_indicator_event_interrupt = 0;
-  priv->scroll_delay_counter = priv->scrollbar_fade_delay;
+  /* if last event was a motion-notify we have to check the movement
+     and launch the animation */
+  if (priv->last_type == 2) {
 
-  /* move all the way to the last position */
-  if (priv->motion_event_scroll_timeout) {
-    g_source_remove (priv->motion_event_scroll_timeout);
-    hildon_pannable_area_motion_event_scroll_timeout (HILDON_PANNABLE_AREA (widget));
-    priv->motion_x = 0;
-    priv->motion_y = 0;
-  }
+    dx = event->x - priv->x;
+    dy = event->y - priv->y;
 
-  if (priv->last_type == 2) {
-    gdouble delta = event->time - priv->last_time;
+    hildon_pannable_area_check_move (area, (GdkEventMotion *) event, &dx, &dy);
 
-    if (priv->mov_mode&HILDON_MOVEMENT_MODE_VERT) {
-      gdouble dist = event->y - priv->y;
+    if (priv->moved) {
+      gdouble delta = event->time - priv->last_time;
 
-      if (ABS (dist) >= 1.0) {
-        hildon_pannable_area_calculate_velocity (&priv->vel_y,
-                                                 delta,
-                                                 dist,
-                                                 priv->vmax,
-                                                 priv->drag_inertia,
-                                                 priv->force,
-                                                 priv->sps);
+      hildon_pannable_area_handle_move (area, (GdkEventMotion *) event, &dx, &dy);
 
-        priv->motion_y = dist;
+      /* move all the way to the last position now */
+      if (priv->motion_event_scroll_timeout) {
+        g_source_remove (priv->motion_event_scroll_timeout);
         hildon_pannable_area_motion_event_scroll_timeout (HILDON_PANNABLE_AREA (widget));
-      } else
-        if (delta >= CURSOR_STOPPED_TIMEOUT) {
-          y = 0;
-          priv->vel_y = 0;
-        }
-    }
+        priv->motion_x = 0;
+        priv->motion_y = 0;
+      }
 
-    if (priv->mov_mode&HILDON_MOVEMENT_MODE_HORIZ) {
-      gdouble dist = event->x - priv->x;
+      if ((ABS (dx) < 4.0) && (delta >= CURSOR_STOPPED_TIMEOUT))
+        priv->vel_x = 0;
 
-      if (ABS (dist) >= 1.0) {
-        hildon_pannable_area_calculate_velocity (&priv->vel_x,
-                                                 delta,
-                                                 dist,
-                                                 priv->vmax,
-                                                 priv->drag_inertia,
-                                                 priv->force,
-                                                 priv->sps);
-        priv->motion_x = dist;
-        hildon_pannable_area_motion_event_scroll_timeout (HILDON_PANNABLE_AREA (widget));
-      } else
-        if (delta >= CURSOR_STOPPED_TIMEOUT) {
-          x = 0;
-          priv->vel_x = 0;
-        }
+      if ((ABS (dy) < 4.0) && (delta >= CURSOR_STOPPED_TIMEOUT))
+        priv->vel_y = 0;
     }
   }
 
-  if ((ABS (priv->vel_y) > priv->vmin)||
-      (ABS (priv->vel_x) > priv->vmin)) {
-    priv->scroll_indicator_alpha = 1.0;
+  /* 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->overshot_dist_y * 0.9;
   }
 
-  hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
-                                            priv->scroll_indicator_alpha);
+  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->overshot_dist_x * 0.9;
+  }
 
   priv->button_pressed = FALSE;
 
-  if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO ||
-      priv->mode == HILDON_PANNABLE_AREA_MODE_ACCEL) {
+  if  ((ABS (priv->vel_y) >= priv->vmin) ||
+       (ABS (priv->vel_x) >= priv->vmin)) {
 
-    /* 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;
-    }
+    /* we have to move because we are in overshooting position*/
+    if (!priv->moved) {
+      gboolean result_val;
 
-    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;
+      g_signal_emit (area,
+                     pannable_area_signals[PANNING_STARTED],
+                     0, &result_val);
     }
 
-    if ((ABS (priv->vel_y) >= priv->vmin) ||
-        (ABS (priv->vel_x) >= priv->vmin)) {
+    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 (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);
+    if (!priv->idle_id)
+      priv->idle_id = gdk_threads_add_timeout ((gint) (1000.0 / (gdouble) priv->sps),
+                                               (GSourceFunc)
+                                               hildon_pannable_area_timeout, widget);
+  } 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;
+
+  hildon_pannable_area_launch_fade_timeout (HILDON_PANNABLE_AREA (widget),
+                                            priv->scroll_indicator_alpha);
+
   priv->last_time = event->time;
   priv->last_type = 3;
 
@@ -2628,6 +2785,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;
 
@@ -2638,6 +2797,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);
@@ -2689,6 +2852,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;
   }
@@ -2748,6 +2913,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)
 {
@@ -3033,6 +3231,7 @@ hildon_pannable_area_jump_to (HildonPannableArea *area,
 {
   HildonPannableAreaPrivate *priv;
   gint width, height;
+  gdouble hv, vv;
 
   g_return_if_fail (HILDON_IS_PANNABLE_AREA (area));
   g_return_if_fail (GTK_WIDGET_REALIZED (area));
@@ -3049,26 +3248,33 @@ hildon_pannable_area_jump_to (HildonPannableArea *area,
 
   g_return_if_fail (x < width || y < height);
 
+  hv = priv->hadjust->value;
+  vv = priv->vadjust->value;
+
   if (x != -1) {
     gdouble jump_to = x - priv->hadjust->page_size/2;
 
-    if (jump_to > priv->hadjust->upper - priv->hadjust->page_size) {
-      jump_to = priv->hadjust->upper - priv->hadjust->page_size;
-    }
-
-    gtk_adjustment_set_value (priv->hadjust, jump_to);
+    priv->hadjust->value = CLAMP (jump_to,
+                                  priv->hadjust->lower,
+                                  priv->hadjust->upper -
+                                  priv->hadjust->page_size);
   }
 
   if (y != -1) {
     gdouble jump_to =  y - priv->vadjust->page_size/2;
 
-    if (jump_to > priv->vadjust->upper - priv->vadjust->page_size) {
-      jump_to = priv->vadjust->upper - priv->vadjust->page_size;
-    }
-
-    gtk_adjustment_set_value (priv->vadjust, jump_to);
+    priv->vadjust->value = CLAMP (jump_to,
+                                  priv->vadjust->lower,
+                                  priv->vadjust->upper -
+                                  priv->vadjust->page_size);
   }
 
+  if (hv != priv->hadjust->value)
+    gtk_adjustment_value_changed (priv->hadjust);
+
+  if (vv != priv->vadjust->value)
+    gtk_adjustment_value_changed (priv->vadjust);
+
   priv->scroll_indicator_alpha = 1.0;
 
   if (priv->scroll_indicator_timeout) {
@@ -3089,6 +3295,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;
   }
@@ -3325,3 +3532,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;
+}