[gtk+/wip/baedert/gtkimageview: 2040/2075] GtkImageView: Improvements!



commit cb0bfb08b1c834f1cd34a341f08f4a6e299f3f8c
Author: Timm Bäder <mail baedert org>
Date:   Mon Jan 18 18:22:28 2016 +0100

    GtkImageView: Improvements!

 gtk/gtkimageview.c | 1139 +++++++++++++++++++++++++++++++---------------------
 gtk/gtkimageview.h |   67 ++--
 2 files changed, 722 insertions(+), 484 deletions(-)
---
diff --git a/gtk/gtkimageview.c b/gtk/gtkimageview.c
index 5051339..fe69b33 100644
--- a/gtk/gtkimageview.c
+++ b/gtk/gtkimageview.c
@@ -19,6 +19,19 @@
 #define RAD_TO_DEG(x) (((x) / (2.0 * M_PI) * 360.0))
 
 #define TRANSITION_DURATION (150.0 * 1000.0)
+/*#define TRANSITION_DURATION (1500.0 * 1000.0)*/
+#define ANGLE_TRANSITION_MIN_DELTA (1.0)
+#define SCALE_TRANSITION_MIN_DELTA (0.01)
+
+
+/*
+ *  TODO:
+ *
+ *   - Angle transition from 395 to 5 does a -390deg rotation
+ *   - transition from !fit-allocation to fit-allocation and back
+ *
+ */
+
 
 typedef struct
 {
@@ -30,10 +43,6 @@ typedef struct
   double scale;
 } State;
 
-
-// XXX Add gtk_image_reset_view that transitions back to scale = 1 and angle = 0.
-
-
 struct _GtkImageViewPrivate
 {
   double   scale;
@@ -48,12 +57,17 @@ struct _GtkImageViewPrivate
   gboolean in_rotate              : 1;
   gboolean in_zoom                : 1;
   gboolean size_valid             : 1;
+  gboolean transitions_enabled    : 1;
+  gboolean in_angle_transition    : 1;
+  gboolean in_scale_transition    : 1;
 
   GtkGesture *rotate_gesture;
   double      gesture_start_angle;
+  double      visible_angle;
 
   GtkGesture *zoom_gesture;
   double      gesture_start_scale;
+  double      visible_scale;
 
   /* Current anchor point, or -1/-1.
    * In widget coordinates. */
@@ -76,9 +90,11 @@ struct _GtkImageViewPrivate
   int                     animation_timeout;
 
   /* Transitions */
-  gint64 angle_transition_start;
   double transition_start_angle;
-  double transition_end_angle;
+  gint64 angle_transition_start;
+
+  double transition_start_scale;
+  gint64 scale_transition_start;
 
   double cached_width;
   double cached_height;
@@ -95,6 +111,7 @@ enum
   PROP_ZOOM_GESTURE_ENABLED,
   PROP_SNAP_ANGLE,
   PROP_FIT_ALLOCATION,
+  PROP_TRANSITIONS_ENABLED,
 
   LAST_WIDGET_PROPERTY,
   PROP_HADJUSTMENT,
@@ -135,59 +152,244 @@ static void gtk_image_view_compute_bounding_box (GtkImageView *image_view,
                                                  double          *width,
                                                  double          *height,
                                                  double       *scale_out);
+static void gtk_image_view_ensure_gestures (GtkImageView *image_view);
 
 static inline void gtk_image_view_restrict_adjustment (GtkAdjustment *adjustment);
+static void gtk_image_view_fix_anchor (GtkImageView *image_view,
+                                       double        anchor_x,
+                                       double        anchor_y,
+                                       State        *old_state);
+
 /* }}} */
 
 
+static inline double
+gtk_image_view_get_real_scale (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->in_zoom || priv->in_scale_transition)
+    return priv->visible_scale;
+  else
+    return priv->scale;
+}
 
 
+static inline double
+gtk_image_view_get_real_angle (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->in_rotate || priv->in_angle_transition)
+    return priv->visible_angle;
+  else
+    return priv->angle;
+}
+
+static inline double
+gtk_image_view_clamp_angle (double angle)
+{
+  double new_angle = angle;
+
+  if (angle > 360.0)
+    new_angle -= (int)(angle / 360.0) * 360;
+  else if (angle < 0.0)
+    new_angle = 360.0 + (int)(angle / 360.0);
+
+  g_assert (new_angle >= 0.0);
+  g_assert (new_angle <= 360.0);
+
+  return new_angle;
+}
+
 static void
 gtk_image_view_get_current_state (GtkImageView *image_view,
                                   State        *state)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
 
-  state->hvalue = gtk_adjustment_get_value (priv->hadjustment);
-  state->vvalue = gtk_adjustment_get_value (priv->vadjustment);
-  state->scale  = priv->scale;
-  state->angle  = priv->angle;
-  state->hupper = gtk_adjustment_get_upper (priv->hadjustment);
-  state->vupper = gtk_adjustment_get_upper (priv->vadjustment);
+  if (priv->hadjustment != NULL && priv->vadjustment != NULL)
+    {
+      state->hvalue = gtk_adjustment_get_value (priv->hadjustment);
+      state->vvalue = gtk_adjustment_get_value (priv->vadjustment);
+      state->hupper = gtk_adjustment_get_upper (priv->hadjustment);
+      state->vupper = gtk_adjustment_get_upper (priv->vadjustment);
+    }
+  state->angle = gtk_image_view_get_real_angle (image_view);
+  state->scale = gtk_image_view_get_real_scale (image_view);
+}
+
+static gboolean
+gtk_image_view_transitions_enabled (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  gboolean animations_enabled;
+
+  g_object_get (gtk_widget_get_settings (GTK_WIDGET (image_view)),
+                "gtk-enable-animations", &animations_enabled,
+                NULL);
+
+
+  return priv->transitions_enabled && animations_enabled;
+}
+
+
+static gboolean
+scale_frameclock_cb (GtkWidget     *widget,
+                     GdkFrameClock *frame_clock,
+                     gpointer       user_data)
+{
+  GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  State state;
+  gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+
+  double t = (now - priv->scale_transition_start) / TRANSITION_DURATION;
+  double new_scale = (priv->scale - priv->transition_start_scale) * t;
+
+  gtk_image_view_get_current_state (image_view, &state);
+
+  priv->visible_scale = priv->transition_start_scale + new_scale;
+  priv->size_valid = FALSE;
+
+  if (priv->hadjustment && priv->vadjustment)
+    {
+      GtkAllocation allocation;
+      gtk_widget_get_allocation (widget, &allocation);
+      gtk_image_view_update_adjustments (image_view);
+
+      gtk_image_view_fix_anchor (image_view,
+                                 allocation.width / 2,
+                                 allocation.height / 2,
+                                 &state);
+    }
+
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw (widget);
+  else
+    gtk_widget_queue_resize (widget);
+
+  if (t >= 1.0)
+    {
+      priv->in_scale_transition = FALSE;
+      return G_SOURCE_REMOVE;
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_image_view_animate_to_scale (GtkImageView *image_view,
+                                 double        new_scale)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  /* Target scale is priv->scale */
+  priv->in_scale_transition = TRUE;
+  priv->visible_scale = priv->scale;
+  priv->transition_start_scale = priv->scale;
+  priv->scale_transition_start = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET 
(image_view)));
+
+  gtk_widget_add_tick_callback (GTK_WIDGET (image_view),
+                                scale_frameclock_cb,
+                                NULL, NULL);
 }
 
