[gtk+/wip/baedert/gtkimageview: 61/169] GtkImageView: Replace zoom-mode with fit-allocation
- From: Timm Bäder <baedert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/baedert/gtkimageview: 61/169] GtkImageView: Replace zoom-mode with fit-allocation
- Date: Wed, 25 May 2016 16:11:40 +0000 (UTC)
commit 3055ef20039085263882dbd41e175adf71a81c58
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]