[gtk+/wip/baedert/gtkimageview: 139/255] GtkImageView: Replace zoom-mode with fit-allocation



commit 4d2be54a8671ae3b96b0580ae7fab9b47c5b6398
Author: Timm Bäder <mail baedert org>
Date:   Tue Jul 7 19:54:19 2015 +0200

    GtkImageView: Replace zoom-mode with fit-allocation

 demos/gtk-demo/image_view.c  |  120 +++-
 demos/gtk-demo/image_view.ui |   94 +++-
 gtk/gtkimageview.c           | 1510 +++++++++++++++++++++++++++++++++---------
 gtk/gtkimageview.h           |   87 ++-
 4 files changed, 1414 insertions(+), 397 deletions(-)
---
diff --git a/demos/gtk-demo/image_view.c b/demos/gtk-demo/image_view.c
index 0d6f95c..2aca214 100644
--- a/demos/gtk-demo/image_view.c
+++ b/demos/gtk-demo/image_view.c
@@ -19,6 +19,7 @@ file_set_cb (GtkFileChooserButton *widget,
   GFile *file = g_file_new_for_path (filename);
   gtk_image_view_load_from_file_async (GTK_IMAGE_VIEW (image_view),
                                        file,
+                                       1,
                                        NULL,
                                        generic_cb,
                                        NULL);
@@ -31,29 +32,13 @@ load_button_cb ()
   GFile *file = g_file_new_for_uri (uri);
   gtk_image_view_load_from_file_async (GTK_IMAGE_VIEW (image_view),
                                        file,
+                                       1,
                                        NULL,
                                        generic_cb,
                                        NULL);
 }
 
 void
-zoom_mode_changed_cb (GtkComboBox *widget,
-                      gpointer     user_data)
-{
-  const gchar *new_id = gtk_combo_box_get_active_id (widget);
-
-  if (g_strcmp0 (new_id, "fit") == 0)
-    gtk_image_view_set_zoom_mode (GTK_IMAGE_VIEW (image_view),
-                                  GTK_IMAGE_VIEW_ZOOM_MODE_FIT);
-  else if (g_strcmp0 (new_id, "original") == 0)
-    gtk_image_view_set_zoom_mode (GTK_IMAGE_VIEW (image_view),
-                                  GTK_IMAGE_VIEW_ZOOM_MODE_ORIGINAL);
-  else
-    g_error (new_id);
-
-}
-
-void
 angle_changed_cb (GtkRange *range,
                   gpointer  user_data)
 {
@@ -97,9 +82,12 @@ scrolled_check_button_active_cb (GObject *source)
     {
       GtkWidget *grandparent = gtk_widget_get_parent (parent);
       g_assert (grandparent != NULL);
+      g_object_ref (G_OBJECT (image_view));
+      gtk_container_remove (GTK_CONTAINER (parent), image_view);
       gtk_container_remove (GTK_CONTAINER (grandparent), parent);
       gtk_container_add (GTK_CONTAINER (grandparent), image_view);
       gtk_widget_show (image_view);
+      g_object_unref (G_OBJECT (image_view));
     }
   else
     {
@@ -107,6 +95,7 @@ scrolled_check_button_active_cb (GObject *source)
       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
                                       GTK_POLICY_ALWAYS,
                                       GTK_POLICY_ALWAYS);
+      gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (scroller), FALSE);
       gtk_widget_show (scroller);
       gtk_container_remove (GTK_CONTAINER (parent), image_view);
       gtk_container_add (GTK_CONTAINER (scroller), image_view);
@@ -132,6 +121,87 @@ scale_scale_format_value_cb (GtkScale *scale,
 }
 
 
+void
+load_pixbuf_button_clicked_cb ()
+{
+  GdkPixbuf *pixbuf;
+
+  /* I really hope you have this. */
+  pixbuf = gdk_pixbuf_new_from_file ("/usr/share/backgrounds/gnome/adwaita-day.jpg",
+                                     NULL);
+
+  g_assert (pixbuf != NULL);
+  gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (image_view), pixbuf, 1);
+
+  g_object_unref (G_OBJECT (pixbuf));
+}
+
+
+void
+load_hidpi_pixbuf_button_clicked_cb ()
+{
+  GdkPixbuf *pixbuf;
+
+  /* I really hope you have this. */
+  pixbuf = gdk_pixbuf_new_from_file ("/usr/share/backgrounds/gnome/adwaita-day.jpg",
+                                     NULL);
+
+  g_assert (pixbuf != NULL);
+  gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (image_view), pixbuf, 2);
+
+  g_object_unref (G_OBJECT (pixbuf));
+}
+
+void
+load_surface_button_clicked_cb ()
+{
+  GdkPixbuf *pixbuf;
+  cairo_surface_t *surface;
+
+  /* I really hope you have this. */
+  pixbuf = gdk_pixbuf_new_from_file ("/usr/share/backgrounds/gnome/adwaita-day.jpg",
+                                     NULL);
+
+  g_assert (pixbuf != NULL);
+
+  surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 1, NULL);
+
+  g_object_unref (G_OBJECT (pixbuf));
+
+  gtk_image_view_set_surface (GTK_IMAGE_VIEW (image_view), surface);
+}
+
+
+void
+clear_button_clicked_cb ()
+{
+  gtk_image_view_set_surface (GTK_IMAGE_VIEW (image_view), NULL);
+}
+
+void
+prepare_image_cb (GtkImageView    *image_view,
+                  cairo_surface_t *surface)
+{
+  cairo_t *ct;
+  int width;
+  int height;
+
+  g_assert (GTK_IS_IMAGE_VIEW (image_view));
+
+  g_assert (surface != NULL);
+  g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+
+  ct = cairo_create (surface);
+  width  = cairo_image_surface_get_width (surface);
+  height = cairo_image_surface_get_height (surface);
+
+
+  cairo_set_source_rgba (ct, 0, 1, 0, 1);
+  cairo_set_line_width (ct, 5.0);
+  cairo_rectangle (ct, 0, 0, width, height);
+  cairo_stroke (ct);
+}
 
 
 GtkWidget *
@@ -142,8 +212,8 @@ do_image_view (GtkWidget *do_widget)
          image_view   = GTK_WIDGET (gtk_builder_get_object (builder, "image_view"));
           uri_entry   = GTK_WIDGET (gtk_builder_get_object (builder, "uri_entry"));
   GtkWidget *box      = GTK_WIDGET (gtk_builder_get_object (builder, "box"));
-  GtkWidget *zoom_mode_combo   = GTK_WIDGET (gtk_builder_get_object (builder, "zoom_mode_combo"));
   GtkWidget *snap_angle_button = GTK_WIDGET (gtk_builder_get_object (builder, "snap_angle_check_button"));
+  GtkWidget *fit_allocation_button = GTK_WIDGET (gtk_builder_get_object (builder, 
"fit_allocation_check_button"));
   GtkWidget *header_bar = gtk_header_bar_new ();
   gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header_bar), TRUE);
   gtk_window_set_titlebar (GTK_WINDOW (window), header_bar);
@@ -151,20 +221,20 @@ do_image_view (GtkWidget *do_widget)
   GtkAdjustment *scale_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "scale_adjustment"));
   GtkAdjustment *angle_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "angle_adjustment"));
 