-static gchar *
-state_str (State *s)
+static gboolean
+angle_frameclock_cb (GtkWidget     *widget,
+                     GdkFrameClock *frame_clock,
+                     gpointer       user_data)
 {
-  gchar *str = g_strdup_printf ("(Angle: %f, Scale: %f, hvalue: %f, vvalue: %f, hupper: %f, vupper: %f)",
-                                s->angle, s->scale, s->hvalue, s->vvalue, s->hupper, s->vupper);
-  return str;
+  GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  State state;
+  gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+
+  double t = (now - priv->angle_transition_start) / TRANSITION_DURATION;
+  double new_angle = (priv->angle - priv->transition_start_angle) * t;
+
+  gtk_image_view_get_current_state (image_view, &state);
+
+  priv->visible_angle = priv->transition_start_angle + new_angle;
+  priv->size_valid = FALSE;
+
+
+  if (priv->hadjustment && priv->vadjustment)
+    {
+      GtkAllocation allocation;
+      gtk_widget_get_allocation (widget, &allocation);
+      gtk_image_view_update_adjustments (image_view);
+
+      gtk_image_view_fix_anchor (image_view,
+                                 allocation.width / 2,
+                                 allocation.height / 2,
+                                 &state);
+    }
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw (widget);
+  else
+    gtk_widget_queue_resize (widget);
+
+  if (t >= 1.0)
+    {
+      priv->in_angle_transition = FALSE;
+      return G_SOURCE_REMOVE;
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
+
+static void
+gtk_image_view_animate_to_angle (GtkImageView *image_view,
+                                 double        target_angle)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  /* Target angle is priv->angle */
+  priv->in_angle_transition = TRUE;
+  priv->visible_angle = priv->angle;
+  priv->transition_start_angle = priv->angle;
+  priv->angle_transition_start = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET 
(image_view)));
+
+  gtk_widget_add_tick_callback (GTK_WIDGET (image_view),
+                                angle_frameclock_cb,
+                                NULL, NULL);
 }
 
 
+static void
+gtk_image_view_do_snapping (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  double new_angle = (int) ((priv->angle + 45.0) / 90.0) * 90;
+
+  g_assert (priv->snap_angle);
+
+  if (gtk_image_view_transitions_enabled (image_view))
+    gtk_image_view_animate_to_angle (image_view, new_angle);
 
+  priv->angle = new_angle;
+
+  /* Don't notify! */
+}
 
 static void
 free_load_task_data (LoadTaskData *data)
 {
   g_clear_object (&data->source);
+  g_slice_free (LoadTaskData, data);
 }
 
-  /* XXX What if the image is rotated by 45deg and the user presses outside of it?
-   *     I.e. the anchor point would lie outside of the image? */
-
 
 static void
 to_rotate_coords (GtkImageView *image_view,
-                  State *state,
+                  State        *state,
                   double  in_x,  double  in_y,
                   double *out_x, double *out_y)
 {
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-
-  g_message ("hupper: %f", gtk_adjustment_get_upper (priv->hadjustment));
-  g_message ("hvalue: %f", gtk_adjustment_get_value (priv->hadjustment));
-
   double cx = state->hupper / 2.0 - state->hvalue;
   double cy = state->vupper / 2.0 - state->vvalue;
 
@@ -203,10 +405,17 @@ gtk_image_view_fix_anchor (GtkImageView *image_view,
                            State        *old_state)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  double hupper_delta = gtk_adjustment_get_upper (priv->hadjustment) - old_state->hupper;
-  double vupper_delta = gtk_adjustment_get_upper (priv->vadjustment) - old_state->vupper;
+  double hupper_delta = gtk_adjustment_get_upper (priv->hadjustment)
+                        - old_state->hupper;
+  double vupper_delta = gtk_adjustment_get_upper (priv->vadjustment)
+                        - old_state->vupper;
+  double hupper_delta_scale, vupper_delta_scale;
+  double hupper_delta_angle, vupper_delta_angle;
+  double cur_scale = gtk_image_view_get_real_scale (image_view);
 
 
+  g_assert (old_state->hupper > 0);
+  g_assert (old_state->vupper > 0);
   g_assert (priv->hadjustment);
   g_assert (priv->vadjustment);
   g_assert (priv->size_valid);
@@ -215,41 +424,15 @@ gtk_image_view_fix_anchor (GtkImageView *image_view,
   g_assert (anchor_x < gtk_widget_get_allocated_width (GTK_WIDGET (image_view)));
   g_assert (anchor_y < gtk_widget_get_allocated_height (GTK_WIDGET (image_view)));
 
-
-
-  g_message ("Old State: %s", state_str (old_state));
-  g_message ("New angle: %f", priv->angle);
-  g_message ("New scale: %f", priv->scale);
-
-  g_message ("Anchor: %f/%f", priv->anchor_x, priv->anchor_y);
-  g_message ("hupper_delta: %f", hupper_delta);
-  g_message ("vupper_delta: %f", vupper_delta);
-
-
-
-
-  double hupper = gtk_adjustment_get_upper (priv->hadjustment);
-  double hdiff_scale = (old_state->hupper / old_state->scale) * priv->scale;
-
-  double hd = hdiff_scale - old_state->hupper;
-
-  g_message ("hupper diff: %f", (hupper - old_state->hupper));
-  g_message ("scale_diff: %f", hd);
-  g_message ("angle_diff: %f", hupper_delta - hd);
-
-
   /* Amount of upper change caused by scale */
-  double hupper_delta_scale = ((old_state->hupper / old_state->scale) * priv->scale)
-                              - old_state->hupper;
-  double vupper_delta_scale = ((old_state->vupper / old_state->scale) * priv->scale)
-                              - old_state->vupper;
+  hupper_delta_scale = ((old_state->hupper / old_state->scale) * cur_scale)
+                       - old_state->hupper;
+  vupper_delta_scale = ((old_state->vupper / old_state->scale) * cur_scale)
+                       - old_state->vupper;
 
   /* Amount of upper change caused by angle */
-  double hupper_delta_angle = hupper_delta - hupper_delta_scale;
-  double vupper_delta_angle = vupper_delta - vupper_delta_scale;
-
-
-
+  hupper_delta_angle = hupper_delta - hupper_delta_scale;
+  vupper_delta_angle = vupper_delta - vupper_delta_scale;
 
   /* As a first step, fix the anchor point with regard to the
    * updated scale
@@ -261,8 +444,8 @@ gtk_image_view_fix_anchor (GtkImageView *image_view,
     double px = anchor_x + hvalue;
     double py = anchor_y + vvalue;
 
-    double px_after = (px / old_state->scale) * priv->scale;
-    double py_after = (py / old_state->scale) * priv->scale;
+    double px_after = (px / old_state->scale) * cur_scale;
+    double py_after = (py / old_state->scale) * cur_scale;
 
     gtk_adjustment_set_value (priv->hadjustment,
                               hvalue + px_after - px);
@@ -280,139 +463,50 @@ gtk_image_view_fix_anchor (GtkImageView *image_view,
 
 
 
+  {
+    double rotate_anchor_x = 0;
+    double rotate_anchor_y = 0;
+    double anchor_angle;
+    double anchor_length;
+    double new_anchor_x, new_anchor_y;
+    double delta_x, delta_y;
+
+    /* Calculate the angle of the given anchor point relative to the
+     * bounding box center and the OLD state */
+    to_rotate_coords (image_view, old_state,
+                      anchor_x, anchor_y,
+                      &rotate_anchor_x, &rotate_anchor_y);
+    anchor_angle = atan2 (rotate_anchor_y, rotate_anchor_x);
+    anchor_length = sqrt ((rotate_anchor_x * rotate_anchor_x) +
+                          (rotate_anchor_y * rotate_anchor_y));
+
+    /* The angle of the anchor point NOW is the old angle plus
+     * the difference between old surface angle and new surface angle */
+    anchor_angle += DEG_TO_RAD (gtk_image_view_get_real_angle (image_view)
+                                - old_state->angle);
+
+    /* Calculate the position of the new anchor point, relative
+     * to the bounding box center */
+    new_anchor_x = cos (anchor_angle) * anchor_length;
+    new_anchor_y = sin (anchor_angle) * anchor_length;
+
+    /* The difference between old anchor and new anchor
+     * is what we care about... */
+    delta_x = rotate_anchor_x - new_anchor_x;
+    delta_y = rotate_anchor_y - new_anchor_y;
+
+    /* At last, make the old anchor match the new anchor */
+    gtk_adjustment_set_value (priv->hadjustment,
+                              gtk_adjustment_get_value (priv->hadjustment) - delta_x);
+    gtk_adjustment_set_value (priv->vadjustment,
+                              gtk_adjustment_get_value (priv->vadjustment) - delta_y);
 
-  double rotate_anchor_x = 0;
-  double rotate_anchor_y = 0;
-
-  to_rotate_coords (image_view, old_state,
-                    anchor_x, anchor_y,
-                    &rotate_anchor_x, &rotate_anchor_y);
-
-  g_message ("Rotate anchor coords: %f/%f", rotate_anchor_x, rotate_anchor_y);
-
-  /* 1) Calculate the angle of our anchor point. */
-  double anchor_angle = atan2 (rotate_anchor_y, rotate_anchor_x);
-
-
-  double anchor_length = sqrt ((rotate_anchor_x * rotate_anchor_x) +
-                               (rotate_anchor_y * rotate_anchor_y));
-  g_message ("Anchor angle: %f", RAD_TO_DEG (anchor_angle));
-  g_message ("Anchor length: %f", anchor_length);
-
-  /* 2) Calculate the position of our anchor point with increased angle */
-  double angle_diff = priv->angle - old_state->angle;
-  anchor_angle += DEG_TO_RAD (angle_diff);
-
-  g_message ("anchor angle after: %f", RAD_TO_DEG (anchor_angle));
-
-
-  g_message ("Angle got increased by %f", angle_diff);
-  double scale_diff = priv->scale - old_state->scale;
-  g_message ("Scale got increased by %f", scale_diff);
-  double new_anchor_x = cos (anchor_angle) * anchor_length;
-  double new_anchor_y = sin (anchor_angle) * anchor_length;
-
-  g_message ("New anchor: %f, %f", new_anchor_x, new_anchor_y);
-
-
-  double diff_x = rotate_anchor_x - new_anchor_x;
-  double diff_y = rotate_anchor_y - new_anchor_y;
-
-  g_message ("Diff: %f/%f", diff_x, diff_y);
-
-  gtk_adjustment_set_value (priv->hadjustment,
-                            gtk_adjustment_get_value (priv->hadjustment) - diff_x);
-
-
-  gtk_adjustment_set_value (priv->vadjustment,
-                            gtk_adjustment_get_value (priv->vadjustment) - diff_y);
-
+  }
 
-  g_message ("-------------------------");
   gtk_widget_queue_draw (GTK_WIDGET (image_view));
 }
 
 static void
