/* Eye of Gnome image viewer - image view widget
 *
 * Copyright (C) 2000 The Free Software Foundation
 *
 * Author: Federico Mena-Quintero <federico@gnu.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */



#include <config.h>
#include <math.h>
#include <stdlib.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkmain.h>
#include <libgnome/gnome-macros.h>
#include <libgnome/gnome-i18n.h>
#include "cursors.h"
#include "image-view.h"
#include "uta.h"
#include "lcms.h"

/* Checks */

#define CHECK_SMALL 4
#define CHECK_MEDIUM 8
#define CHECK_LARGE 16
#define CHECK_BLACK 0x00000000
#define CHECK_DARK 0x00555555
#define CHECK_GRAY 0x00808080
#define CHECK_LIGHT 0x00aaaaaa
#define CHECK_WHITE 0x00ffffff

/* 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 10
#define MIN_ZOOM_FACTOR 0.05

/* Private part of the ImageView structure */
struct _ImageViewPrivate {
	/* Pixbuf being displayed */
	GdkPixbuf *pixbuf;

	/* Current zoom factors */
	double zoomx;
	double zoomy;
	
	/* Minimum Zoom Factor for This Pixbuf */
	double MIN_ZOOM;

	/* Previous zoom factor and zoom anchor point stored for size_allocate */
	double old_zoomx;
	double old_zoomy;
	double zoom_x_anchor;
	double zoom_y_anchor;

	/* Adjustments for scrolling */
	GtkAdjustment *hadj;
	GtkAdjustment *vadj;

	/* Current scrolling offsets */
	int xofs, yofs;

	/* Microtile arrays for dirty region.  This represents the dirty region
	 * for interpolated drawing.
	 */
	ArtUta *uta;

	/* Idle handler ID */
	guint idle_id;

	/* Anchor point and offsets for dragging */
	int drag_anchor_x, drag_anchor_y;
	int drag_ofs_x, drag_ofs_y;

	/* Interpolation type */
	GdkInterpType interp_type;

	/* Check type and size */
	CheckType check_type;
	CheckSize check_size;
	
	/* Transparency indicator */
	gboolean use_check_pattern;
	guint32  transparency_color;

	/* Dither type */
	GdkRgbDither dither;

	/* Whether the image is being dragged */
	guint dragging : 1;

	/* Whether we need to change the zoom factor */
	guint need_zoom_change : 1;
	
	guint enable_late_drawing : 1;

#ifdef LIBEOG_ETTORE_CHANGES
	float display_brightness;
	float display_contrast;
	cmsHTRANSFORM transform;
#endif
};

/* Signal IDs */
enum {
	ZOOM_FIT,
	ZOOM_CHANGED,
	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_INTERP_TYPE,
	PROP_CHECK_TYPE,
	PROP_CHECK_SIZE,
	PROP_DITHER
};

static guint image_view_signals[LAST_SIGNAL];

GNOME_CLASS_BOILERPLATE (ImageView,
			 image_view,
			 GtkWidget,
			 GTK_TYPE_WIDGET);

/* VOID:OBJECT,OBJECT (gtkmarshalers.list:69) */
void
marshal_VOID__OBJECT_OBJECT (GClosure     *closure,
			     GValue       *return_value,
			     guint         n_param_values,
			     const GValue *param_values,
			     gpointer      invocation_hint,
			     gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer     data1,
                                                    gpointer     arg_1,
                                                    gpointer     arg_2,
                                                    gpointer     data2);
  register GMarshalFunc_VOID__OBJECT_OBJECT callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;

  g_return_if_fail (n_param_values == 3);

  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer (param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer (param_values + 0);
      data2 = closure->data;
    }
  callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);

  callback (data1,
            g_value_get_object (param_values + 1),
            g_value_get_object (param_values + 2),
            data2);
}

static void
image_view_get_property (GObject    *object,
			 guint       property_id,
			 GValue     *value,
			 GParamSpec *pspec)
{
	ImageView *image_view = IMAGE_VIEW (object);
	ImageViewPrivate *priv = image_view->priv;

	switch (property_id) {
	case PROP_INTERP_TYPE:
		g_value_set_int (value, priv->interp_type);
		break;
	case PROP_CHECK_TYPE:
		g_value_set_int (value, priv->check_type);
		break;
	case PROP_CHECK_SIZE:
		g_value_set_int (value, priv->check_size);
		break;
	case PROP_DITHER:
		g_value_set_int (value, priv->dither);
		break;
	default:
		g_warning ("unknown property id `%d'", property_id);
		break;
	}
}

static void
image_view_set_property (GObject      *object,
			 guint         property_id,
			 const GValue *value,
			 GParamSpec   *pspec)
{
	ImageView *image_view = IMAGE_VIEW (object);

	switch (property_id) {
	case PROP_INTERP_TYPE:
		image_view_set_interp_type (image_view, g_value_get_int (value));
		break;
	case PROP_CHECK_TYPE:
		image_view_set_check_type (image_view, g_value_get_int (value));
		break;
	case PROP_CHECK_SIZE:
		image_view_set_check_size (image_view, g_value_get_int (value));
		break;
	case PROP_DITHER:
		image_view_set_dither (image_view, g_value_get_int (value));
		break;
	default:
		g_warning ("unknown property id `%d'", property_id);
		break;
	}
}

/* Object initialization function for the image view */
static void
image_view_instance_init (ImageView *view)
{
	ImageViewPrivate *priv;

	priv = g_new0 (ImageViewPrivate, 1);
	view->priv = priv;

	GTK_WIDGET_UNSET_FLAGS (view, GTK_NO_WINDOW);
	GTK_WIDGET_SET_FLAGS (view, GTK_CAN_FOCUS);

	priv->pixbuf = NULL;
	priv->zoomx = priv->zoomy = 1.0;
	priv->hadj = NULL;
	priv->vadj = NULL;
	priv->uta = NULL;

	/* Defaults for rendering */
	priv->interp_type = GDK_INTERP_BILINEAR;
	priv->check_type = CHECK_TYPE_MIDTONE;
	priv->check_size = CHECK_SIZE_LARGE;
	priv->dither = GDK_RGB_DITHER_MAX;
	priv->use_check_pattern = TRUE;
	priv->transparency_color = CHECK_BLACK;

	priv->enable_late_drawing = FALSE;
#ifdef LIBEOG_ETTORE_CHANGES
	priv->display_brightness = 0.0;
	priv->display_contrast = 0.0;
	priv->transform = NULL;
#endif

	/* We don't want to be double-buffered as we are SuperSmart(tm) */
	gtk_widget_set_double_buffered (GTK_WIDGET (view), FALSE);
}