+
+  /*g_signal_connect (G_OBJECT (image_view), "prepare-image", G_CALLBACK (prepare_image_cb), NULL);*/
+
+
   g_object_bind_property (scale_adjustment, "value", image_view, "scale",
-                          G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+                          G_BINDING_BIDIRECTIONAL);
   g_object_bind_property (image_view, "angle", angle_adjustment, "value",
                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
   g_object_bind_property (image_view, "snap-angle", snap_angle_button, "active",
                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+  g_object_bind_property (image_view, "fit-allocation", fit_allocation_button, "active",
+                          G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
 
 
-
-  if (gtk_image_view_get_zoom_mode (GTK_IMAGE_VIEW (image_view)) == GTK_IMAGE_VIEW_ZOOM_MODE_FIT)
-    gtk_combo_box_set_active_id (GTK_COMBO_BOX (zoom_mode_combo), "fit");
-  else if (gtk_image_view_get_zoom_mode (GTK_IMAGE_VIEW (image_view)) == GTK_IMAGE_VIEW_ZOOM_MODE_ORIGINAL)
-    gtk_combo_box_set_active_id (GTK_COMBO_BOX (zoom_mode_combo), "original");
-
   gtk_container_add (GTK_CONTAINER (window), box);
   gtk_builder_connect_signals (builder, NULL);
 
diff --git a/demos/gtk-demo/image_view.ui b/demos/gtk-demo/image_view.ui
index 99233c7..c107057 100644
--- a/demos/gtk-demo/image_view.ui
+++ b/demos/gtk-demo/image_view.ui
@@ -64,22 +64,70 @@
         </child>
 
 
+        <child>
+          <object class="GtkButton">
+            <property name="visible">true</property>
+            <property name="label" translatable="yes">Load Pixbuf</property>
+            <signal name="clicked" handler="load_pixbuf_button_clicked_cb" object="image_view"/>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">2</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkButton">
+            <property name="visible">true</property>
+            <property name="label" translatable="yes">Load HiDPI Pixbuf</property>
+            <signal name="clicked" handler="load_hidpi_pixbuf_button_clicked_cb" object="image_view"/>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">3</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
 
 
         <child>
-          <object class="GtkComboBoxText" id="zoom_mode_combo">
+          <object class="GtkButton">
             <property name="visible">true</property>
-            <signal name="changed" handler="zoom_mode_changed_cb" />
+            <property name="label" translatable="yes">Load Surface</property>
+            <signal name="clicked" handler="load_surface_button_clicked_cb" />
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">4</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
 
-            <items>
-              <item id="fit">ZOOM_MODE_FIT</item>
-              <item id="original">ZOOM_MODE_ORIGINAL</item>
-            </items>
 
+        <child>
+          <object class="GtkButton">
+            <property name="visible">true</property>
+            <property name="label" translatable="yes">Clear</property>
+            <signal name="clicked" handler="clear_button_clicked_cb" />
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">2</property>
+            <property name="top_attach">4</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+
+
+
+        <child>
+          <object class="GtkCheckButton" id="fit_allocation_check_button">
+            <property name="visible">true</property>
+            <property name="label" translatable="yes">Fit Allocation</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">6</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -93,13 +141,27 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">3</property>
+            <property name="top_attach">7</property>
             <property name="width">2</property>
           </packing>
         </child>
 
 
 
+
+        <child>
+          <object class="GtkCheckButton">
+            <property name="visible">true</property>
+            <property name="label" translatable="yes">Draw on Surface</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">8</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+
+
         <child>
           <object class="GtkLabel">
             <property name="visible">true</property>
@@ -109,7 +171,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">4</property>
+            <property name="top_attach">9</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -122,12 +184,11 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">5</property>
+            <property name="top_attach">10</property>
             <property name="width">2</property>
           </packing>
         </child>
 
-
         <child>
           <object class="GtkScale" id="angle_scale">
             <property name="visible">true</property>
@@ -137,7 +198,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">6</property>
+            <property name="top_attach">11</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -180,7 +241,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">7</property>
+            <property name="top_attach">12</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -195,7 +256,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">8</property>
+            <property name="top_attach">13</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -209,7 +270,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">9</property>
+            <property name="top_attach">14</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -219,9 +280,6 @@
 
     </child>
 
-
-
-
     <child>
       <object class="GtkImageView" id="image_view">
         <property name="visible">true</property>
diff --git a/gtk/gtkimageview.c b/gtk/gtkimageview.c
index 5c4865c..33c59d4 100644
--- a/gtk/gtkimageview.c
+++ b/gtk/gtkimageview.c
@@ -16,23 +16,32 @@
 #include <cairo-gobject.h>
 #include <math.h>
 
-
 #define DEG_TO_RAD(x) (((x) / 360.0) * (2 * M_PI))
-#define RAD_TO_DEG(x) ((x / (2.0 * M_PI) * 360.0))
-
-#define TRANSITION_DURATION (200.0 * 1000.0)
-
+#define RAD_TO_DEG(x) (((x) / (2.0 * M_PI) * 360.0))
 
+#define TRANSITION_DURATION (150.0 * 1000.0)
 
 struct _GtkImageViewPrivate
 {
   double   scale;
   double   angle;
-  int      zoom_mode;
   gboolean snap_angle;
+  gboolean fit_allocation;
+  gboolean scale_set;
+  int      scale_factor;
+  gboolean rotate_gesture_enabled;
+  gboolean zoom_gesture_enabled;
 
   GtkGesture *rotate_gesture;
+  gboolean    in_rotate;
+  double      gesture_start_angle;
+
   GtkGesture *zoom_gesture;
+  gboolean    in_zoom;
+  double      gesture_start_scale;
+
+
+  GdkWindow *event_window;
 
   /* GtkScrollable stuff */
   GtkAdjustment       *hadjustment;
@@ -44,6 +53,7 @@ struct _GtkImageViewPrivate
   GdkPixbufAnimation     *source_animation;
   GdkPixbufAnimationIter *source_animation_iter;
   cairo_surface_t        *image_surface;
+  int                     surface_height;
   int                     animation_timeout;
 
   /* Transitions */
@@ -52,17 +62,18 @@ struct _GtkImageViewPrivate
   double transition_end_angle;
 };
 
-// XXX animate image size changes!
-// XXX Double-press gesture to zoom in/out
+// XXX Actually honour the scroll policies
+// XXX Check scale-factor implementation for correctness
 
 enum
 {
   PROP_SCALE = 1,
+  PROP_SCALE_SET,
   PROP_ANGLE,
-  PROP_ZOOM_MODE,
-  PROP_ROTATE_ENABLED,
-  PROP_ZOOM_ENABLED,
+  PROP_ROTATE_GESTURE_ENABLED,
+  PROP_ZOOM_GESTURE_ENABLED,
   PROP_SNAP_ANGLE,
+  PROP_FIT_ALLOCATION,
   LAST_WIDGET_PROPERTY,
   PROP_HADJUSTMENT,
   PROP_VADJUSTMENT,
@@ -87,151 +98,200 @@ G_DEFINE_TYPE_WITH_CODE (GtkImageView, gtk_image_view, GTK_TYPE_WIDGET,
                          G_ADD_PRIVATE (GtkImageView)
                          G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
 
+typedef struct _LoadTaskData LoadTaskData;
 
-static void
-gtk_image_view_init (GtkImageView *image_view)
+struct _LoadTaskData
 {
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  GtkStyleContext *sc = gtk_widget_get_style_context ((GtkWidget *)image_view);
-
-  priv->scale = 1.0;
-  priv->angle = 0.0;
-  priv->snap_angle = FALSE;
-  priv->zoom_mode = GTK_IMAGE_VIEW_ZOOM_MODE_FIT;
-  priv->rotate_gesture = gtk_gesture_rotate_new ((GtkWidget *)image_view);
-  priv->zoom_gesture = gtk_gesture_zoom_new ((GtkWidget *)image_view);
+  int scale_factor;
+  gpointer source;
+};
 
-  gtk_style_context_add_class (sc, GTK_STYLE_CLASS_BACKGROUND);
-}
 
 /* Prototypes {{{ */
-static void gtk_image_view_update_surface (GtkImageView *image_view);
+static void gtk_image_view_update_surface (GtkImageView    *image_view,
+                                           const GdkPixbuf *frame,
+                                           int              scale_factor);
 
 static void adjustment_value_changed_cb (GtkAdjustment *adjustment,
                                          gpointer       user_data);
 
+static void gtk_image_view_update_adjustments (GtkImageView *image_view);
+
 
 /* }}} */
 
 
-static gboolean
-gtk_image_view_update_animation (gpointer user_data)
-{
-  GtkImageView *image_view = user_data;
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
 
-  gdk_pixbuf_animation_iter_advance (priv->source_animation_iter, NULL);
-  gtk_image_view_update_surface (image_view);
 
-  return priv->is_animation;
+
+static void
+free_load_task_data (LoadTaskData *data)
+{
+  g_clear_object (&data->source);
 }
 
 
 static void
-gtk_image_view_start_animation (GtkImageView *image_view)
+gtk_image_view_fix_point_rotate (GtkImageView *image_view,
+                                 double        hupper_before,
+                                 double        vupper_before,
+                                 int           x_before,
+                                 int           y_before)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  int delay_ms;
 
-  if (!priv->is_animation)
-    g_error ("NOPE");
+  /*
+   * XXX
+   * We should rotate around the bounding box center of the rotate gesture,
+   * but we currently only rotate around the image center!
+   */
 
-  delay_ms = gdk_pixbuf_animation_iter_get_delay_time (priv->source_animation_iter);
+  double x_diff = gtk_adjustment_get_value (priv->hadjustment) - hupper_before;
+  double y_diff = gtk_adjustment_get_value (priv->vadjustment) - vupper_before;
 
-  priv->animation_timeout = g_timeout_add (delay_ms, gtk_image_view_update_animation, image_view);
+  if (x_diff == 0 && y_diff == 0)
+    {
+      g_message ("No difference!");
+      return;
+    }
+
+  gtk_adjustment_set_value (priv->hadjustment,
+                            gtk_adjustment_get_value (priv->hadjustment) + x_diff);
+  gtk_adjustment_set_value (priv->vadjustment,
+                            gtk_adjustment_get_value (priv->vadjustment) + y_diff);
 }
 
 static void
-gtk_image_view_stop_animation (GtkImageView *image_view)
+gtk_image_view_fix_point (GtkImageView *image_view,
+                          double        scale_before,
+                          int           x_before,
+                          int           y_before)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  double x_after;
+  double y_after;
+  double x_value;
+  double y_value;
 
-  if (priv->animation_timeout != 0)
-    {
-      g_assert (priv->is_animation);
-      g_source_remove (priv->animation_timeout);
-      priv->animation_timeout = 0;
-    }
-}
-
-
-static gboolean
-frameclock_cb (GtkWidget     *widget,
-               GdkFrameClock *frame_clock,
-               gpointer       user_data)
-{
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private ((GtkImageView *)widget);
-  gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+  g_assert (!(priv->hadjustment == NULL && priv->vadjustment == NULL));
 
-  double t = (now - priv->angle_transition_start) / TRANSITION_DURATION;
+  x_value = gtk_adjustment_get_value (priv->hadjustment);
+  y_value = gtk_adjustment_get_value (priv->vadjustment);
 
-  double new_angle = (priv->transition_end_angle - priv->transition_start_angle) * t;
+  x_before += x_value;
+  y_before += y_value;
 
-  priv->angle = priv->transition_start_angle + new_angle;
-  /*gtk_widget_queue_resize (widget);*/
-  gtk_widget_queue_draw (widget);
 
-  if (t >= 1.0)
-    {
-      priv->angle = priv->transition_end_angle;
-      return FALSE;
-    }
+  x_after = x_before / scale_before * priv->scale;
+  y_after = y_before / scale_before * priv->scale;
 
-  return TRUE;
+  gtk_adjustment_set_value (priv->hadjustment,
+                            x_value + x_after - x_before);
+  gtk_adjustment_set_value (priv->vadjustment,
+                            y_value + y_after - y_before);
 }
 
-
 static void
-gtk_image_view_animate_to_angle (GtkImageView *image_view,
-                                 double        start_angle)
+gesture_rotate_end_cb (GtkGesture       *gesture,
+                       GdkEventSequence *sequence,
+                       gpointer          user_data)
 {
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  /* target angle is priv->angle! */
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
 
-  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 ((GtkWidget 
*)image_view));
-  gtk_widget_add_tick_callback ((GtkWidget *)image_view, frameclock_cb, NULL, NULL);
+  priv->gesture_start_angle = 0.0;
+  priv->in_rotate = FALSE;
+
+  gtk_image_view_set_angle (user_data, priv->angle);
 }
 
 static void
-gtk_image_view_do_snapping (GtkImageView *image_view,
-                            double        angle)
+gesture_rotate_cancel_cb (GtkGesture *gesture,
+                          GdkEventSequence *sequence,
+                          gpointer          user_data)
 {
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  int new_angle;
+  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 = FALSE;
+}
 
-  g_assert (priv->snap_angle);
 
-  /* Snap to angles of 0, 90, 180 and 270 degrees */
+static void
+gesture_angle_changed_cb (GtkGestureRotate *gesture,
+                          double            angle,
+                          double            delta,
+                          GtkWidget        *widget)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+  double new_angle;
+  double hupper_before;
+  double vupper_before;
+  double bb_x;
+  double bb_y;
+
+  if (!priv->rotate_gesture_enabled)
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
 
-  new_angle = (int) ((angle) / 90.0) * 90;
+  if (!priv->in_rotate)
+    {
+      priv->in_rotate = TRUE;
+      priv->gesture_start_angle = priv->angle;
+    }
 
-  if (new_angle != priv->angle)
+  if (priv->hadjustment && priv->vadjustment)
     {
-      double old_angle = priv->angle;
-      priv->angle = new_angle;
-      /* XXX Make this conditional */
-      gtk_image_view_animate_to_angle (image_view,
-                                       old_angle);
+      hupper_before = gtk_adjustment_get_upper (priv->hadjustment);
+      vupper_before = gtk_adjustment_get_upper (priv->vadjustment);
     }
 
+
+  new_angle = priv->gesture_start_angle + RAD_TO_DEG (delta);
+
+  /* Don't notify */
   priv->angle = new_angle;
+  gtk_image_view_update_adjustments (GTK_IMAGE_VIEW (widget));
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw (widget);
+  else
+    gtk_widget_queue_resize (widget);
+
+  gtk_gesture_get_bounding_box_center (GTK_GESTURE (gesture), &bb_x, &bb_y);
+
+  if (priv->hadjustment && priv->vadjustment)
+    gtk_image_view_fix_point_rotate (GTK_IMAGE_VIEW (widget),
+                                     hupper_before,
+                                     vupper_before,
+                                     bb_x,
+                                     bb_y);
 }
 
 static void
 gtk_image_view_compute_bounding_box (GtkImageView *image_view,
                                      int          *width,
                                      int          *height,
-                                     double       *scale)
+                                     double       *scale_out)
 {
-  // XXX Rework this to have less code duplication
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   GtkAllocation alloc;
   int image_width;
   int image_height;
   int bb_width  = 0;
   int bb_height = 0;
+  double upper_right_degrees;
+  double upper_left_degrees;
+  double r;
+  int upper_right_x, upper_right_y;
+  int upper_left_x, upper_left_y;
+  double scale;
+
+
+  /* XXX
+   * Cache the current bounding box and only recompute if scale/rotation changed
+   */
 
 
   if (!priv->image_surface)
@@ -241,182 +301,504 @@ gtk_image_view_compute_bounding_box (GtkImageView *image_view,
       return;
     }
 
-  gtk_widget_get_allocation ((GtkWidget *)image_view, &alloc);
+  gtk_widget_get_allocation (GTK_WIDGET (image_view), &alloc);
+
   image_width  = cairo_image_surface_get_width (priv->image_surface);
   image_height = cairo_image_surface_get_height (priv->image_surface);
 
-  if (priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_FIT ||
-      priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_ORIGINAL)
-    {
-      int final_width  = image_width;
-      int final_height = image_height;
-
-      double upper_right_degrees = DEG_TO_RAD (priv->angle) + atan ((double)final_height / 
(double)final_width);
-      double upper_left_degrees  = DEG_TO_RAD (priv->angle) + atan ((double)final_height / 
-(double)final_width);
-      double r = sqrtf ((final_width / 2) * (final_width / 2) + (final_height / 2) * (final_height / 2));
+  upper_right_degrees = DEG_TO_RAD (priv->angle) + atan ((double)image_height / (double)image_width);
+  upper_left_degrees  = DEG_TO_RAD (priv->angle) + atan ((double)image_height / -(double)image_width);
+  r = sqrtf ((image_width / 2) * (image_width / 2) + (image_height / 2) * (image_height / 2));
 
-      int upper_right_x = r * cos (upper_right_degrees);
-      int upper_right_y = r * sin (upper_right_degrees);
+  upper_right_x = r * cos (upper_right_degrees);
+  upper_right_y = r * sin (upper_right_degrees);
 
-      int upper_left_x = r * cos (upper_left_degrees);
-      int upper_left_y = r * sin (upper_left_degrees);
+  upper_left_x = r * cos (upper_left_degrees);
+  upper_left_y = r * sin (upper_left_degrees);
 
 
-      //
-      bb_width  = MAX (fabs (upper_right_x), fabs (upper_left_x)) * 2;
-      bb_height = MAX (fabs (upper_right_y), fabs (upper_left_y)) * 2;
+  bb_width  = MAX (fabs (upper_right_x), fabs (upper_left_x)) * 2;
+  bb_height = MAX (fabs (upper_right_y), fabs (upper_left_y)) * 2;
 
-      double scale_x = (double)alloc.width / (double)bb_width;
-      double scale_y = (double)alloc.height / (double)bb_height;
+  /* XXX The bounding box is 2px too small when fit-allocation is set */
 
-      if (scale)
-        *scale = MIN (MIN (scale_x, scale_y), 1.0);
-
-      priv->scale = *scale;
-      g_object_notify_by_pspec ((GObject *)image_view,
-                                widget_props[PROP_SCALE]);
 
-      if (priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_FIT)
+  if (priv->scale_set)
+    {
+      scale = priv->scale;
+    }
+  else
+    {
+      if (priv->fit_allocation)
         {
-          *width = bb_width * *scale;
-          *height = bb_height * *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->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_ORIGINAL)
+      else
         {
-          *width = bb_width * *scale;
-          *height = bb_height * *scale;
+          scale = 1.0;
         }
+    }
 
-      return;
+  if (scale_out)
+    *scale_out = scale;
+
+  if (priv->fit_allocation)
+    {
+
+      // XXX We probably don't want to do that here since it will be called fairly often.
+      priv->scale = scale;
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_SCALE]);
+
+      *width  = bb_width  * scale;
+      *height = bb_height * scale;
     }
   else
     {
+      *width  = bb_width  * scale;
+      *height = bb_height * scale;
     }
