/* Xviewer -- Print Preview Widget
 *
 * Copyright (C) 2006-2008 The Free Software Foundation
 *
 * Author: Claudio Saavedra <csaavedra@gnome.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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <gtk/gtk.h>
#include <cairo.h>
#include <gdk/gdkkeysyms.h>

#include "xviewer-image.h"
#include "xviewer-print-preview.h"

struct _XviewerPrintPreviewPrivate {
	GtkWidget *area;
	GdkPixbuf *image;
	GdkPixbuf *image_scaled;

	/* The surface to set to the cairo context, created from the image */
	cairo_surface_t *surface;

        /* Flag whether we have to create surface */
	gboolean flag_create_surface;

	/* the alignment of the image in the page */
	gfloat image_x_align, image_y_align;

	/* real paper size, in inches */
	gfloat p_width, p_height;

	/* page margins, in inches */
	gfloat l_margin, r_margin, t_margin, b_margin;

	/* page margins, relatives to the widget size */
	gint l_rmargin, r_rmargin, t_rmargin, b_rmargin;

	/* image width, relative to the widget size */
	gint r_width, r_height;

	/* scale of the image, as defined by the user */
	gfloat i_scale;

	/* scale of the page, relative to the widget size */
	gfloat p_scale;

	/* whether we are currently grabbing the image */
	gboolean grabbed;

	/* the last cursor position */
	gdouble cursorx, cursory;

	/* if we reject to move the image,
	   store the delta here */
	gdouble r_dx, r_dy;
};

/* Signal IDs */
enum {
	SIGNAL_IMAGE_MOVED,
	SIGNAL_IMAGE_SCALED,
	SIGNAL_LAST
};
static gint preview_signals [SIGNAL_LAST];

enum {
	PROP_0,
	PROP_IMAGE,
	PROP_IMAGE_X_ALIGN,
	PROP_IMAGE_Y_ALIGN,
	PROP_IMAGE_SCALE,
	PROP_PAPER_WIDTH,
	PROP_PAPER_HEIGHT,
	PROP_PAGE_LEFT_MARGIN,
	PROP_PAGE_RIGHT_MARGIN,
	PROP_PAGE_TOP_MARGIN,
	PROP_PAGE_BOTTOM_MARGIN
};

G_DEFINE_TYPE_WITH_PRIVATE (XviewerPrintPreview, xviewer_print_preview, GTK_TYPE_ASPECT_FRAME)

static void xviewer_print_preview_draw (XviewerPrintPreview *preview, cairo_t *cr);
static void xviewer_print_preview_finalize (GObject *object);
static void update_relative_sizes (XviewerPrintPreview *preview);
static void create_surface (XviewerPrintPreview *preview);
static void create_image_scaled (XviewerPrintPreview *preview);
static gboolean create_surface_when_idle (XviewerPrintPreview *preview);