-gesture_rotate_end_cb (GtkGesture       *gesture,
-                       GdkEventSequence *sequence,
-                       gpointer          user_data)
-{
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
-
-  priv->gesture_start_angle = 0.0;
-  priv->in_rotate = FALSE;
-
-  priv->anchor_x = -1;
-  priv->anchor_y = -1;
-}
-
-static void
-gesture_rotate_cancel_cb (GtkGesture       *gesture,
-                          GdkEventSequence *sequence,
-                          gpointer          user_data)
-{
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
-  gtk_image_view_set_angle (user_data, priv->gesture_start_angle);
-  priv->in_rotate = FALSE;
-  priv->gesture_start_angle = 0.0;
-
-  priv->anchor_x = -1;
-  priv->anchor_y = -1;
-}
-
-
-static void
-gesture_angle_changed_cb (GtkGestureRotate *gesture,
-                          double            angle,
-                          double            delta,
-                          GtkWidget        *widget)
-{
-  GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  State old_state;
-  double new_angle;
-
-  if (!priv->rotate_gesture_enabled)
-    {
-      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
-      return;
-    }
-
-  if (!priv->in_rotate)
-    {
-      priv->in_rotate = TRUE;
-      priv->gesture_start_angle = priv->angle;
-    }
-
-
-  new_angle = priv->gesture_start_angle + RAD_TO_DEG (delta);
-
-  if (new_angle == priv->angle)
-    return;
-
-
-  gtk_image_view_get_current_state (image_view, &old_state);
-
-  /* Don't notify */
-  priv->angle = new_angle;
-  priv->size_valid = FALSE;
-  gtk_image_view_update_adjustments (image_view);
-
-  if (priv->hadjustment && priv->vadjustment)
-    gtk_image_view_fix_anchor (image_view,
-                                      priv->anchor_x,
-                                      priv->anchor_y,
-                                      &old_state);
-
-  // XXX Even if fit_allocation is not set, we still don't need to query a resize
-  //     if we are in a scrolledwindow, right?
-  if (priv->fit_allocation)
-    gtk_widget_queue_draw (widget);
-  else
-    gtk_widget_queue_resize (widget);
-}
-
-static void
 gtk_image_view_compute_bounding_box (GtkImageView *image_view,
                                      double       *width,
                                      double       *height,
@@ -430,6 +524,8 @@ gtk_image_view_compute_bounding_box (GtkImageView *image_view,
   double upper_right_x, upper_right_y;
   double upper_left_x, upper_left_y;
   double scale;
+  double angle;
+
   if (priv->size_valid)
     {
       *width = priv->cached_width;
@@ -448,12 +544,13 @@ gtk_image_view_compute_bounding_box (GtkImageView *image_view,
     }
 
   gtk_widget_get_allocation (GTK_WIDGET (image_view), &alloc);
+  angle = gtk_image_view_get_real_angle (image_view);
 
   image_width  = cairo_image_surface_get_width (priv->image_surface);
   image_height = cairo_image_surface_get_height (priv->image_surface);
 
-  upper_right_degrees = DEG_TO_RAD (priv->angle) + atan (image_height / image_width);
-  upper_left_degrees  = DEG_TO_RAD (priv->angle) + atan (image_height / -image_width);
+  upper_right_degrees = DEG_TO_RAD (angle) + atan (image_height / image_width);
+  upper_left_degrees  = DEG_TO_RAD (angle) + atan (image_height / -image_width);
   r = sqrt ((image_width / 2.0) * (image_width / 2.0) + (image_height / 2.0) * (image_height / 2.0));
 
   upper_right_x = r * cos (upper_right_degrees);
@@ -465,23 +562,17 @@ gtk_image_view_compute_bounding_box (GtkImageView *image_view,
   bb_width  = round (MAX (fabs (upper_right_x), fabs (upper_left_x)) * 2.0);
   bb_height = round (MAX (fabs (upper_right_y), fabs (upper_left_y)) * 2.0);
 
-  if (priv->scale_set)
+
+  if (priv->fit_allocation)
     {
-      scale = priv->scale;
+      double scale_x = (double)alloc.width / (double)bb_width;
+      double scale_y = (double)alloc.height / (double)bb_height;
+
+      scale = MIN (MIN (scale_x, scale_y), 1.0);
     }
   else
     {
-      if (priv->fit_allocation)
-        {
-          double scale_x = (double)alloc.width / (double)bb_width;
-          double scale_y = (double)alloc.height / (double)bb_height;
-
-          scale = MIN (MIN (scale_x, scale_y), 1.0);
-        }
-      else
-        {
-          scale = 1.0;
-        }
+      scale = gtk_image_view_get_real_scale (image_view);
     }
 
   priv->cached_scale = scale;
@@ -490,6 +581,7 @@ gtk_image_view_compute_bounding_box (GtkImageView *image_view,
 
   if (priv->fit_allocation)
     {
+      g_assert (!priv->scale_set);
       priv->scale = scale;
       g_object_notify_by_pspec (G_OBJECT (image_view),
                                 widget_props[PROP_SCALE]);
@@ -522,6 +614,7 @@ static void
 gtk_image_view_update_adjustments (GtkImageView *image_view)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  double width, height;
   int widget_width  = gtk_widget_get_allocated_width  (GTK_WIDGET (image_view));
   int widget_height = gtk_widget_get_allocated_height (GTK_WIDGET (image_view));
 
@@ -540,52 +633,28 @@ gtk_image_view_update_adjustments (GtkImageView *image_view)
       return;
     }
 
+  gtk_image_view_compute_bounding_box (image_view,
+                                       &width,
+                                       &height,
+                                       NULL);
 
-  if (priv->fit_allocation)
-    {
-      if (priv->hadjustment)
-        gtk_adjustment_set_upper (priv->hadjustment, widget_width);
-
-      if (priv->vadjustment)
-        gtk_adjustment_set_upper (priv->vadjustment, widget_height);
-    }
-  else
-    {
-      double width, height;
-      gtk_image_view_compute_bounding_box (image_view,
-                                           &width,
-                                           &height,
-                                           NULL);
-
-      if (priv->hadjustment)
-        gtk_adjustment_set_upper (priv->hadjustment, MAX (width,  widget_width));
-
-      if (priv->vadjustment)
-        gtk_adjustment_set_upper (priv->vadjustment, MAX (height, widget_height));
-    }
-
-
+  /* compute_bounding_box makes sure that the bounding box is never bigger than
+   * the widget allocation if fit-allocation is set */
   if (priv->hadjustment)
     {
+      gtk_adjustment_set_upper (priv->hadjustment, MAX (width,  widget_width));
       gtk_adjustment_set_page_size (priv->hadjustment, widget_width);
       gtk_image_view_restrict_adjustment (priv->hadjustment);
     }
 
   if (priv->vadjustment)
     {
+      gtk_adjustment_set_upper (priv->vadjustment, MAX (height, widget_height));
       gtk_adjustment_set_page_size (priv->vadjustment, widget_height);
       gtk_image_view_restrict_adjustment (priv->vadjustment);
     }
 }
 
-
-
-
-/*
- * This is basically the normal _set_scale without the
- * _fix_anchor call at the end, so we can choose the point
- * to fix.
- */
 static void
 gtk_image_view_set_scale_internal (GtkImageView *image_view,
                                    double        scale)
@@ -598,10 +667,9 @@ gtk_image_view_set_scale_internal (GtkImageView *image_view,
   g_object_notify_by_pspec (G_OBJECT (image_view),
                             widget_props[PROP_SCALE]);
 
-
-  if (!priv->scale_set)
+  if (priv->scale_set)
     {
-      priv->scale_set = TRUE;
+      priv->scale_set = FALSE;
       g_object_notify_by_pspec (G_OBJECT (image_view),
                                 widget_props[PROP_SCALE_SET]);
     }
@@ -619,18 +687,40 @@ gtk_image_view_set_scale_internal (GtkImageView *image_view,
   gtk_widget_queue_resize (GTK_WIDGET (image_view));
 }
 
+/* Zoom Gesture {{{ */
+
+static void
+gesture_zoom_begin_cb (GtkGesture       *gesture,
+                       GdkEventSequence *sequence,
+                       gpointer          user_data)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+
+  if (!priv->zoom_gesture_enabled ||
+      !priv->image_surface)
+    {
+      gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  if (priv->anchor_x == -1 && priv->anchor_y == -1)
+    {
+      gtk_gesture_get_bounding_box_center (gesture,
+                                           &priv->anchor_x,
+                                           &priv->anchor_y);
+    }
+}
+
 static void
 gesture_zoom_end_cb (GtkGesture       *gesture,
                      GdkEventSequence *sequence,
-                     gpointer          user_data)
+                     gpointer          image_view)
 {
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
 
-  gtk_image_view_set_scale (user_data, priv->scale);
+  gtk_image_view_set_scale_internal (image_view, priv->visible_scale);
 
-  priv->gesture_start_scale = 0.0;
   priv->in_zoom = FALSE;
-
   priv->anchor_x = -1;
   priv->anchor_y = -1;
 }
@@ -642,17 +732,16 @@ gesture_zoom_cancel_cb (GtkGesture       *gesture,
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
 
-  gtk_image_view_set_scale (user_data, priv->gesture_start_scale);
+  if (priv->in_zoom)
+    gtk_image_view_set_scale (user_data, priv->gesture_start_scale);
 
-  priv->gesture_start_scale = 0.0;
   priv->in_zoom = FALSE;
   priv->anchor_x = -1;
   priv->anchor_y = -1;
 }
 
-
 static void
-gesture_scale_changed_cb (GtkGestureZoom *gesture,
+gesture_zoom_changed_cb (GtkGestureZoom *gesture,
                           double          delta,
                           GtkWidget      *widget)
 {
@@ -661,43 +750,56 @@ gesture_scale_changed_cb (GtkGestureZoom *gesture,
   State state;
   double new_scale;
 
-  if (!priv->rotate_gesture_enabled)
-    {
-      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
-      return;
-    }
-
   if (!priv->in_zoom)
     {
       priv->in_zoom = TRUE;
       priv->gesture_start_scale = priv->scale;
     }
 
+  if (priv->fit_allocation)
+    {
+      priv->fit_allocation = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (widget),
+                                widget_props[PROP_FIT_ALLOCATION]);
+    }
+
   new_scale = priv->gesture_start_scale * delta;
   gtk_image_view_get_current_state (image_view, &state);
 
-  /* Don't emit */
-  priv->scale = new_scale;
-  gtk_image_view_update_adjustments (image_view);
+  priv->visible_scale = new_scale;
+  priv->size_valid = FALSE;
 
-  gtk_image_view_set_scale_internal (image_view, new_scale);
+  gtk_image_view_update_adjustments (image_view);
 
-  if (priv->hadjustment || priv->vadjustment)
+  if (priv->hadjustment != NULL && priv->vadjustment != NULL)
     {
       gtk_image_view_fix_anchor (image_view,
-                                        priv->anchor_x,
-                                        priv->anchor_y,
-                                        &state);
+                                 priv->anchor_x,
+                                 priv->anchor_y,
+                                 &state);
     }
+
+  gtk_widget_queue_resize (GTK_WIDGET (image_view));
 }
 
+/* }}} */
+
+/* Rotate Gesture {{{ */
 static void
-gesture_begin_cb (GtkGesture       *gesture,
-                  GdkEventSequence *sequence,
-                  gpointer          user_data)
+gesture_rotate_begin_cb (GtkGesture       *gesture,
+                         GdkEventSequence *sequence,
+                         gpointer          user_data)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
 
+  if (!priv->rotate_gesture_enabled ||
+      !priv->image_surface)
+    {
+      gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+
   if (priv->anchor_x == -1 && priv->anchor_y == -1)
     {
       gtk_gesture_get_bounding_box_center (gesture,
@@ -706,6 +808,125 @@ gesture_begin_cb (GtkGesture       *gesture,
     }
 }
 
+static void
+gesture_rotate_end_cb (GtkGesture       *gesture,
+                       GdkEventSequence *sequence,
+                       gpointer          image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  priv->angle = gtk_image_view_clamp_angle (priv->visible_angle);
+
+  if (priv->snap_angle)
+    {
+      /* Will update priv->angle */
+      gtk_image_view_do_snapping (image_view);
+    }
+  g_object_notify_by_pspec (image_view,
+                            widget_props[PROP_ANGLE]);
+
+
+  priv->in_rotate = FALSE;
+  priv->anchor_x = -1;
+  priv->anchor_y = -1;
+}
+
+static void
+gesture_rotate_cancel_cb (GtkGesture       *gesture,
+                          GdkEventSequence *sequence,
+                          gpointer          image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  priv->size_valid = FALSE;
+  gtk_image_view_update_adjustments (image_view);
+
+  priv->in_rotate = FALSE;
+  priv->anchor_x = -1;
+  priv->anchor_y = -1;
+}
+
+static void
+gesture_rotate_changed_cb (GtkGestureRotate *gesture,
+                          double            angle,
+                          double            delta,
+                          GtkWidget        *widget)
+{
+  GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  State old_state;
+  double new_angle;
+
+  if (!priv->in_rotate)
+    {
+      priv->in_rotate = TRUE;
+      priv->gesture_start_angle = priv->angle;
+    }
+
+
+  new_angle = priv->gesture_start_angle + RAD_TO_DEG (delta);
+  gtk_image_view_get_current_state (image_view, &old_state);
+
+  priv->visible_angle = new_angle;
+  priv->size_valid = FALSE;
+  gtk_image_view_update_adjustments (image_view);
+
+  if (priv->hadjustment && priv->vadjustment && !priv->fit_allocation)
+    gtk_image_view_fix_anchor (image_view,
+                               priv->anchor_x,
+                               priv->anchor_y,
+                               &old_state);
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw (widget);
+  else
+    gtk_widget_queue_resize (widget);
+}
+/* }}} */
+
+static void
+gtk_image_view_ensure_gestures (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->zoom_gesture_enabled && priv->zoom_gesture == NULL)
+    {
+      priv->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (image_view));
+      g_signal_connect (priv->zoom_gesture, "scale-changed",
+                        (GCallback)gesture_zoom_changed_cb, image_view);
+      g_signal_connect (priv->zoom_gesture, "begin",
+                        (GCallback)gesture_zoom_begin_cb, image_view);
+      g_signal_connect (priv->zoom_gesture, "end",
+                        (GCallback)gesture_zoom_end_cb, image_view);
+      g_signal_connect (priv->zoom_gesture, "cancel",
+                        (GCallback)gesture_zoom_cancel_cb, image_view);
+    }
+  else if (!priv->zoom_gesture_enabled && priv->zoom_gesture != NULL)
+    {
+      g_clear_object (&priv->zoom_gesture);
+    }
+
+  if (priv->rotate_gesture_enabled && priv->rotate_gesture == NULL)
+    {
+      priv->rotate_gesture = gtk_gesture_rotate_new (GTK_WIDGET (image_view));
+      /*gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->rotate_gesture),*/
+                                                  /*GTK_PHASE_CAPTURE);*/
+      g_signal_connect (priv->rotate_gesture, "angle-changed", (GCallback)gesture_rotate_changed_cb, 
image_view);
+      g_signal_connect (priv->rotate_gesture, "begin", (GCallback)gesture_rotate_begin_cb, image_view);
+      g_signal_connect (priv->rotate_gesture, "end", (GCallback)gesture_rotate_end_cb, image_view);
+      g_signal_connect (priv->rotate_gesture, "cancel", (GCallback)gesture_rotate_cancel_cb, image_view);
+
+
+    }
+  else if (!priv->rotate_gesture_enabled && priv->rotate_gesture != NULL)
+    {
+      g_clear_object (&priv->rotate_gesture);
+    }
+
+  if (priv->zoom_gesture && priv->rotate_gesture)
+    gtk_gesture_group (priv->zoom_gesture,
+                       priv->rotate_gesture);
+}
 
 static void
 gtk_image_view_init (GtkImageView *image_view)
@@ -718,6 +939,8 @@ gtk_image_view_init (GtkImageView *image_view)
 
   priv->scale = 1.0;
   priv->angle = 0.0;
+  priv->visible_scale = 1.0;
+  priv->visible_angle = 0.0;
   priv->snap_angle = FALSE;
   priv->fit_allocation = FALSE;
   priv->scale_set = FALSE;
@@ -726,22 +949,9 @@ gtk_image_view_init (GtkImageView *image_view)
   priv->anchor_y = -1;
   priv->rotate_gesture_enabled = TRUE;
   priv->zoom_gesture_enabled = TRUE;
-  priv->rotate_gesture = gtk_gesture_rotate_new (widget);
-  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->rotate_gesture),
-                                              GTK_PHASE_CAPTURE);
-  g_signal_connect (priv->rotate_gesture, "angle-changed", (GCallback)gesture_angle_changed_cb, image_view);
-  g_signal_connect (priv->rotate_gesture, "begin", (GCallback)gesture_begin_cb, image_view);
-  g_signal_connect (priv->rotate_gesture, "end", (GCallback)gesture_rotate_end_cb, image_view);
-  g_signal_connect (priv->rotate_gesture, "cancel", (GCallback)gesture_rotate_cancel_cb, image_view);
+  priv->transitions_enabled = TRUE;
 
-  priv->zoom_gesture = gtk_gesture_zoom_new (widget);
-  g_signal_connect (priv->zoom_gesture, "scale-changed", (GCallback)gesture_scale_changed_cb, image_view);
-  g_signal_connect (priv->zoom_gesture, "begin", (GCallback)gesture_begin_cb, image_view);
-  g_signal_connect (priv->zoom_gesture, "end", (GCallback)gesture_zoom_end_cb, image_view);
-  g_signal_connect (priv->zoom_gesture, "cancel", (GCallback)gesture_zoom_cancel_cb, image_view);
-
-  gtk_gesture_group (priv->zoom_gesture,
-                     priv->rotate_gesture);
+  gtk_image_view_ensure_gestures (image_view);
 }
 
 
@@ -800,74 +1010,6 @@ gtk_image_view_stop_animation (GtkImageView *image_view)
     }
 }
 
