static void
clamp_adjustments (ChamplainKineticScrollView *scroll)
{
  ChamplainKineticScrollViewPrivate *priv = scroll->priv;

  if (priv->viewport)
    {
      ChamplainAdjustment *hadj, *vadj;
      gdouble d, value, lower, step_increment;

      champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport),
          &hadj, &vadj);

      /* Snap to the nearest step increment on hadjustment */

      champlain_adjustment_get_values (hadj, &value, &lower, NULL,
          &step_increment);
      d = (rint ((value - lower) / step_increment) *
           step_increment) + lower;
      champlain_adjustment_set_value (hadj, d);

      /* Snap to the nearest step increment on vadjustment */
      champlain_adjustment_get_values (vadj, &value, &lower, NULL,
          &step_increment);
      d = (rint ((value - lower) / step_increment) *
           step_increment) + lower;
      champlain_adjustment_set_value (vadj, d);
    }
}
static void
deceleration_new_frame_cb (ClutterTimeline *timeline,
    gint frame_num,
    ChamplainKineticScrollView *scroll)
{
  ChamplainKineticScrollViewPrivate *priv = scroll->priv;

  if (priv->viewport)
    {
      gdouble value, lower, upper;
      ChamplainAdjustment *hadjust, *vadjust;
      gint i;
      gboolean stop = TRUE;

      champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport),
          &hadjust,
          &vadjust);

      for (i = 0; i < clutter_timeline_get_delta (timeline) / 15; i++)
        {
          champlain_adjustment_set_value (hadjust,
              priv->dx +
              champlain_adjustment_get_value (hadjust));
          champlain_adjustment_set_value (vadjust,
              priv->dy +
              champlain_adjustment_get_value (vadjust));
          priv->dx = (priv->dx / priv->decel_rate);
          priv->dy = (priv->dy / priv->decel_rate);
        }

      /* Check if we've hit the upper or lower bounds and stop the timeline */
      champlain_adjustment_get_values (hadjust, &value, &lower, &upper,
          NULL);
      if (((priv->dx > 0) && (value < upper)) ||
          ((priv->dx < 0) && (value > lower)))
        stop = FALSE;

      if (stop)
        {
          champlain_adjustment_get_values (vadjust, &value, &lower, &upper,
              NULL);
          if (((priv->dy > 0) && (value < upper)) ||
              ((priv->dy < 0) && (value > lower)))
            stop = FALSE;
        }

      if (stop)
        {
          clutter_timeline_stop (timeline);
          deceleration_completed_cb (timeline, scroll);
        }
    }
}
static void
allocate (ClutterActor *self,
    const ClutterActorBox *box,
    ClutterAllocationFlags flags)
{
  ClutterActorBox child_box;
  CoglFixed prev_value;

  ChamplainViewportPrivate *priv = CHAMPLAIN_VIEWPORT (self)->priv;

  /* Chain up */
  CLUTTER_ACTOR_CLASS (champlain_viewport_parent_class)->
      allocate (self, box, flags);

  /* Refresh adjustments */
  if (priv->sync_adjustments)
    {
      if (priv->hadjustment)
        {
          g_object_set (G_OBJECT (priv->hadjustment),
              "lower", 0.0,
              "upper", (box->x2 - box->x1),
              NULL);

          /* Make sure value is clamped */
          prev_value = champlain_adjustment_get_value (priv->hadjustment);
          champlain_adjustment_set_value (priv->hadjustment, prev_value);
        }

      if (priv->vadjustment)
        {
          g_object_set (G_OBJECT (priv->vadjustment),
              "lower", 0.0,
              "upper", (box->y2 - box->y1),
              NULL);

          prev_value = champlain_adjustment_get_value (priv->vadjustment);
          champlain_adjustment_set_value (priv->vadjustment, prev_value);
        }
    }

  child_box.x1 = 0;
  child_box.x2 = box->x2 - box->x1;
  child_box.y1 = 0;
  child_box.y2 = box->y2 - box->y1;

  clutter_actor_allocate (CLUTTER_ACTOR (priv->content_group), &child_box, flags);
}
void
champlain_viewport_set_origin (ChamplainViewport *viewport,
    float x,
    float y)
{
  ChamplainViewportPrivate *priv;

  g_return_if_fail (CHAMPLAIN_IS_VIEWPORT (viewport));

  priv = viewport->priv;

  g_object_freeze_notify (G_OBJECT (viewport));

  if (x != priv->x)
    {
      priv->x = x;
      g_object_notify (G_OBJECT (viewport), "x-origin");

      if (priv->hadjustment)
        champlain_adjustment_set_value (priv->hadjustment,
            x);
    }

  if (y != priv->y)
    {
      priv->y = y;
      g_object_notify (G_OBJECT (viewport), "y-origin");

      if (priv->vadjustment)
        champlain_adjustment_set_value (priv->vadjustment,
            y);
    }

  g_object_thaw_notify (G_OBJECT (viewport));

  if (priv->child)
    clutter_actor_set_position (priv->child, -x, -y);

  clutter_actor_queue_redraw (CLUTTER_ACTOR (viewport));
}
static gboolean
motion_event_cb (ClutterActor *stage,
    ClutterMotionEvent *event,
    ChamplainKineticScrollView *scroll)
{
  ChamplainKineticScrollViewPrivate *priv = scroll->priv;
  ClutterActor *actor = CLUTTER_ACTOR (scroll);
  gfloat x, y;

  if ((event->type != CLUTTER_MOTION || !(event->modifier_state & CLUTTER_BUTTON1_MASK)) &&
      (event->type != CLUTTER_TOUCH_UPDATE || priv->sequence != clutter_event_get_event_sequence ((ClutterEvent *) event)))
    return FALSE;

  if (clutter_actor_transform_stage_point (actor,
          event->x,
          event->y,
          &x, &y))
    {
      ChamplainKineticScrollViewMotion *motion;

      if (priv->viewport)
        {
          gdouble dx, dy;
          ChamplainAdjustment *hadjust, *vadjust;

          champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport),
              &hadjust,
              &vadjust);

          motion = &g_array_index (priv->motion_buffer,
                ChamplainKineticScrollViewMotion, priv->last_motion);
          if (hadjust)
            {
              dx = (motion->x - x) +
                champlain_adjustment_get_value (hadjust);
              champlain_adjustment_set_value (hadjust, dx);
            }

          if (vadjust)
            {
              dy = (motion->y - y) +
                champlain_adjustment_get_value (vadjust);
              champlain_adjustment_set_value (vadjust, dy);
            }
        }

      priv->last_motion++;
      if (priv->last_motion == priv->motion_buffer->len)
        {
          priv->motion_buffer = g_array_remove_index (priv->motion_buffer, 0);
          g_array_set_size (priv->motion_buffer, priv->last_motion);
          priv->last_motion--;
        }

      motion = &g_array_index (priv->motion_buffer,
            ChamplainKineticScrollViewMotion, priv->last_motion);
      motion->x = x;
      motion->y = y;
      g_get_current_time (&motion->time);
    }

  /* Due to the way gestures in progress connect to the stage in order to
   * receive events, we must let these events go through, as they could be
   * essential for the management of the ClutterZoomGesture in the
   * ChamplainView.
   */
  return FALSE;
}
static gboolean
motion_event_cb (ClutterActor *stage,
    ClutterMotionEvent *event,
    ChamplainKineticScrollView *scroll)
{
  ChamplainKineticScrollViewPrivate *priv = scroll->priv;
  ClutterActor *actor = CLUTTER_ACTOR (scroll);
  gfloat x, y;

  if (event->type != CLUTTER_MOTION || !(event->modifier_state & CLUTTER_BUTTON1_MASK))
    return FALSE;
      
  if (clutter_actor_transform_stage_point (actor,
          event->x,
          event->y,
          &x, &y))
    {
      ChamplainKineticScrollViewMotion *motion;

      if (priv->viewport)
        {
          gdouble dx, dy;
          ChamplainAdjustment *hadjust, *vadjust;

          champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport),
              &hadjust,
              &vadjust);

          motion = &g_array_index (priv->motion_buffer,
                ChamplainKineticScrollViewMotion, priv->last_motion);
          if (hadjust)
            {
              dx = (motion->x - x) +
                champlain_adjustment_get_value (hadjust);
              champlain_adjustment_set_value (hadjust, dx);
            }

          if (vadjust)
            {
              dy = (motion->y - y) +
                champlain_adjustment_get_value (vadjust);
              champlain_adjustment_set_value (vadjust, dy);
            }
        }

      priv->last_motion++;
      if (priv->last_motion == priv->motion_buffer->len)
        {
          priv->motion_buffer = g_array_remove_index (priv->motion_buffer, 0);
          g_array_set_size (priv->motion_buffer, priv->last_motion);
          priv->last_motion--;
        }

      motion = &g_array_index (priv->motion_buffer,
            ChamplainKineticScrollViewMotion, priv->last_motion);
      motion->x = x;
      motion->y = y;
      g_get_current_time (&motion->time);
    }

  return TRUE;
}