/* Frees the dirty region uta and removes the idle handler */
static void
remove_dirty_region (ImageView *view)
{
	ImageViewPrivate *priv;

	priv = view->priv;

	if (priv->uta) {
		g_assert (priv->idle_id != 0);

		art_uta_free (priv->uta);
		priv->uta = NULL;

		g_source_remove (priv->idle_id);
		priv->idle_id = 0;
	} else
		g_assert (priv->idle_id == 0);
}

/* Destroy handler for the image view */
static void
image_view_dispose (GObject *object)
{
	ImageView *view;
	ImageViewPrivate *priv;

	view = IMAGE_VIEW (object);
	priv = view->priv;

	g_signal_handlers_disconnect_matched (
		priv->hadj, G_SIGNAL_MATCH_DATA,
		0, 0, NULL, NULL, view);

	g_signal_handlers_disconnect_matched (
		priv->vadj, G_SIGNAL_MATCH_DATA,
		0, 0, NULL, NULL, view);

	/* Clean up */
	if (view->priv->pixbuf)
		g_object_unref (G_OBJECT (view->priv->pixbuf));
	view->priv->pixbuf = NULL;

	remove_dirty_region (view);

	GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}

/* Finalize handler for the image view */
static void
image_view_finalize (GObject *object)
{
	ImageView *view;
	ImageViewPrivate *priv;

	view = IMAGE_VIEW (object);
	priv = view->priv;

	g_object_unref (priv->hadj);
	g_object_unref (priv->vadj);

	g_free (priv);

	GNOME_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}



/* Drawing core */

/* Computes the size in pixels of the scaled image */
static void
compute_scaled_size (ImageView *view, double zoomx, double zoomy, int *width, int *height)
{
	ImageViewPrivate *priv;

	priv = view->priv;

	if (priv->pixbuf) {
		*width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoomx + 0.5);
		*height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoomy + 0.5);
	} else
		*width = *height = 0;
}

/* 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 (ArtUta *uta, ArtIRect *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 (ImageView *view, ArtIRect *r, ArtIRect *rect)
{
	ArtIRect d;

	art_irect_intersect (&d, r, rect);
	if (!art_irect_empty (&d))
		gdk_draw_rectangle (GTK_WIDGET (view)->window,
				    GTK_WIDGET (view)->style->bg_gc[GTK_STATE_NORMAL],
				    TRUE,
				    d.x0, d.y0,
				    d.x1 - d.x0, d.y1 - d.y0);
}

#if 0
#define PACK_RGBA
#endif

#ifdef PACK_RGBA

/* Packs an RGBA pixbuf into RGB scanlines.  The rowstride is preserved.  NOTE:
 * This will produce a pixbuf that is NOT usable with any other normal function!
 * This is just a hack to accommodate the lack of a
 * gdk_draw_rgb_image_32_dithalign(); the provided
 * gdk_draw_rgb_image_dithalign() does not take in 32-bit pixels.
 */
static void
pack_pixbuf (GdkPixbuf *pixbuf)
{
	int x, y;
	int width, height, rowstride;
	guchar *pixels, *p, *q;

	g_assert (gdk_pixbuf_get_n_channels (pixbuf) == 4);

	width = gdk_pixbuf_get_width (pixbuf);
	height = gdk_pixbuf_get_height (pixbuf);
	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
	pixels = gdk_pixbuf_get_pixels (pixbuf);

	for (y = 0; y < height; y++) {
		p = pixels;
		q = pixels;

		for (x = 0; x < width; x++) {
			*p++ = *q++;
			*p++ = *q++;
			*p++ = *q++;
			q++;
		}

		pixels += rowstride;
	}
}

#endif

#define DOUBLE_EQUAL(a,b) (fabs (a - b) < 1e-6)
static gboolean
unity_zoom (ImageViewPrivate *priv)
{
	return (DOUBLE_EQUAL (priv->zoomx, 1.0) &&
		DOUBLE_EQUAL (priv->zoomy, 1.0));
}

#ifdef LIBEOG_ETTORE_CHANGES

/* Code cut & pasted from GThumb, by Paolo Bacchilega <paolo.bacch@tin.it>.  */

static unsigned char
apply_brightness_and_contrast (unsigned char u_value,
			       float brightness,
			       float contrast)
{
	float  nvalue;
	double power;
	float  value;

	value = (float) u_value / 255.0;

	/* apply brightness */
	if (brightness < 0.0)
		value = value * (1.0 + brightness);
	else
		value = value + ((1.0 - value) * brightness);
	
	/* apply contrast */
	if (contrast < 0.0) {
		if (value > 0.5)
			nvalue = 1.0 - value;
		else
			nvalue = value;

		if (nvalue < 0.0)
			nvalue = 0.0;

		nvalue = 0.5 * pow (nvalue * 2.0 , (double) (1.0 + contrast));

		if (value > 0.5)
			value = 1.0 - nvalue;
		else
			value = nvalue;
	} else {
		if (value > 0.5)
			nvalue = 1.0 - value;
		else
			nvalue = value;
		
		if (nvalue < 0.0)
			nvalue = 0.0;
		
		power = (contrast == 1.0) ? 127 : 1.0 / (1.0 - contrast);
		nvalue = 0.5 * pow (2.0 * nvalue, power);
		
		if (value > 0.5)
			value = 1.0 - nvalue;
		else
			value = nvalue;
	}
	
	return (guchar) (value * 255);
}

static void
apply_brightness_and_contrast_to_pixbuf (ImageView *view,
					 GdkPixbuf *pixbuf,
					 int width,
					 int height)
{
	char *p;
	float display_brightness;
	float display_contrast;
	int line;
	int rowstride;
	int bytes_per_pixel;

	display_brightness = view->priv->display_brightness;
	display_contrast = view->priv->display_contrast;

	if (DOUBLE_EQUAL (display_brightness, 0.0) && DOUBLE_EQUAL (display_contrast, 0.0))
		return;

	p = gdk_pixbuf_get_pixels (pixbuf);
	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
	bytes_per_pixel = gdk_pixbuf_get_has_alpha (pixbuf) ? 4 : 3;

	for (line = 0; line < height; line ++) {
		char *q = p;
		int i;

		for (i = 0; i < width; i ++) {
			q[0] = apply_brightness_and_contrast (q[0], display_brightness, display_contrast);
			q[1] = apply_brightness_and_contrast (q[1], display_brightness, display_contrast);
			q[2] = apply_brightness_and_contrast (q[2], display_brightness, display_contrast);
			
			q += bytes_per_pixel;
		}

		p += rowstride;
	}
}

static void
apply_transform_to_pixbuf (ImageView *view, GdkPixbuf *pixbuf, int x, int y, int width, int height)
{
	char *p;
	int line;
	int rowstride;
	int bytes_per_pixel;

	p = gdk_pixbuf_get_pixels (pixbuf);
	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
	bytes_per_pixel = gdk_pixbuf_get_has_alpha (pixbuf) ? 4 : 3;

	for (line = 0; line < height; line ++) {
		cmsDoTransform (view->priv->transform, p, p, width);
		p += rowstride;
	}
}
#endif