-
-static gboolean
-frameclock_cb (GtkWidget     *widget,
-               GdkFrameClock *frame_clock,
-               gpointer       user_data)
-{
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
-  gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
-
-  double t = (now - priv->angle_transition_start) / TRANSITION_DURATION;
-
-  double new_angle = (priv->transition_end_angle - priv->transition_start_angle) * t;
-
-  priv->angle = priv->transition_start_angle + new_angle;
-
-  if (priv->fit_allocation)
-    gtk_widget_queue_draw (widget);
-  else
-    gtk_widget_queue_resize (widget);
-
-  if (t >= 1.0)
-    {
-      priv->angle = priv->transition_end_angle;
-      return G_SOURCE_REMOVE;
-    }
-
-  return G_SOURCE_CONTINUE;
-}
-
-
-static void
-gtk_image_view_animate_to_angle (GtkImageView *image_view,
-                                 double        start_angle)
-{
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  /* target angle is priv->angle! */
-
-  priv->transition_start_angle = start_angle;
-  priv->transition_end_angle   = priv->angle;
-  priv->angle_transition_start = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET 
(image_view)));
-  gtk_widget_add_tick_callback (GTK_WIDGET (image_view), frameclock_cb, NULL, NULL);
-}
-
-static void
-gtk_image_view_do_snapping (GtkImageView *image_view,
-                            double        angle)
-{
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  int new_angle;
-
-  g_assert (priv->snap_angle);
-
-  /* Snap to angles of 0, 90, 180 and 270 degrees */
-
-  new_angle = (int) ((angle) / 90.0) * 90;
-
-  if (new_angle != priv->angle)
-    {
-      double old_angle = priv->angle;
-      priv->angle = new_angle;
-      /* XXX Make this conditional */
-      gtk_image_view_animate_to_angle (image_view,
-                                       old_angle);
-    }
-
-  priv->angle = new_angle;
-}
-
 static gboolean
 gtk_image_view_draw (GtkWidget *widget, cairo_t *ct)
 {
@@ -951,7 +1093,7 @@ gtk_image_view_draw (GtkWidget *widget, cairo_t *ct)
   cairo_translate (ct,
                    draw_x + (image_width  / 2.0),
                    draw_y + (image_height / 2.0));
-  cairo_rotate (ct, DEG_TO_RAD (priv->angle));
+  cairo_rotate (ct, DEG_TO_RAD (gtk_image_view_get_real_angle (image_view)));
   cairo_translate (ct,
                    - draw_x - (image_width  / 2.0),
                    - draw_y - (image_height / 2.0));
@@ -966,15 +1108,6 @@ gtk_image_view_draw (GtkWidget *widget, cairo_t *ct)
   cairo_fill (ct);
   cairo_restore (ct);
 
-
-  /* XXX @debug */
-  if (priv->anchor_x != -1 && priv->anchor_y != -1)
-    {
-      cairo_set_source_rgba (ct, 0, 1, 0, 1);
-      cairo_rectangle (ct, priv->anchor_x - 1, priv->anchor_y - 1, 2, 2);
-      cairo_fill (ct);
-    }
-
   return GDK_EVENT_PROPAGATE;
 }
 