+}
 
-  *width  = image_width;
-  *height = image_height;
 
-  if (scale)
-    *scale  = 1.0;
+static inline void
+gtk_image_view_restrict_adjustment (GtkAdjustment *adjustment)
+{
+  double value     = gtk_adjustment_get_value (adjustment);
+  double upper     = gtk_adjustment_get_upper (adjustment);
+  double page_size = gtk_adjustment_get_page_size (adjustment);
+
+  value = gtk_adjustment_get_value (adjustment);
+  upper = gtk_adjustment_get_upper (adjustment);
+
+  if (value > upper - page_size)
+    gtk_adjustment_set_value (adjustment, upper - page_size);
+  else if (value < 0)
+    gtk_adjustment_set_value (adjustment, 0);
 }
 
 static void
 gtk_image_view_update_adjustments (GtkImageView *image_view)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  int widget_width  = gtk_widget_get_allocated_width  (GTK_WIDGET (image_view));
+  int widget_height = gtk_widget_get_allocated_height (GTK_WIDGET (image_view));
+
 
   if (!priv->hadjustment && !priv->vadjustment)
     return;
 
-  if (!priv->source_animation)
+  if (!priv->image_surface)
     {
-      gtk_adjustment_configure (priv->vadjustment, 0, 0, 0, 0, 0, 0);
-      gtk_adjustment_configure (priv->hadjustment, 0, 0, 0, 0, 0, 0);
+      if (priv->hadjustment)
+        gtk_adjustment_configure (priv->hadjustment, 0, 0, 1, 0, 0, 1);
+
+      if (priv->vadjustment)
+        gtk_adjustment_configure (priv->vadjustment, 0, 0, 1, 0, 0, 1);
       return;
     }
 
 