/* Paints a rectangle of the dirty region */
#ifdef LIBEOG_ETTORE_CHANGES
static void
paint_extra (ImageView *view,
	     ArtIRect *rect)
{
	ImageViewClass *class = G_TYPE_INSTANCE_GET_CLASS (view, TYPE_IMAGE_VIEW, ImageViewClass);
	GdkRectangle area;

	g_assert (rect->x0 < rect->x1);
	g_assert (rect->y0 < rect->y1);

	area.x = rect->x0;
	area.y = rect->y0;

	area.width = rect->x1 - rect->x0;
	area.height = rect->y1 - rect->y0;

	(* class->paint_extra) (view, &area);
}
#endif

#ifdef LIBEOG_ETTORE_CHANGES
static void
paint_rectangle (ImageView *view, ArtIRect *rect, GdkInterpType interp_type,
		 gboolean apply_brightness_and_contrast)
#else
static void
paint_rectangle (ImageView *view, ArtIRect *rect, GdkInterpType interp_type)
#endif
{
	ImageViewPrivate *priv;
	int scaled_width, scaled_height;
	int width, height;
	int xofs, yofs;
	ArtIRect r, d;
	GdkPixbuf *tmp;
	int check_size;
	guint32 check_1, check_2;

	priv = view->priv;

	compute_scaled_size (view, priv->zoomx, priv->zoomy, &scaled_width, &scaled_height);

	width = GTK_WIDGET (view)->allocation.width;
	height = GTK_WIDGET (view)->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;

	/* Draw background if necessary, in four steps */


	

	/* Top */
	if (yofs > 0) {
		r.x0 = 0;
		r.y0 = 0;
		r.x1 = 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 = 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 = width;
		r.y1 = 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;

	art_irect_intersect (&d, &r, rect);
	if (art_irect_empty (&d))
		return;

	/* Short-circuit the fast case to avoid a memcpy() */

	if (unity_zoom (priv)
	    && 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
	    && priv->transform == NULL) {
		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 (view)->window,
					      GTK_WIDGET (view)->style->black_gc,
					      d.x0, d.y0,
					      d.x1 - d.x0, d.y1 - d.y0,
					      priv->dither,
					      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 (gdk_pixbuf_get_has_alpha (priv->pixbuf))
		gdk_pixbuf_fill (tmp, 0x00000000);
	
	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 check parameters */
	if (priv->use_check_pattern) {
		switch (priv->check_type) {
		case CHECK_TYPE_DARK:
			check_1 = CHECK_BLACK;
			check_2 = CHECK_DARK;
			break;
			
		case CHECK_TYPE_MIDTONE:
			check_1 = CHECK_DARK;
			check_2 = CHECK_LIGHT;
			break;

		case CHECK_TYPE_LIGHT:
			check_1 = CHECK_LIGHT;
			check_2 = CHECK_WHITE;
			break;
		
		case CHECK_TYPE_BLACK:
			check_1 = check_2 = CHECK_BLACK;
			break;

		case CHECK_TYPE_GRAY:
			check_1 = check_2 = CHECK_GRAY;
			break;

		case CHECK_TYPE_WHITE:
			check_1 = check_2 = CHECK_WHITE;
			break;

		default:
			g_assert_not_reached ();
			return;
		}
	}
	else {
		check_1 = check_2 = priv->transparency_color;
	}

	switch (priv->check_size) {
	case CHECK_SIZE_SMALL:
		check_size = CHECK_SMALL;
		break;

	case CHECK_SIZE_MEDIUM:
		check_size = CHECK_MEDIUM;
		break;

	case CHECK_SIZE_LARGE:
		check_size = CHECK_LARGE;
		break;

	default:
		g_assert_not_reached ();
		return;
	}

	/* Draw! */

#if 1
	gdk_pixbuf_composite_color (priv->pixbuf,
				    tmp,
				    0, 0,
				    d.x1 - d.x0, d.y1 - d.y0,
				    -(d.x0 - xofs), -(d.y0 - yofs),
				    priv->zoomx, priv->zoomy,
				    ABS (d.x1 - d.x0) < 20 || ABS (d.y1 - d.y0) < 20 || unity_zoom (priv) ? GDK_INTERP_NEAREST : interp_type,
				    255,
				    d.x0 - xofs, d.y0 - yofs,
				    check_size,
				    check_1, check_2);
#else
	gdk_pixbuf_composite (priv->pixbuf,
			      tmp,
			      0, 0,
			      d.x1 - d.x0, d.y1 - d.y0,
			      -(d.x0 - xofs), -(d.y0 - yofs),
			      priv->zoomx, priv->zoomy,
			      unity_zoom (priv) ? GDK_INTERP_NEAREST : interp_type,
			      255);
#endif

#ifdef LIBEOG_ETTORE_CHANGES
	if (apply_brightness_and_contrast)
		apply_brightness_and_contrast_to_pixbuf (view, tmp, d.x1 - d.x0, d.y1 - d.y0);

	if (priv->transform != NULL)
		apply_transform_to_pixbuf (view, tmp, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0);
#endif

#ifdef PACK_RGBA
	pack_pixbuf (tmp);
#endif

#if 0
	gdk_draw_rgb_image_dithalign (GTK_WIDGET (view)->window,
				      GTK_WIDGET (view)->style->black_gc,
				      d.x0, d.y0,
				      d.x1 - d.x0, d.y1 - d.y0,
				      priv->dither,
				      gdk_pixbuf_get_pixels (tmp),
				      gdk_pixbuf_get_rowstride (tmp),
				      d.x0 - xofs, d.y0 - yofs);
#else
	/*
	  if (gdk_pixbuf_get_has_alpha (priv->pixbuf))
		paint_background (view, &d, rect);

	  gdk_pixbuf_render_to_drawable_alpha (tmp,
		GTK_WIDGET (view)->window,
		0, 0,
		d.x0, d.y0,
		d.x1 - d.x0, d.y1 - d.y0,
		priv->dither,
		GDK_PIXBUF_ALPHA_FULL,
		0,
		d.x0 - xofs, d.y0 - yofs);
	*/

	gdk_pixbuf_render_to_drawable (tmp,
				       GTK_WIDGET (view)->window,
				       GTK_WIDGET (view)->style->black_gc,
				       0, 0,
				       d.x0, d.y0,
				       d.x1 - d.x0, d.y1 - d.y0,
				       priv->dither,
				       d.x0 - xofs, d.y0 - yofs);
	
#endif

	g_object_unref (tmp);

#if 0
	gdk_draw_line (GTK_WIDGET (view)->window,
		       GTK_WIDGET (view)->style->black_gc,
		       d.x0, d.y0,
		       d.x1 - 1, d.y1 - 1);
	gdk_draw_line (GTK_WIDGET (view)->window,
		       GTK_WIDGET (view)->style->black_gc,
		       d.x1 - 1, d.y0,
		       d.x0, d.y1 - 1);
#endif
	paint_extra (view, &r);
}

#include <stdio.h>

/* 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)
{
	ImageView *view;
	ImageViewPrivate *priv;
	ArtIRect rect;

	view = IMAGE_VIEW (data);
	priv = view->priv;

	g_assert (priv->uta != NULL);

	pull_rectangle (priv->uta, &rect, PAINT_RECT_WIDTH, PAINT_RECT_HEIGHT);

	if (art_irect_empty (&rect)) {
		art_uta_free (priv->uta);
		priv->uta = NULL;
	} else {
#ifdef LIBEOG_ETTORE_CHANGES
		paint_rectangle (view, &rect, priv->interp_type, TRUE);
#else
		paint_rectangle (view, &rect, priv->interp_type);
#endif
	}

	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 (ImageView *view, GdkRectangle *area)
{
	ImageViewPrivate *priv;
	ArtIRect r;

	priv = view->priv;

	if (!GTK_WIDGET_DRAWABLE (view))
		return;

	r.x0 = MAX (0, area->x);
	r.y0 = MAX (0, area->y);
	r.x1 = MIN (GTK_WIDGET (view)->allocation.width, area->x + area->width);
	r.y1 = MIN (GTK_WIDGET (view)->allocation.height, area->y + area->height);

	if (r.x0 >= r.x1 || r.y0 >= r.y1)
		return;

	/* Do nearest neighbor or 1:1 zoom synchronously for speed.  */

	if (!priv->enable_late_drawing || priv->interp_type == GDK_INTERP_NEAREST || unity_zoom (priv)) {
#ifdef LIBEOG_ETTORE_CHANGES
		paint_rectangle (view, &r, priv->interp_type, TRUE);
#else
		paint_rectangle (view, &r, priv->interp_type);
#endif
		return;
	}

	/* 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);
	}

#ifdef LIBEOG_ETTORE_CHANGES
	paint_rectangle (view, &r, GDK_INTERP_NEAREST, FALSE);
#else
	paint_rectangle (view, &r, GDK_INTERP_NEAREST);
#endif

	priv->uta = uta_add_rect (priv->uta, r.x0, r.y0, r.x1, r.y1);
}

/* Scrolls the view to the specified offsets.  */
static void
scroll_to (ImageView *view, int x, int y, gboolean change_adjustments)
{
	ImageViewPrivate *priv;
	int xofs, yofs;
	GdkWindow *window;
	int width, height;
	int src_x, src_y;
	int dest_x, dest_y;
	int twidth, theight;

	priv = view->priv;

	/* Check bounds */

	x = CLAMP (x, 0, priv->hadj->upper - priv->hadj->page_size);
	y = CLAMP (y, 0, priv->vadj->upper - priv->vadj->page_size);

	/* Compute offsets */

	xofs = x - priv->xofs;
	yofs = y - priv->yofs;

	if (xofs == 0 && yofs == 0)
		return;

	priv->xofs = x;
	priv->yofs = y;

	if (!GTK_WIDGET_DRAWABLE (view))
		goto out;

	width = GTK_WIDGET (view)->allocation.width;
	height = GTK_WIDGET (view)->allocation.height;

	if (abs (xofs) >= width || abs (yofs) >= height) {
		GdkRectangle area;

		area.x = 0;
		area.y = 0;
		area.width = width;
		area.height = height;

		request_paint_area (view, &area);
		goto out;
	}

	window = GTK_WIDGET (view)->window;

	/* Ensure that the uta has the full size */

	twidth = (width + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT;
	theight = (height + ART_UTILE_SIZE - 1) >> ART_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,
		       width - abs (xofs), 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);

	priv->hadj->value = x;
	priv->vadj->value = y;

	g_signal_emit_by_name (priv->hadj, "value_changed");
	g_signal_emit_by_name (priv->vadj, "value_changed");

	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 (ImageView *view, int xofs, int yofs)
{
	ImageViewPrivate *priv;

	priv = view->priv;

	scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE);
}



/* Widget methods */

/* Unmap handler for the image view */
static void
image_view_unmap (GtkWidget *widget)
{
	g_return_if_fail (widget != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (widget));

	remove_dirty_region (IMAGE_VIEW (widget));

	if (GTK_WIDGET_CLASS (parent_class)->unmap)
		(* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
}

/* Realize handler for the image view */
static void
image_view_realize (GtkWidget *widget)
{
	GdkWindowAttr attr;
	int attr_mask;
	GdkCursor *cursor;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (widget));

	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

	attr.window_type = GDK_WINDOW_CHILD;
	attr.x = widget->allocation.x;
	attr.y = widget->allocation.y;
	attr.width = widget->allocation.width;
	attr.height = widget->allocation.height;
	attr.wclass = GDK_INPUT_OUTPUT;
	attr.visual = gdk_rgb_get_visual ();
	attr.colormap = gdk_rgb_get_colormap ();
	attr.event_mask = (gtk_widget_get_events (widget)
			   | 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);

	attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

	widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attr, attr_mask);
	gdk_window_set_user_data (widget->window, widget);

	cursor = cursor_get (widget, CURSOR_HAND_OPEN);
	gdk_window_set_cursor (widget->window, cursor);
	gdk_cursor_unref (cursor);

	widget->style = gtk_style_attach (widget->style, widget->window);

	gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
}

/* Unrealize handler for the image view */
static void
image_view_unrealize (GtkWidget *widget)
{
	g_return_if_fail (widget != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (widget));

	remove_dirty_region (IMAGE_VIEW (widget));

	if (GTK_WIDGET_CLASS (parent_class)->unrealize)
		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

/* Size_request handler for the image view */
static void
image_view_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
	ImageView *view;
	ImageViewPrivate *priv;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (widget));
	g_return_if_fail (requisition != NULL);

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	requisition->width = requisition->height = 0;
}