@@ -1085,8 +1218,7 @@ gtk_image_view_set_vscroll_policy (GtkImageView        *image_view,
  * @scale: The new scale value
  *
  * Sets the value of the #scale property. This will cause the
- * #scale-set property to be set to #TRUE as well. If the given
- * value of @scale is below zero, 0 will be set instead.
+ * #scale-set property to be set to #TRUE as well
  *
  * If #fit-allocation is #TRUE, it will be set to #FALSE, and @image_view
  * will be resized to the image's current size, taking the new scale into
@@ -1098,25 +1230,51 @@ gtk_image_view_set_scale (GtkImageView *image_view,
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   State state;
-  double pointer_x;
-  double pointer_y;
 
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
-  g_return_if_fail (scale >= 0.0);
-
-  pointer_x = gtk_widget_get_allocated_width (GTK_WIDGET (image_view))  / 2;
-  pointer_y = gtk_widget_get_allocated_height (GTK_WIDGET (image_view)) / 2;
+  g_return_if_fail (scale > 0.0);
 
   gtk_image_view_get_current_state (image_view, &state);
-  gtk_image_view_set_scale_internal (image_view, scale);
+
+  if (gtk_image_view_transitions_enabled (image_view))
+    gtk_image_view_animate_to_scale (image_view, scale);
+
+  priv->scale = scale;
+  g_object_notify_by_pspec (G_OBJECT (image_view),
+                            widget_props[PROP_SCALE]);
+
+  if (priv->scale_set)
+    {
+      priv->scale_set = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_SCALE_SET]);
+    }
+
+  if (priv->fit_allocation)
+    {
+      priv->fit_allocation = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_FIT_ALLOCATION]);
+    }
+
+  priv->size_valid = FALSE;
+  gtk_image_view_update_adjustments (image_view);
+
+  if (!priv->image_surface)
+    return;
+
 
   if (priv->hadjustment != NULL && priv->vadjustment != NULL)
     {
+      int pointer_x = gtk_widget_get_allocated_width (GTK_WIDGET (image_view)) / 2;
+      int pointer_y = gtk_widget_get_allocated_height (GTK_WIDGET (image_view)) / 2;
       gtk_image_view_fix_anchor (image_view,
-                                        pointer_x,
-                                        pointer_y,
-                                        &state);
+                                 pointer_x,
+                                 pointer_y,
+                                 &state);
     }