+  if (priv->fit_allocation)
+    {
+      if (priv->hadjustment)
+        gtk_adjustment_set_upper (priv->hadjustment, widget_width);
 
-
-  if (priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_ORIGINAL)
+      if (priv->vadjustment)
+        gtk_adjustment_set_upper (priv->vadjustment, widget_height);
+    }
+  else
     {
       int width, height;
       gtk_image_view_compute_bounding_box (image_view,
                                            &width,
                                            &height,
                                            NULL);
-      gtk_adjustment_set_upper (priv->hadjustment, width);
-      gtk_adjustment_set_upper (priv->vadjustment, height);
+
+      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));
+    }
+
+
+  if (priv->hadjustment)
+    {
+      gtk_adjustment_set_page_size (priv->hadjustment, widget_width);
+      gtk_image_view_restrict_adjustment (priv->hadjustment);
+    }
+
+  if (priv->vadjustment)
+    {
+      gtk_adjustment_set_page_size (priv->vadjustment, widget_height);
+      gtk_image_view_restrict_adjustment (priv->vadjustment);
+    }
+}
+
+
+
+
+/*
+ * This is basicallt the normal _set_scale without the
+ * _fix_point 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)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  scale = MAX (0, scale);
+
+  priv->scale = scale;
+  g_object_notify_by_pspec (G_OBJECT (image_view),
+                            widget_props[PROP_SCALE]);
+
+
+  if (!priv->scale_set)
+    {
+      priv->scale_set = TRUE;
+      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]);
+    }
+
+  gtk_image_view_update_adjustments (image_view);
+
+  gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+static void
+gesture_zoom_end_cb (GtkGesture       *gesture,
+                     GdkEventSequence *sequence,
+                     gpointer          user_data)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+
+  gtk_image_view_set_scale (user_data, priv->scale);
+
+  priv->gesture_start_scale = 0.0;
+  priv->in_zoom = FALSE;
+}
+
+static void
+gesture_zoom_cancel_cb (GtkGesture       *gesture,
+                        GdkEventSequence *sequence,
+                        gpointer          user_data)
+{
+   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+
+  gtk_image_view_set_scale (user_data, priv->gesture_start_scale);
+
+  priv->gesture_start_scale = 0.0;
+  priv->in_zoom = FALSE;
+}
+
+
+static void
+gesture_scale_changed_cb (GtkGestureZoom *gesture,
+                          double          delta,
+                          GtkWidget      *widget)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+  double bb_x;
+  double bb_y;
+  double new_scale;
+  double old_scale = priv->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;
     }
-  else if (priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_FIT)
+
+  gtk_gesture_get_bounding_box_center (GTK_GESTURE (gesture), &bb_x, &bb_y);
+
+  new_scale = priv->gesture_start_scale * delta;
+
+  /* Don't emit */
+  priv->scale = new_scale;
+  gtk_image_view_update_adjustments (GTK_IMAGE_VIEW (widget));
+
+  gtk_image_view_set_scale_internal (GTK_IMAGE_VIEW (widget),
+                                     new_scale);
+
+  if (priv->hadjustment || priv->vadjustment)
+    gtk_image_view_fix_point (GTK_IMAGE_VIEW (widget),
+                              old_scale,
+                              bb_x,
+                              bb_y);
+}
+
+
+static void
+gtk_image_view_init (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  GtkStyleContext *sc = gtk_widget_get_style_context ((GtkWidget *)image_view);
+  GtkWidget *widget = GTK_WIDGET (image_view);
+
+  gtk_widget_set_can_focus (widget, TRUE);
+  gtk_widget_set_has_window (widget, FALSE);
+
+  priv->scale = 1.0;
+  priv->angle = 0.0;
+  priv->snap_angle = FALSE;
+  priv->fit_allocation = FALSE;
+  priv->scale_set = FALSE;
+  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, "end", (GCallback)gesture_rotate_end_cb, image_view);
+  g_signal_connect (priv->rotate_gesture, "cancel", (GCallback)gesture_rotate_cancel_cb, image_view);
+
+  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, "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_style_context_add_class (sc, "image-view");
+}
+
+
+static GdkPixbuf *
+gtk_image_view_get_current_frame (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  g_assert (priv->source_animation);
+
+  if (priv->is_animation)
+    return gdk_pixbuf_animation_iter_get_pixbuf (priv->source_animation_iter);
+  else
+    return gdk_pixbuf_animation_get_static_image (priv->source_animation);
+}
+
+
+static gboolean
+gtk_image_view_update_animation (gpointer user_data)
+{
+  GtkImageView *image_view = user_data;
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  gdk_pixbuf_animation_iter_advance (priv->source_animation_iter, NULL);
+  gtk_image_view_update_surface (image_view,
+                                 gtk_image_view_get_current_frame (image_view),
+                                 priv->scale_factor);
+
+  return priv->is_animation;
+}
+
+
+static void
+gtk_image_view_start_animation (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  int delay_ms;
+
+  g_assert (priv->is_animation);
+
+  delay_ms = gdk_pixbuf_animation_iter_get_delay_time (priv->source_animation_iter);
+
+  priv->animation_timeout = g_timeout_add (delay_ms, gtk_image_view_update_animation, image_view);
+}
+
+static void
+gtk_image_view_stop_animation (GtkImageView *image_view)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->animation_timeout != 0)
     {
-      gtk_adjustment_set_upper (priv->vadjustment,
-                                gtk_widget_get_allocated_width ((GtkWidget *)image_view));
+      g_assert (priv->is_animation);
+      g_source_remove (priv->animation_timeout);
+      priv->animation_timeout = 0;
+    }
+}
+
+
+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);
 
-      gtk_adjustment_set_upper (priv->hadjustment,
-                                gtk_widget_get_allocated_height((GtkWidget *)image_view));
+  if (t >= 1.0)
+    {
+      priv->angle = priv->transition_end_angle;
+      return FALSE;
     }
 
-  gtk_adjustment_set_page_size (priv->hadjustment,
-                                gtk_widget_get_allocated_width ((GtkWidget *)image_view));
-  gtk_adjustment_set_page_size (priv->vadjustment,
-                                gtk_widget_get_allocated_height ((GtkWidget *)image_view));
+  return TRUE;
+}
+
 
+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)
 {
-  GtkImageView *image_view = (GtkImageView *)widget;
+  GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   GtkStyleContext *sc = gtk_widget_get_style_context (widget);
-  GtkAllocation alloc;
   int draw_x;
   int draw_y;
   int draw_width;
   int draw_height;
-  int image_width;
-  int image_height;
   double scale = 0.0;
+  int widget_width = gtk_widget_get_allocated_width (widget);
+  int widget_height = gtk_widget_get_allocated_height (widget);
 
-  gtk_widget_get_allocation (widget, &alloc);
 
+  if (priv->vadjustment && priv->hadjustment)
+    {
+      int x = - gtk_adjustment_get_value (priv->hadjustment);
+      int y = - gtk_adjustment_get_value (priv->vadjustment);
+      int w = gtk_adjustment_get_upper (priv->hadjustment);
+      int h = gtk_adjustment_get_upper (priv->vadjustment);
 
-  gtk_render_background (sc, ct, 0, 0, alloc.width, alloc.height);
-  gtk_render_frame      (sc, ct, 0, 0, alloc.width, alloc.height);
+      gtk_render_background (sc, ct, x, y, w, h);
+      gtk_render_frame (sc, ct, x, y, w, h);
+    }
+  else
+    {
+      gtk_render_background (sc, ct, 0, 0, widget_width, widget_height);
+      gtk_render_frame      (sc, ct, 0, 0, widget_width, widget_height);
+    }
 
   if (!priv->image_surface)
-    return FALSE;
+    return GDK_EVENT_PROPAGATE;
 
-  gtk_image_view_compute_bounding_box (image_view, &draw_width, &draw_height, &scale);
-
-  image_width = cairo_image_surface_get_width (priv->image_surface) * scale;
-  image_height = cairo_image_surface_get_height (priv->image_surface) * scale;
+  gtk_image_view_compute_bounding_box (image_view,
+                                       &draw_width, &draw_height,
+                                       &scale);
 
-  draw_x = (alloc.width  - draw_width) / 2;
-  draw_y = (alloc.height - draw_height) / 2;
 
-  /* Bounding box, for debugging */
-#if 0
-  {
-    cairo_save (ct);
-    cairo_set_source_rgba (ct, 0.7, 0.7, 0.7, 1);
-    cairo_rectangle (ct, (alloc.width - draw_width) / 2, (alloc.height - draw_height) / 2, draw_width, 
draw_height);
-    cairo_fill (ct);
-    cairo_restore (ct);
-  }
-#endif
+  if (draw_width == 0 || draw_height == 0)
+    return GDK_EVENT_PROPAGATE;
 
 
-  cairo_save (ct);
+  int image_width  = cairo_image_surface_get_width (priv->image_surface)  * scale;
+  int image_height = cairo_image_surface_get_height (priv->image_surface) * scale;
 
 
-  cairo_rectangle (ct, draw_x, draw_y, draw_width, draw_height);
-  /* Handle the h/vadjustments, but keep the image centered in all cases */
-  if (priv->hadjustment &&
-      gtk_adjustment_get_page_size (priv->hadjustment) < draw_width)
-    draw_x = -gtk_adjustment_get_value (priv->hadjustment);
+  if (priv->hadjustment && priv->vadjustment)
+    {
+      draw_x = (gtk_adjustment_get_page_size (priv->hadjustment) - image_width)  / 2;
+      draw_y = (gtk_adjustment_get_page_size (priv->vadjustment) - image_height) / 2;
+    }
   else
-    draw_x = (alloc.width - image_width) / 2;
+    {
+      draw_x = (widget_width  - image_width)  / 2;
+      draw_y = (widget_height - image_height) / 2;
+    }
 
+  cairo_save (ct);
+  /* XXX This is unnecessarily big */
+  /*cairo_rectangle (ct, draw_x, draw_y, draw_width, draw_height);*/
+  cairo_rectangle (ct, 0, 0, widget_width, widget_height);
 
-  if (priv->vadjustment &&
-      gtk_adjustment_get_page_size (priv->vadjustment) < draw_height)
-    draw_y = -gtk_adjustment_get_value (priv->vadjustment);
-  else
-    draw_y = (alloc.height - image_height) / 2;
+
+  if (priv->hadjustment && draw_width >= widget_width)
+    {
+      draw_x = (draw_width - image_width) / 2;
+      draw_x -= gtk_adjustment_get_value (priv->hadjustment);
+    }
 
 
-  /*cairo_rectangle (ct, draw_x, draw_y, draw_width, draw_height);*/
+  if (priv->vadjustment && draw_height >= widget_height)
+    {
+      draw_y = (draw_height - image_height) / 2;
+      draw_y -= gtk_adjustment_get_value (priv->vadjustment);
+    }
+
 
   /* Rotate around the center */
-  cairo_translate (ct, draw_x + (image_width / 2.0), draw_y + (image_height / 2.0));
+  cairo_translate (ct,
+                   draw_x + (image_width  / 2.0),
+                   draw_y + (image_height / 2.0));
   cairo_rotate (ct, DEG_TO_RAD (priv->angle));
-  cairo_translate (ct, -draw_x - (image_width / 2.0), - draw_y - (image_height / 2.0));
+  cairo_translate (ct,
+                   - draw_x - (image_width  / 2.0),
+                   - draw_y - (image_height / 2.0));
 
 
-  cairo_scale (ct, scale, scale);
-  cairo_set_source_surface (ct, priv->image_surface, draw_x/scale, draw_y/scale);
+  cairo_scale (ct, scale * priv->scale_factor, scale * priv->scale_factor);
+  cairo_set_source_surface (ct,
+                            priv->image_surface,
+                            draw_x / scale / priv->scale_factor,
+                            draw_y / scale / priv->scale_factor);
+  cairo_pattern_set_filter (cairo_get_source (ct), CAIRO_FILTER_FAST);
   cairo_fill (ct);
   cairo_restore (ct);
 
@@ -451,6 +833,15 @@ gtk_image_view_set_hadjustment (GtkImageView  *image_view,
       priv->hadjustment = hadjustment;
     }
 
+  g_object_notify ((GObject *)image_view, "hadjustment");
+
+  gtk_image_view_update_adjustments (image_view);
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw ((GtkWidget *)image_view);
+  else
+    gtk_widget_queue_resize ((GtkWidget *)image_view);
+
 }
 
 static void