/* Sets the zoom anchor point with respect to the specified window position */
static void
set_zoom_anchor (ImageView *view, int x, int y)
{
	ImageViewPrivate *priv;

	priv = view->priv;
	priv->zoom_x_anchor = (double) x / GTK_WIDGET (view)->allocation.width;
	priv->zoom_y_anchor = (double) y / GTK_WIDGET (view)->allocation.height;
}

/* Sets the zoom anchor point to be the middle of the visible area */
static void
set_default_zoom_anchor (ImageView *view)
{
	ImageViewPrivate *priv;

	priv = view->priv;
	priv->zoom_x_anchor = priv->zoom_y_anchor = 0.5;
}

/* Computes the offsets for the new zoom value so that they keep the image
 * centered on the view.
 */
static void
compute_center_zoom_offsets (ImageView *view,
			     int old_width, int old_height,
			     int new_width, int new_height,
			     int *xofs, int *yofs)
{
	ImageViewPrivate *priv;
	int old_scaled_width, old_scaled_height;
	int new_scaled_width, new_scaled_height;
	double view_cx, view_cy;

	priv = view->priv;
	g_assert (priv->need_zoom_change);

	compute_scaled_size (view, priv->old_zoomx, priv->old_zoomy,
			     &old_scaled_width, &old_scaled_height);

	if (old_scaled_width < old_width)
		view_cx = (priv->zoom_x_anchor * old_scaled_width) / priv->old_zoomx;
	else
		view_cx = (priv->xofs + priv->zoom_x_anchor * old_width) / priv->old_zoomx;

	if (old_scaled_height < old_height)
		view_cy = (priv->zoom_y_anchor * old_scaled_height) / priv->old_zoomy;
	else
		view_cy = (priv->yofs + priv->zoom_y_anchor * old_height) / priv->old_zoomy;

	compute_scaled_size (view, priv->zoomx, priv->zoomy,
			     &new_scaled_width, &new_scaled_height);

	if (new_scaled_width < new_width)
		*xofs = 0;
	else
		*xofs = floor (view_cx * priv->zoomx - priv->zoom_x_anchor * new_width + 0.5);

	if (new_scaled_height < new_height)
		*yofs = 0;
	else
		*yofs = floor (view_cy * priv->zoomy - priv->zoom_y_anchor * new_height + 0.5);
}