static void
xviewer_print_preview_get_property (GObject    *object,
				guint       prop_id,
				GValue     *value,
				GParamSpec *pspec)
{
	XviewerPrintPreviewPrivate *priv = XVIEWER_PRINT_PREVIEW (object)->priv;

	switch (prop_id) {
	case PROP_IMAGE:
		g_value_set_object (value, priv->image);
		break;
	case PROP_IMAGE_X_ALIGN:
		g_value_set_float (value, priv->image_x_align);
		break;
	case PROP_IMAGE_Y_ALIGN:
		g_value_set_float (value, priv->image_y_align);
		break;
	case PROP_IMAGE_SCALE:
		g_value_set_float (value, priv->i_scale);
		break;
	case PROP_PAPER_WIDTH:
		g_value_set_float (value, priv->p_width);
		break;
	case PROP_PAPER_HEIGHT:
		g_value_set_float (value, priv->p_height);
		break;
	case PROP_PAGE_LEFT_MARGIN:
		g_value_set_float (value, priv->l_margin);
		break;
	case PROP_PAGE_RIGHT_MARGIN:
		g_value_set_float (value, priv->r_margin);
		break;
	case PROP_PAGE_TOP_MARGIN:
		g_value_set_float (value, priv->t_margin);
		break;
	case PROP_PAGE_BOTTOM_MARGIN:
		g_value_set_float (value, priv->b_margin);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
xviewer_print_preview_set_property (GObject      *object,
				guint         prop_id,
				const GValue *value,
				GParamSpec   *pspec)
{
	XviewerPrintPreviewPrivate *priv = XVIEWER_PRINT_PREVIEW (object)->priv;
	gboolean paper_size_changed = FALSE;

	switch (prop_id) {
	case PROP_IMAGE:
		if (priv->image) {
			g_object_unref (priv->image);
		}
		priv->image = GDK_PIXBUF (g_value_dup_object (value));

		if (priv->image_scaled) {
			g_object_unref (priv->image_scaled);
			priv->image_scaled = NULL;
		}

		priv->flag_create_surface = TRUE;
		break;
	case PROP_IMAGE_X_ALIGN:
		priv->image_x_align = g_value_get_float (value);
		break;
	case PROP_IMAGE_Y_ALIGN:
		priv->image_y_align = g_value_get_float (value);
		break;
	case PROP_IMAGE_SCALE:
		priv->i_scale = g_value_get_float (value);
		priv->flag_create_surface = TRUE;
		break;
	case PROP_PAPER_WIDTH:
		priv->p_width = g_value_get_float (value);
		paper_size_changed = TRUE;
		break;
	case PROP_PAPER_HEIGHT:
		priv->p_height = g_value_get_float (value);
		paper_size_changed = TRUE;
		break;
	case PROP_PAGE_LEFT_MARGIN:
		priv->l_margin = g_value_get_float (value);
		break;
	case PROP_PAGE_RIGHT_MARGIN:
		priv->r_margin = g_value_get_float (value);
		break;
	case PROP_PAGE_TOP_MARGIN:
		priv->t_margin = g_value_get_float (value);
		break;
	case PROP_PAGE_BOTTOM_MARGIN:
		priv->b_margin = g_value_get_float (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}

	if (paper_size_changed) {
		g_object_set (object,
			      "ratio", priv->p_width/priv->p_height,
			      NULL);
	}

	update_relative_sizes (XVIEWER_PRINT_PREVIEW (object));
	gtk_widget_queue_draw (priv->area);
}

static void
xviewer_print_preview_class_init (XviewerPrintPreviewClass *klass)
{
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass*) klass;

	gobject_class->get_property = xviewer_print_preview_get_property;
	gobject_class->set_property = xviewer_print_preview_set_property;
	gobject_class->finalize     = xviewer_print_preview_finalize;

/**
 * XviewerPrintPreview:image:
 *
 * The "image" property defines the image that is previewed
 * in the widget.
 */
	g_object_class_install_property (gobject_class,
					 PROP_IMAGE,
					 g_param_spec_object ("image",
							      "Image to show in the preview",
							      "",
							      G_TYPE_OBJECT,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:image-x-align:
 *
 * The "image-x-align" property defines the horizontal alignment
 * of the image in the widget.
 */
	g_object_class_install_property (gobject_class,
					 PROP_IMAGE_X_ALIGN,
					 g_param_spec_float ("image-x-align",
							      "Horizontal alignment for the image",
							      "",
							      0,
							      1,
							      0.5,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:image-y-align:
 *
 * The "image-y-align" property defines the horizontal alignment
 * of the image in the widget.
 */
	g_object_class_install_property (gobject_class,
					 PROP_IMAGE_Y_ALIGN,
					 g_param_spec_float ("image-y-align",
							      "Vertical alignment for the image",
							      "",
							      0,
							      1,
							      0.5,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:image-scale:
 *
 * The "image-scale" property defines the scaling of the image
 * that the user wants for the printing.
 */
	g_object_class_install_property (gobject_class,
					 PROP_IMAGE_SCALE,
					 g_param_spec_float ("image-scale",
							     "The scale for the image",
							      "",
							      0,
							      1,
							      1,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:paper-width:
 *
 * The width of the previewed paper, in inches.
 */
	g_object_class_install_property (gobject_class,
					 PROP_PAPER_WIDTH,
					 g_param_spec_float ("paper-width",
							     "Real paper width in inches",
							     "",
							     0,
							     100,
							     8.5,
							     G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:paper-height:
 *
 * The height of the previewed paper, in inches.
 */
	g_object_class_install_property (gobject_class,
					 PROP_PAPER_HEIGHT,
					 g_param_spec_float ("paper-height",
							     "Real paper height in inches",
							      "",
							      0,
							      200,
							      11,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:page-left-margin:
 *
 * The size of the page's left margin, in inches.
 */
	g_object_class_install_property (gobject_class,
					 PROP_PAGE_LEFT_MARGIN,
					 g_param_spec_float ("page-left-margin",
							     "Left margin of the page in inches",
							     "",
							     0,
							     100,
							     0.25,
							     G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:page-right-margin:
 *
 * The size of the page's right margin, in inches.
 */
	g_object_class_install_property (gobject_class,
					 PROP_PAGE_RIGHT_MARGIN,
					 g_param_spec_float ("page-right-margin",
							     "Right margin of the page in inches",
							      "",
							      0,
							      200,
							      0.25,
							      G_PARAM_READWRITE));
/**
 * XviewerPrintPreview:page-top-margin:
 *
 * The size of the page's top margin, in inches.
 */
	g_object_class_install_property (gobject_class,
					 PROP_PAGE_TOP_MARGIN,
					 g_param_spec_float ("page-top-margin",
							     "Top margin of the page in inches",
							     "",
							      0,
							      100,
							      0.25,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview:page-bottom-margin:
 *
 * The size of the page's bottom margin, in inches.
 */
	g_object_class_install_property (gobject_class,
					 PROP_PAGE_BOTTOM_MARGIN,
					 g_param_spec_float ("page-bottom-margin",
							     "Bottom margin of the page in inches",
							      "",
							      0,
							      200,
							      0.56,
							      G_PARAM_READWRITE));

/**
 * XviewerPrintPreview::image-moved:
 * @preview: the object which received the signal
 *
 * The #XviewerPrintPreview::image-moved signal is emitted when the position
 * of the image is changed.
 */
	preview_signals [SIGNAL_IMAGE_MOVED] =
		g_signal_new ("image_moved",
			      G_TYPE_FROM_CLASS (gobject_class),
			      G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
			      0, NULL);

/**
 * XviewerPrintPreview::image-scaled:
 * @preview: the object which received the signal
 *
 * The ::image-scaled signal is emitted when the scale of the image is changed.
 */
	preview_signals [SIGNAL_IMAGE_SCALED] =
		g_signal_new ("image_scaled",
			      G_TYPE_FROM_CLASS (gobject_class),
			      G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
			      0, NULL);
}

static void
xviewer_print_preview_finalize (GObject *object)
{
	XviewerPrintPreviewPrivate *priv;

	priv = XVIEWER_PRINT_PREVIEW (object)->priv;

	if (priv->image) {
		g_object_unref (priv->image);
		priv->image = NULL;
	}

	if (priv->image_scaled) {
		g_object_unref (priv->image_scaled);
		priv->image_scaled = NULL;
	}

	if (priv->surface) {
		cairo_surface_destroy (priv->surface);
		priv->surface = NULL;
	}

	G_OBJECT_CLASS (xviewer_print_preview_parent_class)->finalize (object);
}

static void
xviewer_print_preview_init (XviewerPrintPreview *preview)
{
	XviewerPrintPreviewPrivate *priv;
	gfloat ratio;

	priv = preview->priv = xviewer_print_preview_get_instance_private (preview);

	priv->area = GTK_WIDGET (gtk_drawing_area_new ());

	gtk_container_add (GTK_CONTAINER (preview), priv->area);

	priv->p_width  =  8.5;
	priv->p_height = 11.0;

	ratio = priv->p_width/priv->p_height;

	gtk_aspect_frame_set (GTK_ASPECT_FRAME (preview),
			      0.5, 0.5, ratio, FALSE);

	priv->image = NULL;
	priv->image_scaled = NULL;
	priv->image_x_align = 0.5;
	priv->image_y_align = 0.5;
	priv->i_scale = 1;

	priv->surface = NULL;
	priv->flag_create_surface = TRUE;

	priv->p_scale = 0;

	priv->l_margin = 0.25;
	priv->r_margin = 0.25;
	priv->t_margin = 0.25;
	priv->b_margin = 0.56;

	priv->grabbed = FALSE;
	priv->cursorx = 0;
	priv->cursory = 0;
	priv->r_dx    = 0;
	priv->r_dy    = 0;
}

static gboolean button_press_event_cb   (GtkWidget *widget, GdkEventButton *bev, gpointer user_data);
static gboolean button_release_event_cb (GtkWidget *widget, GdkEventButton *bev, gpointer user_data);
static gboolean motion_notify_event_cb  (GtkWidget *widget, GdkEventMotion *mev, gpointer user_data);
static gboolean key_press_event_cb      (GtkWidget *widget, GdkEventKey *event, gpointer user_data);

static gboolean draw_cb (GtkDrawingArea *drawing_area, cairo_t *cr, gpointer  user_data);
static void size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data);

/**
 * xviewer_print_preview_new_with_pixbuf:
 * @pixbuf: a #GdkPixbuf
 *
 * Creates a new #XviewerPrintPreview widget, and sets the #GdkPixbuf to preview
 * on it.
 *
 * Returns: A new #XviewerPrintPreview widget.
 **/
GtkWidget *
xviewer_print_preview_new_with_pixbuf (GdkPixbuf *pixbuf)
{
	XviewerPrintPreview *preview;

	g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);

	preview = XVIEWER_PRINT_PREVIEW (xviewer_print_preview_new ());

	preview->priv->image = g_object_ref (pixbuf);

	update_relative_sizes (preview);

	return GTK_WIDGET (preview);
}

/**
 * xviewer_print_preview_new:
 *
 * Creates a new #XviewerPrintPreview widget, setting it to the default values,
 * and leaving the page empty. You still need to set the #XviewerPrintPreview:image
 * property to make it useful.
 *
 * Returns: A new and empty #XviewerPrintPreview widget.
 **/
GtkWidget *
xviewer_print_preview_new (void)
{
	XviewerPrintPreview *preview;
	GtkWidget *area;

	preview = g_object_new (XVIEWER_TYPE_PRINT_PREVIEW, NULL);

	area = preview->priv->area;

	gtk_widget_set_events (area,
			       GDK_EXPOSURE_MASK            |
			       GDK_POINTER_MOTION_MASK      |
			       GDK_BUTTON_PRESS_MASK        |
			       GDK_BUTTON_RELEASE_MASK      |
			       GDK_SCROLL_MASK              |
			       GDK_KEY_PRESS_MASK);

	g_object_set (G_OBJECT (area),
		      "can-focus", TRUE,
		      NULL);

/* 	update_relative_sizes (preview); */

	g_signal_connect (G_OBJECT (area), "draw",
			  G_CALLBACK (draw_cb), preview);

	g_signal_connect (G_OBJECT (area), "motion-notify-event",
			  G_CALLBACK (motion_notify_event_cb), preview);

 	g_signal_connect (G_OBJECT (area), "button-press-event",
 			  G_CALLBACK (button_press_event_cb), preview);

	g_signal_connect (G_OBJECT (area), "button-release-event",
			  G_CALLBACK (button_release_event_cb), preview);

	g_signal_connect (G_OBJECT (area), "key-press-event",
			  G_CALLBACK (key_press_event_cb), preview);

	g_signal_connect (area, "size-allocate",
			  G_CALLBACK (size_allocate_cb), preview);

	return GTK_WIDGET (preview);
}

static gboolean
draw_cb (GtkDrawingArea *drawing_area,
		 cairo_t *cr,
		 gpointer  user_data)
{
	update_relative_sizes (XVIEWER_PRINT_PREVIEW (user_data));

	xviewer_print_preview_draw (XVIEWER_PRINT_PREVIEW (user_data), cr);

	if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
		fprintf (stderr, "Cairo is unhappy: %s\n",
			 cairo_status_to_string (cairo_status (cr)));
	}

	return TRUE;
}

/**
 * get_current_image_coordinates:
 * @preview: an #XviewerPrintPreview
 * @x0: A pointer where to store the x coordinate.
 * @y0: A pointer where to store the y coordinate.
 *
 * This function returns the current image coordinates, according
 * with the properties of the given @preview widget.
 **/
static void
get_current_image_coordinates (XviewerPrintPreview *preview,
			       gint *x0, gint *y0)
{
	XviewerPrintPreviewPrivate *priv;
	GtkAllocation allocation;

	priv = preview->priv;
	gtk_widget_get_allocation (GTK_WIDGET (priv->area), &allocation);

	*x0 = (gint)((1 - priv->image_x_align)*priv->l_rmargin +  priv->image_x_align*(allocation.width - priv->r_rmargin - priv->r_width));
	*y0 = (gint)((1 - priv->image_y_align)*priv->t_rmargin +  priv->image_y_align*(allocation.height - priv->b_rmargin - priv->r_height));
}

/**
 * press_inside_image_area:
 * @preview: an #XviewerPrintPreview
 * @x: the points x coordinate
 * @y: the points y coordinate
 *
 * Returns whether the given point is inside the image area.
 *
 * Returns: %TRUE if the given point is inside of the image area,
 * %FALSE otherwise.
 **/
static gboolean
press_inside_image_area (XviewerPrintPreview *preview,
			 guint x,
			 guint y)
{
	XviewerPrintPreviewPrivate *priv;
	gint x0, y0;

	priv = preview->priv;
	get_current_image_coordinates (preview, &x0, &y0);

	if (x >= x0 &&  y >= y0 &&
	    x <= x0 + priv->r_width && y <= y0 + priv->r_height)
		return TRUE;

	return FALSE;
}

gboolean
xviewer_print_preview_point_in_image_area (XviewerPrintPreview *preview,
				       guint x,
				       guint y)
{
	g_return_val_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview), FALSE);

	return press_inside_image_area (preview, x, y);
}

static void
create_image_scaled (XviewerPrintPreview *preview)
{
	XviewerPrintPreviewPrivate *priv = preview->priv;

	if (!priv->image_scaled) {
		gint i_width, i_height;
		GtkAllocation allocation;

		gtk_widget_get_allocation (priv->area, &allocation);
		i_width = gdk_pixbuf_get_width (priv->image);
		i_height = gdk_pixbuf_get_height (priv->image);

                if ((i_width > allocation.width) ||
		    (i_height > allocation.height)) {
			gdouble scale;
			scale = MIN ((gdouble) allocation.width/i_width,
				     (gdouble) allocation.height/i_height);
			priv->image_scaled = gdk_pixbuf_scale_simple (priv->image,
								      i_width*scale,
								      i_height*scale,
								      GDK_INTERP_TILES);
		} else {
			priv->image_scaled = priv->image;
			g_object_ref (priv->image_scaled);
		}
	}
}

static GdkPixbuf *
create_preview_buffer (XviewerPrintPreview *preview)
{
	GdkPixbuf *pixbuf;
	gint width, height;
	GdkInterpType type = GDK_INTERP_TILES;

	if (preview->priv->image == NULL) {
		return NULL;
	}

	create_image_scaled (preview);

	width  = gdk_pixbuf_get_width (preview->priv->image);
	height = gdk_pixbuf_get_height (preview->priv->image);

	width   *= preview->priv->i_scale * preview->priv->p_scale;
	height  *= preview->priv->i_scale * preview->priv->p_scale;

	if (width < 1 || height < 1)
		return NULL;

	/* to use GDK_INTERP_TILES for small pixbufs is expensive and unnecessary */
	if (width < 25 || height < 25)
		type = GDK_INTERP_NEAREST;

	if (preview->priv->image_scaled) {
		pixbuf = gdk_pixbuf_scale_simple (preview->priv->image_scaled,
						  width, height, type);
	} else {
		pixbuf = gdk_pixbuf_scale_simple (preview->priv->image,
						  width, height, type);
	}

	return pixbuf;
}

/*
  Function inspired from gdk_cairo_set_source_pixbuf (). The main reason is
  that I want to save the cairo_surface_t created from the scaled buffer to
  improve performance.
*/
static cairo_surface_t *
create_surface_from_pixbuf (GdkPixbuf *pixbuf)
{
  gint width = gdk_pixbuf_get_width (pixbuf);
  gint height = gdk_pixbuf_get_height (pixbuf);
  guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
  int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
  int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
  int cairo_stride;
  guchar *cairo_pixels;
  cairo_format_t format;
  cairo_surface_t *surface;
  static const cairo_user_data_key_t key;
  int j;

  if (n_channels == 3)
    format = CAIRO_FORMAT_RGB24;
  else
    format = CAIRO_FORMAT_ARGB32;

  cairo_stride = cairo_format_stride_for_width (format, width);
  cairo_pixels = g_malloc_n (height, cairo_stride);
  surface = cairo_image_surface_create_for_data ((unsigned char *)cairo_pixels,
						 format,
						 width, height, cairo_stride);
  cairo_surface_set_user_data (surface, &key,
			       cairo_pixels, (cairo_destroy_func_t)g_free);

  for (j = height; j; j--)
    {
      guchar *p = gdk_pixels;
      guchar *q = cairo_pixels;

      if (n_channels == 3)
	{
	  guchar *end = p + 3 * width;

	  while (p < end)
	    {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	      q[0] = p[2];
	      q[1] = p[1];
	      q[2] = p[0];
#else
	      q[1] = p[0];
	      q[2] = p[1];
	      q[3] = p[2];
#endif
	      p += 3;
	      q += 4;
	    }
	}
      else
	{
	  guchar *end = p + 4 * width;
	  guint t1,t2,t3;

#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END

	  while (p < end)
	    {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	      MULT(q[0], p[2], p[3], t1);
	      MULT(q[1], p[1], p[3], t2);
	      MULT(q[2], p[0], p[3], t3);
	      q[3] = p[3];
#else
	      q[0] = p[3];
	      MULT(q[1], p[0], p[3], t1);
	      MULT(q[2], p[1], p[3], t2);
	      MULT(q[3], p[2], p[3], t3);
#endif

	      p += 4;
	      q += 4;
	    }

#undef MULT
	}

      gdk_pixels += gdk_rowstride;
      cairo_pixels += cairo_stride;
    }

  return surface;
}

static void
create_surface (XviewerPrintPreview *preview)
{
	XviewerPrintPreviewPrivate *priv = preview->priv;
	GdkPixbuf *pixbuf;

	if (priv->surface) {
		cairo_surface_destroy (priv->surface);
		priv->surface = NULL;
	}

	pixbuf = create_preview_buffer (preview);
	if (pixbuf) {
		priv->surface = create_surface_from_pixbuf (pixbuf);
		g_object_unref (pixbuf);
	}
	priv->flag_create_surface = FALSE;
}

static gboolean
create_surface_when_idle (XviewerPrintPreview *preview)
{
	create_surface (preview);

	return FALSE;
}

static gboolean
button_press_event_cb (GtkWidget *widget,
		       GdkEventButton *event,
		       gpointer user_data)
{
	XviewerPrintPreview *preview = XVIEWER_PRINT_PREVIEW (user_data);

	preview->priv->cursorx = event->x;
	preview->priv->cursory = event->y;

	switch (event->button) {
	case 1:
		preview->priv->grabbed = press_inside_image_area (preview, event->x, event->y);
		break;
	}

	if (preview->priv->grabbed) {
		gtk_widget_queue_draw (GTK_WIDGET (preview));
	}

	gtk_widget_grab_focus (preview->priv->area);

	return FALSE;
}

static gboolean
button_release_event_cb (GtkWidget *widget,
			 GdkEventButton *event,
			 gpointer user_data)
{
	XviewerPrintPreview *preview = XVIEWER_PRINT_PREVIEW (user_data);

	switch (event->button) {
	case 1:
		preview->priv->grabbed = FALSE;
		preview->priv->r_dx = 0;
		preview->priv->r_dy = 0;
		gtk_widget_queue_draw (GTK_WIDGET (preview));

	}
	return FALSE;
}

static gboolean
key_press_event_cb (GtkWidget   *widget,
		    GdkEventKey *event,
		    gpointer     user_data)
{
	gfloat delta, align;
	gboolean stop_emission = FALSE;
	const gchar *property;

	delta = 0;

	switch (event->keyval) {
	case GDK_KEY_Left:
		property = "image-x-align";
		delta = -0.01;
		break;
	case GDK_KEY_Right:
		property = "image-x-align";
		delta = 0.01;
		break;
	case GDK_KEY_Up:
		property = "image-y-align";
		delta = -0.01;
		break;
	case GDK_KEY_Down:
		property = "image-y-align";
		delta = 0.01;
		break;
	}

	if (delta != 0) {
		g_object_get (G_OBJECT (user_data),
			      property, &align,
			      NULL);

		align += delta;
		align = CLAMP (align, 0, 1);
		g_object_set (G_OBJECT (user_data),
			      property, align,
			      NULL);

		stop_emission = TRUE;
		g_signal_emit (G_OBJECT (user_data),
			       preview_signals
			       [SIGNAL_IMAGE_MOVED], 0);
	}

	return stop_emission;
}

static gboolean
motion_notify_event_cb (GtkWidget      *widget,
			GdkEventMotion *event,
			gpointer        user_data)
{
	XviewerPrintPreviewPrivate *priv = XVIEWER_PRINT_PREVIEW (user_data)->priv;
	gdouble dx, dy;
	GtkAllocation allocation;

	if (priv->grabbed) {
		dx = event->x - priv->cursorx;
		dy = event->y - priv->cursory;

		gtk_widget_get_allocation (widget, &allocation);

		/* Make sure the image stays inside the margins */

		priv->image_x_align += (dx + priv->r_dx)/(allocation.width  - priv->r_width - priv->l_rmargin - priv->r_rmargin);
		if (priv->image_x_align < 0. || priv->image_x_align > 1.) {
			priv->image_x_align = CLAMP (priv->image_x_align, 0., 1.);
			priv->r_dx += dx;
		}
		else
			priv->r_dx = 0;

		priv->image_y_align += (dy + priv->r_dy)/(allocation.height - priv->r_height - priv->t_rmargin - priv->b_rmargin);
		if (priv->image_y_align < 0. || priv->image_y_align > 1.) {
			priv->image_y_align = CLAMP (priv->image_y_align, 0., 1.);
			priv->r_dy += dy;
		} else
			priv->r_dy = 0;

		/* we do this to correctly change the property values */
		g_object_set (XVIEWER_PRINT_PREVIEW (user_data),
			      "image-x-align", priv->image_x_align,
			      "image-y-align", priv->image_y_align,
			      NULL);

		priv->cursorx = event->x;
		priv->cursory = event->y;

		g_signal_emit (G_OBJECT (user_data),
			       preview_signals
			       [SIGNAL_IMAGE_MOVED], 0);
	} else {
		if (press_inside_image_area (XVIEWER_PRINT_PREVIEW (user_data), event->x, event->y)) {
		  	GdkCursor *cursor;
			cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
							     GDK_FLEUR);
			gdk_window_set_cursor (gtk_widget_get_window (widget),
					       cursor);
			g_object_unref (cursor);
		} else {
			gdk_window_set_cursor (gtk_widget_get_window (widget),
					       NULL);
		}
	}
	return FALSE;
}

static void
size_allocate_cb (GtkWidget *widget,
		  GtkAllocation *allocation,
		  gpointer user_data)
{
	XviewerPrintPreview *preview;

	preview = XVIEWER_PRINT_PREVIEW (user_data);
	update_relative_sizes (preview);

	preview->priv->flag_create_surface = TRUE;

	if (preview->priv->image_scaled) {
		g_object_unref (preview->priv->image_scaled);
		preview->priv->image_scaled = NULL;
	}

	g_idle_add ((GSourceFunc) create_surface_when_idle, preview);
}

static void
xviewer_print_preview_draw (XviewerPrintPreview *preview, cairo_t *cr)
{
	XviewerPrintPreviewPrivate *priv;
	GtkWidget *area;
	GtkAllocation allocation;
	gint x0, y0;
	gboolean has_focus;

	priv = preview->priv;
	area = priv->area;

	has_focus = gtk_widget_has_focus (area);

	gtk_widget_get_allocation (area, &allocation);

	/* draw the page */
	cairo_set_source_rgb (cr, 1., 1., 1.);
 	cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
 	cairo_fill (cr);

	/* draw the page margins */
	cairo_set_source_rgb (cr, 0., 0., 0.);
	cairo_set_line_width (cr, 0.1);
	cairo_rectangle (cr,
			 priv->l_rmargin, priv->t_rmargin,
			 allocation.width - priv->l_rmargin - priv->r_rmargin,
			 allocation.height - priv->t_rmargin - priv->b_rmargin);
	cairo_stroke (cr);

	get_current_image_coordinates (preview, &x0, &y0);

	if (priv->flag_create_surface) {
		create_surface (preview);
	}

	if (priv->surface) {
		cairo_set_source_surface (cr, priv->surface, x0, y0);
		cairo_paint (cr);
	} else if (priv->image_scaled) {
		/* just in the remote case we don't have the surface */

		/* adjust (x0, y0) to the new scale */
		gdouble scale = priv->i_scale * priv->p_scale *
			gdk_pixbuf_get_width (priv->image) / gdk_pixbuf_get_width (priv->image_scaled);
		x0 /= scale;
		y0 /= scale;

		cairo_scale (cr, scale, scale);
		gdk_cairo_set_source_pixbuf (cr, priv->image_scaled, x0, y0);
		cairo_paint (cr);
	} else if (priv->image) {
		/* just in the remote case we don't have the surface */

		/* adjust (x0, y0) to the new scale */
		x0 /=  priv->i_scale * priv->p_scale;
		y0 /=  priv->i_scale * priv->p_scale;

		cairo_scale (cr, priv->i_scale*priv->p_scale, priv->i_scale*priv->p_scale);
		gdk_cairo_set_source_pixbuf (cr, priv->image, x0, y0);
		cairo_paint (cr);
	}

	if (has_focus) {
		GtkStyleContext *ctx;

		ctx = gtk_widget_get_style_context (area);
		gtk_render_focus (ctx, cr, x0, y0,
				  priv->r_width, priv->r_height);
	}
}

static void
update_relative_sizes (XviewerPrintPreview *preview)
{
	XviewerPrintPreviewPrivate *priv;
	GtkAllocation allocation;
	gint i_width, i_height;

	priv = preview->priv;

	if (priv->image != NULL) {
		i_width = gdk_pixbuf_get_width (priv->image);
		i_height = gdk_pixbuf_get_height (priv->image);
	} else {
		i_width = i_height = 0;
	}

	gtk_widget_get_allocation (priv->area, &allocation);

	priv->p_scale = (gfloat) allocation.width / (priv->p_width * 72.0);

	priv->r_width  = (gint) i_width  * priv->i_scale * priv->p_scale;
	priv->r_height = (gint) i_height * priv->i_scale * priv->p_scale;

	priv->l_rmargin = (gint) (72. * priv->l_margin * priv->p_scale);
	priv->r_rmargin = (gint) (72. * priv->r_margin * priv->p_scale);
	priv->t_rmargin = (gint) (72. * priv->t_margin * priv->p_scale);
	priv->b_rmargin = (gint) (72. * priv->b_margin * priv->p_scale);
}

/**
 * xviewer_print_preview_set_page_margins:
 * @preview: a #XviewerPrintPreview
 * @l_margin: Left margin.
 * @r_margin: Right margin.
 * @t_margin: Top margin.
 * @b_margin: Bottom margin.
 *
 * Manually set the margins, in inches.
 **/
void
xviewer_print_preview_set_page_margins (XviewerPrintPreview *preview,
				    gfloat l_margin,
				    gfloat r_margin,
				    gfloat t_margin,
				    gfloat b_margin)
{
	g_return_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview));

	g_object_set (G_OBJECT(preview),
		      "page-left-margin",   l_margin,
		      "page-right-margin",  r_margin,
		      "page-top-margin",    t_margin,
		      "page-bottom-margin", r_margin,
		      NULL);
}

/**
 * xviewer_print_preview_set_from_page_setup:
 * @preview: a #XviewerPrintPreview
 * @setup: a #GtkPageSetup to set the properties from
 *
 * Sets up the page properties from a #GtkPageSetup. Useful when using the
 * widget with the GtkPrint API.
 **/
void
xviewer_print_preview_set_from_page_setup (XviewerPrintPreview *preview,
				       GtkPageSetup *setup)
{
	g_return_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview));
	g_return_if_fail (GTK_IS_PAGE_SETUP (setup));

	g_object_set (G_OBJECT (preview),
		      "page-left-margin", gtk_page_setup_get_left_margin (setup, GTK_UNIT_INCH),
		      "page-right-margin", gtk_page_setup_get_right_margin (setup, GTK_UNIT_INCH),
		      "page-top-margin", gtk_page_setup_get_top_margin (setup, GTK_UNIT_INCH),
		      "page-bottom-margin", gtk_page_setup_get_bottom_margin (setup, GTK_UNIT_INCH),
		      "paper-width", gtk_page_setup_get_paper_width (setup, GTK_UNIT_INCH),
		      "paper-height", gtk_page_setup_get_paper_height (setup, GTK_UNIT_INCH),
		      NULL);

}

/**
 * xviewer_print_preview_get_image_position:
 * @preview: a #XviewerPrintPreview
 * @x: a pointer to a #gdouble, or %NULL to ignore it
 * @y: a pointer to a #gdouble, or %NULL to ignore it
 *
 * Gets current image position in inches, relative to the margins. A
 * (0, 0) position is the intersection between the left and top margins.
 **/
void
xviewer_print_preview_get_image_position (XviewerPrintPreview *preview,
				      gdouble *x,
				      gdouble *y)
{
	XviewerPrintPreviewPrivate *priv;
	gdouble width, height;

	g_return_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview));

	priv = preview->priv;

	if (x != NULL) {
		width  = gdk_pixbuf_get_width (priv->image)  * priv->i_scale / 72.;
		*x = priv->image_x_align * (priv->p_width  - priv->l_margin - priv->r_margin - width);
	}
	if (y != NULL) {
		height = gdk_pixbuf_get_height (priv->image) * priv->i_scale / 72.;
		*y = priv->image_y_align * (priv->p_height - priv->t_margin - priv->b_margin - height);
	}
}

/**
 * xviewer_print_preview_set_image_position:
 * @preview: a #XviewerPrintPreview
 * @x: The X coordinate, in inches, or -1 to ignore it.
 * @y: The Y coordinate, in inches, or -1 to ignore it.
 *
 * Sets the image position. You can pass -1 to one of the coordinates if you
 * only want to set the other.
 **/
void
xviewer_print_preview_set_image_position (XviewerPrintPreview *preview,
				      gdouble x,
				      gdouble y)
{
	XviewerPrintPreviewPrivate *priv;
	gfloat x_align, y_align;
	gdouble width, height;

	g_return_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview));

	priv = preview->priv;

	if (x != -1) {
		width  = gdk_pixbuf_get_width (priv->image) * priv->i_scale / 72.;
		x_align = CLAMP (x/(priv->p_width - priv->l_margin - priv->r_margin - width), 0, 1);
		g_object_set (preview, "image-x-align", x_align, NULL);
	}

	if (y != -1) {
		height  = gdk_pixbuf_get_height (priv->image) * priv->i_scale / 72.;
		y_align = CLAMP (y/(priv->p_height - priv->t_margin - priv->b_margin - height), 0, 1);
		g_object_set (preview, "image-y-align", y_align, NULL);
	}
}

/**
 * xviewer_print_preview_set_scale:
 * @preview: a #XviewerPrintPreview
 * @scale: a scale value, between 0 and 1.
 *
 * Sets the scale for the image.
 **/
void
xviewer_print_preview_set_scale (XviewerPrintPreview *preview,
			     gfloat           scale)
{
	g_return_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview));

	g_object_set (preview,
		      "image-scale", scale,
		      NULL);

	g_signal_emit (G_OBJECT (preview),
		       preview_signals
		       [SIGNAL_IMAGE_SCALED], 0);

}

/**
 * xviewer_print_preview_get_scale:
 * @preview: A #XviewerPrintPreview.
 *
 * Gets the scale for the image.
 *
 * Returns: The scale for the image.
 **/
gfloat
xviewer_print_preview_get_scale (XviewerPrintPreview *preview)
{
	gfloat scale;

	g_return_val_if_fail (XVIEWER_IS_PRINT_PREVIEW (preview), 0);

	g_object_get (preview,
		      "image-scale", &scale,
		      NULL);

	return scale;
}