@@ -478,37 +869,99 @@ gtk_image_view_set_vadjustment (GtkImageView  *image_view,
     {
       priv->vadjustment = vadjustment;
     }
+
+  g_object_notify ((GObject *)image_view, "vadjustment");
+
+  gtk_image_view_update_adjustments (image_view);
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw ((GtkWidget *)image_view);
+  else
+    gtk_widget_queue_resize ((GtkWidget *)image_view);
 }
 
+static void
+gtk_image_view_set_hscroll_policy (GtkImageView        *image_view,
+                                   GtkScrollablePolicy  hscroll_policy)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->hscroll_policy == hscroll_policy)
+    return;
 
+  priv->hscroll_policy = hscroll_policy;
+  gtk_image_view_update_adjustments (image_view);
+}
 
+static void
+gtk_image_view_set_vscroll_policy (GtkImageView        *image_view,
+                                   GtkScrollablePolicy  vscroll_policy)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->vscroll_policy == vscroll_policy)
+    return;
+
+  priv->vscroll_policy = vscroll_policy;
+  gtk_image_view_update_adjustments (image_view);
+}
+
+/**
+ * gtk_image_view_set_scale:
+ * @image_view: A #GtkImageView instance
+ * @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.
+ *
+ * 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
+ * account.
+ */
 void
 gtk_image_view_set_scale (GtkImageView *image_view,
                           double        scale)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  double old_scale;
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
 
-  /*g_message ("Want to set new scale: %f", scale);*/
+  old_scale = priv->scale;
 
-  priv->scale = scale;
-  /*gtk_image_view_set_zoom_mode (image_view, GTK_IMAGE_VIEW_ZOOM_MODE_NONE);*/
-  g_object_notify_by_pspec ((GObject *)image_view,
-                            widget_props[PROP_SCALE]);
-  gtk_widget_queue_resize ((GtkWidget *)image_view);
+  /*
+   * XXX
+   * If both gestures are enabled, do we always handle both at the same time,
+   * or do we decide for one at the beginning and then stick to it?
+   *
+   */
+
+  gtk_image_view_set_scale_internal (image_view, scale);
+
+  if (priv->hadjustment != NULL && priv->vadjustment != NULL)
+    gtk_image_view_fix_point (image_view,
+                              old_scale,
+                              gtk_widget_get_allocated_width ((GtkWidget *)image_view) / 2,
+                              gtk_widget_get_allocated_height ((GtkWidget *)image_view) / 2);
 }
 
 double
 gtk_image_view_get_scale (GtkImageView *image_view)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+  g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), 0.0);
 
   return priv->scale;
 }
 
 
-
+/**
+ * gtk_image_view_set_angle:
+ * @image_view:
+ * @angle: The angle to rotate the image about, in
+ *   degrees. If this is < 0 or > 360, the value wil
+ *   be wrapped.
+ */
 void
 gtk_image_view_set_angle (GtkImageView *image_view,
                           double        angle)
@@ -516,6 +969,14 @@ gtk_image_view_set_angle (GtkImageView *image_view,
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (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);
+
   if (priv->snap_angle)
     gtk_image_view_do_snapping (image_view, angle);
   else
@@ -524,78 +985,173 @@ gtk_image_view_set_angle (GtkImageView *image_view,
   g_object_notify_by_pspec ((GObject *)image_view,
                             widget_props[PROP_ANGLE]);
 
-  gtk_widget_queue_resize ((GtkWidget *)image_view);
+
+  gtk_image_view_update_adjustments (image_view);
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw ((GtkWidget *)image_view);
+  else
+    gtk_widget_queue_resize ((GtkWidget *)image_view);
 }
 
 double
 gtk_image_view_get_angle (GtkImageView *image_view)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+  g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), 0.0);
 
   return priv->angle;
 }
 
 
 
+/**
+ * gtk_image_view_set_snap_angle:
+ * @image_view: A #GtkImageView instance
+ * @snap_angle: The new value of the #snap-angle property
+ *
+ * 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.
+ */
 void
-gtk_image_view_set_zoom_mode (GtkImageView         *image_view,
-                              GtkImageViewZoomMode  zoom_mode)
+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));
 
-  if (priv->zoom_mode == zoom_mode)
+  snap_angle = !!snap_angle;
+
+  if (snap_angle == priv->snap_angle)
     return;
 
-  priv->zoom_mode = zoom_mode;
+  priv->snap_angle = snap_angle;
   g_object_notify_by_pspec ((GObject *)image_view,
-                            widget_props[PROP_ANGLE]);
+                            widget_props[PROP_SNAP_ANGLE]);
 
-  gtk_widget_queue_resize ((GtkWidget *)image_view);
+  if (priv->snap_angle)
+    gtk_image_view_do_snapping (image_view, priv->angle);
 }
 
-GtkImageViewZoomMode
-gtk_image_view_get_zoom_mode (GtkImageView *image_view)
+gboolean
+gtk_image_view_get_snap_angle (GtkImageView *image_view)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+  g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
 
-  return priv->zoom_mode;
+  return priv->snap_angle;
 }
 
 
 
+/**
+ * gtk_image_view_set_fit_allocation:
+ * @image_view: A #GtkImageView instance
+ * @fit_allocation: The new value of the #fit-allocation property.
+ *
+ * Setting #fit-allocation to #TRUE will cause the image to be scaled
+ * to the widget's allocation, unless it would cause the image to be
+ * scaled up.
+ *
+ * Setting #fit-allocation will have the side effect of setting
+ * #scale-set set to #FALSE, thus giving the #GtkImageView the control
+ * over the image's scale. Additionally, if the new #fit-allocation
+ * value is #FALSE, the scale will be reset to 1.0 and the #GtkImageView
+ * will be resized to take at least the image's real size.
+ */
 void
-gtk_image_view_set_snap_angle (GtkImageView *image_view,
-                               gboolean     snap_angle)
+gtk_image_view_set_fit_allocation (GtkImageView *image_view,
+                                   gboolean      fit_allocation)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
 
-  snap_angle = !!snap_angle;
+  fit_allocation = !!fit_allocation;
 
-  if (snap_angle == priv->snap_angle)
+  if (fit_allocation == priv->fit_allocation)
     return;
 
-  priv->snap_angle = snap_angle;
+  priv->fit_allocation = fit_allocation;
   g_object_notify_by_pspec ((GObject *)image_view,
-                            widget_props[PROP_SNAP_ANGLE]);
+                            widget_props[PROP_FIT_ALLOCATION]);
 
-  if (priv->snap_angle)
-    gtk_image_view_do_snapping (image_view, priv->angle);
+  priv->scale_set = FALSE;
+  g_object_notify_by_pspec ((GObject *)image_view,
+                            widget_props[PROP_SCALE_SET]);
+
+  if (!priv->fit_allocation && !priv->scale_set)
+    {
+      priv->scale = 1.0;
+      g_object_notify_by_pspec ((GObject *)image_view,
+                                widget_props[PROP_SCALE]);
+    }
+
+  gtk_image_view_update_adjustments (image_view);
+
+  gtk_widget_queue_resize ((GtkWidget *)image_view);
+  gtk_image_view_update_adjustments (image_view);
 }
 
 gboolean
-gtk_image_view_get_snap_angle (GtkImageView *image_view)
+gtk_image_view_get_fit_allocation (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->fit_allocation;
+}
+
+
+
+void
+gtk_image_view_set_rotate_gesture_enabled (GtkImageView *image_view,
+                                           gboolean      rotate_gesture_enabled)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
 
-  return priv->snap_angle;
+  rotate_gesture_enabled = !!rotate_gesture_enabled;
+
+  priv->rotate_gesture_enabled = rotate_gesture_enabled;
+  g_object_notify_by_pspec ((GObject *)image_view,
+                            widget_props[PROP_ROTATE_GESTURE_ENABLED]);
 }
 
+gboolean
+gtk_image_view_get_rotate_gesture_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->rotate_gesture_enabled;
+}
 
+
+
+void
+gtk_image_view_set_zoom_gesture_enabled (GtkImageView *image_view,
+                                         gboolean      zoom_gesture_enabled)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+  zoom_gesture_enabled = !!zoom_gesture_enabled;
+
+  priv->zoom_gesture_enabled = zoom_gesture_enabled;
+  g_object_notify_by_pspec ((GObject *)image_view,
+                            widget_props[PROP_ZOOM_GESTURE_ENABLED]);
+}
+
+gboolean
+gtk_image_view_get_zoom_gesture_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->zoom_gesture_enabled;
+}
 /* }}} */
 
 
@@ -604,6 +1160,7 @@ gtk_image_view_get_snap_angle (GtkImageView *image_view)
 static void
 gtk_image_view_realize (GtkWidget *widget)
 {
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private ((GtkImageView *)widget);
   GtkAllocation allocation;
   GdkWindowAttr attributes = { 0, };
   GdkWindow *window;
@@ -617,14 +1174,24 @@ gtk_image_view_realize (GtkWidget *widget)
   attributes.height = allocation.height;
   attributes.window_type = GDK_WINDOW_CHILD;
   attributes.event_mask = gtk_widget_get_events (widget) |
-                           GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK |
-                           GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
-  attributes.wclass = GDK_INPUT_OUTPUT;
+                          GDK_POINTER_MOTION_MASK |
+                          GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK |
+                          GDK_SMOOTH_SCROLL_MASK |
+                          GDK_SCROLL_MASK;
+  attributes.wclass = GDK_INPUT_ONLY;
+
+  window = gtk_widget_get_parent_window (widget);
+
+  gtk_widget_set_window (widget, window);
+  g_object_ref ((GObject *)window);
 
   window = gdk_window_new (gtk_widget_get_parent_window (widget),
                            &attributes, GDK_WA_X | GDK_WA_Y);
+  priv->event_window = window;
+
+  gtk_widget_register_window (widget, priv->event_window);
   gdk_window_set_user_data (window, (GObject *) widget);
-  gtk_widget_set_window (widget, window); /* Passes ownership */
 }
 
 static void