/* Size_allocate handler for the image view */
static void
image_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
	ImageView *view;
	ImageViewPrivate *priv;
	int xofs, yofs;
	int scaled_width, scaled_height;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (widget));
	g_return_if_fail (allocation != NULL);

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	/* Compute new scroll offsets */

	if (priv->need_zoom_change) {
		compute_center_zoom_offsets (view,
					     widget->allocation.width, widget->allocation.height,
					     allocation->width, allocation->height,
					     &xofs, &yofs);
		
		set_default_zoom_anchor (view);
		priv->need_zoom_change = FALSE;
	} else {
		xofs = priv->xofs;
		yofs = priv->yofs;
	}

	/* Resize the window */

	widget->allocation = *allocation;

	if (GTK_WIDGET_REALIZED (widget))
		gdk_window_move_resize (widget->window,
					allocation->x,
					allocation->y,
					allocation->width,
					allocation->height);

	/* Set scroll increments */

	compute_scaled_size (view, priv->zoomx, priv->zoomy, &scaled_width, &scaled_height);

	priv->hadj->page_size = MIN (scaled_width, allocation->width);
	priv->hadj->page_increment = allocation->width / 2;
	priv->hadj->step_increment = SCROLL_STEP_SIZE;

	priv->vadj->page_size = MIN (scaled_height, allocation->height);
	priv->vadj->page_increment = allocation->height / 2;
	priv->vadj->step_increment = SCROLL_STEP_SIZE;

	/* Set scroll bounds and new offsets */

	priv->hadj->lower = 0;
	priv->hadj->upper = scaled_width;
	xofs = CLAMP (xofs, 0, priv->hadj->upper - priv->hadj->page_size);

	priv->vadj->lower = 0;
	priv->vadj->upper = scaled_height;
	yofs = CLAMP (yofs, 0, priv->vadj->upper - priv->vadj->page_size);

	g_signal_emit_by_name (priv->hadj, "changed");
	g_signal_emit_by_name (priv->vadj, "changed");

	if (priv->hadj->value != xofs) {
		priv->hadj->value = xofs;
		priv->xofs = xofs;

		g_signal_handlers_block_matched (
			priv->hadj, G_SIGNAL_MATCH_DATA,
			0, 0, NULL, NULL, view);

		g_signal_emit_by_name (priv->hadj, "value_changed");

		g_signal_handlers_unblock_matched (
			priv->hadj, G_SIGNAL_MATCH_DATA,
			0, 0, NULL, NULL, view);
	}

	if (priv->vadj->value != yofs) {
		priv->vadj->value = yofs;
		priv->yofs = yofs;

		g_signal_handlers_block_matched (
			priv->vadj, G_SIGNAL_MATCH_DATA,
			0, 0, NULL, NULL, view);

		g_signal_emit_by_name (priv->vadj, "value_changed");

		g_signal_handlers_unblock_matched (
			priv->vadj, G_SIGNAL_MATCH_DATA,
			0, 0, NULL, NULL, view);

	}
}

/* Button press event handler for the image view */
static gboolean
image_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
{
	ImageView *view;
	ImageViewPrivate *priv;
	GdkCursor *cursor;

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	if (event->type != GDK_BUTTON_PRESS)
		return FALSE;

	if (!GTK_WIDGET_HAS_FOCUS (widget))
		gtk_widget_grab_focus (widget);

	if (priv->dragging)
		return FALSE;

	switch (event->button) {
	case 1:
		cursor = cursor_get (widget, CURSOR_HAND_CLOSED);
		gdk_window_set_cursor (widget->window, cursor);
		gdk_cursor_unref (cursor);

		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;
}

/* Drags the image to the specified position */
static void
drag_to (ImageView *view, int x, int y)
{
	ImageViewPrivate *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);
}