+
+  gtk_widget_queue_resize (GTK_WIDGET (image_view));
 }
 
 double
@@ -1146,41 +1304,33 @@ gtk_image_view_set_angle (GtkImageView *image_view,
 
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
 
-
-
-  if (angle > 360.0)
-    angle -= (int)(angle / 360.0) * 360;
-  else if (angle < 0.0)
-    angle = 360.0 + (int)(angle / 360.0);
-
-  g_assert (angle >= 0.0);
-  g_assert (angle <= 360.0);
-
-
-
-
-
   gtk_image_view_get_current_state (image_view, &state);
 
-  priv->angle = angle;
-  priv->size_valid = FALSE;
+  if (gtk_image_view_transitions_enabled (image_view) &&
+      ABS(gtk_image_view_clamp_angle (angle) - priv->angle) > ANGLE_TRANSITION_MIN_DELTA)
+      gtk_image_view_animate_to_angle (image_view, angle);
 
 
+  priv->angle = gtk_image_view_clamp_angle (angle);
+  priv->size_valid = FALSE;
 
   gtk_image_view_update_adjustments (image_view);
 
-
   g_object_notify_by_pspec (G_OBJECT (image_view),
                             widget_props[PROP_ANGLE]);
 
-  // XXX Pass a width/2, height/2 anchor here.
-  //
-  // TODO: Would we have to document this behavior? Or make it configurable?
+  if (!priv->image_surface)
+    return;
 
-  gtk_image_view_fix_anchor (image_view,
-                                    priv->anchor_x,
-                                    priv->anchor_y,
-                                    &state);
+  if (priv->hadjustment && priv->vadjustment && !priv->fit_allocation)
+    {
+      int pointer_x = gtk_widget_get_allocated_width (GTK_WIDGET (image_view)) / 2;
+      int pointer_y = gtk_widget_get_allocated_height (GTK_WIDGET (image_view)) / 2;
+      gtk_image_view_fix_anchor (image_view,
+                                 pointer_x,
+                                 pointer_y,
+                                 &state);
+    }
 
   if (priv->fit_allocation)
     gtk_widget_queue_draw (GTK_WIDGET (image_view));
@@ -1206,14 +1356,15 @@ gtk_image_view_get_angle (GtkImageView *image_view)
  *
  * Setting #snap-angle to #TRUE will cause @image_view's  angle to
  * be snapped to 90° steps. Setting the #angle property will cause it to
- * be set to the lower 90° step, e.g. setting #angle to 359 will cause
- * the new value to be 270.
+ * be set to the closest 90° step, so e.g. using an angle of 40 will result
+ * in an angle of 0, using using 240 will result in 270, etc.
  */
 void
 gtk_image_view_set_snap_angle (GtkImageView *image_view,
                                gboolean     snap_angle)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
 
   snap_angle = !!snap_angle;
@@ -1226,7 +1377,11 @@ gtk_image_view_set_snap_angle (GtkImageView *image_view,
                             widget_props[PROP_SNAP_ANGLE]);
 
   if (priv->snap_angle)
-    gtk_image_view_do_snapping (image_view, priv->angle);
+    {
+      gtk_image_view_do_snapping (image_view);
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_ANGLE]);
+    }
 }
 
 gboolean
@@ -1276,7 +1431,7 @@ gtk_image_view_set_fit_allocation (GtkImageView *image_view,
   g_object_notify_by_pspec (G_OBJECT (image_view),
                             widget_props[PROP_SCALE_SET]);
 
-  if (!priv->fit_allocation && !priv->scale_set)
+  if (!priv->fit_allocation)
     {
       priv->scale = 1.0;
       g_object_notify_by_pspec (G_OBJECT (image_view),
@@ -1286,7 +1441,6 @@ gtk_image_view_set_fit_allocation (GtkImageView *image_view,
   gtk_image_view_update_adjustments (image_view);
 
   gtk_widget_queue_resize (GTK_WIDGET (image_view));
-  gtk_image_view_update_adjustments (image_view);
 }
 
 gboolean
@@ -1309,9 +1463,13 @@ gtk_image_view_set_rotate_gesture_enabled (GtkImageView *image_view,
 
   rotate_gesture_enabled = !!rotate_gesture_enabled;
 
-  priv->rotate_gesture_enabled = rotate_gesture_enabled;
-  g_object_notify_by_pspec (G_OBJECT (image_view),
-                            widget_props[PROP_ROTATE_GESTURE_ENABLED]);
+  if (priv->rotate_gesture_enabled != rotate_gesture_enabled)
+    {
+      priv->rotate_gesture_enabled = rotate_gesture_enabled;
+      gtk_image_view_ensure_gestures (image_view);
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_ROTATE_GESTURE_ENABLED]);
+    }
 }
 
 gboolean
@@ -1334,9 +1492,13 @@ gtk_image_view_set_zoom_gesture_enabled (GtkImageView *image_view,
 
   zoom_gesture_enabled = !!zoom_gesture_enabled;
 
-  priv->zoom_gesture_enabled = zoom_gesture_enabled;
-  g_object_notify_by_pspec (G_OBJECT (image_view),
-                            widget_props[PROP_ZOOM_GESTURE_ENABLED]);
+  if (zoom_gesture_enabled != priv->zoom_gesture_enabled)
+    {
+      priv->zoom_gesture_enabled = zoom_gesture_enabled;
+      gtk_image_view_ensure_gestures (image_view);
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_ZOOM_GESTURE_ENABLED]);
+    }
 }
 
 gboolean
@@ -1347,6 +1509,34 @@ gtk_image_view_get_zoom_gesture_enabled (GtkImageView *image_view)
 
   return priv->zoom_gesture_enabled;
 }
+
+
+
+void
+gtk_image_view_set_transitions_enabled (GtkImageView *image_view,
+                                        gboolean      transitions_enabled)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+  transitions_enabled = !!transitions_enabled;
+
+  if (transitions_enabled != priv->transitions_enabled)
+    {
+      priv->transitions_enabled = transitions_enabled;
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_TRANSITIONS_ENABLED]);
+    }
+}
+
+gboolean
+gtk_image_view_get_transitions_enabled (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+  return priv->transitions_enabled;
+}
 /* }}} */
 
 
@@ -1373,7 +1563,8 @@ gtk_image_view_realize (GtkWidget *widget)
                           GDK_BUTTON_PRESS_MASK |
                           GDK_BUTTON_RELEASE_MASK |
                           GDK_SMOOTH_SCROLL_MASK |
-                          GDK_SCROLL_MASK;
+                          GDK_SCROLL_MASK |
+                          GDK_TOUCH_MASK;
   attributes.wclass = GDK_INPUT_ONLY;
 
   window = gtk_widget_get_parent_window (widget);
@@ -1390,6 +1581,21 @@ gtk_image_view_realize (GtkWidget *widget)
 }
 
 static void