@@ -632,12 +1199,13 @@ gtk_image_view_size_allocate (GtkWidget     *widget,
                               GtkAllocation *allocation)
 {
   GtkImageView *image_view = (GtkImageView *)widget;
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
 
   gtk_widget_set_allocation (widget, allocation);
 
   if (gtk_widget_get_realized (widget))
     {
-      gdk_window_move_resize (gtk_widget_get_window (widget),
+      gdk_window_move_resize (priv->event_window,
                               allocation->x, allocation->y,
                               allocation->width, allocation->height);
     }
@@ -646,11 +1214,39 @@ gtk_image_view_size_allocate (GtkWidget     *widget,
 }
 
 static void
+gtk_image_view_map (GtkWidget *widget)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private ((GtkImageView *)widget);
+
+  if (priv->is_animation)
+    gtk_image_view_start_animation ((GtkImageView *)widget);
+
+  if (priv->event_window)
+    gdk_window_show (priv->event_window);
+
+  GTK_WIDGET_CLASS (gtk_image_view_parent_class)->map (widget);
+}
+
+static void
+gtk_image_view_unmap (GtkWidget *widget)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private ((GtkImageView *)widget);
+
+
+  if (priv->is_animation)
+    gtk_image_view_stop_animation ((GtkImageView *)widget);
+
+  GTK_WIDGET_CLASS (gtk_image_view_parent_class)->unmap (widget);
+}
+
+static void
 adjustment_value_changed_cb (GtkAdjustment *adjustment,
                              gpointer       user_data)
 {
   GtkImageView *image_view = user_data;
 
+  gtk_image_view_update_adjustments (image_view);
+
   gtk_widget_queue_draw ((GtkWidget *)image_view);
 }
 
@@ -663,14 +1259,12 @@ gtk_image_view_get_preferred_height (GtkWidget *widget,
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
 
   int width, height;
-  double scale;
   gtk_image_view_compute_bounding_box (image_view,
                                        &width,
                                        &height,
-                                       &scale);
-
+                                       NULL);
 