/* Button release event handler for the image view */
static gboolean
image_view_button_release_event (GtkWidget *widget, GdkEventButton *event)
{
	ImageView *view;
	ImageViewPrivate *priv;
	GdkCursor *cursor;

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	if (!priv->dragging || event->button != 1)
		return FALSE;

	drag_to (view, event->x, event->y);
	priv->dragging = FALSE;

	cursor = cursor_get (widget, CURSOR_HAND_OPEN);
	gdk_window_set_cursor (widget->window, cursor);
	gdk_cursor_unref (cursor);

	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 EOG you scan scroll by
 * dragging the image with button 1 anyways.
 */
static gboolean
image_view_scroll_event (GtkWidget *widget, GdkEventScroll *event)
{
	ImageView *view;
	ImageViewPrivate *priv;

	view = IMAGE_VIEW (widget);
	priv = view->priv;
	
	/* Compute zoom factor and scrolling offsets; we'll only use either of them */

	double zoom_factor;
	int xofs, yofs;

	xofs = priv->hadj->page_increment / 2; /* same as in gtkscrolledwindow.c */
	yofs = priv->vadj->page_increment / 2;

	switch (event->direction) {
	case GDK_SCROLL_UP:
		zoom_factor = IMAGE_VIEW_ZOOM_MULTIPLIER;
		xofs = 0;
		yofs = -yofs;
		break;

	case GDK_SCROLL_LEFT:
		zoom_factor = 1.0 / IMAGE_VIEW_ZOOM_MULTIPLIER;
		xofs = -xofs;
		yofs = 0;
		break;

	case GDK_SCROLL_DOWN:
		zoom_factor = 1.0 / IMAGE_VIEW_ZOOM_MULTIPLIER;
		xofs = 0;
		yofs = yofs;
		break;

	case GDK_SCROLL_RIGHT:
		zoom_factor = IMAGE_VIEW_ZOOM_MULTIPLIER;
		xofs = xofs;
		yofs = 0;
		break;

	default:
		g_assert_not_reached ();
		return FALSE;
	}

	if ((event->state & GDK_SHIFT_MASK) == 0)
		image_view_set_zoom (view, priv->zoomx * zoom_factor, priv->zoomy * zoom_factor,
				     TRUE, event->x, event->y);
	else if ((event->state & GDK_CONTROL_MASK) == 0)
		scroll_by (view, xofs, yofs);
	else
		scroll_by (view, yofs, xofs);

	return TRUE;
}

/* Motion event handler for the image view */
static gboolean
image_view_motion_event (GtkWidget *widget, GdkEventMotion *event)
{
	ImageView *view;
	ImageViewPrivate *priv;
	gint x, y;
	GdkModifierType mods;

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	if (!priv->dragging)
		return FALSE;

	if (event->is_hint)
		gdk_window_get_pointer (widget->window, &x, &y, &mods);
	else {
		x = event->x;
		y = event->y;
	}

	drag_to (view, x, y);
	return TRUE;
}

/* Expose event handler for the image view.  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
image_view_expose_event (GtkWidget *widget, GdkEventExpose *event)
{
	ImageView *view;
	GdkRectangle *rects;
	gint n_rects;
	int i;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (IS_IMAGE_VIEW (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	view = IMAGE_VIEW (widget);

	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;
}

/* Key press event handler for the image view */
static gboolean
image_view_key_press_event (GtkWidget *widget, GdkEventKey *event)
{
	ImageView *view;
	ImageViewPrivate *priv;
	gboolean handled;
	gboolean do_zoom;
	double zoomx, zoomy;
	gboolean do_scroll;
	int xofs, yofs;

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	handled = FALSE;

	do_zoom = FALSE;
	do_scroll = FALSE;
	xofs = yofs = 0;
	zoomx = zoomy = 1.0;

	if ((event->state & (GDK_MODIFIER_MASK & ~GDK_LOCK_MASK)) != 0)
		goto out;

	switch (event->keyval) {
	case GDK_Up:
		do_scroll = TRUE;
		xofs = 0;
		yofs = -SCROLL_STEP_SIZE;
		break;

	case GDK_Down:
		do_scroll = TRUE;
		xofs = 0;
		yofs = SCROLL_STEP_SIZE;
		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_KP_Add:
		do_zoom = TRUE;
		zoomx = priv->zoomx * IMAGE_VIEW_ZOOM_MULTIPLIER;
		zoomy = priv->zoomy * IMAGE_VIEW_ZOOM_MULTIPLIER;
		break;

	case GDK_minus:
	case GDK_KP_Subtract:
		do_zoom = TRUE;
		zoomx = priv->zoomx / IMAGE_VIEW_ZOOM_MULTIPLIER;
		zoomy = priv->zoomy / IMAGE_VIEW_ZOOM_MULTIPLIER;
		break;

	case GDK_1:
		do_zoom = TRUE;
		zoomx = zoomy = 1.0;
		break;
		/* NOTE Larry disabled this binding 
	case GDK_F:
	case GDK_f:
		g_signal_emit (view, image_view_signals [ZOOM_FIT], 0);
		break;
		*/
	default:
		goto out;
	}

	if (do_zoom) {
		gint x, y;

		gdk_window_get_pointer (widget->window, &x, &y, NULL);
		image_view_set_zoom (view, zoomx, zoomy, TRUE, x, y);
	}

	if (do_scroll)
		scroll_by (view, xofs, yofs);

	handled = TRUE;

 out:
	if (handled)
		return TRUE;
	else
		return (* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event);
}

/* Callback used when an adjustment is changed */
static void
adjustment_changed_cb (GtkAdjustment *adj, gpointer data)
{
	ImageView *view;
	ImageViewPrivate *priv;

	view = IMAGE_VIEW (data);
	priv = view->priv;

	scroll_to (view, priv->hadj->value, priv->vadj->value, FALSE);
}

/* Set_scroll_adjustments handler for the image view */
static void
image_view_set_scroll_adjustments (GtkWidget *widget,
				   GtkAdjustment *hadj,
				   GtkAdjustment *vadj)
{
	ImageView *view;
	ImageViewPrivate *priv;
	gboolean need_adjust;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (widget));

	view = IMAGE_VIEW (widget);
	priv = view->priv;

	if (hadj)
		g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
	else
		hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));

	if (vadj)
		g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
	else
		vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));

	if (priv->hadj && priv->hadj != hadj) {
		g_signal_handlers_disconnect_matched (
			priv->hadj, G_SIGNAL_MATCH_DATA,
			0, 0, NULL, NULL, view);
		g_object_unref (priv->hadj);
	}

	if (priv->vadj && priv->vadj != vadj) {
		g_signal_handlers_disconnect_matched (
			priv->vadj, G_SIGNAL_MATCH_DATA,
			0, 0, NULL, NULL, view);
		g_object_unref (priv->vadj);
	}

	need_adjust = FALSE;

	if (priv->hadj != hadj) {
		priv->hadj = hadj;
		g_object_ref (priv->hadj);
		gtk_object_sink (GTK_OBJECT (priv->hadj));

		g_signal_connect (priv->hadj, "value_changed",
				  G_CALLBACK (adjustment_changed_cb),
				  view);

		need_adjust = TRUE;
	}

	if (priv->vadj != vadj) {
		priv->vadj = vadj;
		g_object_ref (priv->vadj);
		gtk_object_sink (GTK_OBJECT (priv->vadj));

		g_signal_connect (priv->vadj, "value_changed",
				  G_CALLBACK (adjustment_changed_cb),
				  view);

		need_adjust = TRUE;
	}

	if (need_adjust)
		adjustment_changed_cb (NULL, view);
}

static gboolean
image_view_focus_in_event (GtkWidget     *widget,
			   GdkEventFocus *event)
{
	return FALSE;
}

static gboolean
image_view_focus_out_event (GtkWidget     *widget,
			    GdkEventFocus *event)
{
	return FALSE;
}



/**
 * image_view_new:
 * @void:
 *
 * Creates a new empty image view widget.
 *
 * Return value: A newly-created image view.
 **/
GtkWidget *
image_view_new (void)
{
	return g_object_new (TYPE_IMAGE_VIEW, NULL);
}