+gtk_image_view_unrealize (GtkWidget *widget)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+
+  if (priv->event_window)
+    {
+      gtk_widget_unregister_window (widget, priv->event_window);
+      gdk_window_destroy (priv->event_window);
+      priv->event_window = NULL;
+    }
+
+  GTK_WIDGET_CLASS (gtk_image_view_parent_class)->unrealize (widget);
+}
+
+static void
 gtk_image_view_size_allocate (GtkWidget     *widget,
                               GtkAllocation *allocation)
 {
@@ -1482,11 +1688,11 @@ gtk_image_view_get_preferred_width (GtkWidget *widget,
   GtkImageView *image_view  = GTK_IMAGE_VIEW (widget);
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   double width, height;
+
   gtk_image_view_compute_bounding_box (image_view,
                                        &width,
                                        &height,
                                        NULL);
-
   if (priv->fit_allocation)
     {
       *minimal = 0;
@@ -1497,7 +1703,6 @@ gtk_image_view_get_preferred_width (GtkWidget *widget,
       *minimal = width;
       *natural = width;
     }
-
 }
 
 
@@ -1510,16 +1715,24 @@ gtk_image_view_scroll_event (GtkWidget       *widget,
   double new_scale = priv->scale - (0.1 * event->delta_y);
   State state;
 
+  if (!priv->image_surface)
+    return GDK_EVENT_PROPAGATE;
+
+  if (event->state & GDK_SHIFT_MASK ||
+      event->state & GDK_CONTROL_MASK)
+    return GDK_EVENT_PROPAGATE;
+
+
   gtk_image_view_get_current_state (image_view, &state);
 
   gtk_image_view_set_scale_internal (image_view, new_scale);
 
-  if (priv->hadjustment || priv->vadjustment)
+  if (priv->hadjustment && priv->vadjustment)
     {
       gtk_image_view_fix_anchor (image_view,
-                                        event->x,
-                                        event->y,
-                                        &state);
+                                 event->x,
+                                 event->y,
+                                 &state);
     }
 
   return GDK_EVENT_STOP;
@@ -1543,8 +1756,6 @@ gtk_image_view_set_property (GObject      *object,
       case PROP_SCALE:
         gtk_image_view_set_scale (image_view, g_value_get_double (value));
         break;
-      /*case PROP_SCALE_SET:*/
-        /*break;*/
       case PROP_ANGLE:
         gtk_image_view_set_angle (image_view, g_value_get_double (value));
         break;
@@ -1554,6 +1765,12 @@ gtk_image_view_set_property (GObject      *object,
       case PROP_FIT_ALLOCATION:
         gtk_image_view_set_fit_allocation (image_view, g_value_get_boolean (value));
         break;
+      case PROP_ROTATE_GESTURE_ENABLED:
+        gtk_image_view_set_rotate_gesture_enabled (image_view, g_value_get_boolean (value));
+        break;
+      case PROP_ZOOM_GESTURE_ENABLED:
+        gtk_image_view_set_zoom_gesture_enabled (image_view, g_value_get_boolean (value));
+        break;
       case PROP_HADJUSTMENT:
         gtk_image_view_set_hadjustment (image_view, g_value_get_object (value));
         break;
@@ -1585,9 +1802,9 @@ gtk_image_view_get_property (GObject    *object,
       case PROP_SCALE:
         g_value_set_double (value, priv->scale);
         break;
-      /*case PROP_SCALE_SET:*/
-        /*g_value_set_boolean (value, priv->scale_set);*/
-        /*break;*/
+      case PROP_SCALE_SET:
+        g_value_set_boolean (value, priv->scale_set);
+        break;
       case PROP_ANGLE:
         g_value_set_double (value, priv->angle);
         break;
@@ -1597,6 +1814,12 @@ gtk_image_view_get_property (GObject    *object,
       case PROP_FIT_ALLOCATION:
         g_value_set_boolean (value, priv->fit_allocation);
         break;
+      case PROP_ROTATE_GESTURE_ENABLED:
+        g_value_set_boolean (value, priv->rotate_gesture_enabled);
+        break;
+      case PROP_ZOOM_GESTURE_ENABLED:
+        g_value_set_boolean (value, priv->zoom_gesture_enabled);
+        break;
       case PROP_HADJUSTMENT:
         g_value_set_object (value, priv->hadjustment);
         break;
@@ -1650,6 +1873,7 @@ gtk_image_view_class_init (GtkImageViewClass *view_class)
 
   widget_class->draw          = gtk_image_view_draw;
   widget_class->realize       = gtk_image_view_realize;
+  widget_class->unrealize     = gtk_image_view_unrealize;
   widget_class->size_allocate = gtk_image_view_size_allocate;
   widget_class->map           = gtk_image_view_map;
   widget_class->unmap         = gtk_image_view_unmap;
@@ -1683,7 +1907,7 @@ gtk_image_view_class_init (GtkImageViewClass *view_class)
                                                        P_(""),
                                                        P_("fooar"),
                                                        FALSE,
-                                                       GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+                                                       GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
   /**
    * GtkImageView:angle:
    * The angle the surface gets rotated about.
@@ -1750,6 +1974,20 @@ gtk_image_view_class_init (GtkImageViewClass *view_class)
                                                             FALSE,
                                                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   *  GtkImageView:transitions-enabled
+   *
+   *  Whether or not certain property changes will be interpolated.
+   *
+   *  Since: 3.20
+   */
+  widget_props[PROP_TRANSITIONS_ENABLED] = g_param_spec_boolean ("transitions-enabled",
+                                                                 P_("Foo"),
+                                                                 P_("fooar"),
+                                                                 TRUE,
+                                                                 
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+
 
   g_object_class_install_properties (object_class, LAST_WIDGET_PROPERTY, widget_props);
 
@@ -1777,7 +2015,11 @@ gtk_image_view_replace_surface (GtkImageView    *image_view,
   if (priv->image_surface)
     cairo_surface_destroy (priv->image_surface);
 
-  priv->scale_factor = scale_factor;
+  if (scale_factor == 0)
+    priv->scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (image_view));
+  else
+    priv->scale_factor = scale_factor;
+
   priv->image_surface = surface;
   priv->size_valid = FALSE;
 
@@ -1853,6 +2095,7 @@ gtk_image_view_replace_animation (GtkImageView       *image_view,
       gtk_image_view_update_surface (image_view,
                                      gdk_pixbuf_animation_get_static_image (animation),
                                      scale_factor);
+      g_object_unref (animation);
     }
 
 }
@@ -1868,15 +2111,15 @@ gtk_image_view_load_image_from_stream (GtkImageView *image_view,
 {
   GdkPixbufAnimation *result;
 
-
   g_assert (error == NULL);
   result = gdk_pixbuf_animation_new_from_stream (G_INPUT_STREAM (input_stream),
                                                  cancellable,
                                                  &error);
 
-  g_object_unref (input_stream);
   if (!error)
-    gtk_image_view_replace_animation (image_view, result,scale_factor);
+    gtk_image_view_replace_animation (image_view, result, scale_factor);
+
+  g_object_unref (input_stream);
 }
 
 static void
@@ -1891,16 +2134,17 @@ gtk_image_view_load_image_contents (GTask        *task,
   GError *error = NULL;
   GFileInputStream *in_stream;
 
-  g_free (task_data);
   in_stream = g_file_read (file, cancellable, &error);
 
   if (error)
     {
+      /* in_stream is NULL */
       g_task_return_error (task, error);
       return;
     }
 
 
+  /* Closes and unrefs the input stream */
   gtk_image_view_load_image_from_stream (image_view,
                                          G_INPUT_STREAM (in_stream),
                                          data->scale_factor,
@@ -1912,9 +2156,9 @@ gtk_image_view_load_image_contents (GTask        *task,
 }
 
 static void
-gtk_image_view_load_from_input_stream (GTask *task,
-                                       gpointer source_object,
-                                       gpointer task_data,
+gtk_image_view_load_from_input_stream (GTask        *task,
+                                       gpointer      source_object,
+                                       gpointer      task_data,
                                        GCancellable *cancellable)
 {
   GtkImageView *image_view = source_object;
@@ -1922,6 +2166,7 @@ gtk_image_view_load_from_input_stream (GTask *task,
   GInputStream *in_stream = G_INPUT_STREAM (data->source);
   GError *error = NULL;
 
+  /* Closes and unrefs the input stream */
   gtk_image_view_load_image_from_stream (image_view,
                                          in_stream,
                                          data->scale_factor,
@@ -1946,7 +2191,7 @@ gtk_image_view_load_from_file_async (GtkImageView        *image_view,
   LoadTaskData *task_data;
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
   g_return_if_fail (G_IS_FILE (file));
-  g_return_if_fail (scale_factor > 0);
+  g_return_if_fail (scale_factor >= 0);
 
   task_data = g_slice_new (LoadTaskData);
   task_data->scale_factor = scale_factor;
@@ -1958,12 +2203,14 @@ gtk_image_view_load_from_file_async (GtkImageView        *image_view,
 
   g_object_unref (task);
 }
-void
+gboolean
 gtk_image_view_load_from_file_finish   (GtkImageView  *image_view,
                                         GAsyncResult  *result,
                                         GError       **error)
 {
-  g_return_if_fail (g_task_is_valid (result, image_view));
+  g_return_val_if_fail (g_task_is_valid (result, image_view), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
 }
 
 
@@ -1981,7 +2228,7 @@ gtk_image_view_load_from_stream_async (GtkImageView        *image_view,
   LoadTaskData *task_data;
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
   g_return_if_fail (G_IS_INPUT_STREAM (input_stream));
-  g_return_if_fail (scale_factor > 0);
+  g_return_if_fail (scale_factor >= 0);
 
   task_data = g_slice_new (LoadTaskData);
   task_data->scale_factor = scale_factor;
@@ -1993,18 +2240,20 @@ gtk_image_view_load_from_stream_async (GtkImageView        *image_view,
 
   g_object_unref (task);
 }
-void
+gboolean
 gtk_image_view_load_from_stream_finish (GtkImageView  *image_view,
                                         GAsyncResult  *result,
                                         GError       **error)
 {
-  g_return_if_fail (g_task_is_valid (result, image_view));
+  g_return_val_if_fail (g_task_is_valid (result, image_view), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
 }
 
 /*
  * gtk_image_view_set_pixbuf:
  * @image_view: A #GtkImageView instance
- * @pixbuf: A #GdkPixbuf instance
+ * @pixbuf: (transfer none): A #GdkPixbuf instance
  * @scale_factor: The scale factor of the pixbuf. This will
  *   be interpreted as "the given pixbuf is supposed to be used
  *   with the given scale factor", i.e. if the pixbuf's scale
@@ -2020,7 +2269,7 @@ gtk_image_view_set_pixbuf (GtkImageView    *image_view,
 
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
   g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
-  g_return_if_fail (scale_factor > 0);
+  g_return_if_fail (scale_factor >= 0);
 
 
   if (priv->is_animation)
@@ -2033,25 +2282,12 @@ gtk_image_view_set_pixbuf (GtkImageView    *image_view,
   gtk_image_view_update_surface (image_view, pixbuf, scale_factor);
 
   gtk_image_view_update_adjustments (image_view);
-
-
-  /* XXX @debug */
-  double value = gtk_adjustment_get_upper (priv->hadjustment) / 2.0 -
-                 gtk_adjustment_get_page_size (priv->hadjustment) / 2.0;
-
-  gtk_adjustment_set_value (priv->hadjustment, value);
-
-  value = gtk_adjustment_get_upper (priv->vadjustment) / 2.0 -
-          gtk_adjustment_get_page_size (priv->vadjustment) / 2.0;
-
-  gtk_adjustment_set_value (priv->vadjustment, value);
-
 }
 
 /**
  * gtk_image_view_set_surface:
  * @image_view: A #GtkImageView instance
- * @surface: (nullable): A #cairo_surface_t of type #CAIRO_SURFACE_TYPE_IMAGE, or
+ * @surface: (nullable) (transfer full): A #cairo_surface_t of type #CAIRO_SURFACE_TYPE_IMAGE, or
  *   %NULL to unset any internal image data. In case this is %NULL, the scale will
  *   be reset to 1.0.
  */
@@ -2078,6 +2314,9 @@ gtk_image_view_set_surface (GtkImageView    *image_view,
       priv->scale = 1.0;
       g_object_notify_by_pspec (G_OBJECT (image_view),
                                 widget_props[PROP_SCALE]);
+      priv->scale_set = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_SCALE_SET]);
     }
 
 
@@ -2104,7 +2343,7 @@ gtk_image_view_set_surface (GtkImageView    *image_view,
 /**
  * gtk_image_view_set_animation:
  * @image_view: A #GtkImageView instance
- * @animation: The #GdkPixbufAnimation to use
+ * @animation: (transfer full): The #GdkPixbufAnimation to use
  * @scale_factor: The scale factor of the animation. This will
  *   be interpreted as "the given animation is supposed to be used
  *   with the given scale factor", i.e. if the animation's scale
@@ -2118,7 +2357,7 @@ gtk_image_view_set_animation (GtkImageView       *image_view,
 {
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
   g_return_if_fail (GDK_IS_PIXBUF_ANIMATION (animation));
-  g_return_if_fail (scale_factor > 0);
+  g_return_if_fail (scale_factor >= 0);
 
   gtk_image_view_replace_animation (image_view, animation, scale_factor);
 }
diff --git a/gtk/gtkimageview.h b/gtk/gtkimageview.h
index 8052589..39b3c33 100644
--- a/gtk/gtkimageview.h
+++ b/gtk/gtkimageview.h
@@ -32,26 +32,25 @@ struct _GtkImageView
 struct _GtkImageViewClass
 {
   GtkWidgetClass parent_class;
-  void (* prepare_image) (cairo_surface_t *image);
 };
 
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 GType         gtk_image_view_get_type (void) G_GNUC_CONST;
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 GtkWidget *   gtk_image_view_new      (void);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_pixbuf (GtkImageView    *image_view,
                                 const GdkPixbuf *pixbuf,
                                 int              scale_factor);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_surface (GtkImageView    *image_view,
                                  cairo_surface_t *surface);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_animation (GtkImageView       *image_view,
                                    GdkPixbufAnimation *animation,
                                    int                 scale_factor);
@@ -59,18 +58,18 @@ void gtk_image_view_set_animation (GtkImageView       *image_view,
 
 /* Loading {{{ */
 
-GDK_AVAILABLE_IN_3_18
-void gtk_image_view_load_from_file_async    (GtkImageView        *image_view,
-                                             GFile               *file,
-                                             int                  scale_factor,
-                                             GCancellable        *cancellable,
-                                             GAsyncReadyCallback  callback,
-                                             gpointer             user_data);
-GDK_AVAILABLE_IN_3_18
-void  gtk_image_view_load_from_file_finish   (GtkImageView  *image_view,
-                                              GAsyncResult  *result,
-                                              GError       **error);
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
+void gtk_image_view_load_from_file_async (GtkImageView        *image_view,
+                                          GFile               *file,
+                                          int                  scale_factor,
+                                          GCancellable        *cancellable,
+                                          GAsyncReadyCallback  callback,
+                                          gpointer             user_data);
+GDK_AVAILABLE_IN_3_20
+gboolean gtk_image_view_load_from_file_finish (GtkImageView  *image_view,
+                                               GAsyncResult  *result,
+                                               GError       **error);
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_load_from_stream_async (GtkImageView        *image_view,
                                             GInputStream        *input_stream,
                                             int                  scale_factor,
@@ -78,64 +77,64 @@ void gtk_image_view_load_from_stream_async (GtkImageView        *image_view,
                                             GAsyncReadyCallback  callback,
                                             gpointer             user_data);
 
-GDK_AVAILABLE_IN_3_18
-void gtk_image_view_load_from_stream_finish (GtkImageView  *image_view,
-                                             GAsyncResult  *result,
-                                             GError       **error);
+GDK_AVAILABLE_IN_3_20
+gboolean gtk_image_view_load_from_stream_finish (GtkImageView  *image_view,
+                                                 GAsyncResult  *result,
+                                                 GError       **error);
 
 /* }}} */
 
 /* Setters/Getters {{{ */
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void   gtk_image_view_set_scale (GtkImageView *image_view,
                                  double        scale);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 double gtk_image_view_get_scale (GtkImageView *image_view);
 
 
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void  gtk_image_view_set_angle (GtkImageView *image_view,
                                 double        angle);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 double gtk_image_view_get_angle (GtkImageView *image_view);
 
 
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_snap_angle (GtkImageView *image_view,
                                     gboolean      snap_angle);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 gboolean gtk_image_view_get_snap_angle (GtkImageView *image_view);
 
 
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_fit_allocation (GtkImageView *image_view,
                                         gboolean      fit_allocation);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 gboolean gtk_image_view_get_fit_allocation (GtkImageView *image_view);
 
 
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_rotate_gesture_enabled (GtkImageView *image_view,
                                                 gboolean      rotate_gesture_enabled);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 gboolean gtk_image_view_get_rotate_gesture_enabled (GtkImageView *image_view);
 
 
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 void gtk_image_view_set_zoom_gesture_enabled (GtkImageView *image_view,
                                               gboolean      zoom_gesture_enabled);
 
-GDK_AVAILABLE_IN_3_18
+GDK_AVAILABLE_IN_3_20
 gboolean gtk_image_view_get_zoom_gesture_enabled (GtkImageView *image_view);
 
 /* }}} */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]