-  if (priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_FIT)
+  if (priv->fit_allocation)
     {
       *minimal = 0;
       *natural = height;
@@ -690,13 +1284,12 @@ gtk_image_view_get_preferred_width (GtkWidget *widget,
   GtkImageView *image_view  = (GtkImageView *)widget;
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
   int width, height;
-  double scale;
   gtk_image_view_compute_bounding_box (image_view,
                                        &width,
                                        &height,
-                                       &scale);
+                                       NULL);
 
-  if (priv->zoom_mode == GTK_IMAGE_VIEW_ZOOM_MODE_FIT)
+  if (priv->fit_allocation)
     {
       *minimal = 0;
       *natural = width;
@@ -708,6 +1301,28 @@ gtk_image_view_get_preferred_width (GtkWidget *widget,
     }
 
 }
+
+
+static gboolean
+gtk_image_view_scroll_event (GtkWidget       *widget,
+                             GdkEventScroll  *event)
+{
+  GtkImageView *image_view = (GtkImageView *)widget;
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  double old_scale = priv->scale;
+  double new_scale = priv->scale - (0.1 * event->delta_y);
+
+  gtk_image_view_set_scale_internal (image_view, new_scale);
+
+  if (priv->hadjustment || priv->vadjustment)
+    gtk_image_view_fix_point (image_view,
+                              old_scale,
+                              event->x,
+                              event->y);
+
+  return GDK_EVENT_STOP;
+}
+
 /* }}} */
 
 
@@ -726,15 +1341,17 @@ 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;
-      case PROP_ZOOM_MODE:
-        gtk_image_view_set_zoom_mode (image_view, g_value_get_enum (value));
-        break;
       case PROP_SNAP_ANGLE:
         gtk_image_view_set_snap_angle (image_view, g_value_get_boolean (value));
         break;
+      case PROP_FIT_ALLOCATION:
+        gtk_image_view_set_fit_allocation (image_view, g_value_get_boolean (value));
+        break;
       case PROP_HADJUSTMENT:
         gtk_image_view_set_hadjustment (image_view, g_value_get_object (value));
         break;
@@ -742,10 +1359,10 @@ gtk_image_view_set_property (GObject      *object,
         gtk_image_view_set_vadjustment (image_view, g_value_get_object (value));
         break;
       case PROP_HSCROLL_POLICY:
-        ;
+        gtk_image_view_set_hscroll_policy (image_view, g_value_get_enum (value));
         break;
       case PROP_VSCROLL_POLICY:
-        ;
+        gtk_image_view_set_vscroll_policy (image_view, g_value_get_enum (value));
         break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -766,15 +1383,18 @@ 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_ANGLE:
         g_value_set_double (value, priv->angle);
         break;
-      case PROP_ZOOM_MODE:
-        g_value_set_enum (value, priv->zoom_mode);
-        break;
       case PROP_SNAP_ANGLE:
         g_value_set_boolean (value, priv->snap_angle);
         break;
+      case PROP_FIT_ALLOCATION:
+        g_value_set_boolean (value, priv->fit_allocation);
+        break;
       case PROP_HADJUSTMENT:
         g_value_set_object (value, priv->hadjustment);
         break;
@@ -782,10 +1402,10 @@ gtk_image_view_get_property (GObject    *object,
         g_value_set_object (value, priv->vadjustment);
         break;
       case PROP_HSCROLL_POLICY:
-        ;
+        g_value_set_enum (value, priv->hscroll_policy);
         break;
       case PROP_VSCROLL_POLICY:
-        ;
+        g_value_set_enum (value, priv->vscroll_policy);
         break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -804,13 +1424,12 @@ gtk_image_view_finalize (GObject *object)
   g_clear_object (&priv->zoom_gesture);
 
   g_clear_object (&priv->hadjustment);
-  g_clear_object (&priv->hadjustment);
+  g_clear_object (&priv->vadjustment);
 
   if (priv->image_surface)
     cairo_surface_destroy (priv->image_surface);
 
 
-
   G_OBJECT_CLASS (gtk_image_view_parent_class)->finalize (object);
 }
 
@@ -829,58 +1448,117 @@ gtk_image_view_class_init (GtkImageViewClass *view_class)
   widget_class->draw          = gtk_image_view_draw;
   widget_class->realize       = gtk_image_view_realize;
   widget_class->size_allocate = gtk_image_view_size_allocate;
+  widget_class->map           = gtk_image_view_map;
+  widget_class->unmap         = gtk_image_view_unmap;
+  widget_class->scroll_event  = gtk_image_view_scroll_event;
   widget_class->get_preferred_width  = gtk_image_view_get_preferred_width;
   widget_class->get_preferred_height = gtk_image_view_get_preferred_height;
 
+  /**
+   * GtkImageView:scale:
+   * The scale the internal surface gets drawn with.
+   *
+   * Since: 3.18
+   */
   widget_props[PROP_SCALE] = g_param_spec_double ("scale",
                                                   P_("Scale"),
-                                                  P_("foobar scale"),
-                                                  -G_MAXDOUBLE,
-                                                  G_MAXDOUBLE,
+                                                  P_("The scale the internal surface gets drawn with"),
                                                   0.0,
+                                                  G_MAXDOUBLE,
+                                                  1.0,
                                                   GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
-
+  /**
+   * GtkImageView:scale-set:
+   * Whether or not the current value of the scale property was set by the user.
+   * This is to distringuish between scale values set by the GtkImageView itself,
+   * e.g. when fit-allocation is true, which will change the scale depeding on the
+   * widget allocation.
+   *
+   * Since: 3.18
+   */
+  widget_props[PROP_SCALE_SET] = g_param_spec_boolean ("scale-set",
+                                                       P_(""),
+                                                       P_("fooar"),
+                                                       FALSE,
+                                                       GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+  /**
+   * GtkImageView:angle:
+   * The angle the surface gets rotated about.
+   * This is in degrees and we rotate in the mathematically negative direction,
+   * i.e. clock wise.
+   *
+   * Since: 3.18
+   */
   widget_props[PROP_ANGLE] = g_param_spec_double ("angle",
                                                   P_("angle"),
                                                   P_("angle"),
-                                                  -G_MAXDOUBLE,
-                                                  G_MAXDOUBLE,
+                                                  0.0,
+                                                  360.0,
                                                   0.0,
                                                   GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
-
-  widget_props[PROP_ZOOM_MODE] = g_param_spec_enum("zoom-mode",
-                                                   P_("zoom mode"),
-                                                   P_("zoommode"),
-                                                   GTK_TYPE_IMAGE_VIEW_ZOOM_MODE,
-                                                   GTK_IMAGE_VIEW_ZOOM_MODE_NONE,
-                                                   GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
-
-  widget_props[PROP_ROTATE_ENABLED] = g_param_spec_boolean ("rotate-gesture-enabled",
-                                                            P_("Foo"),
-                                                            P_("fooar"),
-                                                            TRUE,
-                                                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
-  widget_props[PROP_ZOOM_ENABLED] = g_param_spec_boolean ("zoom-gesture-enabled",
-                                                            P_("Foo"),
-                                                            P_("fooar"),
-                                                            TRUE,
-                                                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
-
+  /**
+   * GtkImageView:rotate-gesture-enabled:
+   * Whether or not the image can be rotated using a two-finger rotate gesture.
+   *
+   * Since: 3.18
+   */
+  widget_props[PROP_ROTATE_GESTURE_ENABLED] = g_param_spec_boolean ("rotate-gesture-enabled",
+                                                                    P_("Foo"),
+                                                                    P_("fooar"),
+                                                                    TRUE,
+                                                                    
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+  /**
+   * GtkImageView:zoom-gesture-enabled:
+   * Whether or not image can be scaled using a two-finger zoom gesture or not.
+   *
+   * Since: 3.18
+   */
+  widget_props[PROP_ZOOM_GESTURE_ENABLED] = g_param_spec_boolean ("zoom-gesture-enabled",
+                                                                  P_("Foo"),
+                                                                  P_("fooar"),
+                                                                  TRUE,
+                                                                  
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+  /**
+   * GtkImageView:snap-angle:
+   * Whether or not the angle property snaps to 90° steps. If this is enabled
+   * and the angle property gets set to a non-90° step, the new value will be
+   * set to the closest 90° step that is lower than the given angle.
+   * Changing the angle from one 90° step to another will be transitioned. XXX
+   *
+   * Since: 3.18
+   */
   widget_props[PROP_SNAP_ANGLE] = g_param_spec_boolean ("snap-angle",
                                                         P_("Foo"),
                                                         P_("fooar"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * GtkImageView:fit-allocation:
+   * If this is TRUE, the scale the image will be drawn in will depend on the current
+   * widget allocation. The image will be scaled down to fit into the widget allocation,
+   * but never scaled up.
+   *
+   * Since: 3.18
+   */
+  widget_props[PROP_FIT_ALLOCATION] = g_param_spec_boolean ("fit-allocation",
+                                                            P_("Foo"),
+                                                            P_("fooar"),
+                                                            FALSE,
+                                                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
 
 
+  /**
+   * GtkImageView::prepare-image:
+   * @image_view: A #GtkImageView instance
+   * @surface: A #cairo_surface_t of type #CAIRO_TYPE_IMAGE_SURFACE.
+   */
   widget_signals[PREPARE_IMAGE] = g_signal_new (I_("prepare-image"),
                                                 G_TYPE_FROM_CLASS (object_class),
                                                 G_SIGNAL_RUN_LAST,
                                                 G_STRUCT_OFFSET (GtkImageViewClass, prepare_image),
-                                                NULL, NULL,
-                                                _gtk_marshal_VOID__VOID,
-                                                G_TYPE_NONE, 0);
+                                                NULL, NULL, NULL,
+                                                G_TYPE_NONE, 1, CAIRO_GOBJECT_TYPE_SURFACE);
 
   g_object_class_install_properties (object_class, LAST_WIDGET_PROPERTY, widget_props);
 
@@ -896,94 +1574,125 @@ gtk_image_view_new ()
   return g_object_new (GTK_TYPE_IMAGE_VIEW, NULL);
 }
 
-
-static GdkPixbuf *
-gtk_image_view_get_current_frame (GtkImageView *image_view)
+static void
+gtk_image_view_replace_surface (GtkImageView    *image_view,
+                                cairo_surface_t *surface,
+                                int              scale_factor)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
 
-  g_assert (priv->source_animation);
+  if (priv->image_surface)
+    cairo_surface_destroy (priv->image_surface);
 
-  if (priv->is_animation)
-    return gdk_pixbuf_animation_iter_get_pixbuf (priv->source_animation_iter);
-  else
-    return gdk_pixbuf_animation_get_static_image (priv->source_animation);
-}
+  priv->scale_factor = scale_factor;
+  priv->image_surface = surface;
+
+  if (surface)
+    {
+      cairo_surface_reference (priv->image_surface);
 
+      g_signal_emit (image_view, widget_signals[PREPARE_IMAGE], 0, priv->image_surface);
+    }
+}
 
 static void
-gtk_image_view_update_surface (GtkImageView *image_view)
+gtk_image_view_update_surface (GtkImageView    *image_view,
+                               const GdkPixbuf *frame,
+                               int              scale_factor)
 {
   GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
-  int cur_width  = gdk_pixbuf_animation_get_width (priv->source_animation);
-  int cur_height = gdk_pixbuf_animation_get_height (priv->source_animation);
-  GdkPixbuf *frame = gtk_image_view_get_current_frame (image_view);
+  int new_width    = gdk_pixbuf_get_width (frame);
+  int new_height   = gdk_pixbuf_get_height (frame);
+  int widget_scale = gtk_widget_get_scale_factor (GTK_WIDGET (image_view));
+  gboolean resize  = TRUE;
+  int real_width   = (new_width * scale_factor)  / widget_scale;
+  int real_height  = (new_height * scale_factor) / widget_scale;
 
   if (!priv->image_surface ||
-      cairo_image_surface_get_width  (priv->image_surface) != cur_width ||
-      cairo_image_surface_get_height (priv->image_surface) != cur_height)
+      cairo_image_surface_get_width (priv->image_surface)  != real_width ||
+      cairo_image_surface_get_height (priv->image_surface) != real_height ||
+      priv->scale_factor != scale_factor)
     {
-      if (priv->image_surface)
-        cairo_surface_destroy (priv->image_surface);
-
-      priv->image_surface = gdk_cairo_surface_create_from_pixbuf (frame,
-                                                                  1,
-                                                                  gtk_widget_get_window ((GtkWidget 
*)image_view));
-      cairo_surface_reference (priv->image_surface);
+      GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (image_view));
+      cairo_surface_t *new_surface = gdk_cairo_surface_create_from_pixbuf (frame,
+                                                                           scale_factor,
+                                                                           window);
+      g_assert (new_surface != NULL);
+      /* replace_surface will emit prepare-image */
+      gtk_image_view_replace_surface (image_view,
+                                      new_surface,
+                                      scale_factor);
     }
   else
     {
       gdk_cairo_surface_paint_pixbuf (priv->image_surface, frame);
+      g_signal_emit (image_view, widget_signals[PREPARE_IMAGE], 0, priv->image_surface);
+      resize = FALSE;
     }
   g_assert (priv->image_surface != NULL);
 
-  g_signal_emit (image_view, widget_signals[PREPARE_IMAGE], 0, priv->image_surface);
+  if (resize)
+    gtk_widget_queue_resize (GTK_WIDGET (image_view));
+  else
+    gtk_widget_queue_draw (GTK_WIDGET (image_view));
+}
+
+static void
+gtk_image_view_replace_animation (GtkImageView       *image_view,
+                                  GdkPixbufAnimation *animation,
+                                  int                 scale_factor)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  if (priv->source_animation)
+    {
+      g_assert (priv->image_surface);
+      if (priv->is_animation)
+        gtk_image_view_stop_animation (image_view);
+    }
+
+  priv->is_animation = !gdk_pixbuf_animation_is_static_image (animation);
+
+  if (priv->is_animation)
+    {
+      priv->source_animation = animation;
+      priv->source_animation_iter = gdk_pixbuf_animation_get_iter (priv->source_animation,
+                                                                   NULL);
+      gtk_image_view_update_surface (image_view,
+                                     gtk_image_view_get_current_frame (image_view),
+                                     scale_factor);
+
+      gtk_image_view_start_animation (image_view);
+    }
+  else
+    {
+      gtk_image_view_update_surface (image_view,
+                                     gdk_pixbuf_animation_get_static_image (animation),
+                                     scale_factor);
+    }
 
-  gtk_widget_queue_resize ((GtkWidget *)image_view);
 }
 
 
+
 static void
 gtk_image_view_load_image_from_stream (GtkImageView *image_view,
                                        GInputStream *input_stream,
+                                       int           scale_factor,
                                        GCancellable *cancellable,
                                        GError       *error)
 {
-  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (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)
-    {
-      g_error ("error!");
-    }
-  else
-    {
-      if (priv->source_animation)
-        {
-          g_assert (priv->image_surface);
-          /*cairo_surface_destroy (priv->image_surface);*/
-          // Cleanup old pixbufanimation, iter, surface, ...
-          if (priv->is_animation)
-            gtk_image_view_stop_animation (image_view);
-
-        }
-      /*g_task_return_pointer (task, result, g_object_unref);*/
-      g_message ("Updating surface...");
-      priv->source_animation = result;
-      priv->is_animation = !gdk_pixbuf_animation_is_static_image (result);
-      if (priv->is_animation)
-        {
-          priv->source_animation_iter = gdk_pixbuf_animation_get_iter (priv->source_animation,
-                                                                       NULL);
-          gtk_image_view_start_animation (image_view);
-        }
-      gtk_image_view_update_surface (image_view);
-    }
-
+  if (!error)
+    gtk_image_view_replace_animation (image_view, result,scale_factor);
 }
 
 static void
@@ -993,7 +1702,8 @@ gtk_image_view_load_image_contents (GTask        *task,
                                     GCancellable *cancellable)
 {
   GtkImageView *image_view = source_object;
-  GFile *file = task_data;
+  LoadTaskData *data = task_data;
+  GFile *file = G_FILE (data->source);
   GError *error = NULL;
   GFileInputStream *in_stream;
 
@@ -1002,13 +1712,34 @@ gtk_image_view_load_image_contents (GTask        *task,
   if (error)
     {
       g_task_return_error (task, error);
-      g_message ("g_file_read error: %s", error->message);
       return;
     }
 
 
   gtk_image_view_load_image_from_stream (image_view,
                                          G_INPUT_STREAM (in_stream),
+                                         data->scale_factor,
+                                         cancellable,
+                                         error);
+
+  if (error)
+    g_task_return_error (task, error);
+}
+
+static void
+gtk_image_view_load_from_input_stream (GTask *task,
+                                       gpointer source_object,
+                                       gpointer task_data,
+                                       GCancellable *cancellable)
+{
+  GtkImageView *image_view = source_object;
+  LoadTaskData *data = task_data;
+  GInputStream *in_stream = G_INPUT_STREAM (data->source);
+  GError *error = NULL;
+
+  gtk_image_view_load_image_from_stream (image_view,
+                                         in_stream,
+                                         data->scale_factor,
                                          cancellable,
                                          error);
 
@@ -1017,19 +1748,27 @@ gtk_image_view_load_image_contents (GTask        *task,
 }
 
 
+
 void
 gtk_image_view_load_from_file_async (GtkImageView        *image_view,
                                      GFile               *file,
+                                     int                  scale_factor,
                                      GCancellable        *cancellable,
                                      GAsyncReadyCallback  callback,
                                      gpointer             user_data)
 {
   GTask *task;
+  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);
+
+  task_data = g_slice_new (LoadTaskData);
+  task_data->scale_factor = scale_factor;
+  task_data->source = file;
 
   task = g_task_new (image_view, cancellable, callback, user_data);
-  g_task_set_task_data (task, file, (GDestroyNotify)g_object_unref);
+  g_task_set_task_data (task, task_data, (GDestroyNotify)free_load_task_data);
   g_task_run_in_thread (task, gtk_image_view_load_image_contents);
 
   g_object_unref (task);
@@ -1046,15 +1785,138 @@ gtk_image_view_load_from_file_finish   (GtkImageView  *image_view,
 
 
 void
-gtk_image_view_load_from_stream_async (GtkImageView *image_view,
-                                       GInputStream *input_stream)
+gtk_image_view_load_from_stream_async (GtkImageView        *image_view,
+                                       GInputStream        *input_stream,
+                                       int                  scale_factor,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
 {
+  GTask *task;
+  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);
+
+  task_data = g_slice_new (LoadTaskData);
+  task_data->scale_factor = scale_factor;
+  task_data->source = input_stream;
 
+  task = g_task_new (image_view, cancellable, callback, user_data);
+  g_task_set_task_data (task, task_data, (GDestroyNotify)free_load_task_data);
+  g_task_run_in_thread (task, gtk_image_view_load_from_input_stream);
+
+  g_object_unref (task);
 }
 void
 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));
+}
+
+/*
+ * gtk_image_view_set_pixbuf:
+ * @image_view: A #GtkImageView instance
+ * @pixbuf: 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
+ *   factor is 2, and the screen's scale factor is also 2, the
+ *   pixbuf won't be scaled up.
+ */
+void
+gtk_image_view_set_pixbuf (GtkImageView    *image_view,
+                           const GdkPixbuf *pixbuf,
+                           int              scale_factor)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+  g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+
+  if (priv->is_animation)
+    {
+      g_clear_object (&priv->source_animation);
+      gtk_image_view_stop_animation (image_view);
+      priv->is_animation = FALSE;
+    }
+
+  gtk_image_view_update_surface (image_view, pixbuf, scale_factor);
+}
+
+/**
+ * gtk_image_view_set_surface:
+ * @image_view: A #GtkImageView instance
+ * @surface: (nullable): 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.
+ */
+void
+gtk_image_view_set_surface (GtkImageView    *image_view,
+                            cairo_surface_t *surface)
+{
+  GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+  double scale_x = 0.0;
+  double scale_y;
+
+  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+  if (surface)
+    {
+      g_return_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+      cairo_surface_get_device_scale (surface, &scale_x, &scale_y);
+
+      g_return_if_fail (scale_x == scale_y);
+    }
+  else
+    {
+      priv->scale = 1.0;
+      g_object_notify_by_pspec (G_OBJECT (image_view),
+                                widget_props[PROP_SCALE]);
+    }
+
+
+
+  if (priv->is_animation)
+    {
+      g_clear_object (&priv->source_animation);
+      gtk_image_view_stop_animation (image_view);
+      priv->is_animation = FALSE;
+    }
+
+  gtk_image_view_replace_surface (image_view,
+                                  surface,
+                                  scale_x);
+
+  gtk_image_view_update_adjustments (image_view);
+
+  if (priv->fit_allocation)
+    gtk_widget_queue_draw (GTK_WIDGET (image_view));
+  else
+    gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+/**
+ * gtk_image_view_set_animation:
+ * @image_view: A #GtkImageView instance
+ * @animation: The #GdkAnimation 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
+ *   factor is 2, and the screen's scale factor is also 2, the
+ *   animation won't be scaled up.
+ */
+void
+gtk_image_view_set_animation (GtkImageView       *image_view,
+                              GdkPixbufAnimation *animation,
+                              int                 scale_factor)
+{
+  g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+  g_return_if_fail (GDK_IS_PIXBUF_ANIMATION (animation));
 
+  gtk_image_view_replace_animation (image_view, animation, scale_factor);
 }
diff --git a/gtk/gtkimageview.h b/gtk/gtkimageview.h
index bcc3109..8052589 100644
--- a/gtk/gtkimageview.h
+++ b/gtk/gtkimageview.h
@@ -35,46 +35,60 @@ struct _GtkImageViewClass
   void (* prepare_image) (cairo_surface_t *image);
 };
 
-typedef enum
-{
-  GTK_IMAGE_VIEW_ZOOM_MODE_ORIGINAL,
-  GTK_IMAGE_VIEW_ZOOM_MODE_FIT,
-  GTK_IMAGE_VIEW_ZOOM_MODE_NONE
-} GtkImageViewZoomMode;
-
 
 GDK_AVAILABLE_IN_3_18
 GType         gtk_image_view_get_type (void) G_GNUC_CONST;
 
 GDK_AVAILABLE_IN_3_18
-GtkWidget *   gtk_image_view_new      ();
+GtkWidget *   gtk_image_view_new      (void);
+
+GDK_AVAILABLE_IN_3_18
+void gtk_image_view_set_pixbuf (GtkImageView    *image_view,
+                                const GdkPixbuf *pixbuf,
+                                int              scale_factor);
+
+GDK_AVAILABLE_IN_3_18
+void gtk_image_view_set_surface (GtkImageView    *image_view,
+                                 cairo_surface_t *surface);
+
+GDK_AVAILABLE_IN_3_18
+void gtk_image_view_set_animation (GtkImageView       *image_view,
+                                   GdkPixbufAnimation *animation,
+                                   int                 scale_factor);
 
 
 /* Loading {{{ */
 
 GDK_AVAILABLE_IN_3_18
-void          gtk_image_view_load_from_file_async    (GtkImageView        *image_view,
-                                                      GFile               *file,
-                                                      GCancellable        *cancellable,
-                                                      GAsyncReadyCallback  callback,
-                                                      gpointer             user_data);
+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);
+void  gtk_image_view_load_from_file_finish   (GtkImageView  *image_view,
+                                              GAsyncResult  *result,
+                                              GError       **error);
 GDK_AVAILABLE_IN_3_18
-void          gtk_image_view_load_from_stream_async  (GtkImageView  *image_view,
-                                                      GInputStream  *stream);
+void gtk_image_view_load_from_stream_async (GtkImageView        *image_view,
+                                            GInputStream        *input_stream,
+                                            int                  scale_factor,
+                                            GCancellable        *cancellable,
+                                            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);
+void gtk_image_view_load_from_stream_finish (GtkImageView  *image_view,
+                                             GAsyncResult  *result,
+                                             GError       **error);
 
 /* }}} */
 
 /* Setters/Getters {{{ */
 GDK_AVAILABLE_IN_3_18
-void   gtk_image_view_set_scale (GtkImageView *image_view, double scale);
+void   gtk_image_view_set_scale (GtkImageView *image_view,
+                                 double        scale);
 
 GDK_AVAILABLE_IN_3_18
 double gtk_image_view_get_scale (GtkImageView *image_view);
@@ -82,7 +96,8 @@ double gtk_image_view_get_scale (GtkImageView *image_view);
 
 
 GDK_AVAILABLE_IN_3_18
-void  gtk_image_view_set_angle (GtkImageView *image_view, double angle);
+void  gtk_image_view_set_angle (GtkImageView *image_view,
+                                double        angle);
 
 GDK_AVAILABLE_IN_3_18
 double gtk_image_view_get_angle (GtkImageView *image_view);
@@ -90,27 +105,39 @@ double gtk_image_view_get_angle (GtkImageView *image_view);
 
 
 GDK_AVAILABLE_IN_3_18
-void gtk_image_view_set_zoom_mode (GtkImageView         *image_view,
-                                   GtkImageViewZoomMode  zoom_mode);
+void gtk_image_view_set_snap_angle (GtkImageView *image_view,
+                                    gboolean      snap_angle);
 
 GDK_AVAILABLE_IN_3_18
-GtkImageViewZoomMode gtk_image_view_get_zoom_mode (GtkImageView *image_view);
+gboolean gtk_image_view_get_snap_angle (GtkImageView *image_view);
 
 
 
 GDK_AVAILABLE_IN_3_18
-gboolean gtk_image_view_snap_angle (GtkImageView *image_view);
+void gtk_image_view_set_fit_allocation (GtkImageView *image_view,
+                                        gboolean      fit_allocation);
 
 GDK_AVAILABLE_IN_3_18
-void gtk_image_view_set_snap_angle (GtkImageView *image_view,
-                                    gboolean      snap_rotation);
+gboolean gtk_image_view_get_fit_allocation (GtkImageView *image_view);
 
 
 
-// XXX Adding a gtk_image_view_set_pixbuf would work, but we are working with animations internally...
+GDK_AVAILABLE_IN_3_18
+void gtk_image_view_set_rotate_gesture_enabled (GtkImageView *image_view,
+                                                gboolean      rotate_gesture_enabled);
+
+GDK_AVAILABLE_IN_3_18
+gboolean gtk_image_view_get_rotate_gesture_enabled (GtkImageView *image_view);
 
 
 
+GDK_AVAILABLE_IN_3_18
+void gtk_image_view_set_zoom_gesture_enabled (GtkImageView *image_view,
+                                              gboolean      zoom_gesture_enabled);
+
+GDK_AVAILABLE_IN_3_18
+gboolean gtk_image_view_get_zoom_gesture_enabled (GtkImageView *image_view);
+
 /* }}} */
 
 G_END_DECLS



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