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