diff options
Diffstat (limited to 'src/eom-print-preview.c')
-rw-r--r-- | src/eom-print-preview.c | 1226 |
1 files changed, 1226 insertions, 0 deletions
diff --git a/src/eom-print-preview.c b/src/eom-print-preview.c new file mode 100644 index 0000000..13957c6 --- /dev/null +++ b/src/eom-print-preview.c @@ -0,0 +1,1226 @@ +/* Eye Of MATE -- Print Preview Widget + * + * Copyright (C) 2006-2008 The Free Software Foundation + * + * Author: Claudio Saavedra <[email protected]> + * + * 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 <gtk/gtk.h> +#include <cairo.h> +#include <gdk/gdkkeysyms.h> + +#include "eom-image.h" +#include "eom-print-preview.h" + +#define EOM_PRINT_PREVIEW_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PRINT_PREVIEW, EomPrintPreviewPrivate)) + +G_DEFINE_TYPE (EomPrintPreview, eom_print_preview, GTK_TYPE_ASPECT_FRAME) + +struct _EomPrintPreviewPrivate { + 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_LAST +}; +static gint preview_signals [SIGNAL_LAST]; + +enum { + PROP_IMAGE = 1, + 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 +}; + +static void eom_print_preview_draw (EomPrintPreview *preview, cairo_t *cr); +static void eom_print_preview_finalize (GObject *object); +static void update_relative_sizes (EomPrintPreview *preview); +static void create_surface (EomPrintPreview *preview); +static void create_image_scaled (EomPrintPreview *preview); +static gboolean create_surface_when_idle (EomPrintPreview *preview); + +static void +eom_print_preview_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomPrintPreviewPrivate *priv = EOM_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 +eom_print_preview_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomPrintPreviewPrivate *priv = EOM_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 (EOM_PRINT_PREVIEW (object)); + gtk_widget_queue_draw (priv->area); +} + +static void +eom_print_preview_class_init (EomPrintPreviewClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass*) klass; + + gobject_class->get_property = eom_print_preview_get_property; + gobject_class->set_property = eom_print_preview_set_property; + gobject_class->finalize = eom_print_preview_finalize; + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview: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)); + +/** + * EomPrintPreview::image-moved: + * @preview: the object which received the signal + * + * The #EomPrintPreview::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); + + g_type_class_add_private (klass, sizeof (EomPrintPreviewPrivate)); +} + +static void +eom_print_preview_finalize (GObject *object) +{ + EomPrintPreviewPrivate *priv; + + priv = EOM_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 (eom_print_preview_parent_class)->finalize (object); +} + +static void +eom_print_preview_init (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *priv; + gfloat ratio; + + priv = preview->priv = EOM_PRINT_PREVIEW_GET_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 void expose_event_cb (GtkDrawingArea *drawing_area, GdkEventExpose *eev, gpointer user_data); +static void size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data); + +/** + * eom_print_preview_new_with_pixbuf: + * @pixbuf: a #GdkPixbuf + * + * Creates a new #EomPrintPreview widget, and sets the #GdkPixbuf to preview + * on it. + * + * Returns: A new #EomPrintPreview widget. + **/ +GtkWidget * +eom_print_preview_new_with_pixbuf (GdkPixbuf *pixbuf) +{ + EomPrintPreview *preview; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + preview = EOM_PRINT_PREVIEW (eom_print_preview_new ()); + + preview->priv->image = g_object_ref (pixbuf); + + update_relative_sizes (preview); + + return GTK_WIDGET (preview); +} + +/** + * eom_print_preview_new: + * + * Creates a new #EomPrintPreview widget, setting it to the default values, + * and leaving the page empty. You still need to set the #EomPrintPreview:image + * property to make it useful. + * + * Returns: A new and empty #EomPrintPreview widget. + **/ +GtkWidget * +eom_print_preview_new (void) +{ + EomPrintPreview *preview; + GtkWidget *area; + + preview = g_object_new (EOM_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_KEY_PRESS_MASK); + + g_object_set (G_OBJECT (area), + "can-focus", TRUE, + NULL); + +/* update_relative_sizes (preview); */ + + g_signal_connect (G_OBJECT (area), "expose-event", + G_CALLBACK (expose_event_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 void +expose_event_cb (GtkDrawingArea *drawing_area, + GdkEventExpose *eev, + gpointer user_data) +{ + GtkWidget *widget; + EomPrintPreviewPrivate *priv; + cairo_t *cr; + + widget = GTK_WIDGET (drawing_area); + priv = EOM_PRINT_PREVIEW (user_data)->priv; + + update_relative_sizes (EOM_PRINT_PREVIEW (user_data)); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + eom_print_preview_draw (EOM_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))); + } + + cairo_destroy (cr); + + gdk_window_get_pointer (gtk_widget_get_window (widget), NULL, NULL, NULL); +} + +/** + * get_current_image_coordinates: + * @preview: an #EomPrintPreview + * @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 (EomPrintPreview *preview, + gint *x0, gint *y0) +{ + EomPrintPreviewPrivate *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 #EomPrintPreview + * @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 (EomPrintPreview *preview, + guint x, + guint y) +{ + EomPrintPreviewPrivate *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; +} + +static void +create_image_scaled (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *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 (EomPrintPreview *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 (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 (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *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 (EomPrintPreview *preview) +{ + create_surface (preview); + + return FALSE; +} + +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + EomPrintPreview *preview = EOM_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) +{ + EomPrintPreview *preview = EOM_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) +{ + EomPrintPreviewPrivate *priv; + gfloat delta, align; + gboolean stop_emission = FALSE; + const gchar *property; + + priv = EOM_PRINT_PREVIEW (user_data)->priv; + + delta = 0; + + switch (event->keyval) { + case GDK_Left: + property = "image-x-align"; + delta = -0.01; + break; + case GDK_Right: + property = "image-x-align"; + delta = 0.01; + break; + case GDK_Up: + property = "image-y-align"; + delta = -0.01; + break; + case GDK_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) +{ + EomPrintPreviewPrivate *priv = EOM_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 (EOM_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 (EOM_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); + gdk_cursor_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) +{ + EomPrintPreview *preview; + + preview = EOM_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 +eom_print_preview_draw (EomPrintPreview *preview, cairo_t *cr) +{ + EomPrintPreviewPrivate *priv; + GtkWidget *area; + GtkAllocation allocation; + gint x0, y0; + GtkStyle *style; + gboolean has_focus; + + priv = preview->priv; + area = priv->area; + + has_focus = gtk_widget_has_focus (area); + + style = gtk_widget_get_style (area); + + gtk_widget_get_allocation (area, &allocation); + + /* draw the page */ + gdk_cairo_set_source_color (cr, &style->white); + cairo_rectangle (cr, 0, 0, allocation.width, allocation.height); + cairo_fill (cr); + + /* draw the page margins */ + gdk_cairo_set_source_color (cr, &style->black); + 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) { + gtk_paint_focus (style, gtk_widget_get_window (area), + GTK_STATE_NORMAL, NULL, NULL, NULL, + 0, 0, allocation.width, allocation.height); + } +} + +static void +update_relative_sizes (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *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); +} + +/** + * eom_print_preview_set_page_margins: + * @preview: a #EomPrintPreview + * @l_margin: Left margin. + * @r_margin: Right margin. + * @t_margin: Top margin. + * @b_margin: Bottom margin. + * + * Manually set the margins, in inches. + **/ +void +eom_print_preview_set_page_margins (EomPrintPreview *preview, + gfloat l_margin, + gfloat r_margin, + gfloat t_margin, + gfloat b_margin) +{ + g_return_if_fail (EOM_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); +} + +/** + * eom_print_preview_set_from_page_setup: + * @preview: a #EomPrintPreview + * @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 +eom_print_preview_set_from_page_setup (EomPrintPreview *preview, + GtkPageSetup *setup) +{ + g_return_if_fail (EOM_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); + +} + +/** + * eom_print_preview_get_image_position: + * @preview: a #EomPrintPreview + * @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 +eom_print_preview_get_image_position (EomPrintPreview *preview, + gdouble *x, + gdouble *y) +{ + EomPrintPreviewPrivate *priv; + gdouble width, height; + + g_return_if_fail (EOM_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); + } +} + +/** + * eom_print_preview_set_image_position: + * @preview: a #EomPrintPreview + * @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 +eom_print_preview_set_image_position (EomPrintPreview *preview, + gdouble x, + gdouble y) +{ + EomPrintPreviewPrivate *priv; + gfloat x_align, y_align; + gdouble width, height; + + g_return_if_fail (EOM_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); + } +} + +/** + * eom_print_preview_set_scale: + * @preview: a #EomPrintPreview + * @scale: a scale value, between 0 and 1. + * + * Sets the scale for the image. + **/ +void +eom_print_preview_set_scale (EomPrintPreview *preview, + gfloat scale) +{ + g_return_if_fail (EOM_IS_PRINT_PREVIEW (preview)); + + g_object_set (preview, + "image-scale", scale, + NULL); +} |