summaryrefslogtreecommitdiff
path: root/src/eom-scroll-view.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eom-scroll-view.c')
-rw-r--r--src/eom-scroll-view.c2633
1 files changed, 2633 insertions, 0 deletions
diff --git a/src/eom-scroll-view.c b/src/eom-scroll-view.c
new file mode 100644
index 0000000..f37561c
--- /dev/null
+++ b/src/eom-scroll-view.c
@@ -0,0 +1,2633 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <math.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdkkeysyms.h>
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#include <librsvg/rsvg-cairo.h>
+#endif
+
+#include "eom-marshal.h"
+#include "eom-scroll-view.h"
+#include "eom-debug.h"
+#include "uta.h"
+#include "zoom.h"
+
+/* Maximum size of delayed repaint rectangles */
+#define PAINT_RECT_WIDTH 128
+#define PAINT_RECT_HEIGHT 128
+
+/* Scroll step increment */
+#define SCROLL_STEP_SIZE 32
+
+/* Maximum zoom factor */
+#define MAX_ZOOM_FACTOR 20
+#define MIN_ZOOM_FACTOR 0.02
+
+#define CHECK_MEDIUM 8
+#define CHECK_BLACK 0x00000000
+#define CHECK_DARK 0x00555555
+#define CHECK_GRAY 0x00808080
+#define CHECK_LIGHT 0x00cccccc
+#define CHECK_WHITE 0x00ffffff
+
+/* Default increment for zooming. The current zoom factor is multiplied or
+ * divided by this amount on every zooming step. For consistency, you should
+ * use the same value elsewhere in the program.
+ */
+#define IMAGE_VIEW_ZOOM_MULTIPLIER 1.05
+
+/* States for automatically adjusting the zoom factor */
+typedef enum {
+ ZOOM_MODE_FIT, /* Image is fitted to scroll view even if the latter changes size */
+ ZOOM_MODE_FREE /* The image remains at its current zoom factor even if the scrollview changes size */
+} ZoomMode;
+
+/* Progressive loading state */
+typedef enum {
+ PROGRESSIVE_NONE, /* We are not loading an image or it is already loaded */
+ PROGRESSIVE_LOADING, /* An image is being loaded */
+ PROGRESSIVE_POLISHING /* We have finished loading an image but have not scaled it with interpolation */
+} ProgressiveState;
+
+/* Signal IDs */
+enum {
+ SIGNAL_ZOOM_CHANGED,
+ SIGNAL_LAST
+};
+static gint view_signals [SIGNAL_LAST];
+
+typedef enum {
+ EOM_SCROLL_VIEW_CURSOR_NORMAL,
+ EOM_SCROLL_VIEW_CURSOR_HIDDEN,
+ EOM_SCROLL_VIEW_CURSOR_DRAG
+} EomScrollViewCursor;
+
+/* Drag 'n Drop */
+static GtkTargetEntry target_table[] = {
+ { "text/uri-list", 0, 0},
+};
+
+enum {
+ PROP_0,
+ PROP_USE_BG_COLOR,
+ PROP_BACKGROUND_COLOR
+};
+
+/* Private part of the EomScrollView structure */
+struct _EomScrollViewPrivate {
+ /* some widgets we rely on */
+ GtkWidget *display;
+ GtkAdjustment *hadj;
+ GtkAdjustment *vadj;
+ GtkWidget *hbar;
+ GtkWidget *vbar;
+ GtkWidget *menu;
+
+ /* actual image */
+ EomImage *image;
+ guint image_changed_id;
+ guint frame_changed_id;
+ GdkPixbuf *pixbuf;
+
+ /* zoom mode, either ZOOM_MODE_FIT or ZOOM_MODE_FREE */
+ ZoomMode zoom_mode;
+
+ /* whether to allow zoom > 1.0 on zoom fit */
+ gboolean upscale;
+
+ /* the actual zoom factor */
+ double zoom;
+
+ /* the minimum possible (reasonable) zoom factor */
+ double min_zoom;
+
+ /* Current scrolling offsets */
+ int xofs, yofs;
+
+ /* Microtile arrays for dirty region. This represents the dirty region
+ * for interpolated drawing.
+ */
+ EomUta *uta;
+
+ /* handler ID for paint idle callback */
+ guint idle_id;
+
+ /* Interpolation type when zoomed in*/
+ GdkInterpType interp_type_in;
+
+ /* Interpolation type when zoomed out*/
+ GdkInterpType interp_type_out;
+
+ /* Scroll wheel zoom */
+ gboolean scroll_wheel_zoom;
+
+ /* Scroll wheel zoom */
+ gdouble zoom_multiplier;
+
+ /* dragging stuff */
+ int drag_anchor_x, drag_anchor_y;
+ int drag_ofs_x, drag_ofs_y;
+ guint dragging : 1;
+
+ /* status of progressive loading */
+ ProgressiveState progressive_state;
+
+ /* how to indicate transparency in images */
+ EomTransparencyStyle transp_style;
+ guint32 transp_color;
+
+ /* the type of the cursor we are currently showing */
+ EomScrollViewCursor cursor;
+
+ gboolean use_bg_color;
+ GdkColor *background_color;
+ GdkColor *override_bg_color;
+
+ cairo_surface_t *background_surface;
+};
+
+static void scroll_by (EomScrollView *view, int xofs, int yofs);
+static void set_zoom_fit (EomScrollView *view);
+static void request_paint_area (EomScrollView *view, GdkRectangle *area);
+static void set_minimum_zoom_factor (EomScrollView *view);
+
+#define EOM_SCROLL_VIEW_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_SCROLL_VIEW, EomScrollViewPrivate))
+
+G_DEFINE_TYPE (EomScrollView, eom_scroll_view, GTK_TYPE_TABLE)
+
+
+/*===================================
+ widget size changing handler &
+ util functions
+ ---------------------------------*/
+
+/* Disconnects from the EomImage and removes references to it */
+static void
+free_image_resources (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+
+ if (priv->image_changed_id > 0) {
+ g_signal_handler_disconnect (G_OBJECT (priv->image), priv->image_changed_id);
+ priv->image_changed_id = 0;
+ }
+
+ if (priv->frame_changed_id > 0) {
+ g_signal_handler_disconnect (G_OBJECT (priv->image), priv->frame_changed_id);
+ priv->frame_changed_id = 0;
+ }
+
+ if (priv->image != NULL) {
+ eom_image_data_unref (priv->image);
+ priv->image = NULL;
+ }
+
+ if (priv->pixbuf != NULL) {
+ g_object_unref (priv->pixbuf);
+ priv->pixbuf = NULL;
+ }
+}
+
+/* Computes the size in pixels of the scaled image */
+static void
+compute_scaled_size (EomScrollView *view, double zoom, int *width, int *height)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+
+ if (priv->pixbuf) {
+ *width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoom + 0.5);
+ *height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoom + 0.5);
+ } else
+ *width = *height = 0;
+}
+
+/* Computes the offsets for the new zoom value so that they keep the image
+ * centered on the view.
+ */
+static void
+compute_center_zoom_offsets (EomScrollView *view,
+ double old_zoom, double new_zoom,
+ int width, int height,
+ double zoom_x_anchor, double zoom_y_anchor,
+ int *xofs, int *yofs)
+{
+ EomScrollViewPrivate *priv;
+ int old_scaled_width, old_scaled_height;
+ int new_scaled_width, new_scaled_height;
+ double view_cx, view_cy;
+
+ priv = view->priv;
+
+ compute_scaled_size (view, old_zoom,
+ &old_scaled_width, &old_scaled_height);
+
+ if (old_scaled_width < width)
+ view_cx = (zoom_x_anchor * old_scaled_width) / old_zoom;
+ else
+ view_cx = (priv->xofs + zoom_x_anchor * width) / old_zoom;
+
+ if (old_scaled_height < height)
+ view_cy = (zoom_y_anchor * old_scaled_height) / old_zoom;
+ else
+ view_cy = (priv->yofs + zoom_y_anchor * height) / old_zoom;
+
+ compute_scaled_size (view, new_zoom,
+ &new_scaled_width, &new_scaled_height);
+
+ if (new_scaled_width < width)
+ *xofs = 0;
+ else {
+ *xofs = floor (view_cx * new_zoom - zoom_x_anchor * width + 0.5);
+ if (*xofs < 0)
+ *xofs = 0;
+ }
+
+ if (new_scaled_height < height)
+ *yofs = 0;
+ else {
+ *yofs = floor (view_cy * new_zoom - zoom_y_anchor * height + 0.5);
+ if (*yofs < 0)
+ *yofs = 0;
+ }
+}
+
+/* Sets the scrollbar values based on the current scrolling offset */
+static void
+update_scrollbar_values (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+ int scaled_width, scaled_height;
+ int xofs, yofs;
+ gdouble page_size,page_increment,step_increment;
+ gdouble lower, upper, value;
+ GtkAllocation allocation;
+
+ priv = view->priv;
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (priv->hbar))
+ && !gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
+ return;
+
+ compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
+ gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
+
+ if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) {
+ /* Set scroll increments */
+ page_size = MIN (scaled_width, allocation.width);
+ page_increment = allocation.width / 2;
+ step_increment = SCROLL_STEP_SIZE;
+
+ /* Set scroll bounds and new offsets */
+ lower = 0;
+ upper = scaled_width;
+ xofs = CLAMP (priv->xofs, 0, upper - page_size);
+ if (gtk_adjustment_get_value (priv->hadj) != xofs) {
+ value = xofs;
+ priv->xofs = xofs;
+
+ g_signal_handlers_block_matched (
+ priv->hadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+
+ gtk_adjustment_configure (priv->hadj, value, lower,
+ upper, step_increment,
+ page_increment, page_size);
+
+ g_signal_handlers_unblock_matched (
+ priv->hadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+ }
+ }
+
+ if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) {
+ page_size = MIN (scaled_height, allocation.height);
+ page_increment = allocation.height / 2;
+ step_increment = SCROLL_STEP_SIZE;
+
+ lower = 0;
+ upper = scaled_height;
+ yofs = CLAMP (priv->yofs, 0, upper - page_size);
+
+ if (gtk_adjustment_get_value (priv->vadj) != yofs) {
+ value = yofs;
+ priv->yofs = yofs;
+
+ g_signal_handlers_block_matched (
+ priv->vadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+
+ gtk_adjustment_configure (priv->vadj, value, lower,
+ upper, step_increment,
+ page_increment, page_size);
+
+ g_signal_handlers_unblock_matched (
+ priv->vadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+ }
+ }
+}
+
+static void
+eom_scroll_view_set_cursor (EomScrollView *view, EomScrollViewCursor new_cursor)
+{
+ GdkCursor *cursor = NULL;
+ GdkDisplay *display;
+ GtkWidget *widget;
+
+ if (view->priv->cursor == new_cursor) {
+ return;
+ }
+
+ widget = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ display = gtk_widget_get_display (widget);
+ view->priv->cursor = new_cursor;
+
+ switch (new_cursor) {
+ case EOM_SCROLL_VIEW_CURSOR_NORMAL:
+ gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
+ break;
+ case EOM_SCROLL_VIEW_CURSOR_HIDDEN:
+ cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
+ break;
+ case EOM_SCROLL_VIEW_CURSOR_DRAG:
+ cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
+ break;
+ }
+
+ if (cursor) {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
+ gdk_cursor_unref (cursor);
+ gdk_flush();
+ }
+}
+
+/* Changes visibility of the scrollbars based on the zoom factor and the
+ * specified allocation, or the current allocation if NULL is specified.
+ */
+static void
+check_scrollbar_visibility (EomScrollView *view, GtkAllocation *alloc)
+{
+ EomScrollViewPrivate *priv;
+ int bar_height;
+ int bar_width;
+ int img_width;
+ int img_height;
+ GtkRequisition req;
+ int width, height;
+ gboolean hbar_visible, vbar_visible;
+
+ priv = view->priv;
+
+ if (alloc) {
+ width = alloc->width;
+ height = alloc->height;
+ } else {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
+ width = allocation.width;
+ height = allocation.height;
+ }
+
+ compute_scaled_size (view, priv->zoom, &img_width, &img_height);
+
+ /* this should work fairly well in this special case for scrollbars */
+ gtk_widget_size_request (priv->hbar, &req);
+ bar_height = req.height;
+ gtk_widget_size_request (priv->vbar, &req);
+ bar_width = req.width;
+
+ eom_debug_message (DEBUG_WINDOW, "Widget Size allocate: %i, %i Bar: %i, %i\n",
+ width, height, bar_width, bar_height);
+
+ hbar_visible = vbar_visible = FALSE;
+ if (priv->zoom_mode == ZOOM_MODE_FIT)
+ hbar_visible = vbar_visible = FALSE;
+ else if (img_width <= width && img_height <= height)
+ hbar_visible = vbar_visible = FALSE;
+ else if (img_width > width && img_height > height)
+ hbar_visible = vbar_visible = TRUE;
+ else if (img_width > width) {
+ hbar_visible = TRUE;
+ if (img_height <= (height - bar_height))
+ vbar_visible = FALSE;
+ else
+ vbar_visible = TRUE;
+ }
+ else if (img_height > height) {
+ vbar_visible = TRUE;
+ if (img_width <= (width - bar_width))
+ hbar_visible = FALSE;
+ else
+ hbar_visible = TRUE;
+ }
+
+ if (hbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->hbar)))
+ g_object_set (G_OBJECT (priv->hbar), "visible", hbar_visible, NULL);
+
+ if (vbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->vbar)))
+ g_object_set (G_OBJECT (priv->vbar), "visible", vbar_visible, NULL);
+}
+
+#define DOUBLE_EQUAL_MAX_DIFF 1e-6
+#define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
+
+/* Returns whether the zoom factor is 1.0 */
+static gboolean
+is_unity_zoom (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+ return DOUBLE_EQUAL (priv->zoom, 1.0);
+}
+
+/* Returns whether the image is zoomed in */
+static gboolean
+is_zoomed_in (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+ return priv->zoom - 1.0 > DOUBLE_EQUAL_MAX_DIFF;
+}
+
+/* Returns whether the image is zoomed out */
+static gboolean
+is_zoomed_out (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+ return DOUBLE_EQUAL_MAX_DIFF + priv->zoom - 1.0 < 0.0;
+}
+
+/* Returns wether the image is movable, that means if it is larger then
+ * the actual visible area.
+ */
+static gboolean
+is_image_movable (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+
+ return (gtk_widget_get_visible (priv->hbar) || gtk_widget_get_visible (priv->vbar));
+}
+
+
+/* Computes the image offsets with respect to the window */
+/*
+static void
+get_image_offsets (EomScrollView *view, int *xofs, int *yofs)
+{
+ EomScrollViewPrivate *priv;
+ int scaled_width, scaled_height;
+ int width, height;
+
+ priv = view->priv;
+
+ compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
+
+ width = GTK_WIDGET (priv->display)->allocation.width;
+ height = GTK_WIDGET (priv->display)->allocation.height;
+
+ // Compute image offsets with respect to the window
+ if (scaled_width <= width)
+ *xofs = (width - scaled_width) / 2;
+ else
+ *xofs = -priv->xofs;
+
+ if (scaled_height <= height)
+ *yofs = (height - scaled_height) / 2;
+ else
+ *yofs = -priv->yofs;
+}
+*/
+
+/*===================================
+ drawing core
+ ---------------------------------*/
+
+
+/* Pulls a rectangle from the specified microtile array. The rectangle is the
+ * first one that would be glommed together by art_rect_list_from_uta(), and its
+ * size is bounded by max_width and max_height. The rectangle is also removed
+ * from the microtile array.
+ */
+static void
+pull_rectangle (EomUta *uta, EomIRect *rect, int max_width, int max_height)
+{
+ uta_find_first_glom_rect (uta, rect, max_width, max_height);
+ uta_remove_rect (uta, rect->x0, rect->y0, rect->x1, rect->y1);
+}
+
+/* Paints a rectangle with the background color if the specified rectangle
+ * intersects the dirty rectangle.
+ */
+static void
+paint_background (EomScrollView *view, EomIRect *r, EomIRect *rect)
+{
+ EomScrollViewPrivate *priv;
+ EomIRect d;
+
+ priv = view->priv;
+
+ eom_irect_intersect (&d, r, rect);
+ if (!eom_irect_empty (&d)) {
+ gdk_window_clear_area (gtk_widget_get_window (priv->display),
+ d.x0, d.y0,
+ d.x1 - d.x0, d.y1 - d.y0);
+ }
+}
+
+static void
+get_transparency_params (EomScrollView *view, int *size, guint32 *color1, guint32 *color2)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+
+ /* Compute transparency parameters */
+ switch (priv->transp_style) {
+ case EOM_TRANSP_BACKGROUND: {
+ GdkColor color = gtk_widget_get_style (GTK_WIDGET (priv->display))->bg[GTK_STATE_NORMAL];
+
+ *color1 = *color2 = (((color.red & 0xff00) << 8)
+ | (color.green & 0xff00)
+ | ((color.blue & 0xff00) >> 8));
+ break; }
+
+ case EOM_TRANSP_CHECKED:
+ *color1 = CHECK_GRAY;
+ *color2 = CHECK_LIGHT;
+ break;
+
+ case EOM_TRANSP_COLOR:
+ *color1 = *color2 = priv->transp_color;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ };
+
+ *size = CHECK_MEDIUM;
+}
+
+#ifdef HAVE_RSVG
+static cairo_surface_t *
+create_background_surface (EomScrollView *view)
+{
+ int check_size;
+ guint32 check_1 = 0;
+ guint32 check_2 = 0;
+ cairo_surface_t *surface;
+ cairo_t *check_cr;
+
+ get_transparency_params (view, &check_size, &check_1, &check_2);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, check_size * 2, check_size * 2);
+ check_cr = cairo_create (surface);
+ cairo_set_source_rgba (check_cr,
+ ((check_1 & 0xff0000) >> 16) / 255.,
+ ((check_1 & 0x00ff00) >> 8) / 255.,
+ (check_1 & 0x0000ff) / 255.,
+ 1.);
+ cairo_rectangle (check_cr, 0., 0., check_size, check_size);
+ cairo_fill (check_cr);
+ cairo_translate (check_cr, check_size, check_size);
+ cairo_rectangle (check_cr, 0., 0., check_size, check_size);
+ cairo_fill (check_cr);
+
+ cairo_set_source_rgba (check_cr,
+ ((check_2 & 0xff0000) >> 16) / 255.,
+ ((check_2 & 0x00ff00) >> 8) / 255.,
+ (check_2 & 0x0000ff) / 255.,
+ 1.);
+ cairo_translate (check_cr, -check_size, 0);
+ cairo_rectangle (check_cr, 0., 0., check_size, check_size);
+ cairo_fill (check_cr);
+ cairo_translate (check_cr, check_size, -check_size);
+ cairo_rectangle (check_cr, 0., 0., check_size, check_size);
+ cairo_fill (check_cr);
+ cairo_destroy (check_cr);
+
+ return surface;
+}
+
+static void
+draw_svg_background (EomScrollView *view, cairo_t *cr, EomIRect *render_rect, EomIRect *image_rect)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+
+ if (priv->background_surface == NULL)
+ priv->background_surface = create_background_surface (view);
+
+ cairo_set_source_surface (cr, priv->background_surface,
+ - (render_rect->x0 - image_rect->x0) % (CHECK_MEDIUM * 2),
+ - (render_rect->y0 - image_rect->y0) % (CHECK_MEDIUM * 2));
+ cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle (cr,
+ 0,
+ 0,
+ render_rect->x1 - render_rect->x0,
+ render_rect->y1 - render_rect->y0);
+ cairo_fill (cr);
+}
+
+static cairo_surface_t *
+draw_svg_on_image_surface (EomScrollView *view, EomIRect *render_rect, EomIRect *image_rect)
+{
+ EomScrollViewPrivate *priv;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ cairo_matrix_t matrix, translate, scale;
+ EomTransform *transform;
+
+ priv = view->priv;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ render_rect->x1 - render_rect->x0,
+ render_rect->y1 - render_rect->y0);
+ cr = cairo_create (surface);
+
+ cairo_save (cr);
+ draw_svg_background (view, cr, render_rect, image_rect);
+ cairo_restore (cr);
+
+ cairo_matrix_init_identity (&matrix);
+ transform = eom_image_get_transform (priv->image);
+ if (transform) {
+ cairo_matrix_t affine;
+ double image_offset_x = 0., image_offset_y = 0.;
+
+ eom_transform_get_affine (transform, &affine);
+ cairo_matrix_multiply (&matrix, &affine, &matrix);
+
+ switch (eom_transform_get_transform_type (transform)) {
+ case EOM_TRANSFORM_ROT_90:
+ case EOM_TRANSFORM_FLIP_HORIZONTAL:
+ image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
+ break;
+ case EOM_TRANSFORM_ROT_270:
+ case EOM_TRANSFORM_FLIP_VERTICAL:
+ image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
+ break;
+ case EOM_TRANSFORM_ROT_180:
+ case EOM_TRANSFORM_TRANSPOSE:
+ case EOM_TRANSFORM_TRANSVERSE:
+ image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf);
+ image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf);
+ break;
+ case EOM_TRANSFORM_NONE:
+ default:
+ break;
+ }
+
+ cairo_matrix_init_translate (&translate, image_offset_x, image_offset_y);
+ cairo_matrix_multiply (&matrix, &matrix, &translate);
+ }
+
+ cairo_matrix_init_scale (&scale, priv->zoom, priv->zoom);
+ cairo_matrix_multiply (&matrix, &matrix, &scale);
+ cairo_matrix_init_translate (&translate, image_rect->x0, image_rect->y0);
+ cairo_matrix_multiply (&matrix, &matrix, &translate);
+ cairo_matrix_init_translate (&translate, -render_rect->x0, -render_rect->y0);
+ cairo_matrix_multiply (&matrix, &matrix, &translate);
+
+ cairo_set_matrix (cr, &matrix);
+
+ rsvg_handle_render_cairo (eom_image_get_svg (priv->image), cr);
+ cairo_destroy (cr);
+
+ return surface;
+}
+
+static void
+draw_svg (EomScrollView *view, EomIRect *render_rect, EomIRect *image_rect)
+{
+ EomScrollViewPrivate *priv;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ GdkWindow *window;
+
+ priv = view->priv;
+
+ window = gtk_widget_get_window (GTK_WIDGET (priv->display));
+ surface = draw_svg_on_image_surface (view, render_rect, image_rect);
+
+ cr = gdk_cairo_create (window);
+ cairo_set_source_surface (cr, surface, render_rect->x0, render_rect->y0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+}
+#endif
+
+/* Paints a rectangle of the dirty region */
+static void
+paint_rectangle (EomScrollView *view, EomIRect *rect, GdkInterpType interp_type)
+{
+ EomScrollViewPrivate *priv;
+ GdkPixbuf *tmp;
+ char *str;
+ GtkAllocation allocation;
+ int scaled_width, scaled_height;
+ int xofs, yofs;
+ EomIRect r, d;
+ int check_size;
+ guint32 check_1 = 0;
+ guint32 check_2 = 0;
+
+ priv = view->priv;
+
+ if (!gtk_widget_is_drawable (priv->display))
+ return;
+
+ compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
+
+ gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
+
+ if (scaled_width < 1 || scaled_height < 1)
+ {
+ r.x0 = 0;
+ r.y0 = 0;
+ r.x1 = allocation.width;
+ r.y1 = allocation.height;
+ paint_background (view, &r, rect);
+ return;
+ }
+
+ /* Compute image offsets with respect to the window */
+
+ if (scaled_width <= allocation.width)
+ xofs = (allocation.width - scaled_width) / 2;
+ else
+ xofs = -priv->xofs;
+
+ if (scaled_height <= allocation.height)
+ yofs = (allocation.height - scaled_height) / 2;
+ else
+ yofs = -priv->yofs;
+
+ eom_debug_message (DEBUG_WINDOW, "zoom %.2f, xofs: %i, yofs: %i scaled w: %i h: %i\n",
+ priv->zoom, xofs, yofs, scaled_width, scaled_height);
+
+ /* Draw background if necessary, in four steps */
+
+ /* Top */
+ if (yofs > 0) {
+ r.x0 = 0;
+ r.y0 = 0;
+ r.x1 = allocation.width;
+ r.y1 = yofs;
+ paint_background (view, &r, rect);
+ }
+
+ /* Left */
+ if (xofs > 0) {
+ r.x0 = 0;
+ r.y0 = yofs;
+ r.x1 = xofs;
+ r.y1 = yofs + scaled_height;
+ paint_background (view, &r, rect);
+ }
+
+ /* Right */
+ if (xofs >= 0) {
+ r.x0 = xofs + scaled_width;
+ r.y0 = yofs;
+ r.x1 = allocation.width;
+ r.y1 = yofs + scaled_height;
+ if (r.x0 < r.x1)
+ paint_background (view, &r, rect);
+ }
+
+ /* Bottom */
+ if (yofs >= 0) {
+ r.x0 = 0;
+ r.y0 = yofs + scaled_height;
+ r.x1 = allocation.width;
+ r.y1 = allocation.height;
+ if (r.y0 < r.y1)
+ paint_background (view, &r, rect);
+ }
+
+
+ /* Draw the scaled image
+ *
+ * FIXME: this is not using the color correction tables!
+ */
+
+ if (!priv->pixbuf)
+ return;
+
+ r.x0 = xofs;
+ r.y0 = yofs;
+ r.x1 = xofs + scaled_width;
+ r.y1 = yofs + scaled_height;
+
+ eom_irect_intersect (&d, &r, rect);
+ if (eom_irect_empty (&d))
+ return;
+
+ switch (interp_type) {
+ case GDK_INTERP_NEAREST:
+ str = "NEAREST";
+ break;
+ default:
+ str = "ALIASED";
+ }
+
+ eom_debug_message (DEBUG_WINDOW, "%s: x0: %i,\t y0: %i,\t x1: %i,\t y1: %i\n",
+ str, d.x0, d.y0, d.x1, d.y1);
+
+#ifdef HAVE_RSVG
+ if (eom_image_is_svg (view->priv->image) && interp_type != GDK_INTERP_NEAREST) {
+ draw_svg (view, &d, &r);
+ return;
+ }
+#endif
+ /* Short-circuit the fast case to avoid a memcpy() */
+
+ if (is_unity_zoom (view)
+ && gdk_pixbuf_get_colorspace (priv->pixbuf) == GDK_COLORSPACE_RGB
+ && !gdk_pixbuf_get_has_alpha (priv->pixbuf)
+ && gdk_pixbuf_get_bits_per_sample (priv->pixbuf) == 8) {
+ guchar *pixels;
+ int rowstride;
+
+ rowstride = gdk_pixbuf_get_rowstride (priv->pixbuf);
+
+ pixels = (gdk_pixbuf_get_pixels (priv->pixbuf)
+ + (d.y0 - yofs) * rowstride
+ + 3 * (d.x0 - xofs));
+
+ gdk_draw_rgb_image_dithalign (gtk_widget_get_window (GTK_WIDGET (priv->display)),
+ gtk_widget_get_style (GTK_WIDGET (priv->display))->black_gc,
+ d.x0, d.y0,
+ d.x1 - d.x0, d.y1 - d.y0,
+ GDK_RGB_DITHER_MAX,
+ pixels,
+ rowstride,
+ d.x0 - xofs, d.y0 - yofs);
+ return;
+ }
+
+ /* For all other cases, create a temporary pixbuf */
+
+ tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, d.x1 - d.x0, d.y1 - d.y0);
+
+ if (!tmp) {
+ g_message ("paint_rectangle(): Could not allocate temporary pixbuf of "
+ "size (%d, %d); skipping", d.x1 - d.x0, d.y1 - d.y0);
+ return;
+ }
+
+ /* Compute transparency parameters */
+ get_transparency_params (view, &check_size, &check_1, &check_2);
+
+ /* Draw! */
+ gdk_pixbuf_composite_color (priv->pixbuf,
+ tmp,
+ 0, 0,
+ d.x1 - d.x0, d.y1 - d.y0,
+ -(d.x0 - xofs), -(d.y0 - yofs),
+ priv->zoom, priv->zoom,
+ is_unity_zoom (view) ? GDK_INTERP_NEAREST : interp_type,
+ 255,
+ d.x0 - xofs, d.y0 - yofs,
+ check_size,
+ check_1, check_2);
+
+ gdk_draw_rgb_image_dithalign (gtk_widget_get_window (priv->display),
+ gtk_widget_get_style (priv->display)->black_gc,
+ d.x0, d.y0,
+ d.x1 - d.x0, d.y1 - d.y0,
+ GDK_RGB_DITHER_MAX,
+ gdk_pixbuf_get_pixels (tmp),
+ gdk_pixbuf_get_rowstride (tmp),
+ d.x0 - xofs, d.y0 - yofs);
+
+ g_object_unref (tmp);
+}
+
+
+/* Idle handler for the drawing process. We pull a rectangle from the dirty
+ * region microtile array, paint it, and leave the rest to the next idle
+ * iteration.
+ */
+static gboolean
+paint_iteration_idle (gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+ EomIRect rect;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ g_assert (priv->uta != NULL);
+
+ pull_rectangle (priv->uta, &rect, PAINT_RECT_WIDTH, PAINT_RECT_HEIGHT);
+
+ if (eom_irect_empty (&rect)) {
+ eom_uta_free (priv->uta);
+ priv->uta = NULL;
+ } else {
+ if (is_zoomed_in (view))
+ paint_rectangle (view, &rect, priv->interp_type_in);
+ else if (is_zoomed_out (view))
+ paint_rectangle (view, &rect, priv->interp_type_out);
+ else
+ paint_rectangle (view, &rect, GDK_INTERP_NEAREST);
+ }
+
+ if (!priv->uta) {
+ priv->idle_id = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Paints the requested area in non-interpolated mode. Then, if we are
+ * configured to use interpolation, we queue an idle handler to redraw the area
+ * with interpolation. The area is in window coordinates.
+ */
+static void
+request_paint_area (EomScrollView *view, GdkRectangle *area)
+{
+ EomScrollViewPrivate *priv;
+ EomIRect r;
+ GtkAllocation allocation;
+
+ priv = view->priv;
+
+ eom_debug_message (DEBUG_WINDOW, "x: %i, y: %i, width: %i, height: %i\n",
+ area->x, area->y, area->width, area->height);
+
+ if (!gtk_widget_is_drawable (priv->display))
+ return;
+
+ gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
+ r.x0 = MAX (0, area->x);
+ r.y0 = MAX (0, area->y);
+ r.x1 = MIN (allocation.width, area->x + area->width);
+ r.y1 = MIN (allocation.height, area->y + area->height);
+
+ eom_debug_message (DEBUG_WINDOW, "r: %i, %i, %i, %i\n", r.x0, r.y0, r.x1, r.y1);
+
+ if (r.x0 >= r.x1 || r.y0 >= r.y1)
+ return;
+
+ /* Do nearest neighbor, 1:1 zoom or active progressive loading synchronously for speed. */
+ if ((is_zoomed_in (view) && priv->interp_type_in == GDK_INTERP_NEAREST) ||
+ (is_zoomed_out (view) && priv->interp_type_out == GDK_INTERP_NEAREST) ||
+ is_unity_zoom (view) ||
+ priv->progressive_state == PROGRESSIVE_LOADING) {
+ paint_rectangle (view, &r, GDK_INTERP_NEAREST);
+ return;
+ }
+
+ if (priv->progressive_state == PROGRESSIVE_POLISHING)
+ /* We have already a complete image with nearest neighbor mode.
+ * It's sufficient to add only a antitaliased idle update
+ */
+ priv->progressive_state = PROGRESSIVE_NONE;
+ else if (!priv->image || !eom_image_is_animation (priv->image))
+ /* do nearest neigbor before anti aliased version,
+ except for animations to avoid a "blinking" effect. */
+ paint_rectangle (view, &r, GDK_INTERP_NEAREST);
+
+ /* All other interpolation types are delayed. */
+ if (priv->uta)
+ g_assert (priv->idle_id != 0);
+ else {
+ g_assert (priv->idle_id == 0);
+ priv->idle_id = g_idle_add (paint_iteration_idle, view);
+ }
+
+ priv->uta = uta_add_rect (priv->uta, r.x0, r.y0, r.x1, r.y1);
+}
+
+
+/* =======================================
+
+ scrolling stuff
+
+ --------------------------------------*/
+
+
+/* Scrolls the view to the specified offsets. */
+static void
+scroll_to (EomScrollView *view, int x, int y, gboolean change_adjustments)
+{
+ EomScrollViewPrivate *priv;
+ GtkAllocation allocation;
+ int xofs, yofs;
+ GdkWindow *window;
+ int src_x, src_y;
+ int dest_x, dest_y;
+ int twidth, theight;
+
+ priv = view->priv;
+
+ /* Check bounds & Compute offsets */
+ if (gtk_widget_get_visible (priv->hbar)) {
+ x = CLAMP (x, 0, gtk_adjustment_get_upper (priv->hadj)
+ - gtk_adjustment_get_page_size (priv->hadj));
+ xofs = x - priv->xofs;
+ } else
+ xofs = 0;
+
+ if (gtk_widget_get_visible (priv->vbar)) {
+ y = CLAMP (y, 0, gtk_adjustment_get_upper (priv->vadj)
+ - gtk_adjustment_get_page_size (priv->vadj));
+ yofs = y - priv->yofs;
+ } else
+ yofs = 0;
+
+ if (xofs == 0 && yofs == 0)
+ return;
+
+ priv->xofs = x;
+ priv->yofs = y;
+
+ if (!gtk_widget_is_drawable (priv->display))
+ goto out;
+
+ gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
+
+ if (abs (xofs) >= allocation.width || abs (yofs) >= allocation.height) {
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ goto out;
+ }
+
+ window = gtk_widget_get_window (GTK_WIDGET (priv->display));
+
+ /* Ensure that the uta has the full size */
+
+ twidth = (allocation.width + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT;
+ theight = (allocation.height + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT;
+
+ if (priv->uta)
+ g_assert (priv->idle_id != 0);
+ else
+ priv->idle_id = g_idle_add (paint_iteration_idle, view);
+
+ priv->uta = uta_ensure_size (priv->uta, 0, 0, twidth, theight);
+
+ /* Copy the uta area. Our synchronous handling of expose events, below,
+ * will queue the new scrolled-in areas.
+ */
+ src_x = xofs < 0 ? 0 : xofs;
+ src_y = yofs < 0 ? 0 : yofs;
+ dest_x = xofs < 0 ? -xofs : 0;
+ dest_y = yofs < 0 ? -yofs : 0;
+
+ uta_copy_area (priv->uta,
+ src_x, src_y,
+ dest_x, dest_y,
+ allocation.width - abs (xofs),
+ allocation.height - abs (yofs));
+
+ /* Scroll the window area and process exposure synchronously. */
+
+ gdk_window_scroll (window, -xofs, -yofs);
+ gdk_window_process_updates (window, TRUE);
+
+ out:
+ if (!change_adjustments)
+ return;
+
+ g_signal_handlers_block_matched (
+ priv->hadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+ g_signal_handlers_block_matched (
+ priv->vadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+
+ gtk_adjustment_set_value (priv->hadj, x);
+ gtk_adjustment_set_value (priv->vadj, y);
+
+ g_signal_handlers_unblock_matched (
+ priv->hadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+ g_signal_handlers_unblock_matched (
+ priv->vadj, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, view);
+}
+
+/* Scrolls the image view by the specified offsets. Notifies the adjustments
+ * about their new values.
+ */
+static void
+scroll_by (EomScrollView *view, int xofs, int yofs)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv;
+
+ scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE);
+}
+
+
+/* Callback used when an adjustment is changed */
+static void
+adjustment_changed_cb (GtkAdjustment *adj, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ scroll_to (view, gtk_adjustment_get_value (priv->hadj),
+ gtk_adjustment_get_value (priv->vadj), FALSE);
+}
+
+
+/* Drags the image to the specified position */
+static void
+drag_to (EomScrollView *view, int x, int y)
+{
+ EomScrollViewPrivate *priv;
+ int dx, dy;
+
+ priv = view->priv;
+
+ dx = priv->drag_anchor_x - x;
+ dy = priv->drag_anchor_y - y;
+
+ x = priv->drag_ofs_x + dx;
+ y = priv->drag_ofs_y + dy;
+
+ scroll_to (view, x, y, TRUE);
+}
+
+static void
+set_minimum_zoom_factor (EomScrollView *view)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ view->priv->min_zoom = MAX (1.0 / gdk_pixbuf_get_width (view->priv->pixbuf),
+ MAX(1.0 / gdk_pixbuf_get_height (view->priv->pixbuf),
+ MIN_ZOOM_FACTOR) );
+ return;
+}
+
+/**
+ * set_zoom:
+ * @view: A scroll view.
+ * @zoom: Zoom factor.
+ * @have_anchor: Whether the anchor point specified by (@anchorx, @anchory)
+ * should be used.
+ * @anchorx: Horizontal anchor point in pixels.
+ * @anchory: Vertical anchor point in pixels.
+ *
+ * Sets the zoom factor for an image view. The anchor point can be used to
+ * specify the point that stays fixed when the image is zoomed. If @have_anchor
+ * is %TRUE, then (@anchorx, @anchory) specify the point relative to the image
+ * view widget's allocation that will stay fixed when zooming. If @have_anchor
+ * is %FALSE, then the center point of the image view will be used.
+ **/
+static void
+set_zoom (EomScrollView *view, double zoom,
+ gboolean have_anchor, int anchorx, int anchory)
+{
+ EomScrollViewPrivate *priv;
+ GtkAllocation allocation;
+ int xofs, yofs;
+ double x_rel, y_rel;
+
+ g_assert (zoom > 0.0);
+
+ priv = view->priv;
+
+ if (priv->pixbuf == NULL)
+ return;
+
+ if (zoom > MAX_ZOOM_FACTOR)
+ zoom = MAX_ZOOM_FACTOR;
+ else if (zoom < MIN_ZOOM_FACTOR)
+ zoom = MIN_ZOOM_FACTOR;
+
+ if (DOUBLE_EQUAL (priv->zoom, zoom))
+ return;
+ if (DOUBLE_EQUAL (priv->zoom, priv->min_zoom) && zoom < priv->zoom)
+ return;
+
+ priv->zoom_mode = ZOOM_MODE_FREE;
+
+ gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
+
+ /* compute new xofs/yofs values */
+ if (have_anchor) {
+ x_rel = (double) anchorx / allocation.width;
+ y_rel = (double) anchory / allocation.height;
+ } else {
+ x_rel = 0.5;
+ y_rel = 0.5;
+ }
+
+ compute_center_zoom_offsets (view, priv->zoom, zoom,
+ allocation.width, allocation.height,
+ x_rel, y_rel,
+ &xofs, &yofs);
+
+ /* set new values */
+ priv->xofs = xofs; /* (img_width * x_rel * zoom) - anchorx; */
+ priv->yofs = yofs; /* (img_height * y_rel * zoom) - anchory; */
+#if 0
+ g_print ("xofs: %i yofs: %i\n", priv->xofs, priv->yofs);
+#endif
+ if (zoom <= priv->min_zoom)
+ priv->zoom = priv->min_zoom;
+ else
+ priv->zoom = zoom;
+
+ /* we make use of the new values here */
+ check_scrollbar_visibility (view, NULL);
+ update_scrollbar_values (view);
+
+ /* repaint the whole image */
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+
+ g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom);
+}
+
+/* Zooms the image to fit the available allocation */
+static void
+set_zoom_fit (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+ GtkAllocation allocation;
+ double new_zoom;
+
+ priv = view->priv;
+
+ priv->zoom_mode = ZOOM_MODE_FIT;
+
+ if (!gtk_widget_get_mapped (GTK_WIDGET (view)))
+ return;
+
+ if (priv->pixbuf == NULL)
+ return;
+
+ gtk_widget_get_allocation (GTK_WIDGET(priv->display), &allocation);
+
+ new_zoom = zoom_fit_scale (allocation.width, allocation.height,
+ gdk_pixbuf_get_width (priv->pixbuf),
+ gdk_pixbuf_get_height (priv->pixbuf),
+ priv->upscale);
+
+ if (new_zoom > MAX_ZOOM_FACTOR)
+ new_zoom = MAX_ZOOM_FACTOR;
+ else if (new_zoom < MIN_ZOOM_FACTOR)
+ new_zoom = MIN_ZOOM_FACTOR;
+
+ priv->zoom = new_zoom;
+ priv->xofs = 0;
+ priv->yofs = 0;
+
+ g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom);
+}
+
+/*===================================
+
+ internal signal callbacks
+
+ ---------------------------------*/
+
+/* Key press event handler for the image view */
+static gboolean
+display_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+ GtkAllocation allocation;
+ gboolean do_zoom;
+ double zoom;
+ gboolean do_scroll;
+ int xofs, yofs;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ do_zoom = FALSE;
+ do_scroll = FALSE;
+ xofs = yofs = 0;
+ zoom = 1.0;
+
+ gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation);
+
+ /* EomScrollView doesn't handle/have any Alt+Key combos */
+ if (event->state & GDK_MOD1_MASK) {
+ return FALSE;
+ }
+
+ switch (event->keyval) {
+ case GDK_Up:
+ do_scroll = TRUE;
+ xofs = 0;
+ yofs = -SCROLL_STEP_SIZE;
+ break;
+
+ case GDK_Page_Up:
+ do_scroll = TRUE;
+ if (event->state & GDK_CONTROL_MASK) {
+ xofs = -(allocation.width * 3) / 4;
+ yofs = 0;
+ } else {
+ xofs = 0;
+ yofs = -(allocation.height * 3) / 4;
+ }
+ break;
+
+ case GDK_Down:
+ do_scroll = TRUE;
+ xofs = 0;
+ yofs = SCROLL_STEP_SIZE;
+ break;
+
+ case GDK_Page_Down:
+ do_scroll = TRUE;
+ if (event->state & GDK_CONTROL_MASK) {
+ xofs = (allocation.width * 3) / 4;
+ yofs = 0;
+ } else {
+ xofs = 0;
+ yofs = (allocation.height * 3) / 4;
+ }
+ break;
+
+ case GDK_Left:
+ do_scroll = TRUE;
+ xofs = -SCROLL_STEP_SIZE;
+ yofs = 0;
+ break;
+
+ case GDK_Right:
+ do_scroll = TRUE;
+ xofs = SCROLL_STEP_SIZE;
+ yofs = 0;
+ break;
+
+ case GDK_plus:
+ case GDK_equal:
+ case GDK_KP_Add:
+ do_zoom = TRUE;
+ zoom = priv->zoom * priv->zoom_multiplier;
+ break;
+
+ case GDK_minus:
+ case GDK_KP_Subtract:
+ do_zoom = TRUE;
+ zoom = priv->zoom / priv->zoom_multiplier;
+ break;
+
+ case GDK_1:
+ do_zoom = TRUE;
+ zoom = 1.0;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (do_zoom) {
+ gint x, y;
+
+ gdk_window_get_pointer (gtk_widget_get_window (widget),
+ &x, &y, NULL);
+ set_zoom (view, zoom, TRUE, x, y);
+ }
+
+ if (do_scroll)
+ scroll_by (view, xofs, yofs);
+
+ return TRUE;
+}
+
+
+/* Button press event handler for the image view */
+static gboolean
+eom_scroll_view_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ if (!gtk_widget_has_focus (priv->display))
+ gtk_widget_grab_focus (GTK_WIDGET (priv->display));
+
+ if (priv->dragging)
+ return FALSE;
+
+ switch (event->button) {
+ case 1:
+ case 2:
+ if (event->button == 1 && !priv->scroll_wheel_zoom &&
+ !(event->state & GDK_CONTROL_MASK))
+ break;
+
+ if (is_image_movable (view)) {
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG);
+
+ priv->dragging = TRUE;
+ priv->drag_anchor_x = event->x;
+ priv->drag_anchor_y = event->y;
+
+ priv->drag_ofs_x = priv->xofs;
+ priv->drag_ofs_y = priv->yofs;
+
+ return TRUE;
+ }
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+eom_scroll_view_style_set (GtkWidget *widget, GtkStyle *old_style)
+{
+ GtkStyle *style;
+ EomScrollViewPrivate *priv;
+
+ style = gtk_widget_get_style (widget);
+ priv = EOM_SCROLL_VIEW (widget)->priv;
+
+ gtk_widget_set_style (priv->display, style);
+}
+
+
+/* Button release event handler for the image view */
+static gboolean
+eom_scroll_view_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ if (!priv->dragging)
+ return FALSE;
+
+ switch (event->button) {
+ case 1:
+ case 2:
+ drag_to (view, event->x, event->y);
+ priv->dragging = FALSE;
+
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL);
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+/* Scroll event handler for the image view. We zoom with an event without
+ * modifiers rather than scroll; we use the Shift modifier to scroll.
+ * Rationale: images are not primarily vertical, and in EOM you scan scroll by
+ * dragging the image with button 1 anyways.
+ */
+static gboolean
+eom_scroll_view_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+ double zoom_factor;
+ int xofs, yofs;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ /* Compute zoom factor and scrolling offsets; we'll only use either of them */
+ /* same as in gtkscrolledwindow.c */
+ xofs = gtk_adjustment_get_page_increment (priv->hadj) / 2;
+ yofs = gtk_adjustment_get_page_increment (priv->vadj) / 2;
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ zoom_factor = priv->zoom_multiplier;
+ xofs = 0;
+ yofs = -yofs;
+ break;
+
+ case GDK_SCROLL_LEFT:
+ zoom_factor = 1.0 / priv->zoom_multiplier;
+ xofs = -xofs;
+ yofs = 0;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ zoom_factor = 1.0 / priv->zoom_multiplier;
+ xofs = 0;
+ yofs = yofs;
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ zoom_factor = priv->zoom_multiplier;
+ xofs = xofs;
+ yofs = 0;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+
+ if (priv->scroll_wheel_zoom) {
+ if (event->state & GDK_SHIFT_MASK)
+ scroll_by (view, yofs, xofs);
+ else if (event->state & GDK_CONTROL_MASK)
+ scroll_by (view, xofs, yofs);
+ else
+ set_zoom (view, priv->zoom * zoom_factor,
+ TRUE, event->x, event->y);
+ } else {
+ if (event->state & GDK_SHIFT_MASK)
+ scroll_by (view, yofs, xofs);
+ else if (event->state & GDK_CONTROL_MASK)
+ set_zoom (view, priv->zoom * zoom_factor,
+ TRUE, event->x, event->y);
+ else
+ scroll_by (view, xofs, yofs);
+ }
+
+ return TRUE;
+}
+
+/* Motion event handler for the image view */
+static gboolean
+eom_scroll_view_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+ gint x, y;
+ GdkModifierType mods;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ if (!priv->dragging)
+ return FALSE;
+
+ if (event->is_hint)
+ gdk_window_get_pointer (gtk_widget_get_window (GTK_WIDGET (priv->display)), &x, &y, &mods);
+ else {
+ x = event->x;
+ y = event->y;
+ }
+
+ drag_to (view, x, y);
+ return TRUE;
+}
+
+static void
+display_map_event (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ eom_debug (DEBUG_WINDOW);
+
+ set_zoom_fit (view);
+ check_scrollbar_visibility (view, NULL);
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+}
+
+static void
+eom_scroll_view_size_allocate (GtkWidget *widget, GtkAllocation *alloc)
+{
+ EomScrollView *view;
+
+ view = EOM_SCROLL_VIEW (widget);
+ check_scrollbar_visibility (view, alloc);
+
+ GTK_WIDGET_CLASS (eom_scroll_view_parent_class)->size_allocate (widget
+ ,alloc);
+}
+
+static void
+display_size_change (GtkWidget *widget, GdkEventConfigure *event, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ if (priv->zoom_mode == ZOOM_MODE_FIT) {
+ GtkAllocation alloc;
+
+ alloc.width = event->width;
+ alloc.height = event->height;
+
+ set_zoom_fit (view);
+ check_scrollbar_visibility (view, &alloc);
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ } else {
+ int scaled_width, scaled_height;
+ int x_offset = 0;
+ int y_offset = 0;
+
+ compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
+
+ if (priv->xofs + event->width > scaled_width)
+ x_offset = scaled_width - event->width - priv->xofs;
+
+ if (priv->yofs + event->height > scaled_height)
+ y_offset = scaled_height - event->height - priv->yofs;
+
+ scroll_by (view, x_offset, y_offset);
+ }
+
+ update_scrollbar_values (view);
+}
+
+
+static gboolean
+eom_scroll_view_focus_in_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ gpointer data)
+{
+ g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_in_event");
+ return FALSE;
+}
+
+static gboolean
+eom_scroll_view_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ gpointer data)
+{
+ g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_out_event");
+ return FALSE;
+}
+
+/* Expose event handler for the drawing area. First we process the whole dirty
+ * region by drawing a non-interpolated version, which is "instantaneous", and
+ * we do this synchronously. Then, if we are set to use interpolation, we queue
+ * an idle handler to handle interpolated drawing there.
+ */
+static gboolean
+display_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ EomScrollView *view;
+ GdkRectangle *rects;
+ gint n_rects;
+ int i;
+
+ g_return_val_if_fail (GTK_IS_DRAWING_AREA (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (EOM_IS_SCROLL_VIEW (data), FALSE);
+
+ view = EOM_SCROLL_VIEW (data);
+
+ gdk_region_get_rectangles (event->region, &rects, &n_rects);
+
+ for (i = 0; i < n_rects; i++) {
+ request_paint_area (view, rects + i);
+ }
+
+ g_free (rects);
+
+ return TRUE;
+}
+
+
+/*==================================
+
+ image loading callbacks
+
+ -----------------------------------*/
+/*
+static void
+image_loading_update_cb (EomImage *img, int x, int y, int width, int height, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+ GdkRectangle area;
+ int xofs, yofs;
+ int sx0, sy0, sx1, sy1;
+
+ view = (EomScrollView*) data;
+ priv = view->priv;
+
+ eom_debug_message (DEBUG_IMAGE_LOAD, "x: %i, y: %i, width: %i, height: %i\n",
+ x, y, width, height);
+
+ if (priv->pixbuf == NULL) {
+ priv->pixbuf = eom_image_get_pixbuf (img);
+ set_zoom_fit (view);
+ check_scrollbar_visibility (view, NULL);
+ }
+ priv->progressive_state = PROGRESSIVE_LOADING;
+
+ get_image_offsets (view, &xofs, &yofs);
+
+ sx0 = floor (x * priv->zoom + xofs);
+ sy0 = floor (y * priv->zoom + yofs);
+ sx1 = ceil ((x + width) * priv->zoom + xofs);
+ sy1 = ceil ((y + height) * priv->zoom + yofs);
+
+ area.x = sx0;
+ area.y = sy0;
+ area.width = sx1 - sx0;
+ area.height = sy1 - sy0;
+
+ if (GTK_WIDGET_DRAWABLE (priv->display))
+ gdk_window_invalidate_rect (GTK_WIDGET (priv->display)->window, &area, FALSE);
+}
+
+
+static void
+image_loading_finished_cb (EomImage *img, gpointer data)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ view = (EomScrollView*) data;
+ priv = view->priv;
+
+ if (priv->pixbuf == NULL) {
+ priv->pixbuf = eom_image_get_pixbuf (img);
+ priv->progressive_state = PROGRESSIVE_NONE;
+ set_zoom_fit (view);
+ check_scrollbar_visibility (view, NULL);
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+
+ }
+ else if (priv->interp_type != GDK_INTERP_NEAREST &&
+ !is_unity_zoom (view))
+ {
+ // paint antialiased image version
+ priv->progressive_state = PROGRESSIVE_POLISHING;
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ }
+}
+
+static void
+image_loading_failed_cb (EomImage *img, char *msg, gpointer data)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = EOM_SCROLL_VIEW (data)->priv;
+
+ g_print ("loading failed: %s.\n", msg);
+
+ if (priv->pixbuf != 0) {
+ g_object_unref (priv->pixbuf);
+ priv->pixbuf = 0;
+ }
+
+ if (GTK_WIDGET_DRAWABLE (priv->display)) {
+ gdk_window_clear (GTK_WIDGET (priv->display)->window);
+ }
+}
+
+static void
+image_loading_cancelled_cb (EomImage *img, gpointer data)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = EOM_SCROLL_VIEW (data)->priv;
+
+ if (priv->pixbuf != NULL) {
+ g_object_unref (priv->pixbuf);
+ priv->pixbuf = NULL;
+ }
+
+ if (GTK_WIDGET_DRAWABLE (priv->display)) {
+ gdk_window_clear (GTK_WIDGET (priv->display)->window);
+ }
+}
+*/
+static void
+image_changed_cb (EomImage *img, gpointer data)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = EOM_SCROLL_VIEW (data)->priv;
+
+ if (priv->pixbuf != NULL) {
+ g_object_unref (priv->pixbuf);
+ priv->pixbuf = NULL;
+ }
+
+ priv->pixbuf = eom_image_get_pixbuf (img);
+
+ set_zoom_fit (EOM_SCROLL_VIEW (data));
+ check_scrollbar_visibility (EOM_SCROLL_VIEW (data), NULL);
+
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+}
+
+/*===================================
+ public API
+ ---------------------------------*/
+
+void
+eom_scroll_view_hide_cursor (EomScrollView *view)
+{
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_HIDDEN);
+}
+
+void
+eom_scroll_view_show_cursor (EomScrollView *view)
+{
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL);
+}
+
+/* general properties */
+void
+eom_scroll_view_set_zoom_upscale (EomScrollView *view, gboolean upscale)
+{
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (priv->upscale != upscale) {
+ priv->upscale = upscale;
+
+ if (priv->zoom_mode == ZOOM_MODE_FIT) {
+ set_zoom_fit (view);
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ }
+ }
+}
+
+void
+eom_scroll_view_set_antialiasing_in (EomScrollView *view, gboolean state)
+{
+ EomScrollViewPrivate *priv;
+ GdkInterpType new_interp_type;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ new_interp_type = state ? GDK_INTERP_BILINEAR : GDK_INTERP_NEAREST;
+
+ if (priv->interp_type_in != new_interp_type) {
+ priv->interp_type_in = new_interp_type;
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ }
+}
+
+void
+eom_scroll_view_set_antialiasing_out (EomScrollView *view, gboolean state)
+{
+ EomScrollViewPrivate *priv;
+ GdkInterpType new_interp_type;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ new_interp_type = state ? GDK_INTERP_BILINEAR : GDK_INTERP_NEAREST;
+
+ if (priv->interp_type_out != new_interp_type) {
+ priv->interp_type_out = new_interp_type;
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ }
+}
+
+void
+eom_scroll_view_set_transparency (EomScrollView *view, EomTransparencyStyle style, GdkColor *color)
+{
+ EomScrollViewPrivate *priv;
+ guint32 col = 0;
+ guint32 red, green, blue;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (color != NULL) {
+ red = (color->red >> 8) << 16;
+ green = (color->green >> 8) << 8;
+ blue = (color->blue >> 8);
+ col = red + green + blue;
+ }
+
+ if (priv->transp_style != style) {
+ priv->transp_style = style;
+ changed = TRUE;
+ }
+
+ if (priv->transp_style == EOM_TRANSP_COLOR && priv->transp_color != col) {
+ priv->transp_color = col;
+ changed = TRUE;
+ }
+
+ if (changed && priv->pixbuf != NULL && gdk_pixbuf_get_has_alpha (priv->pixbuf)) {
+ if (priv->background_surface) {
+ cairo_surface_destroy (priv->background_surface);
+ /* Will be recreated if needed during redraw */
+ priv->background_surface = NULL;
+ }
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ }
+}
+
+/* zoom api */
+
+static double preferred_zoom_levels[] = {
+ 1.0 / 100, 1.0 / 50, 1.0 / 20,
+ 1.0 / 10.0, 1.0 / 5.0, 1.0 / 3.0, 1.0 / 2.0, 1.0 / 1.5,
+ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
+ 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0
+};
+static const gint n_zoom_levels = (sizeof (preferred_zoom_levels) / sizeof (double));
+
+void
+eom_scroll_view_zoom_in (EomScrollView *view, gboolean smooth)
+{
+ EomScrollViewPrivate *priv;
+ double zoom;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (smooth) {
+ zoom = priv->zoom * priv->zoom_multiplier;
+ }
+ else {
+ int i;
+ int index = -1;
+
+ for (i = 0; i < n_zoom_levels; i++) {
+ if (preferred_zoom_levels [i] - priv->zoom
+ > DOUBLE_EQUAL_MAX_DIFF) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1) {
+ zoom = priv->zoom;
+ }
+ else {
+ zoom = preferred_zoom_levels [i];
+ }
+ }
+ set_zoom (view, zoom, FALSE, 0, 0);
+
+}
+
+void
+eom_scroll_view_zoom_out (EomScrollView *view, gboolean smooth)
+{
+ EomScrollViewPrivate *priv;
+ double zoom;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (smooth) {
+ zoom = priv->zoom / priv->zoom_multiplier;
+ }
+ else {
+ int i;
+ int index = -1;
+
+ for (i = n_zoom_levels - 1; i >= 0; i--) {
+ if (priv->zoom - preferred_zoom_levels [i]
+ > DOUBLE_EQUAL_MAX_DIFF) {
+ index = i;
+ break;
+ }
+ }
+ if (index == -1) {
+ zoom = priv->zoom;
+ }
+ else {
+ zoom = preferred_zoom_levels [i];
+ }
+ }
+ set_zoom (view, zoom, FALSE, 0, 0);
+}
+
+void
+eom_scroll_view_zoom_fit (EomScrollView *view)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ set_zoom_fit (view);
+ check_scrollbar_visibility (view, NULL);
+ gtk_widget_queue_draw (GTK_WIDGET (view->priv->display));
+}
+
+void
+eom_scroll_view_set_zoom (EomScrollView *view, double zoom)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ set_zoom (view, zoom, FALSE, 0, 0);
+}
+
+double
+eom_scroll_view_get_zoom (EomScrollView *view)
+{
+ g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), 0.0);
+
+ return view->priv->zoom;
+}
+
+gboolean
+eom_scroll_view_get_zoom_is_min (EomScrollView *view)
+{
+ g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE);
+
+ set_minimum_zoom_factor (view);
+
+ return DOUBLE_EQUAL (view->priv->zoom, MIN_ZOOM_FACTOR) ||
+ DOUBLE_EQUAL (view->priv->zoom, view->priv->min_zoom);
+}
+
+gboolean
+eom_scroll_view_get_zoom_is_max (EomScrollView *view)
+{
+ g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE);
+
+ return DOUBLE_EQUAL (view->priv->zoom, MAX_ZOOM_FACTOR);
+}
+
+static void
+display_next_frame_cb (EomImage *image, gint delay, gpointer data)
+{
+ EomScrollViewPrivate *priv;
+ EomScrollView *view;
+
+ if (!EOM_IS_SCROLL_VIEW (data))
+ return;
+
+ view = EOM_SCROLL_VIEW (data);
+ priv = view->priv;
+
+ if (priv->pixbuf != NULL) {
+ g_object_unref (priv->pixbuf);
+ priv->pixbuf = NULL;
+ }
+
+ priv->pixbuf = eom_image_get_pixbuf (image);
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+}
+
+void
+eom_scroll_view_set_image (EomScrollView *view, EomImage *image)
+{
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (priv->image == image) {
+ return;
+ }
+
+ if (priv->image != NULL) {
+ free_image_resources (view);
+ if (gtk_widget_is_drawable (priv->display) && image == NULL) {
+ gdk_window_clear (gtk_widget_get_window (priv->display));
+ }
+ }
+ g_assert (priv->image == NULL);
+ g_assert (priv->pixbuf == NULL);
+
+ priv->progressive_state = PROGRESSIVE_NONE;
+ if (image != NULL) {
+ eom_image_data_ref (image);
+
+ if (priv->pixbuf == NULL) {
+ priv->pixbuf = eom_image_get_pixbuf (image);
+ priv->progressive_state = PROGRESSIVE_NONE;
+ set_zoom_fit (view);
+ check_scrollbar_visibility (view, NULL);
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+
+ }
+ else if ((is_zoomed_in (view) && priv->interp_type_in != GDK_INTERP_NEAREST) ||
+ (is_zoomed_out (view) && priv->interp_type_out != GDK_INTERP_NEAREST))
+ {
+ /* paint antialiased image version */
+ priv->progressive_state = PROGRESSIVE_POLISHING;
+ gtk_widget_queue_draw (GTK_WIDGET (priv->display));
+ }
+
+ priv->image_changed_id = g_signal_connect (image, "changed",
+ (GCallback) image_changed_cb, view);
+ if (eom_image_is_animation (image) == TRUE ) {
+ eom_image_start_animation (image);
+ priv->frame_changed_id = g_signal_connect (image, "next-frame",
+ (GCallback) display_next_frame_cb, view);
+ }
+ }
+
+ priv->image = image;
+}
+
+gboolean
+eom_scroll_view_scrollbars_visible (EomScrollView *view)
+{
+ if (!gtk_widget_get_visible (GTK_WIDGET (view->priv->hbar)) &&
+ !gtk_widget_get_visible (GTK_WIDGET (view->priv->vbar)))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*===================================
+ object creation/freeing
+ ---------------------------------*/
+
+static void
+eom_scroll_view_init (EomScrollView *view)
+{
+ EomScrollViewPrivate *priv;
+
+ priv = view->priv = EOM_SCROLL_VIEW_GET_PRIVATE (view);
+
+ priv->zoom = 1.0;
+ priv->min_zoom = MIN_ZOOM_FACTOR;
+ priv->zoom_mode = ZOOM_MODE_FIT;
+ priv->upscale = FALSE;
+ priv->uta = NULL;
+ priv->interp_type_in = GDK_INTERP_BILINEAR;
+ priv->interp_type_out = GDK_INTERP_BILINEAR;
+ priv->scroll_wheel_zoom = FALSE;
+ priv->zoom_multiplier = IMAGE_VIEW_ZOOM_MULTIPLIER;
+ priv->image = NULL;
+ priv->pixbuf = NULL;
+ priv->progressive_state = PROGRESSIVE_NONE;
+ priv->transp_style = EOM_TRANSP_BACKGROUND;
+ priv->transp_color = 0;
+ priv->cursor = EOM_SCROLL_VIEW_CURSOR_NORMAL;
+ priv->menu = NULL;
+ priv->background_color = NULL;
+ priv->override_bg_color = NULL;
+ priv->background_surface = NULL;
+}
+
+static void
+eom_scroll_view_dispose (GObject *object)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (object));
+
+ view = EOM_SCROLL_VIEW (object);
+ priv = view->priv;
+
+ if (priv->uta != NULL) {
+ eom_uta_free (priv->uta);
+ priv->uta = NULL;
+ }
+
+ if (priv->idle_id != 0) {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
+ }
+
+ if (priv->background_color != NULL) {
+ gdk_color_free (priv->background_color);
+ priv->background_color = NULL;
+ }
+
+ if (priv->override_bg_color != NULL) {
+ gdk_color_free (priv->override_bg_color);
+ priv->override_bg_color = NULL;
+ }
+
+ if (priv->background_surface != NULL) {
+ cairo_surface_destroy (priv->background_surface);
+ priv->background_surface = NULL;
+ }
+
+ free_image_resources (view);
+
+ G_OBJECT_CLASS (eom_scroll_view_parent_class)->dispose (object);
+}
+
+static void
+eom_scroll_view_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (object));
+
+ view = EOM_SCROLL_VIEW (object);
+ priv = view->priv;
+
+ switch (property_id) {
+ case PROP_USE_BG_COLOR:
+ g_value_set_boolean (value, priv->use_bg_color);
+ break;
+ case PROP_BACKGROUND_COLOR:
+ //FIXME: This doesn't really handle the NULL color.
+ g_value_set_boxed (value, priv->background_color);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+eom_scroll_view_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (object));
+
+ view = EOM_SCROLL_VIEW (object);
+ priv = view->priv;
+
+ switch (property_id) {
+ case PROP_USE_BG_COLOR:
+ eom_scroll_view_set_use_bg_color (view, g_value_get_boolean (value));
+ break;
+ case PROP_BACKGROUND_COLOR:
+ {
+ const GdkColor *color = g_value_get_boxed (value);
+ eom_scroll_view_set_background_color (view, color);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+
+static void
+eom_scroll_view_class_init (EomScrollViewClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = (GObjectClass*) klass;
+ widget_class = (GtkWidgetClass*) klass;
+
+ gobject_class->dispose = eom_scroll_view_dispose;
+ gobject_class->set_property = eom_scroll_view_set_property;
+ gobject_class->get_property = eom_scroll_view_get_property;
+
+ /**
+ * EomScrollView:background-color:
+ *
+ * This is the default background color used for painting the background
+ * of the image view. If set to %NULL the color is determined by the
+ * active GTK theme.
+ */
+ g_object_class_install_property (
+ gobject_class, PROP_BACKGROUND_COLOR,
+ g_param_spec_boxed ("background-color", NULL, NULL,
+ GDK_TYPE_COLOR,
+ G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
+
+ g_object_class_install_property (
+ gobject_class, PROP_USE_BG_COLOR,
+ g_param_spec_boolean ("use-background-color", NULL, NULL, FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_NAME));
+
+ view_signals [SIGNAL_ZOOM_CHANGED] =
+ g_signal_new ("zoom_changed",
+ EOM_TYPE_SCROLL_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomScrollViewClass, zoom_changed),
+ NULL, NULL,
+ eom_marshal_VOID__DOUBLE,
+ G_TYPE_NONE, 1,
+ G_TYPE_DOUBLE);
+
+ widget_class->size_allocate = eom_scroll_view_size_allocate;
+ widget_class->style_set = eom_scroll_view_style_set;
+
+ g_type_class_add_private (klass, sizeof (EomScrollViewPrivate));
+}
+
+static void
+view_on_drag_begin_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ EomScrollView *view;
+ EomImage *image;
+ GdkPixbuf *thumbnail;
+ gint width, height;
+
+ view = EOM_SCROLL_VIEW (user_data);
+ image = view->priv->image;
+
+ thumbnail = eom_image_get_thumbnail (image);
+
+ if (thumbnail) {
+ width = gdk_pixbuf_get_width (thumbnail);
+ height = gdk_pixbuf_get_height (thumbnail);
+ gtk_drag_set_icon_pixbuf (context, thumbnail, width/2, height/2);
+ g_object_unref (thumbnail);
+ }
+}
+
+static void
+view_on_drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *drag_context,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ EomScrollView *view;
+ EomImage *image;
+ gchar *uris[2];
+ GFile *file;
+
+ view = EOM_SCROLL_VIEW (user_data);
+
+ image = view->priv->image;
+
+ file = eom_image_get_file (image);
+ uris[0] = g_file_get_uri (file);
+ uris[1] = NULL;
+
+ gtk_selection_data_set_uris (data, uris);
+
+ g_free (uris[0]);
+ g_object_unref (file);
+}
+
+GtkWidget*
+eom_scroll_view_new (void)
+{
+ GtkWidget *widget;
+ GtkTable *table;
+ EomScrollView *view;
+ EomScrollViewPrivate *priv;
+
+ widget = g_object_new (EOM_TYPE_SCROLL_VIEW,
+ "can-focus", TRUE,
+ "n_rows", 2,
+ "n_columns", 2,
+ "homogeneous", FALSE,
+ NULL);
+
+ table = GTK_TABLE (widget);
+ view = EOM_SCROLL_VIEW (widget);
+ priv = view->priv;
+
+ priv->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100));
+ g_signal_connect (priv->hadj, "value_changed",
+ G_CALLBACK (adjustment_changed_cb),
+ view);
+ priv->hbar = gtk_hscrollbar_new (priv->hadj);
+ priv->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100));
+ g_signal_connect (priv->vadj, "value_changed",
+ G_CALLBACK (adjustment_changed_cb),
+ view);
+ priv->vbar = gtk_vscrollbar_new (priv->vadj);
+ priv->display = g_object_new (GTK_TYPE_DRAWING_AREA,
+ "can-focus", TRUE,
+ NULL);
+ /* We don't want to be double-buffered as we are SuperSmart(tm) */
+ gtk_widget_set_double_buffered (GTK_WIDGET (priv->display), FALSE);
+
+ gtk_widget_add_events (GTK_WIDGET (priv->display),
+ GDK_EXPOSURE_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_SCROLL_MASK
+ | GDK_KEY_PRESS_MASK);
+ g_signal_connect (G_OBJECT (priv->display), "configure_event", G_CALLBACK (display_size_change), view);
+ g_signal_connect (G_OBJECT (priv->display), "expose_event", G_CALLBACK (display_expose_event), view);
+ g_signal_connect (G_OBJECT (priv->display), "map_event", G_CALLBACK (display_map_event), view);
+ g_signal_connect (G_OBJECT (priv->display), "button_press_event", G_CALLBACK (eom_scroll_view_button_press_event), view);
+ g_signal_connect (G_OBJECT (priv->display), "motion_notify_event", G_CALLBACK (eom_scroll_view_motion_event), view);
+ g_signal_connect (G_OBJECT (priv->display), "button_release_event", G_CALLBACK (eom_scroll_view_button_release_event), view);
+ g_signal_connect (G_OBJECT (priv->display), "scroll_event", G_CALLBACK (eom_scroll_view_scroll_event), view);
+ g_signal_connect (G_OBJECT (priv->display), "focus_in_event", G_CALLBACK (eom_scroll_view_focus_in_event), NULL);
+ g_signal_connect (G_OBJECT (priv->display), "focus_out_event", G_CALLBACK (eom_scroll_view_focus_out_event), NULL);
+
+ g_signal_connect (G_OBJECT (widget), "key_press_event", G_CALLBACK (display_key_press_event), view);
+
+ gtk_drag_source_set (priv->display, GDK_BUTTON1_MASK,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY);
+ g_signal_connect (G_OBJECT (priv->display), "drag-data-get",
+ G_CALLBACK (view_on_drag_data_get_cb), widget);
+ g_signal_connect (G_OBJECT (priv->display), "drag-begin",
+ G_CALLBACK (view_on_drag_begin_cb), widget);
+
+ gtk_table_attach (table, priv->display,
+ 0, 1, 0, 1,
+ GTK_EXPAND | GTK_FILL,
+ GTK_EXPAND | GTK_FILL,
+ 0,0);
+ gtk_table_attach (table, priv->hbar,
+ 0, 1, 1, 2,
+ GTK_FILL,
+ GTK_FILL,
+ 0, 0);
+ gtk_table_attach (table, priv->vbar,
+ 1, 2, 0, 1,
+ GTK_FILL, GTK_FILL,
+ 0, 0);
+
+ gtk_widget_show_all (widget);
+
+ return widget;
+}
+
+static void
+eom_scroll_view_popup_menu (EomScrollView *view, GdkEventButton *event)
+{
+ GtkWidget *popup;
+ int button, event_time;
+
+ popup = view->priv->menu;
+
+ if (event) {
+ button = event->button;
+ event_time = event->time;
+ } else {
+ button = 0;
+ event_time = gtk_get_current_event_time ();
+ }
+
+ gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL,
+ button, event_time);
+}
+
+static gboolean
+view_on_button_press_event_cb (GtkWidget *view, GdkEventButton *event,
+ gpointer user_data)
+{
+ /* Ignore double-clicks and triple-clicks */
+ if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+ {
+ eom_scroll_view_popup_menu (EOM_SCROLL_VIEW (view), event);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+eom_scroll_view_set_popup (EomScrollView *view,
+ GtkMenu *menu)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+ g_return_if_fail (view->priv->menu == NULL);
+
+ view->priv->menu = g_object_ref (menu);
+
+ gtk_menu_attach_to_widget (GTK_MENU (view->priv->menu),
+ GTK_WIDGET (view),
+ NULL);
+
+ g_signal_connect (G_OBJECT (view), "button_press_event",
+ G_CALLBACK (view_on_button_press_event_cb), NULL);
+}
+
+static gboolean
+_eom_gdk_color_equal0 (const GdkColor *a, const GdkColor *b)
+{
+ if (a == NULL || b == NULL)
+ return (a == b);
+
+ return gdk_color_equal (a, b);
+}
+
+static gboolean
+_eom_replace_gdk_color (GdkColor **dest, const GdkColor *new)
+{
+ GdkColor *old = *dest;
+
+ if (_eom_gdk_color_equal0 (old, new))
+ return FALSE;
+
+ if (old != NULL)
+ gdk_color_free (old);
+
+ *dest = (new) ? gdk_color_copy (new) : NULL;
+
+ return TRUE;
+}
+
+static void
+_eom_scroll_view_update_bg_color (EomScrollView *view)
+{
+ const GdkColor *selected;
+ EomScrollViewPrivate *priv = view->priv;
+
+ if (priv->override_bg_color)
+ selected = priv->override_bg_color;
+ else if (priv->use_bg_color)
+ selected = priv->background_color;
+ else
+ selected = NULL;
+
+ if (priv->transp_style == EOM_TRANSP_BACKGROUND
+ && priv->background_surface != NULL) {
+ /* Delete the SVG background to have it recreated with
+ * the correct color during the next SVG redraw */
+ cairo_surface_destroy (priv->background_surface);
+ priv->background_surface = NULL;
+ }
+
+ gtk_widget_modify_bg (GTK_WIDGET (view),
+ GTK_STATE_NORMAL,
+ selected);
+}
+
+void
+eom_scroll_view_set_background_color (EomScrollView *view,
+ const GdkColor *color)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ if (_eom_replace_gdk_color (&view->priv->background_color, color))
+ _eom_scroll_view_update_bg_color (view);
+}
+
+void
+eom_scroll_view_override_bg_color (EomScrollView *view,
+ const GdkColor *color)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ if (_eom_replace_gdk_color (&view->priv->override_bg_color, color))
+ _eom_scroll_view_update_bg_color (view);
+}
+
+void
+eom_scroll_view_set_use_bg_color (EomScrollView *view, gboolean use)
+{
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (use != priv->use_bg_color) {
+ priv->use_bg_color = use;
+
+ _eom_scroll_view_update_bg_color (view);
+
+ g_object_notify (G_OBJECT (view), "use-background-color");
+ }
+}
+
+void
+eom_scroll_view_set_scroll_wheel_zoom (EomScrollView *view,
+ gboolean scroll_wheel_zoom)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ view->priv->scroll_wheel_zoom = scroll_wheel_zoom;
+}
+
+void
+eom_scroll_view_set_zoom_multiplier (EomScrollView *view,
+ gdouble zoom_multiplier)
+{
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ view->priv->zoom_multiplier = 1.0 + zoom_multiplier;
+}