GdkPixbuf *
image_view_get_pixbuf (ImageView *view)
{
	g_return_val_if_fail (IS_IMAGE_VIEW (view), NULL);

	if (view->priv->pixbuf)
		g_object_ref (view->priv->pixbuf);

	return view->priv->pixbuf;
}

/**
 * image_view_set_pixbuf:
 * @view: An image view.
 * @pixbuf: A pixbuf.
 *
 * Sets the pixbuf that an image view will display.
 **/
void
image_view_set_pixbuf (ImageView *view, GdkPixbuf *pixbuf)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	if (pixbuf) {
		g_object_ref (pixbuf);
	}

	if (view->priv->pixbuf)
		g_object_unref (view->priv->pixbuf);

	view->priv->pixbuf = pixbuf;

	remove_dirty_region (view);
	scroll_to (view, 0, 0, TRUE);

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

/**
 * image_view_set_zoom:
 * @view: An image view.
 * @zoomx: Horizontal zoom factor.
 * @zoomy: Vertical 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.
 **/
void
image_view_set_zoom (ImageView *view, double zoomx, double zoomy,
		     gboolean have_anchor, int anchorx, int anchory)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));
	g_return_if_fail (zoomx > 0.0);
	g_return_if_fail (zoomy > 0.0);

	priv = view->priv;
		
	image_view_update_min_zoom (view);

	if (zoomx > MAX_ZOOM_FACTOR)
		zoomx = MAX_ZOOM_FACTOR;
	else if (zoomx < priv->MIN_ZOOM)
		zoomx = priv->MIN_ZOOM;
	if (zoomy > MAX_ZOOM_FACTOR)
		zoomy = MAX_ZOOM_FACTOR;
	else if (zoomy < priv->MIN_ZOOM)
		zoomy = priv->MIN_ZOOM;

	if (DOUBLE_EQUAL (priv->zoomx, zoomx) &&
	    DOUBLE_EQUAL (priv->zoomy, zoomy))
		return;

	if (!priv->need_zoom_change) {
		priv->old_zoomx = priv->zoomx;
		priv->old_zoomy = priv->zoomy;
		priv->need_zoom_change = TRUE;
	}

	priv->zoomx = zoomx;
	priv->zoomy = zoomy;

	g_signal_emit (view, image_view_signals [ZOOM_CHANGED], 0);

	if (have_anchor) {
		anchorx = CLAMP (anchorx, 0, GTK_WIDGET (view)->allocation.width);
		anchory = CLAMP (anchory, 0, GTK_WIDGET (view)->allocation.height);
		set_zoom_anchor (view, anchorx, anchory);
	} else
		set_default_zoom_anchor (view);

	gtk_widget_queue_resize (GTK_WIDGET (view));
}

/**
 * image_view_get_zoom:
 * @view: An image view.
 * @zoomx: If non-NULL, the horizontal zoom factor is returned here.
 * @zoomy: If non-NULL, the vertical zoom factor is returned here.
 *
 * Queries the zoom factor of an image view.
 **/
void
image_view_get_zoom (ImageView *view, double *zoomx, double *zoomy)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	if (zoomx)
		*zoomx = priv->zoomx;

	if (zoomy)
		*zoomy = priv->zoomy;
}

/**
 * image_view_set_interp_type:
 * @view: An image view.
 * @interp_type: Interpolation type.
 *
 * Sets the interpolation type on an image view.
 **/
void
image_view_set_interp_type (ImageView *view, GdkInterpType interp_type)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	if (priv->interp_type == interp_type)
		return;

	priv->interp_type = interp_type;
	gtk_widget_queue_draw (GTK_WIDGET (view));
}

/**
 * image_view_get_interp_type:
 * @view: An image view.
 *
 * Queries the interpolation type of an image view.
 *
 * Return value: Interpolation type.
 **/
GdkInterpType
image_view_get_interp_type (ImageView *view)
{
	ImageViewPrivate *priv;

	g_return_val_if_fail (view != NULL, GDK_INTERP_NEAREST);
	g_return_val_if_fail (IS_IMAGE_VIEW (view), GDK_INTERP_NEAREST);

	priv = view->priv;
	return priv->interp_type;
}

/**
 * image_view_set_check_type:
 * @view: An image view.
 * @check_type: Check type.
 *
 * Sets the check type on an image view.
 **/
void
image_view_set_check_type (ImageView *view, CheckType check_type)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	if (priv->check_type == check_type &&
	    priv->use_check_pattern)
		return;

	priv->check_type = check_type;
	priv->use_check_pattern = TRUE;

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

/**
 * image_view_get_check_type:
 * @view: An image view.
 *
 * Queries the check type of an image view.
 *
 * Return value: Check type.
 **/
CheckType
image_view_get_check_type (ImageView *view)
{
	ImageViewPrivate *priv;

	g_return_val_if_fail (view != NULL, CHECK_TYPE_BLACK);
	g_return_val_if_fail (IS_IMAGE_VIEW (view), CHECK_TYPE_BLACK);

	priv = view->priv;
	return priv->check_type;
}

/**
 * image_view_set_check_size:
 * @view: An image view.
 * @check_size: Check size.
 *
 * Sets the check size on an image view.
 **/
void
image_view_set_check_size (ImageView *view, CheckSize check_size)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	if (priv->check_size == check_size &&
	    priv->use_check_pattern)
		return;

	priv->check_size = check_size;
	priv->use_check_pattern = TRUE;

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

/**
 * image_view_get_check_size:
 * @view: An image view.
 *
 * Queries the check size on an image view.
 *
 * Return value: Check size.
 **/
CheckSize
image_view_get_check_size (ImageView *view)
{
	ImageViewPrivate *priv;

	g_return_val_if_fail (view != NULL, CHECK_SIZE_SMALL);
	g_return_val_if_fail (IS_IMAGE_VIEW (view), CHECK_SIZE_SMALL);

	priv = view->priv;
	return priv->check_size;
}

/**
 * image_view_set_dither:
 * @view: An image view.
 * @dither: Dither type.
 *
 * Sets the dither type on an image view.
 **/
