1 /* Eye Of GNOME -- Print Preview Widget
2  *
3  * Copyright (C) 2006-2008 The Free Software Foundation
4  *
5  * Author: Claudio Saavedra <csaavedra@gnome.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21 
22 #include <gtk/gtk.h>
23 #include <cairo.h>
24 #include <gdk/gdkkeysyms.h>
25 
26 #include "eog-image.h"
27 #include "eog-print-preview.h"
28 
29 struct _EogPrintPreviewPrivate {
30 	GtkWidget *area;
31 	GdkPixbuf *image;
32 	GdkPixbuf *image_scaled;
33 
34 	/* The surface to set to the cairo context, created from the image */
35 	cairo_surface_t *surface;
36 
37         /* Flag whether we have to create surface */
38 	gboolean flag_create_surface;
39 
40 	/* the alignment of the image in the page */
41 	gfloat image_x_align, image_y_align;
42 
43 	/* real paper size, in inches */
44 	gfloat p_width, p_height;
45 
46 	/* page margins, in inches */
47 	gfloat l_margin, r_margin, t_margin, b_margin;
48 
49 	/* page margins, relatives to the widget size */
50 	gint l_rmargin, r_rmargin, t_rmargin, b_rmargin;
51 
52 	/* image width, relative to the widget size */
53 	gint r_width, r_height;
54 
55 	/* scale of the image, as defined by the user */
56 	gfloat i_scale;
57 
58 	/* scale of the page, relative to the widget size */
59 	gfloat p_scale;
60 
61 	/* whether we are currently grabbing the image */
62 	gboolean grabbed;
63 
64 	/* the last cursor position */
65 	gdouble cursorx, cursory;
66 
67 	/* if we reject to move the image,
68 	   store the delta here */
69 	gdouble r_dx, r_dy;
70 };
71 
72 /* Signal IDs */
73 enum {
74 	SIGNAL_IMAGE_MOVED,
75 	SIGNAL_IMAGE_SCALED,
76 	SIGNAL_LAST
77 };
78 static gint preview_signals [SIGNAL_LAST];
79 
80 enum {
81 	PROP_0,
82 	PROP_IMAGE,
83 	PROP_IMAGE_X_ALIGN,
84 	PROP_IMAGE_Y_ALIGN,
85 	PROP_IMAGE_SCALE,
86 	PROP_PAPER_WIDTH,
87 	PROP_PAPER_HEIGHT,
88 	PROP_PAGE_LEFT_MARGIN,
89 	PROP_PAGE_RIGHT_MARGIN,
90 	PROP_PAGE_TOP_MARGIN,
91 	PROP_PAGE_BOTTOM_MARGIN
92 };
93 
94 G_DEFINE_TYPE_WITH_PRIVATE (EogPrintPreview, eog_print_preview, GTK_TYPE_ASPECT_FRAME)
95 
96 static void eog_print_preview_draw (EogPrintPreview *preview, cairo_t *cr);
97 static void eog_print_preview_finalize (GObject *object);
98 static void update_relative_sizes (EogPrintPreview *preview);
99 static void create_surface (EogPrintPreview *preview);
100 static void create_image_scaled (EogPrintPreview *preview);
101 static gboolean create_surface_when_idle (EogPrintPreview *preview);
102 
103 static void
eog_print_preview_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)104 eog_print_preview_get_property (GObject    *object,
105 				guint       prop_id,
106 				GValue     *value,
107 				GParamSpec *pspec)
108 {
109 	EogPrintPreviewPrivate *priv = EOG_PRINT_PREVIEW (object)->priv;
110 
111 	switch (prop_id) {
112 	case PROP_IMAGE:
113 		g_value_set_object (value, priv->image);
114 		break;
115 	case PROP_IMAGE_X_ALIGN:
116 		g_value_set_float (value, priv->image_x_align);
117 		break;
118 	case PROP_IMAGE_Y_ALIGN:
119 		g_value_set_float (value, priv->image_y_align);
120 		break;
121 	case PROP_IMAGE_SCALE:
122 		g_value_set_float (value, priv->i_scale);
123 		break;
124 	case PROP_PAPER_WIDTH:
125 		g_value_set_float (value, priv->p_width);
126 		break;
127 	case PROP_PAPER_HEIGHT:
128 		g_value_set_float (value, priv->p_height);
129 		break;
130 	case PROP_PAGE_LEFT_MARGIN:
131 		g_value_set_float (value, priv->l_margin);
132 		break;
133 	case PROP_PAGE_RIGHT_MARGIN:
134 		g_value_set_float (value, priv->r_margin);
135 		break;
136 	case PROP_PAGE_TOP_MARGIN:
137 		g_value_set_float (value, priv->t_margin);
138 		break;
139 	case PROP_PAGE_BOTTOM_MARGIN:
140 		g_value_set_float (value, priv->b_margin);
141 		break;
142 	default:
143 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
144 	}
145 }
146 
147 static void
eog_print_preview_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)148 eog_print_preview_set_property (GObject      *object,
149 				guint         prop_id,
150 				const GValue *value,
151 				GParamSpec   *pspec)
152 {
153 	EogPrintPreviewPrivate *priv = EOG_PRINT_PREVIEW (object)->priv;
154 	gboolean paper_size_changed = FALSE;
155 
156 	switch (prop_id) {
157 	case PROP_IMAGE:
158 		if (priv->image) {
159 			g_object_unref (priv->image);
160 		}
161 		priv->image = GDK_PIXBUF (g_value_dup_object (value));
162 
163 		if (priv->image_scaled) {
164 			g_object_unref (priv->image_scaled);
165 			priv->image_scaled = NULL;
166 		}
167 
168 		priv->flag_create_surface = TRUE;
169 		break;
170 	case PROP_IMAGE_X_ALIGN:
171 		priv->image_x_align = g_value_get_float (value);
172 		break;
173 	case PROP_IMAGE_Y_ALIGN:
174 		priv->image_y_align = g_value_get_float (value);
175 		break;
176 	case PROP_IMAGE_SCALE:
177 		priv->i_scale = g_value_get_float (value);
178 		priv->flag_create_surface = TRUE;
179 		break;
180 	case PROP_PAPER_WIDTH:
181 		priv->p_width = g_value_get_float (value);
182 		paper_size_changed = TRUE;
183 		break;
184 	case PROP_PAPER_HEIGHT:
185 		priv->p_height = g_value_get_float (value);
186 		paper_size_changed = TRUE;
187 		break;
188 	case PROP_PAGE_LEFT_MARGIN:
189 		priv->l_margin = g_value_get_float (value);
190 		break;
191 	case PROP_PAGE_RIGHT_MARGIN:
192 		priv->r_margin = g_value_get_float (value);
193 		break;
194 	case PROP_PAGE_TOP_MARGIN:
195 		priv->t_margin = g_value_get_float (value);
196 		break;
197 	case PROP_PAGE_BOTTOM_MARGIN:
198 		priv->b_margin = g_value_get_float (value);
199 		break;
200 	default:
201 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
202 	}
203 
204 	if (paper_size_changed) {
205 		g_object_set (object,
206 			      "ratio", priv->p_width/priv->p_height,
207 			      NULL);
208 	}
209 
210 	update_relative_sizes (EOG_PRINT_PREVIEW (object));
211 	gtk_widget_queue_draw (priv->area);
212 }
213 
214 static void
eog_print_preview_class_init(EogPrintPreviewClass * klass)215 eog_print_preview_class_init (EogPrintPreviewClass *klass)
216 {
217 	GObjectClass *gobject_class;
218 
219 	gobject_class = (GObjectClass*) klass;
220 
221 	gobject_class->get_property = eog_print_preview_get_property;
222 	gobject_class->set_property = eog_print_preview_set_property;
223 	gobject_class->finalize     = eog_print_preview_finalize;
224 
225 /**
226  * EogPrintPreview:image:
227  *
228  * The "image" property defines the image that is previewed
229  * in the widget.
230  */
231 	g_object_class_install_property (gobject_class,
232 					 PROP_IMAGE,
233 					 g_param_spec_object ("image",
234 							      "Image to show in the preview",
235 							      "",
236 							      G_TYPE_OBJECT,
237 							      G_PARAM_READWRITE));
238 
239 /**
240  * EogPrintPreview:image-x-align:
241  *
242  * The "image-x-align" property defines the horizontal alignment
243  * of the image in the widget.
244  */
245 	g_object_class_install_property (gobject_class,
246 					 PROP_IMAGE_X_ALIGN,
247 					 g_param_spec_float ("image-x-align",
248 							      "Horizontal alignment for the image",
249 							      "",
250 							      0,
251 							      1,
252 							      0.5,
253 							      G_PARAM_READWRITE));
254 
255 /**
256  * EogPrintPreview:image-y-align:
257  *
258  * The "image-y-align" property defines the horizontal alignment
259  * of the image in the widget.
260  */
261 	g_object_class_install_property (gobject_class,
262 					 PROP_IMAGE_Y_ALIGN,
263 					 g_param_spec_float ("image-y-align",
264 							      "Vertical alignment for the image",
265 							      "",
266 							      0,
267 							      1,
268 							      0.5,
269 							      G_PARAM_READWRITE));
270 
271 /**
272  * EogPrintPreview:image-scale:
273  *
274  * The "image-scale" property defines the scaling of the image
275  * that the user wants for the printing.
276  */
277 	g_object_class_install_property (gobject_class,
278 					 PROP_IMAGE_SCALE,
279 					 g_param_spec_float ("image-scale",
280 							     "The scale for the image",
281 							      "",
282 							      0,
283 							      1,
284 							      1,
285 							      G_PARAM_READWRITE));
286 
287 /**
288  * EogPrintPreview:paper-width:
289  *
290  * The width of the previewed paper, in inches.
291  */
292 	g_object_class_install_property (gobject_class,
293 					 PROP_PAPER_WIDTH,
294 					 g_param_spec_float ("paper-width",
295 							     "Real paper width in inches",
296 							     "",
297 							     0,
298 							     100,
299 							     8.5,
300 							     G_PARAM_READWRITE));
301 
302 /**
303  * EogPrintPreview:paper-height:
304  *
305  * The height of the previewed paper, in inches.
306  */
307 	g_object_class_install_property (gobject_class,
308 					 PROP_PAPER_HEIGHT,
309 					 g_param_spec_float ("paper-height",
310 							     "Real paper height in inches",
311 							      "",
312 							      0,
313 							      200,
314 							      11,
315 							      G_PARAM_READWRITE));
316 
317 /**
318  * EogPrintPreview:page-left-margin:
319  *
320  * The size of the page's left margin, in inches.
321  */
322 	g_object_class_install_property (gobject_class,
323 					 PROP_PAGE_LEFT_MARGIN,
324 					 g_param_spec_float ("page-left-margin",
325 							     "Left margin of the page in inches",
326 							     "",
327 							     0,
328 							     100,
329 							     0.25,
330 							     G_PARAM_READWRITE));
331 
332 /**
333  * EogPrintPreview:page-right-margin:
334  *
335  * The size of the page's right margin, in inches.
336  */
337 	g_object_class_install_property (gobject_class,
338 					 PROP_PAGE_RIGHT_MARGIN,
339 					 g_param_spec_float ("page-right-margin",
340 							     "Right margin of the page in inches",
341 							      "",
342 							      0,
343 							      200,
344 							      0.25,
345 							      G_PARAM_READWRITE));
346 /**
347  * EogPrintPreview:page-top-margin:
348  *
349  * The size of the page's top margin, in inches.
350  */
351 	g_object_class_install_property (gobject_class,
352 					 PROP_PAGE_TOP_MARGIN,
353 					 g_param_spec_float ("page-top-margin",
354 							     "Top margin of the page in inches",
355 							     "",
356 							      0,
357 							      100,
358 							      0.25,
359 							      G_PARAM_READWRITE));
360 
361 /**
362  * EogPrintPreview:page-bottom-margin:
363  *
364  * The size of the page's bottom margin, in inches.
365  */
366 	g_object_class_install_property (gobject_class,
367 					 PROP_PAGE_BOTTOM_MARGIN,
368 					 g_param_spec_float ("page-bottom-margin",
369 							     "Bottom margin of the page in inches",
370 							      "",
371 							      0,
372 							      200,
373 							      0.56,
374 							      G_PARAM_READWRITE));
375 
376 /**
377  * EogPrintPreview::image-moved:
378  * @preview: the object which received the signal
379  *
380  * The #EogPrintPreview::image-moved signal is emitted when the position
381  * of the image is changed.
382  */
383 	preview_signals [SIGNAL_IMAGE_MOVED] =
384 		g_signal_new ("image_moved",
385 			      G_TYPE_FROM_CLASS (gobject_class),
386 			      G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
387 			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
388 			      0, NULL);
389 
390 /**
391  * EogPrintPreview::image-scaled:
392  * @preview: the object which received the signal
393  *
394  * The ::image-scaled signal is emitted when the scale of the image is changed.
395  */
396 	preview_signals [SIGNAL_IMAGE_SCALED] =
397 		g_signal_new ("image_scaled",
398 			      G_TYPE_FROM_CLASS (gobject_class),
399 			      G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
400 			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
401 			      0, NULL);
402 }
403 
404 static void
eog_print_preview_finalize(GObject * object)405 eog_print_preview_finalize (GObject *object)
406 {
407 	EogPrintPreviewPrivate *priv;
408 
409 	priv = EOG_PRINT_PREVIEW (object)->priv;
410 
411 	if (priv->image) {
412 		g_object_unref (priv->image);
413 		priv->image = NULL;
414 	}
415 
416 	if (priv->image_scaled) {
417 		g_object_unref (priv->image_scaled);
418 		priv->image_scaled = NULL;
419 	}
420 
421 	if (priv->surface) {
422 		cairo_surface_destroy (priv->surface);
423 		priv->surface = NULL;
424 	}
425 
426 	G_OBJECT_CLASS (eog_print_preview_parent_class)->finalize (object);
427 }
428 
429 static void
eog_print_preview_init(EogPrintPreview * preview)430 eog_print_preview_init (EogPrintPreview *preview)
431 {
432 	EogPrintPreviewPrivate *priv;
433 	gfloat ratio;
434 
435 	priv = preview->priv = eog_print_preview_get_instance_private (preview);
436 
437 	priv->area = GTK_WIDGET (gtk_drawing_area_new ());
438 
439 	gtk_container_add (GTK_CONTAINER (preview), priv->area);
440 
441 	priv->p_width  =  8.5;
442 	priv->p_height = 11.0;
443 
444 	ratio = priv->p_width/priv->p_height;
445 
446 	gtk_aspect_frame_set (GTK_ASPECT_FRAME (preview),
447 			      0.5, 0.5, ratio, FALSE);
448 
449 	priv->image = NULL;
450 	priv->image_scaled = NULL;
451 	priv->image_x_align = 0.5;
452 	priv->image_y_align = 0.5;
453 	priv->i_scale = 1;
454 
455 	priv->surface = NULL;
456 	priv->flag_create_surface = TRUE;
457 
458 	priv->p_scale = 0;
459 
460 	priv->l_margin = 0.25;
461 	priv->r_margin = 0.25;
462 	priv->t_margin = 0.25;
463 	priv->b_margin = 0.56;
464 
465 	priv->grabbed = FALSE;
466 	priv->cursorx = 0;
467 	priv->cursory = 0;
468 	priv->r_dx    = 0;
469 	priv->r_dy    = 0;
470 }
471 
472 static gboolean button_press_event_cb   (GtkWidget *widget, GdkEventButton *bev, gpointer user_data);
473 static gboolean button_release_event_cb (GtkWidget *widget, GdkEventButton *bev, gpointer user_data);
474 static gboolean motion_notify_event_cb  (GtkWidget *widget, GdkEventMotion *mev, gpointer user_data);
475 static gboolean key_press_event_cb      (GtkWidget *widget, GdkEventKey *event, gpointer user_data);
476 
477 static gboolean draw_cb (GtkDrawingArea *drawing_area, cairo_t *cr, gpointer  user_data);
478 static void size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data);
479 
480 /**
481  * eog_print_preview_new_with_pixbuf:
482  * @pixbuf: a #GdkPixbuf
483  *
484  * Creates a new #EogPrintPreview widget, and sets the #GdkPixbuf to preview
485  * on it.
486  *
487  * Returns: A new #EogPrintPreview widget.
488  **/
489 GtkWidget *
eog_print_preview_new_with_pixbuf(GdkPixbuf * pixbuf)490 eog_print_preview_new_with_pixbuf (GdkPixbuf *pixbuf)
491 {
492 	EogPrintPreview *preview;
493 
494 	g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
495 
496 	preview = EOG_PRINT_PREVIEW (eog_print_preview_new ());
497 
498 	preview->priv->image = g_object_ref (pixbuf);
499 
500 	update_relative_sizes (preview);
501 
502 	return GTK_WIDGET (preview);
503 }
504 
505 /**
506  * eog_print_preview_new:
507  *
508  * Creates a new #EogPrintPreview widget, setting it to the default values,
509  * and leaving the page empty. You still need to set the #EogPrintPreview:image
510  * property to make it useful.
511  *
512  * Returns: A new and empty #EogPrintPreview widget.
513  **/
514 GtkWidget *
eog_print_preview_new(void)515 eog_print_preview_new (void)
516 {
517 	EogPrintPreview *preview;
518 	GtkWidget *area;
519 
520 	preview = g_object_new (EOG_TYPE_PRINT_PREVIEW, NULL);
521 
522 	area = preview->priv->area;
523 
524 	gtk_widget_set_events (area,
525 			       GDK_EXPOSURE_MASK            |
526 			       GDK_POINTER_MOTION_MASK      |
527 			       GDK_BUTTON_PRESS_MASK        |
528 			       GDK_BUTTON_RELEASE_MASK      |
529 			       GDK_SCROLL_MASK              |
530 			       GDK_KEY_PRESS_MASK);
531 
532 	g_object_set (G_OBJECT (area),
533 		      "can-focus", TRUE,
534 		      NULL);
535 
536 /* 	update_relative_sizes (preview); */
537 
538 	g_signal_connect (G_OBJECT (area), "draw",
539 			  G_CALLBACK (draw_cb), preview);
540 
541 	g_signal_connect (G_OBJECT (area), "motion-notify-event",
542 			  G_CALLBACK (motion_notify_event_cb), preview);
543 
544  	g_signal_connect (G_OBJECT (area), "button-press-event",
545  			  G_CALLBACK (button_press_event_cb), preview);
546 
547 	g_signal_connect (G_OBJECT (area), "button-release-event",
548 			  G_CALLBACK (button_release_event_cb), preview);
549 
550 	g_signal_connect (G_OBJECT (area), "key-press-event",
551 			  G_CALLBACK (key_press_event_cb), preview);
552 
553 	g_signal_connect (area, "size-allocate",
554 			  G_CALLBACK (size_allocate_cb), preview);
555 
556 	return GTK_WIDGET (preview);
557 }
558 
559 static gboolean
draw_cb(GtkDrawingArea * drawing_area,cairo_t * cr,gpointer user_data)560 draw_cb (GtkDrawingArea *drawing_area,
561 		 cairo_t *cr,
562 		 gpointer  user_data)
563 {
564 	update_relative_sizes (EOG_PRINT_PREVIEW (user_data));
565 
566 	eog_print_preview_draw (EOG_PRINT_PREVIEW (user_data), cr);
567 
568 	if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
569 		fprintf (stderr, "Cairo is unhappy: %s\n",
570 			 cairo_status_to_string (cairo_status (cr)));
571 	}
572 
573 	return TRUE;
574 }
575 
576 /**
577  * get_current_image_coordinates:
578  * @preview: an #EogPrintPreview
579  * @x0: A pointer where to store the x coordinate.
580  * @y0: A pointer where to store the y coordinate.
581  *
582  * This function returns the current image coordinates, according
583  * with the properties of the given @preview widget.
584  **/
585 static void
get_current_image_coordinates(EogPrintPreview * preview,gint * x0,gint * y0)586 get_current_image_coordinates (EogPrintPreview *preview,
587 			       gint *x0, gint *y0)
588 {
589 	EogPrintPreviewPrivate *priv;
590 	GtkAllocation allocation;
591 
592 	priv = preview->priv;
593 	gtk_widget_get_allocation (GTK_WIDGET (priv->area), &allocation);
594 
595 	*x0 = (gint)((1 - priv->image_x_align)*priv->l_rmargin +  priv->image_x_align*(allocation.width - priv->r_rmargin - priv->r_width));
596 	*y0 = (gint)((1 - priv->image_y_align)*priv->t_rmargin +  priv->image_y_align*(allocation.height - priv->b_rmargin - priv->r_height));
597 }
598 
599 /**
600  * press_inside_image_area:
601  * @preview: an #EogPrintPreview
602  * @x: the points x coordinate
603  * @y: the points y coordinate
604  *
605  * Returns whether the given point is inside the image area.
606  *
607  * Returns: %TRUE if the given point is inside of the image area,
608  * %FALSE otherwise.
609  **/
610 static gboolean
press_inside_image_area(EogPrintPreview * preview,guint x,guint y)611 press_inside_image_area (EogPrintPreview *preview,
612 			 guint x,
613 			 guint y)
614 {
615 	EogPrintPreviewPrivate *priv;
616 	const gint xs = (gint) x;
617 	const gint ys = (gint) y;
618 	gint x0, y0;
619 
620 	priv = preview->priv;
621 	get_current_image_coordinates (preview, &x0, &y0);
622 
623 	if (xs >= x0 &&  ys >= y0 &&
624 	    xs <= x0 + priv->r_width && ys <= y0 + priv->r_height)
625 		return TRUE;
626 
627 	return FALSE;
628 }
629 
630 gboolean
eog_print_preview_point_in_image_area(EogPrintPreview * preview,guint x,guint y)631 eog_print_preview_point_in_image_area (EogPrintPreview *preview,
632 				       guint x,
633 				       guint y)
634 {
635 	g_return_val_if_fail (EOG_IS_PRINT_PREVIEW (preview), FALSE);
636 
637 	return press_inside_image_area (preview, x, y);
638 }
639 
640 static void
create_image_scaled(EogPrintPreview * preview)641 create_image_scaled (EogPrintPreview *preview)
642 {
643 	EogPrintPreviewPrivate *priv = preview->priv;
644 
645 	if (!priv->image_scaled) {
646 		gint i_width, i_height;
647 		GtkAllocation allocation;
648 
649 		gtk_widget_get_allocation (priv->area, &allocation);
650 		i_width = gdk_pixbuf_get_width (priv->image);
651 		i_height = gdk_pixbuf_get_height (priv->image);
652 
653                 if ((i_width > allocation.width) ||
654 		    (i_height > allocation.height)) {
655 			gdouble scale;
656 			scale = MIN ((gdouble) allocation.width/i_width,
657 				     (gdouble) allocation.height/i_height);
658 			scale *= gtk_widget_get_scale_factor (GTK_WIDGET (priv->area));
659 			priv->image_scaled = gdk_pixbuf_scale_simple (priv->image,
660 								      i_width*scale,
661 								      i_height*scale,
662 								      GDK_INTERP_TILES);
663 		} else {
664 			priv->image_scaled = priv->image;
665 			g_object_ref (priv->image_scaled);
666 		}
667 	}
668 }
669 
670 static GdkPixbuf *
create_preview_buffer(EogPrintPreview * preview)671 create_preview_buffer (EogPrintPreview *preview)
672 {
673 	GdkPixbuf *pixbuf;
674 	gint width, height, widget_scale;
675 	GdkInterpType type = GDK_INTERP_TILES;
676 
677 	if (preview->priv->image == NULL) {
678 		return NULL;
679 	}
680 
681 	create_image_scaled (preview);
682 
683 	width  = gdk_pixbuf_get_width (preview->priv->image);
684 	height = gdk_pixbuf_get_height (preview->priv->image);
685 	widget_scale = gtk_widget_get_scale_factor (GTK_WIDGET (preview->priv->area));
686 
687 	width   *= preview->priv->i_scale * preview->priv->p_scale
688 		* widget_scale;
689 	height  *= preview->priv->i_scale * preview->priv->p_scale
690 		* widget_scale;
691 
692 	if (width < 1 || height < 1)
693 		return NULL;
694 
695 	/* to use GDK_INTERP_TILES for small pixbufs is expensive and unnecessary */
696 	if (width < 25 || height < 25)
697 		type = GDK_INTERP_NEAREST;
698 
699 	if (preview->priv->image_scaled) {
700 		pixbuf = gdk_pixbuf_scale_simple (preview->priv->image_scaled,
701 						  width, height, type);
702 	} else {
703 		pixbuf = gdk_pixbuf_scale_simple (preview->priv->image,
704 						  width, height, type);
705 	}
706 
707 	return pixbuf;
708 }
709 
710 static void
create_surface(EogPrintPreview * preview)711 create_surface (EogPrintPreview *preview)
712 {
713 	EogPrintPreviewPrivate *priv = preview->priv;
714 	GdkPixbuf *pixbuf;
715 
716 	if (priv->surface) {
717 		cairo_surface_destroy (priv->surface);
718 		priv->surface = NULL;
719 	}
720 
721 	pixbuf = create_preview_buffer (preview);
722 	if (pixbuf) {
723 		priv->surface =
724 			gdk_cairo_surface_create_from_pixbuf (pixbuf, 0,
725 							      gtk_widget_get_window (GTK_WIDGET (preview)));
726 		g_object_unref (pixbuf);
727 	}
728 	priv->flag_create_surface = FALSE;
729 }
730 
731 static gboolean
create_surface_when_idle(EogPrintPreview * preview)732 create_surface_when_idle (EogPrintPreview *preview)
733 {
734 	create_surface (preview);
735 
736 	return FALSE;
737 }
738 
739 static gboolean
button_press_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)740 button_press_event_cb (GtkWidget *widget,
741 		       GdkEventButton *event,
742 		       gpointer user_data)
743 {
744 	EogPrintPreview *preview = EOG_PRINT_PREVIEW (user_data);
745 
746 	preview->priv->cursorx = event->x;
747 	preview->priv->cursory = event->y;
748 
749 	switch (event->button) {
750 	case 1:
751 		preview->priv->grabbed = press_inside_image_area (preview, event->x, event->y);
752 		break;
753 	default:
754 		break;
755 	}
756 
757 	if (preview->priv->grabbed) {
758 		gtk_widget_queue_draw (GTK_WIDGET (preview));
759 	}
760 
761 	gtk_widget_grab_focus (preview->priv->area);
762 
763 	return FALSE;
764 }
765 
766 static gboolean
button_release_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)767 button_release_event_cb (GtkWidget *widget,
768 			 GdkEventButton *event,
769 			 gpointer user_data)
770 {
771 	EogPrintPreview *preview = EOG_PRINT_PREVIEW (user_data);
772 
773 	switch (event->button) {
774 	case 1:
775 		preview->priv->grabbed = FALSE;
776 		preview->priv->r_dx = 0;
777 		preview->priv->r_dy = 0;
778 		gtk_widget_queue_draw (GTK_WIDGET (preview));
779 		break;
780 	default:
781 		break;
782 	}
783 	return FALSE;
784 }
785 
786 static gboolean
key_press_event_cb(GtkWidget * widget,GdkEventKey * event,gpointer user_data)787 key_press_event_cb (GtkWidget   *widget,
788 		    GdkEventKey *event,
789 		    gpointer     user_data)
790 {
791 	gfloat delta, align;
792 	gboolean stop_emission = FALSE;
793 	const gchar *property;
794 
795 	delta = 0;
796 
797 	switch (event->keyval) {
798 	case GDK_KEY_Left:
799 		property = "image-x-align";
800 		delta = -0.01;
801 		break;
802 	case GDK_KEY_Right:
803 		property = "image-x-align";
804 		delta = 0.01;
805 		break;
806 	case GDK_KEY_Up:
807 		property = "image-y-align";
808 		delta = -0.01;
809 		break;
810 	case GDK_KEY_Down:
811 		property = "image-y-align";
812 		delta = 0.01;
813 		break;
814 	default:
815 		break;
816 	}
817 
818 	if (delta != 0) {
819 		g_object_get (G_OBJECT (user_data),
820 			      property, &align,
821 			      NULL);
822 
823 		align += delta;
824 		align = CLAMP (align, 0, 1);
825 		g_object_set (G_OBJECT (user_data),
826 			      property, align,
827 			      NULL);
828 
829 		stop_emission = TRUE;
830 		g_signal_emit (G_OBJECT (user_data),
831 			       preview_signals
832 			       [SIGNAL_IMAGE_MOVED], 0);
833 	}
834 
835 	return stop_emission;
836 }
837 
838 static gboolean
motion_notify_event_cb(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)839 motion_notify_event_cb (GtkWidget      *widget,
840 			GdkEventMotion *event,
841 			gpointer        user_data)
842 {
843 	EogPrintPreviewPrivate *priv = EOG_PRINT_PREVIEW (user_data)->priv;
844 	gdouble dx, dy;
845 	GtkAllocation allocation;
846 
847 	if (priv->grabbed) {
848 		dx = event->x - priv->cursorx;
849 		dy = event->y - priv->cursory;
850 
851 		gtk_widget_get_allocation (widget, &allocation);
852 
853 		/* Make sure the image stays inside the margins */
854 
855 		priv->image_x_align += (dx + priv->r_dx)/(allocation.width  - priv->r_width - priv->l_rmargin - priv->r_rmargin);
856 		if (priv->image_x_align < 0. || priv->image_x_align > 1.) {
857 			priv->image_x_align = CLAMP (priv->image_x_align, 0., 1.);
858 			priv->r_dx += dx;
859 		}
860 		else
861 			priv->r_dx = 0;
862 
863 		priv->image_y_align += (dy + priv->r_dy)/(allocation.height - priv->r_height - priv->t_rmargin - priv->b_rmargin);
864 		if (priv->image_y_align < 0. || priv->image_y_align > 1.) {
865 			priv->image_y_align = CLAMP (priv->image_y_align, 0., 1.);
866 			priv->r_dy += dy;
867 		} else
868 			priv->r_dy = 0;
869 
870 		/* we do this to correctly change the property values */
871 		g_object_set (EOG_PRINT_PREVIEW (user_data),
872 			      "image-x-align", priv->image_x_align,
873 			      "image-y-align", priv->image_y_align,
874 			      NULL);
875 
876 		priv->cursorx = event->x;
877 		priv->cursory = event->y;
878 
879 		g_signal_emit (G_OBJECT (user_data),
880 			       preview_signals
881 			       [SIGNAL_IMAGE_MOVED], 0);
882 	} else {
883 		if (press_inside_image_area (EOG_PRINT_PREVIEW (user_data), event->x, event->y)) {
884 		  	GdkCursor *cursor;
885 			cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
886 							     GDK_FLEUR);
887 			gdk_window_set_cursor (gtk_widget_get_window (widget),
888 					       cursor);
889 			g_object_unref (cursor);
890 		} else {
891 			gdk_window_set_cursor (gtk_widget_get_window (widget),
892 					       NULL);
893 		}
894 	}
895 	return FALSE;
896 }
897 
898 static void
size_allocate_cb(GtkWidget * widget,GtkAllocation * allocation,gpointer user_data)899 size_allocate_cb (GtkWidget *widget,
900 		  GtkAllocation *allocation,
901 		  gpointer user_data)
902 {
903 	EogPrintPreview *preview;
904 
905 	preview = EOG_PRINT_PREVIEW (user_data);
906 	update_relative_sizes (preview);
907 
908 	preview->priv->flag_create_surface = TRUE;
909 
910 	if (preview->priv->image_scaled) {
911 		g_object_unref (preview->priv->image_scaled);
912 		preview->priv->image_scaled = NULL;
913 	}
914 
915 	g_idle_add ((GSourceFunc) create_surface_when_idle, preview);
916 }
917 
918 static void
eog_print_preview_draw(EogPrintPreview * preview,cairo_t * cr)919 eog_print_preview_draw (EogPrintPreview *preview, cairo_t *cr)
920 {
921 	EogPrintPreviewPrivate *priv;
922 	GtkWidget *area;
923 	GtkAllocation allocation;
924 	gint x0, y0;
925 	gboolean has_focus;
926 
927 	priv = preview->priv;
928 	area = priv->area;
929 
930 	has_focus = gtk_widget_has_focus (area);
931 
932 	gtk_widget_get_allocation (area, &allocation);
933 
934 	/* draw the page */
935 	cairo_set_source_rgb (cr, 1., 1., 1.);
936  	cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
937  	cairo_fill (cr);
938 
939 	/* draw the page margins */
940 	cairo_set_source_rgb (cr, 0., 0., 0.);
941 	cairo_set_line_width (cr, 0.1);
942 	cairo_rectangle (cr,
943 			 priv->l_rmargin, priv->t_rmargin,
944 			 allocation.width - priv->l_rmargin - priv->r_rmargin,
945 			 allocation.height - priv->t_rmargin - priv->b_rmargin);
946 	cairo_stroke (cr);
947 
948 	get_current_image_coordinates (preview, &x0, &y0);
949 
950 	if (priv->flag_create_surface) {
951 		create_surface (preview);
952 	}
953 
954 	if (priv->surface) {
955 		cairo_set_source_surface (cr, priv->surface, x0, y0);
956 		cairo_paint (cr);
957 	} else if (priv->image_scaled) {
958 		/* just in the remote case we don't have the surface */
959 
960 		/* adjust (x0, y0) to the new scale */
961 		gdouble scale = priv->i_scale * priv->p_scale *
962 			gdk_pixbuf_get_width (priv->image) / gdk_pixbuf_get_width (priv->image_scaled);
963 		x0 /= scale;
964 		y0 /= scale;
965 
966 		cairo_scale (cr, scale, scale);
967 		gdk_cairo_set_source_pixbuf (cr, priv->image_scaled, x0, y0);
968 		cairo_paint (cr);
969 	} else if (priv->image) {
970 		/* just in the remote case we don't have the surface */
971 
972 		/* adjust (x0, y0) to the new scale */
973 		x0 /=  priv->i_scale * priv->p_scale;
974 		y0 /=  priv->i_scale * priv->p_scale;
975 
976 		cairo_scale (cr, priv->i_scale*priv->p_scale, priv->i_scale*priv->p_scale);
977 		gdk_cairo_set_source_pixbuf (cr, priv->image, x0, y0);
978 		cairo_paint (cr);
979 	}
980 
981 	if (has_focus) {
982 		GtkStyleContext *ctx;
983 
984 		ctx = gtk_widget_get_style_context (area);
985 		gtk_render_focus (ctx, cr, x0, y0,
986 				  priv->r_width, priv->r_height);
987 	}
988 }
989 
990 static void
update_relative_sizes(EogPrintPreview * preview)991 update_relative_sizes (EogPrintPreview *preview)
992 {
993 	EogPrintPreviewPrivate *priv;
994 	GtkAllocation allocation;
995 	gint i_width, i_height;
996 
997 	priv = preview->priv;
998 
999 	if (priv->image != NULL) {
1000 		i_width = gdk_pixbuf_get_width (priv->image);
1001 		i_height = gdk_pixbuf_get_height (priv->image);
1002 	} else {
1003 		i_width = i_height = 0;
1004 	}
1005 
1006 	gtk_widget_get_allocation (priv->area, &allocation);
1007 
1008 	priv->p_scale = (gfloat) allocation.width / (priv->p_width * 72.0);
1009 
1010 	priv->r_width  = (gint) i_width  * priv->i_scale * priv->p_scale;
1011 	priv->r_height = (gint) i_height * priv->i_scale * priv->p_scale;
1012 
1013 	priv->l_rmargin = (gint) (72. * priv->l_margin * priv->p_scale);
1014 	priv->r_rmargin = (gint) (72. * priv->r_margin * priv->p_scale);
1015 	priv->t_rmargin = (gint) (72. * priv->t_margin * priv->p_scale);
1016 	priv->b_rmargin = (gint) (72. * priv->b_margin * priv->p_scale);
1017 }
1018 
1019 /**
1020  * eog_print_preview_set_page_margins:
1021  * @preview: a #EogPrintPreview
1022  * @l_margin: Left margin.
1023  * @r_margin: Right margin.
1024  * @t_margin: Top margin.
1025  * @b_margin: Bottom margin.
1026  *
1027  * Manually set the margins, in inches.
1028  **/
1029 void
eog_print_preview_set_page_margins(EogPrintPreview * preview,gfloat l_margin,gfloat r_margin,gfloat t_margin,gfloat b_margin)1030 eog_print_preview_set_page_margins (EogPrintPreview *preview,
1031 				    gfloat l_margin,
1032 				    gfloat r_margin,
1033 				    gfloat t_margin,
1034 				    gfloat b_margin)
1035 {
1036 	g_return_if_fail (EOG_IS_PRINT_PREVIEW (preview));
1037 
1038 	g_object_set (G_OBJECT(preview),
1039 		      "page-left-margin",   l_margin,
1040 		      "page-right-margin",  r_margin,
1041 		      "page-top-margin",    t_margin,
1042 		      "page-bottom-margin", r_margin,
1043 		      NULL);
1044 }
1045 
1046 /**
1047  * eog_print_preview_set_from_page_setup:
1048  * @preview: a #EogPrintPreview
1049  * @setup: a #GtkPageSetup to set the properties from
1050  *
1051  * Sets up the page properties from a #GtkPageSetup. Useful when using the
1052  * widget with the GtkPrint API.
1053  **/
1054 void
eog_print_preview_set_from_page_setup(EogPrintPreview * preview,GtkPageSetup * setup)1055 eog_print_preview_set_from_page_setup (EogPrintPreview *preview,
1056 				       GtkPageSetup *setup)
1057 {
1058 	g_return_if_fail (EOG_IS_PRINT_PREVIEW (preview));
1059 	g_return_if_fail (GTK_IS_PAGE_SETUP (setup));
1060 
1061 	g_object_set (G_OBJECT (preview),
1062 		      "page-left-margin", gtk_page_setup_get_left_margin (setup, GTK_UNIT_INCH),
1063 		      "page-right-margin", gtk_page_setup_get_right_margin (setup, GTK_UNIT_INCH),
1064 		      "page-top-margin", gtk_page_setup_get_top_margin (setup, GTK_UNIT_INCH),
1065 		      "page-bottom-margin", gtk_page_setup_get_bottom_margin (setup, GTK_UNIT_INCH),
1066 		      "paper-width", gtk_page_setup_get_paper_width (setup, GTK_UNIT_INCH),
1067 		      "paper-height", gtk_page_setup_get_paper_height (setup, GTK_UNIT_INCH),
1068 		      NULL);
1069 
1070 }
1071 
1072 /**
1073  * eog_print_preview_get_image_position:
1074  * @preview: a #EogPrintPreview
1075  * @x: a pointer to a #gdouble, or %NULL to ignore it
1076  * @y: a pointer to a #gdouble, or %NULL to ignore it
1077  *
1078  * Gets current image position in inches, relative to the margins. A
1079  * (0, 0) position is the intersection between the left and top margins.
1080  **/
1081 void
eog_print_preview_get_image_position(EogPrintPreview * preview,gdouble * x,gdouble * y)1082 eog_print_preview_get_image_position (EogPrintPreview *preview,
1083 				      gdouble *x,
1084 				      gdouble *y)
1085 {
1086 	EogPrintPreviewPrivate *priv;
1087 	gdouble width, height;
1088 
1089 	g_return_if_fail (EOG_IS_PRINT_PREVIEW (preview));
1090 
1091 	priv = preview->priv;
1092 
1093 	if (x != NULL) {
1094 		width  = gdk_pixbuf_get_width (priv->image)  * priv->i_scale / 72.;
1095 		*x = priv->image_x_align * (priv->p_width  - priv->l_margin - priv->r_margin - width);
1096 	}
1097 	if (y != NULL) {
1098 		height = gdk_pixbuf_get_height (priv->image) * priv->i_scale / 72.;
1099 		*y = priv->image_y_align * (priv->p_height - priv->t_margin - priv->b_margin - height);
1100 	}
1101 }
1102 
1103 /**
1104  * eog_print_preview_set_image_position:
1105  * @preview: a #EogPrintPreview
1106  * @x: The X coordinate, in inches, or -1 to ignore it.
1107  * @y: The Y coordinate, in inches, or -1 to ignore it.
1108  *
1109  * Sets the image position. You can pass -1 to one of the coordinates if you
1110  * only want to set the other.
1111  **/
1112 void
eog_print_preview_set_image_position(EogPrintPreview * preview,gdouble x,gdouble y)1113 eog_print_preview_set_image_position (EogPrintPreview *preview,
1114 				      gdouble x,
1115 				      gdouble y)
1116 {
1117 	EogPrintPreviewPrivate *priv;
1118 	gfloat x_align, y_align;
1119 	gdouble width, height;
1120 
1121 	g_return_if_fail (EOG_IS_PRINT_PREVIEW (preview));
1122 
1123 	priv = preview->priv;
1124 
1125 	if (x != -1) {
1126 		width  = gdk_pixbuf_get_width (priv->image) * priv->i_scale / 72.;
1127 		x_align = CLAMP (x/(priv->p_width - priv->l_margin - priv->r_margin - width), 0, 1);
1128 		g_object_set (preview, "image-x-align", x_align, NULL);
1129 	}
1130 
1131 	if (y != -1) {
1132 		height  = gdk_pixbuf_get_height (priv->image) * priv->i_scale / 72.;
1133 		y_align = CLAMP (y/(priv->p_height - priv->t_margin - priv->b_margin - height), 0, 1);
1134 		g_object_set (preview, "image-y-align", y_align, NULL);
1135 	}
1136 }
1137 
1138 /**
1139  * eog_print_preview_set_scale:
1140  * @preview: a #EogPrintPreview
1141  * @scale: a scale value, between 0 and 1.
1142  *
1143  * Sets the scale for the image.
1144  **/
1145 void
eog_print_preview_set_scale(EogPrintPreview * preview,gfloat scale)1146 eog_print_preview_set_scale (EogPrintPreview *preview,
1147 			     gfloat           scale)
1148 {
1149 	g_return_if_fail (EOG_IS_PRINT_PREVIEW (preview));
1150 
1151 	g_object_set (preview,
1152 		      "image-scale", scale,
1153 		      NULL);
1154 
1155 	g_signal_emit (G_OBJECT (preview),
1156 		       preview_signals
1157 		       [SIGNAL_IMAGE_SCALED], 0);
1158 
1159 }
1160 
1161 /**
1162  * eog_print_preview_get_scale:
1163  * @preview: A #EogPrintPreview.
1164  *
1165  * Gets the scale for the image.
1166  *
1167  * Returns: The scale for the image.
1168  **/
1169 gfloat
eog_print_preview_get_scale(EogPrintPreview * preview)1170 eog_print_preview_get_scale (EogPrintPreview *preview)
1171 {
1172 	gfloat scale;
1173 
1174 	g_return_val_if_fail (EOG_IS_PRINT_PREVIEW (preview), 0);
1175 
1176 	g_object_get (preview,
1177 		      "image-scale", &scale,
1178 		      NULL);
1179 
1180 	return scale;
1181 }
1182