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