void
image_view_set_dither (ImageView *view, GdkRgbDither dither)
{
	ImageViewPrivate *priv;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	if (priv->dither == dither)
		return;

	priv->dither = dither;

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

void 
image_view_set_transparent_color (ImageView *view, const GdkColor *color)
{
	ImageViewPrivate *priv;
	guint32 col = 0;
	guint32 red_part;
	guint32 green_part;
	guint32 blue_part;
	
	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	red_part = (color->red / 256) << 16;
	green_part = (color->green / 256) << 8;
	blue_part = (color->blue / 256);

	col = red_part + green_part + blue_part;

	priv->use_check_pattern = FALSE;
	priv->transparency_color = col;

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

/**
 * image_view_get_dither:
 * @view: An image view.
 *
 * Queries the dither type of an image view.
 *
 * Return value: Dither type.
 **/
GdkRgbDither
image_view_get_dither (ImageView *view)
{
	ImageViewPrivate *priv;

	g_return_val_if_fail (view != NULL, GDK_RGB_DITHER_NONE);
	g_return_val_if_fail (IS_IMAGE_VIEW (view), GDK_RGB_DITHER_NONE);

	priv = view->priv;
	return priv->dither;
}

/**
 * image_view_get_scaled_size
 * @view: An image view.
 * @width: Image width result.
 * @height: Image height result.
 *
 * Returns the size of the image after applying the zoom factor.
 *
 * Return value: Image size according to zoom factor.
 **/
void
image_view_get_scaled_size (ImageView *view, gint *width, gint *height)
{
	ImageViewPrivate *priv;

	*width = *height = 0;

	g_return_if_fail (view != NULL);
	g_return_if_fail (IS_IMAGE_VIEW (view));

	priv = view->priv;

	compute_scaled_size (view, priv->zoomx, priv->zoomy, width, height);
}


/* Class initialization function for the image view */
static void
image_view_class_init (ImageViewClass *class)
{
	GObjectClass *gobject_class;
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;

	gobject_class = (GObjectClass *) class;
	object_class = (GtkObjectClass *) class;
	widget_class = (GtkWidgetClass *) class;

	gobject_class->set_property = image_view_set_property;
	gobject_class->get_property = image_view_get_property;

	/* FIXME: we should use the enum types but that's a pain */
	g_object_class_install_property (
		gobject_class,
		PROP_INTERP_TYPE,
		g_param_spec_int ("interp_type",
				  _("interpolation type"),
				  _("the type of interpolation to use"),
				  0, G_MAXINT, 0, 0));
	g_object_class_install_property (
		gobject_class,
		PROP_CHECK_TYPE,
		g_param_spec_int ("check_type",
				  _("check type"),
				  _("the type of chequering to use"),
				  0, G_MAXINT, 0, 0));
	g_object_class_install_property (
		gobject_class,
		PROP_CHECK_SIZE,
		g_param_spec_int ("check_size",
				  _("check type"),
				  _("the size of chequers to use"),
				  0, G_MAXINT, 0, 0));
	g_object_class_install_property (
		gobject_class,
		PROP_DITHER,
		g_param_spec_int ("dither",
				  _("dither"),
				  _("dither type"),
				  0, G_MAXINT, 0, 0));
  	image_view_signals[ZOOM_FIT] =
 		g_signal_new ("zoom_fit",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_FIRST,
			      G_STRUCT_OFFSET (ImageViewClass, zoom_fit),
			      NULL,
			      NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE,
			      0);
  	image_view_signals[ZOOM_CHANGED] =
 		g_signal_new ("zoom_changed",
			      G_TYPE_FROM_CLASS(object_class),
			      G_SIGNAL_RUN_FIRST,
			      G_STRUCT_OFFSET (ImageViewClass, zoom_changed),
			      NULL,
			      NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE,
			      0);

	gobject_class->dispose = image_view_dispose;
  	gobject_class->finalize = image_view_finalize;

  	class->set_scroll_adjustments = image_view_set_scroll_adjustments;
  	widget_class->set_scroll_adjustments_signal =
 		g_signal_new ("set_scroll_adjustments",
			      G_TYPE_FROM_CLASS(object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ImageViewClass, set_scroll_adjustments),
			      NULL,
			      NULL,
			      marshal_VOID__OBJECT_OBJECT,
			      G_TYPE_NONE,
			      2,
			      GTK_TYPE_ADJUSTMENT,
			      GTK_TYPE_ADJUSTMENT);

	widget_class->unmap = image_view_unmap;
	widget_class->realize = image_view_realize;
	widget_class->unrealize = image_view_unrealize;
	widget_class->size_request = image_view_size_request;
	widget_class->size_allocate = image_view_size_allocate;
	widget_class->button_press_event = image_view_button_press_event;
	widget_class->button_release_event = image_view_button_release_event;
	widget_class->scroll_event = image_view_scroll_event;
	widget_class->motion_notify_event = image_view_motion_event;
	widget_class->expose_event = image_view_expose_event;
	widget_class->key_press_event = image_view_key_press_event;
	widget_class->focus_in_event  = image_view_focus_in_event;
	widget_class->focus_out_event = image_view_focus_out_event;
}

void
image_view_update_min_zoom (ImageView *view)
{
	ImageViewPrivate *priv = view->priv;

	priv->MIN_ZOOM = MIN_ZOOM_FACTOR;
	if (priv->pixbuf) {
		double width = (double) gdk_pixbuf_get_width (priv->pixbuf);
		double height = (double) gdk_pixbuf_get_height (priv->pixbuf);

		priv->MIN_ZOOM = ((double) GTK_WIDGET (view)->allocation.width)/ width;

		if (((double) GTK_WIDGET (view)->allocation.height) / height < priv->MIN_ZOOM)
			priv->MIN_ZOOM = ((double) GTK_WIDGET (view)->allocation.height)/ height;

		if (priv->MIN_ZOOM > 1.0)
			priv->MIN_ZOOM = 1.0;
	}
}

#ifdef LIBEOG_ETTORE_CHANGES

void
image_view_get_offsets_and_size (ImageView *view,
				 int *xofs_return,
				 int *yofs_return,
				 int *scaled_width_return,
				 int *scaled_height_return)
{
	ImageViewPrivate *priv = view->priv;
	int width, height;
	int scaled_width, scaled_height;
	int xofs, yofs;

	/* FIXME duplicate code from paint_rectangle() */

	compute_scaled_size (view, priv->zoomx, priv->zoomy, &scaled_width, &scaled_height);

	width = GTK_WIDGET (view)->allocation.width;
	height = GTK_WIDGET (view)->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;

	if (xofs_return != NULL)
		*xofs_return = xofs;
	if (yofs_return != NULL)
		*yofs_return = yofs;

	if (scaled_width_return != NULL)
		*scaled_width_return = scaled_width;
	if (scaled_height_return != NULL)
		*scaled_height_return = scaled_height;
}

void
image_view_set_display_brightness (ImageView *view,
				   float display_brightness)
{
	view->priv->display_brightness = display_brightness;

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

void
image_view_set_display_contrast (ImageView *view,
				 float display_contrast)
{
	view->priv->display_contrast = display_contrast;

	gtk_widget_queue_draw (GTK_WIDGET (view));
}

void
image_view_set_display_transform (ImageView *view, cmsHTRANSFORM transform)
{
	view->priv->transform = transform;
}

void
image_view_set_delay_scaling (ImageView *view, gboolean enable)
{
	view->priv->enable_late_drawing = enable;
}

#endif
