+ if (delta_y != 0.0 && priv->vscrollbar &&
+ gtk_widget_get_visible (priv->vscrollbar))
+ {
+ GtkAdjustment *adj;
+ gdouble new_value;
+ gdouble page_size;
+ gdouble scroll_unit;
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
+ page_size = gtk_adjustment_get_page_size (adj);
+ scroll_unit = pow (page_size, 2.0 / 3.0);
+
+ new_value = CLAMP (gtk_adjustment_get_value (adj) + delta_y * scroll_unit,
+ gtk_adjustment_get_lower (adj),
+ gtk_adjustment_get_upper (adj) -
+ gtk_adjustment_get_page_size (adj));
+
+ gtk_adjustment_set_value (adj, new_value);
+
+ handled = TRUE;
+ }
+ }
+ else
+ {
+ GtkWidget *range;
+
+ if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN)
+ range = priv->vscrollbar;
+ else
+ range = priv->hscrollbar;
+
+ if (range && gtk_widget_get_visible (range))
+ {
+ GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (range));
+ gdouble new_value;
+
+ delta = _gtk_range_get_wheel_delta (GTK_RANGE (range), event);
+
+ new_value = CLAMP (gtk_adjustment_get_value (adj) + delta,
+ gtk_adjustment_get_lower (adj),
+ gtk_adjustment_get_upper (adj) -
+ gtk_adjustment_get_page_size (adj));
+
+ gtk_adjustment_set_value (adj, new_value);
+
+ handled = TRUE;
+ }
+ }
+
+ return handled;
+}
+
+static gboolean
+_gtk_scrolled_window_set_adjustment_value (GtkScrolledWindow *scrolled_window,
+ GtkAdjustment *adjustment,
+ gdouble value,
+ gboolean allow_overshooting,
+ gboolean snap_to_border)
+{
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ gdouble lower, upper, *prev_value;
+
+ lower = gtk_adjustment_get_lower (adjustment);
+ upper = gtk_adjustment_get_upper (adjustment) -
+ gtk_adjustment_get_page_size (adjustment);
+
+ if (adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar)))
+ prev_value = &priv->unclamped_hadj_value;
+ else if (adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar)))
+ prev_value = &priv->unclamped_vadj_value;
+ else
+ return FALSE;
+
+ if (snap_to_border)
+ {
+ if (*prev_value < 0 && value > 0)
+ value = 0;
+ else if (*prev_value > upper && value < upper)
+ value = upper;
+ }
+
+ if (allow_overshooting)
+ {
+ lower -= MAX_OVERSHOOT_DISTANCE;
+ upper += MAX_OVERSHOOT_DISTANCE;
+ }
+
+ *prev_value = CLAMP (value, lower, upper);
+ gtk_adjustment_set_value (adjustment, value);
+
+ return (*prev_value != value);
+}
+
+static gboolean
+scrolled_window_deceleration_cb (GtkWidget *widdget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ KineticScrollData *data = user_data;
+ GtkScrolledWindow *scrolled_window = data->scrolled_window;
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkAdjustment *hadjustment, *vadjustment;
+ gint old_overshoot_x, old_overshoot_y, overshoot_x, overshoot_y;
+ gdouble value;
+ gint64 current_time;
+ guint elapsed;
+
+ hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
+ vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
+
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &old_overshoot_x, &old_overshoot_y);
+
+ current_time = gdk_frame_clock_get_frame_time (frame_clock);
+ elapsed = (current_time - data->last_deceleration_time) / 1000;
+ data->last_deceleration_time = current_time;
+
+ if (hadjustment && priv->hscrollbar_visible)
+ {
+ value = priv->unclamped_hadj_value + (data->x_velocity * elapsed);
+
+ if (_gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ hadjustment,
+ value, TRUE, TRUE))
+ data->x_velocity = 0;
+ }
+ else
+ data->x_velocity = 0;
+
+ if (vadjustment && priv->vscrollbar_visible)
+ {
+ value = priv->unclamped_vadj_value + (data->y_velocity * elapsed);
+
+ if (_gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ vadjustment,
+ value, TRUE, TRUE))
+ data->y_velocity = 0;
+ }
+ else
+ data->y_velocity = 0;
+
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &overshoot_x, &overshoot_y);
+
+ if (overshoot_x == 0)
+ {
+ if (old_overshoot_x != 0)
+ {
+ /* Overshooting finished snapping back */
+ data->x_velocity = 0;
+ }
+ else if (data->x_velocity > 0)
+ {
+ data->x_velocity -= FRICTION_DECELERATION * elapsed * data->vel_sine;
+ data->x_velocity = MAX (0, data->x_velocity);
+ }
+ else if (data->x_velocity < 0)
+ {
+ data->x_velocity += FRICTION_DECELERATION * elapsed * data->vel_sine;
+ data->x_velocity = MIN (0, data->x_velocity);
+ }
+ }
+ else if (overshoot_x < 0)
+ data->x_velocity += OVERSHOOT_INVERSE_ACCELERATION * elapsed;
+ else if (overshoot_x > 0)
+ data->x_velocity -= OVERSHOOT_INVERSE_ACCELERATION * elapsed;
+
+ if (overshoot_y == 0)
+ {
+ if (old_overshoot_y != 0)
+ {
+ /* Overshooting finished snapping back */
+ data->y_velocity = 0;
+ }
+ else if (data->y_velocity > 0)
+ {
+ data->y_velocity -= FRICTION_DECELERATION * elapsed * data->vel_cosine;
+ data->y_velocity = MAX (0, data->y_velocity);
+ }
+ else if (data->y_velocity < 0)
+ {
+ data->y_velocity += FRICTION_DECELERATION * elapsed * data->vel_cosine;
+ data->y_velocity = MIN (0, data->y_velocity);
+ }
+ }
+ else if (overshoot_y < 0)
+ data->y_velocity += OVERSHOOT_INVERSE_ACCELERATION * elapsed;
+ else if (overshoot_y > 0)
+ data->y_velocity -= OVERSHOOT_INVERSE_ACCELERATION * elapsed;
+
+ if (old_overshoot_x != overshoot_x ||
+ old_overshoot_y != overshoot_y)
+ {
+ if (overshoot_x >= 0 || overshoot_y >= 0)
+ {
+ /* We need to reallocate the widget to have it at
+ * negative offset, so there's a "gravity" on the
+ * bottom/right corner
+ */
+ gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
+ }
+ else if (overshoot_x < 0 || overshoot_y < 0)
+ _gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
+ }
+
+ if (overshoot_x == 0 && overshoot_y == 0 &&
+ data->x_velocity == 0 && data->y_velocity == 0)
+ gtk_scrolled_window_cancel_deceleration (scrolled_window);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window)
+{
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+
+ if (priv->deceleration_id)
+ {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (scrolled_window),
+ priv->deceleration_id);
+ priv->deceleration_id = 0;
+ }
+}
+
+static void
+gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window)
+{
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GdkFrameClock *frame_clock;
+ KineticScrollData *data;
+ gdouble angle;
+
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (scrolled_window));
+
+ data = g_new0 (KineticScrollData, 1);
+ data->scrolled_window = scrolled_window;
+ data->last_deceleration_time = gdk_frame_clock_get_frame_time (frame_clock);
+ data->x_velocity = priv->x_velocity;
+ data->y_velocity = priv->y_velocity;
+
+ /* We use sine/cosine as a factor to deceleration x/y components
+ * of the vector, so we care about the sign later.
+ */
+ angle = atan2 (ABS (data->x_velocity), ABS (data->y_velocity));
+ data->vel_cosine = cos (angle);
+ data->vel_sine = sin (angle);
+
+ scrolled_window->priv->deceleration_id =
+ gtk_widget_add_tick_callback (GTK_WIDGET (scrolled_window),
+ scrolled_window_deceleration_cb,
+ data,
+ (GDestroyNotify) g_free);
+}
+
+static gboolean
+gtk_scrolled_window_release_captured_event (GtkScrolledWindow *scrolled_window)
+{
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+
+ /* Cancel the scrolling and send the button press
+ * event to the child widget
+ */
+ if (!priv->button_press_event)
+ return FALSE;
+
+ if (priv->drag_device)
+ {
+ gtk_device_grab_remove (GTK_WIDGET (scrolled_window), priv->drag_device);
+ priv->drag_device = NULL;
+ }
+
+ if (priv->capture_button_press)
+ {
+ GtkWidget *event_widget;
+
+ event_widget = gtk_get_event_widget (priv->button_press_event);
+
+ if (!_gtk_propagate_captured_event (event_widget,
+ priv->button_press_event,
+ gtk_bin_get_child (GTK_BIN (scrolled_window))))
+ gtk_propagate_event (event_widget, priv->button_press_event);
+
+ gdk_event_free (priv->button_press_event);
+ priv->button_press_event = NULL;
+ }
+
+ if (_gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
+ gtk_scrolled_window_start_deceleration (scrolled_window);
+
+ return FALSE;
+}
+
+static gboolean
+gtk_scrolled_window_calculate_velocity (GtkScrolledWindow *scrolled_window,
+ GdkEvent *event)
+{
+ GtkScrolledWindowPrivate *priv;
+ gdouble x_root, y_root;
+ guint32 _time;
+
+#define STILL_THRESHOLD 40
+
+ if (!gdk_event_get_root_coords (event, &x_root, &y_root))
+ return FALSE;
+
+ priv = scrolled_window->priv;
+ _time = gdk_event_get_time (event);
+
+ if (priv->last_motion_event_x_root != x_root ||
+ priv->last_motion_event_y_root != y_root ||
+ ABS (_time - priv->last_motion_event_time) > STILL_THRESHOLD)
+ {
+ priv->x_velocity = (priv->last_motion_event_x_root - x_root) /
+ (gdouble) (_time - priv->last_motion_event_time);
+ priv->y_velocity = (priv->last_motion_event_y_root - y_root) /
+ (gdouble) (_time - priv->last_motion_event_time);
+ }
+
+ priv->last_motion_event_x_root = x_root;
+ priv->last_motion_event_y_root = y_root;
+ priv->last_motion_event_time = _time;
+
+#undef STILL_THRESHOLD
+
+ return TRUE;
+}
+
+static gboolean
+gtk_scrolled_window_captured_button_release (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkWidget *child;
+ gboolean overshoot;
+ guint button;
+ gdouble x_root, y_root;
+
+ if (gdk_event_get_button (event, &button) && button != 1)
+ return FALSE;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (!child)
+ return FALSE;
+
+ gtk_device_grab_remove (widget, priv->drag_device);
+ priv->drag_device = NULL;
+
+ if (priv->release_timeout_id)
+ {
+ g_source_remove (priv->release_timeout_id);
+ priv->release_timeout_id = 0;
+ }
+
+ overshoot = _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL);
+
+ if (priv->in_drag)
+ gdk_device_ungrab (gdk_event_get_device (event), gdk_event_get_time (event));
+ else
+ {
+ /* There hasn't been scrolling at all, so just let the
+ * child widget handle the button press normally
+ */
+ gtk_scrolled_window_release_captured_event (scrolled_window);
+
+ if (!overshoot)
+ return FALSE;
+ }
+ priv->in_drag = FALSE;
+
+ if (priv->button_press_event)
+ {
+ gdk_event_free (priv->button_press_event);
+ priv->button_press_event = NULL;
+ }
+
+ gtk_scrolled_window_calculate_velocity (scrolled_window, event);
+
+ /* Zero out vector components without a visible scrollbar */
+ if (!priv->hscrollbar_visible)
+ priv->x_velocity = 0;
+ if (!priv->vscrollbar_visible)
+ priv->y_velocity = 0;
+
+ if (priv->x_velocity != 0 || priv->y_velocity != 0 || overshoot)
+ {
+ gtk_scrolled_window_start_deceleration (scrolled_window);
+ priv->x_velocity = priv->y_velocity = 0;
+ priv->last_button_event_valid = FALSE;
+ }
+ else
+ {
+ gdk_event_get_root_coords (event, &x_root, &y_root);
+ priv->last_button_event_x_root = x_root;
+ priv->last_button_event_y_root = y_root;
+ priv->last_button_event_valid = TRUE;
+ }
+
+ if (priv->capture_button_press)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gboolean
+gtk_scrolled_window_captured_motion_notify (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ gint old_overshoot_x, old_overshoot_y;
+ gint new_overshoot_x, new_overshoot_y;
+ GtkWidget *child;
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+ gdouble dx, dy;
+ GdkModifierType state;
+ gdouble x_root, y_root;
+
+ gdk_event_get_state (event, &state);
+ if (!(state & GDK_BUTTON1_MASK))
+ return FALSE;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (!child)
+ return FALSE;
+
+ /* Check if we've passed the drag threshold */
+ gdk_event_get_root_coords (event, &x_root, &y_root);
+ if (!priv->in_drag)
+ {
+ if (gtk_drag_check_threshold (widget,
+ priv->last_button_event_x_root,
+ priv->last_button_event_y_root,
+ x_root, y_root))
+ {
+ if (priv->release_timeout_id)
+ {
+ g_source_remove (priv->release_timeout_id);
+ priv->release_timeout_id = 0;
+ }
+
+ priv->last_button_event_valid = FALSE;
+ priv->in_drag = TRUE;
+ }
+ else
+ return TRUE;
+ }
+
+ gdk_device_grab (priv->drag_device,
+ gtk_widget_get_window (widget),
+ GDK_OWNERSHIP_WINDOW,
+ TRUE,
+ GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK,
+ NULL,
+ gdk_event_get_time (event));
+
+ priv->last_button_event_valid = FALSE;
+
+ if (priv->button_press_event)
+ {
+ gdk_event_free (priv->button_press_event);
+ priv->button_press_event = NULL;
+ }
+
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &old_overshoot_x, &old_overshoot_y);
+
+ hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
+ if (hadjustment && priv->hscrollbar_visible)
+ {
+ dx = (priv->last_motion_event_x_root - x_root) + priv->unclamped_hadj_value;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, hadjustment,
+ dx, TRUE, FALSE);
+ }
+
+ vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
+ if (vadjustment && priv->vscrollbar_visible)
+ {
+ dy = (priv->last_motion_event_y_root - y_root) + priv->unclamped_vadj_value;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, vadjustment,
+ dy, TRUE, FALSE);
+ }
+
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &new_overshoot_x, &new_overshoot_y);
+
+ if (old_overshoot_x != new_overshoot_x ||
+ old_overshoot_y != new_overshoot_y)
+ {
+ if (new_overshoot_x >= 0 || new_overshoot_y >= 0)
+ {
+ /* We need to reallocate the widget to have it at
+ * negative offset, so there's a "gravity" on the
+ * bottom/right corner
+ */
+ gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
+ }
+ else if (new_overshoot_x < 0 || new_overshoot_y < 0)
+ _gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
+ }
+
+ gtk_scrolled_window_calculate_velocity (scrolled_window, event);
+
+ return TRUE;
+}
+
+static gboolean
+gtk_scrolled_window_captured_button_press (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkWidget *child;
+ GtkWidget *event_widget;
+ GdkDevice *source_device;
+ GdkInputSource source;
+ gdouble x_root, y_root;
+ guint button;
+
+ /* If scrollbars are not visible, we don't do kinetic scrolling */
+ if (!priv->vscrollbar_visible && !priv->hscrollbar_visible)
+ return FALSE;
+
+ source_device = gdk_event_get_source_device (event);
+ source = gdk_device_get_source (source_device);
+
+ if (source != GDK_SOURCE_TOUCHSCREEN)
+ return FALSE;
+
+ event_widget = gtk_get_event_widget (event);
+
+ /* If there's another scrolled window between the widget
+ * receiving the event and this capturing scrolled window,
+ * let it handle the events.
+ */
+ if (widget != gtk_widget_get_ancestor (event_widget, GTK_TYPE_SCROLLED_WINDOW))
+ return FALSE;
+
+ /* Check whether the button press is close to the previous one,
+ * take that as a shortcut to get the child widget handle events
+ */
+ gdk_event_get_root_coords (event, &x_root, &y_root);
+ if (priv->last_button_event_valid &&
+ ABS (x_root - priv->last_button_event_x_root) < TOUCH_BYPASS_CAPTURED_THRESHOLD &&
+ ABS (y_root - priv->last_button_event_y_root) < TOUCH_BYPASS_CAPTURED_THRESHOLD)
+ {
+ priv->last_button_event_valid = FALSE;
+ return FALSE;
+ }
+
+ priv->last_button_event_x_root = priv->last_motion_event_x_root = x_root;
+ priv->last_button_event_y_root = priv->last_motion_event_y_root = y_root;
+ priv->last_motion_event_time = gdk_event_get_time (event);
+ priv->last_button_event_valid = TRUE;
+
+ if (gdk_event_get_button (event, &button) && button != 1)
+ return FALSE;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (!child)
+ return FALSE;
+
+ if (priv->hscrollbar == event_widget || priv->vscrollbar == event_widget)
+ return FALSE;
+
+ priv->drag_device = gdk_event_get_device (event);
+ gtk_device_grab_add (widget, priv->drag_device, TRUE);
+
+ gtk_scrolled_window_cancel_deceleration (scrolled_window);
+
+ /* Only set the timeout if we're going to store an event */
+ if (priv->capture_button_press)
+ priv->release_timeout_id =
+ gdk_threads_add_timeout (RELEASE_EVENT_TIMEOUT,
+ (GSourceFunc) gtk_scrolled_window_release_captured_event,
+ scrolled_window);
+
+ priv->in_drag = FALSE;
+
+ if (priv->capture_button_press)
+ {
+ /* Store the button press event in
+ * case we need to propagate it later
+ */
+ priv->button_press_event = gdk_event_copy (event);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+gtk_scrolled_window_captured_event (GtkWidget *widget,
+ GdkEvent *event)
+{
+ gboolean retval = FALSE;
+ GtkScrolledWindowPrivate *priv = GTK_SCROLLED_WINDOW (widget)->priv;
+
+ if (gdk_window_get_window_type (event->any.window) == GDK_WINDOW_TEMP)
+ return FALSE;
+
+ switch (event->type)
+ {
+ case GDK_TOUCH_BEGIN:
+ case GDK_BUTTON_PRESS:
+ retval = gtk_scrolled_window_captured_button_press (widget, event);
+ break;
+ case GDK_TOUCH_END:
+ case GDK_BUTTON_RELEASE:
+ if (priv->drag_device)
+ retval = gtk_scrolled_window_captured_button_release (widget, event);
+ else
+ priv->last_button_event_valid = FALSE;
+ break;
+ case GDK_TOUCH_UPDATE:
+ case GDK_MOTION_NOTIFY:
+ if (priv->drag_device)
+ retval = gtk_scrolled_window_captured_motion_notify (widget, event);
+ break;
+ case GDK_LEAVE_NOTIFY:
+ case GDK_ENTER_NOTIFY:
+ if (priv->in_drag &&
+ event->crossing.mode != GDK_CROSSING_GRAB)
+ retval = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static gboolean
+gtk_scrolled_window_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{