1 /*
2  * gedit-print-preview.c
3  *
4  * Copyright (C) 2008 Paolo Borelli
5  * Copyright (C) 2015 Sébastien Wilmet
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
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "gedit-print-preview.h"
22 
23 #include <math.h>
24 #include <stdlib.h>
25 #include <glib/gi18n.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <cairo-pdf.h>
28 
29 #define PRINTER_DPI (72.0)
30 #define TOOLTIP_THRESHOLD 20
31 #define PAGE_PAD 12
32 #define PAGE_SHADOW_OFFSET 5
33 #define ZOOM_IN_FACTOR (1.2)
34 #define ZOOM_OUT_FACTOR (1.0 / ZOOM_IN_FACTOR)
35 
36 struct _GeditPrintPreview
37 {
38 	GtkGrid parent_instance;
39 
40 	GtkPrintOperation *operation;
41 	GtkPrintContext *context;
42 	GtkPrintOperationPreview *gtk_preview;
43 
44 	GtkButton *prev_button;
45 	GtkButton *next_button;
46 	GtkEntry *page_entry;
47 	GtkLabel *last_page_label;
48 	GtkButton *multi_pages_button;
49 	GtkButton *zoom_one_button;
50 	GtkButton *zoom_fit_button;
51 	GtkButton *zoom_in_button;
52 	GtkButton *zoom_out_button;
53 	GtkButton *close_button;
54 
55 	/* The GtkLayout is where the pages are drawn. The layout should have
56 	 * the focus, because key-press-events and scroll-events are handled on
57 	 * the layout. It is AFAIK not easily possible to handle those events on
58 	 * the GeditPrintPreview itself because when a toolbar item has the
59 	 * focus, some key presses (like the arrows) moves the focus to a
60 	 * sibling toolbar item instead.
61 	 */
62 	GtkLayout *layout;
63 
64 	gdouble scale;
65 
66 	/* multipage support */
67 	gint n_columns;
68 
69 	/* FIXME: handle correctly page selection (e.g. print only
70 	 * page 1-3, 7 and 12.
71 	 */
72 	guint cur_page; /* starts at 0 */
73 
74 	gint cursor_x;
75 	gint cursor_y;
76 
77 	guint has_tooltip : 1;
78 };
79 
G_DEFINE_TYPE(GeditPrintPreview,gedit_print_preview,GTK_TYPE_GRID)80 G_DEFINE_TYPE (GeditPrintPreview, gedit_print_preview, GTK_TYPE_GRID)
81 
82 static void
83 gedit_print_preview_dispose (GObject *object)
84 {
85 	GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (object);
86 
87 	if (preview->gtk_preview != NULL)
88 	{
89 		GtkPrintOperationPreview *gtk_preview;
90 
91 		/* Set preview->gtk_preview to NULL because when calling
92 		 * end_preview() this dispose() function can be run a second
93 		 * time.
94 		 */
95 		gtk_preview = preview->gtk_preview;
96 		preview->gtk_preview = NULL;
97 
98 		gtk_print_operation_preview_end_preview (gtk_preview);
99 
100 		g_object_unref (gtk_preview);
101 	}
102 
103 	g_clear_object (&preview->operation);
104 	g_clear_object (&preview->context);
105 
106 	G_OBJECT_CLASS (gedit_print_preview_parent_class)->dispose (object);
107 }
108 
109 static void
gedit_print_preview_grab_focus(GtkWidget * widget)110 gedit_print_preview_grab_focus (GtkWidget *widget)
111 {
112 	GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (widget);
113 
114 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
115 }
116 
117 static void
gedit_print_preview_class_init(GeditPrintPreviewClass * klass)118 gedit_print_preview_class_init (GeditPrintPreviewClass *klass)
119 {
120 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
121 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
122 
123 	object_class->dispose = gedit_print_preview_dispose;
124 
125 	widget_class->grab_focus = gedit_print_preview_grab_focus;
126 
127 	/* Bind class to template */
128 	gtk_widget_class_set_template_from_resource (widget_class,
129 	                                             "/org/gnome/gedit/ui/gedit-print-preview.ui");
130 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, prev_button);
131 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, next_button);
132 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, page_entry);
133 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, last_page_label);
134 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, multi_pages_button);
135 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_one_button);
136 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_fit_button);
137 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_in_button);
138 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_out_button);
139 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, close_button);
140 	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, layout);
141 }
142 
143 static gint
get_n_pages(GeditPrintPreview * preview)144 get_n_pages (GeditPrintPreview *preview)
145 {
146 	gint n_pages;
147 
148 	g_object_get (preview->operation, "n-pages", &n_pages, NULL);
149 
150 	return n_pages;
151 }
152 
153 static gdouble
get_screen_dpi(GeditPrintPreview * preview)154 get_screen_dpi (GeditPrintPreview *preview)
155 {
156 	GdkScreen *screen;
157 	gdouble dpi;
158 	static gboolean warning_shown = FALSE;
159 
160 	screen = gtk_widget_get_screen (GTK_WIDGET (preview));
161 
162 	if (screen == NULL)
163 	{
164 		return PRINTER_DPI;
165 	}
166 
167 	dpi = gdk_screen_get_resolution (screen);
168 	if (dpi < 30.0 || 600.0 < dpi)
169 	{
170 		if (!warning_shown)
171 		{
172 			g_warning ("Invalid the x-resolution for the screen, assuming 96dpi");
173 			warning_shown = TRUE;
174 		}
175 
176 		dpi = 96.0;
177 	}
178 
179 	return dpi;
180 }
181 
182 /* Get the paper size in points: these must be used only
183  * after the widget has been mapped and the dpi is known.
184  */
185 static gdouble
get_paper_width(GeditPrintPreview * preview)186 get_paper_width (GeditPrintPreview *preview)
187 {
188 	GtkPageSetup *page_setup;
189 	gdouble paper_width;
190 
191 	page_setup = gtk_print_context_get_page_setup (preview->context);
192 	paper_width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_INCH);
193 
194 	return paper_width * get_screen_dpi (preview);
195 }
196 
197 static gdouble
get_paper_height(GeditPrintPreview * preview)198 get_paper_height (GeditPrintPreview *preview)
199 {
200 	GtkPageSetup *page_setup;
201 	gdouble paper_height;
202 
203 	page_setup = gtk_print_context_get_page_setup (preview->context);
204 	paper_height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_INCH);
205 
206 	return paper_height * get_screen_dpi (preview);
207 }
208 
209 /* The tile size is the size in pixels of the area where a page will be
210  * drawn, including the padding. The size is independent of the
211  * orientation.
212  */
213 static void
get_tile_size(GeditPrintPreview * preview,gint * tile_width,gint * tile_height)214 get_tile_size (GeditPrintPreview *preview,
215 	       gint              *tile_width,
216 	       gint              *tile_height)
217 {
218 	if (tile_width != NULL)
219 	{
220 		*tile_width = 2 * PAGE_PAD + round (preview->scale * get_paper_width (preview));
221 	}
222 
223 	if (tile_height != NULL)
224 	{
225 		*tile_height = 2 * PAGE_PAD + round (preview->scale * get_paper_height (preview));
226 	}
227 }
228 
229 static void
get_adjustments(GeditPrintPreview * preview,GtkAdjustment ** hadj,GtkAdjustment ** vadj)230 get_adjustments (GeditPrintPreview  *preview,
231 		 GtkAdjustment     **hadj,
232 		 GtkAdjustment     **vadj)
233 {
234 	*hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (preview->layout));
235 	*vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (preview->layout));
236 }
237 
238 static void
update_layout_size(GeditPrintPreview * preview)239 update_layout_size (GeditPrintPreview *preview)
240 {
241 	gint tile_width;
242 	gint tile_height;
243 
244 	get_tile_size (preview, &tile_width, &tile_height);
245 
246 	/* force size of the drawing area to make the scrolled window work */
247 	gtk_layout_set_size (preview->layout,
248 	                     tile_width * preview->n_columns,
249 	                     tile_height);
250 
251 	gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
252 }
253 
254 /* Zoom should always be set with one of these two function
255  * so that the tile size is properly updated.
256  */
257 
258 static void
set_zoom_factor(GeditPrintPreview * preview,gdouble zoom)259 set_zoom_factor (GeditPrintPreview *preview,
260 		 gdouble            zoom)
261 {
262 	preview->scale = zoom;
263 	update_layout_size (preview);
264 }
265 
266 static void
set_zoom_fit_to_size(GeditPrintPreview * preview)267 set_zoom_fit_to_size (GeditPrintPreview *preview)
268 {
269 	GtkAdjustment *hadj, *vadj;
270 	gdouble width, height;
271 	gdouble paper_width, paper_height;
272 	gdouble zoomx, zoomy;
273 
274 	get_adjustments (preview, &hadj, &vadj);
275 
276 	width = gtk_adjustment_get_page_size (hadj);
277 	height = gtk_adjustment_get_page_size (vadj);
278 
279 	width /= preview->n_columns;
280 
281 	paper_width = get_paper_width (preview);
282 	paper_height = get_paper_height (preview);
283 
284 	zoomx = MAX (1, width - 2 * PAGE_PAD) / paper_width;
285 	zoomy = MAX (1, height - 2 * PAGE_PAD) / paper_height;
286 
287 	set_zoom_factor (preview, zoomx <= zoomy ? zoomx : zoomy);
288 }
289 
290 static void
zoom_in(GeditPrintPreview * preview)291 zoom_in (GeditPrintPreview *preview)
292 {
293 	set_zoom_factor (preview, preview->scale * ZOOM_IN_FACTOR);
294 }
295 
296 static void
zoom_out(GeditPrintPreview * preview)297 zoom_out (GeditPrintPreview *preview)
298 {
299 	set_zoom_factor (preview, preview->scale * ZOOM_OUT_FACTOR);
300 }
301 
302 static void
goto_page(GeditPrintPreview * preview,gint page)303 goto_page (GeditPrintPreview *preview,
304            gint               page)
305 {
306 	gchar *page_str;
307 	gint n_pages;
308 
309 	page_str = g_strdup_printf ("%d", page + 1);
310 	gtk_entry_set_text (preview->page_entry, page_str);
311 	g_free (page_str);
312 
313 	n_pages = get_n_pages (preview);
314 
315 	gtk_widget_set_sensitive (GTK_WIDGET (preview->prev_button),
316 	                          page > 0 &&
317 				  n_pages > 1);
318 
319 	gtk_widget_set_sensitive (GTK_WIDGET (preview->next_button),
320 	                          page < (n_pages - 1) &&
321 	                          n_pages > 1);
322 
323 	if (page != preview->cur_page)
324 	{
325 		preview->cur_page = page;
326 		if (n_pages > 0)
327 		{
328 			gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
329 		}
330 	}
331 }
332 
333 static void
prev_button_clicked(GtkWidget * button,GeditPrintPreview * preview)334 prev_button_clicked (GtkWidget         *button,
335 		     GeditPrintPreview *preview)
336 {
337 	GdkEvent *event;
338 	gint page;
339 
340 	event = gtk_get_current_event ();
341 
342 	if (event->button.state & GDK_SHIFT_MASK)
343 	{
344 		page = 0;
345 	}
346 	else
347 	{
348 		page = preview->cur_page - preview->n_columns;
349 	}
350 
351 	goto_page (preview, MAX (page, 0));
352 
353 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
354 
355 	gdk_event_free (event);
356 }
357 
358 static void
next_button_clicked(GtkWidget * button,GeditPrintPreview * preview)359 next_button_clicked (GtkWidget         *button,
360 		     GeditPrintPreview *preview)
361 {
362 	GdkEvent *event;
363 	gint page;
364 	gint n_pages = get_n_pages (preview);
365 
366 	event = gtk_get_current_event ();
367 
368 	if (event->button.state & GDK_SHIFT_MASK)
369 	{
370 		page = n_pages - 1;
371 	}
372 	else
373 	{
374 		page = preview->cur_page + preview->n_columns;
375 	}
376 
377 	goto_page (preview, MIN (page, n_pages - 1));
378 
379 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
380 
381 	gdk_event_free (event);
382 }
383 
384 static void
page_entry_activated(GtkEntry * entry,GeditPrintPreview * preview)385 page_entry_activated (GtkEntry          *entry,
386 		      GeditPrintPreview *preview)
387 {
388 	const gchar *text;
389 	gint page;
390 	gint n_pages = get_n_pages (preview);
391 
392 	text = gtk_entry_get_text (entry);
393 
394 	page = CLAMP (atoi (text), 1, n_pages) - 1;
395 	goto_page (preview, page);
396 
397 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
398 }
399 
400 static void
page_entry_insert_text(GtkEditable * editable,const gchar * text,gint length,gint * position)401 page_entry_insert_text (GtkEditable *editable,
402 			const gchar *text,
403 			gint         length,
404 			gint        *position)
405 {
406 	const gchar *end;
407 	const gchar *p;
408 
409 	end = text + length;
410 
411 	for (p = text; p < end; p = g_utf8_next_char (p))
412 	{
413 		if (!g_unichar_isdigit (g_utf8_get_char (p)))
414 		{
415 			g_signal_stop_emission_by_name (editable, "insert-text");
416 			break;
417 		}
418 	}
419 }
420 
421 static gboolean
page_entry_focus_out(GtkEntry * entry,GdkEventFocus * event,GeditPrintPreview * preview)422 page_entry_focus_out (GtkEntry          *entry,
423 		      GdkEventFocus     *event,
424 		      GeditPrintPreview *preview)
425 {
426 	const gchar *text;
427 	gint page;
428 
429 	text = gtk_entry_get_text (entry);
430 	page = atoi (text) - 1;
431 
432 	/* Reset the page number only if really needed */
433 	if (page != preview->cur_page)
434 	{
435 		gchar *str;
436 
437 		str = g_strdup_printf ("%d", preview->cur_page + 1);
438 		gtk_entry_set_text (entry, str);
439 		g_free (str);
440 	}
441 
442 	return GDK_EVENT_PROPAGATE;
443 }
444 
445 static void
on_1x1_clicked(GtkMenuItem * item,GeditPrintPreview * preview)446 on_1x1_clicked (GtkMenuItem       *item,
447 		GeditPrintPreview *preview)
448 {
449 	preview->n_columns = 1;
450 	update_layout_size (preview);
451 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
452 }
453 
454 static void
on_1x2_clicked(GtkMenuItem * item,GeditPrintPreview * preview)455 on_1x2_clicked (GtkMenuItem       *item,
456 		GeditPrintPreview *preview)
457 {
458 	preview->n_columns = 2;
459 	set_zoom_fit_to_size (preview);
460 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
461 }
462 
463 static void
multi_pages_button_clicked(GtkWidget * button,GeditPrintPreview * preview)464 multi_pages_button_clicked (GtkWidget         *button,
465 			    GeditPrintPreview *preview)
466 {
467 	GtkWidget *menu;
468 	GtkWidget *item;
469 
470 	menu = gtk_menu_new ();
471 	gtk_widget_show (menu);
472 	g_signal_connect (menu,
473 			  "selection-done",
474 			  G_CALLBACK (gtk_widget_destroy),
475 			  NULL);
476 
477 	item = gtk_menu_item_new_with_label ("1x1");
478 	gtk_widget_show (item);
479 	gtk_menu_attach (GTK_MENU (menu), item, 0, 1, 0, 1);
480 	g_signal_connect (item, "activate", G_CALLBACK (on_1x1_clicked), preview);
481 
482 	item = gtk_menu_item_new_with_label ("1x2");
483 	gtk_widget_show (item);
484 	gtk_menu_attach (GTK_MENU (menu), item, 1, 2, 0, 1);
485 	g_signal_connect (item, "activate", G_CALLBACK (on_1x2_clicked), preview);
486 
487 	gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
488 }
489 
490 static void
zoom_one_button_clicked(GtkWidget * button,GeditPrintPreview * preview)491 zoom_one_button_clicked (GtkWidget         *button,
492 			 GeditPrintPreview *preview)
493 {
494 	set_zoom_factor (preview, 1);
495 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
496 }
497 
498 static void
zoom_fit_button_clicked(GtkWidget * button,GeditPrintPreview * preview)499 zoom_fit_button_clicked (GtkWidget         *button,
500 			 GeditPrintPreview *preview)
501 {
502 	set_zoom_fit_to_size (preview);
503 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
504 }
505 
506 static void
zoom_in_button_clicked(GtkWidget * button,GeditPrintPreview * preview)507 zoom_in_button_clicked (GtkWidget         *button,
508 			GeditPrintPreview *preview)
509 {
510 	zoom_in (preview);
511 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
512 }
513 
514 static void
zoom_out_button_clicked(GtkWidget * button,GeditPrintPreview * preview)515 zoom_out_button_clicked (GtkWidget         *button,
516 			 GeditPrintPreview *preview)
517 {
518 	zoom_out (preview);
519 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
520 }
521 
522 static void
close_button_clicked(GtkWidget * button,GeditPrintPreview * preview)523 close_button_clicked (GtkWidget         *button,
524 		      GeditPrintPreview *preview)
525 {
526 	gtk_widget_destroy (GTK_WIDGET (preview));
527 }
528 
529 static gboolean
scroll_event_activated(GtkWidget * widget,GdkEventScroll * event,GeditPrintPreview * preview)530 scroll_event_activated (GtkWidget         *widget,
531 		        GdkEventScroll    *event,
532 		        GeditPrintPreview *preview)
533 {
534 	if (event->state & GDK_CONTROL_MASK)
535 	{
536 		if ((event->direction == GDK_SCROLL_UP) ||
537 		    (event->direction == GDK_SCROLL_SMOOTH &&
538 		     event->delta_y < 0))
539 		{
540 			zoom_in (preview);
541 		}
542 		else if ((event->direction == GDK_SCROLL_DOWN) ||
543 		         (event->direction == GDK_SCROLL_SMOOTH &&
544 		          event->delta_y > 0))
545 		{
546 			zoom_out (preview);
547 		}
548 
549 		return GDK_EVENT_STOP;
550 	}
551 
552 	return GDK_EVENT_PROPAGATE;
553 }
554 
555 static gint
get_first_page_displayed(GeditPrintPreview * preview)556 get_first_page_displayed (GeditPrintPreview *preview)
557 {
558 	return preview->cur_page - (preview->cur_page % preview->n_columns);
559 }
560 
561 /* Returns the page number (starting from 0) or -1 if no page. */
562 static gint
get_page_at_coords(GeditPrintPreview * preview,gint x,gint y)563 get_page_at_coords (GeditPrintPreview *preview,
564                     gint               x,
565                     gint               y)
566 {
567 	gint tile_width, tile_height;
568 	GtkAdjustment *hadj, *vadj;
569 	gint col, page;
570 
571 	get_tile_size (preview, &tile_width, &tile_height);
572 
573 	if (tile_height <= 0 || tile_width <= 0)
574 	{
575 		return -1;
576 	}
577 
578 	get_adjustments (preview, &hadj, &vadj);
579 
580 	x += gtk_adjustment_get_value (hadj);
581 	y += gtk_adjustment_get_value (vadj);
582 
583 	col = x / tile_width;
584 
585 	if (col >= preview->n_columns || y > tile_height)
586 	{
587 		return -1;
588 	}
589 
590 	page = get_first_page_displayed (preview) + col;
591 
592 	if (page >= get_n_pages (preview))
593 	{
594 		return -1;
595 	}
596 
597 	/* FIXME: we could try to be picky and check if we actually are inside
598 	 * the page (i.e. not in the padding or shadow).
599 	 */
600 	return page;
601 }
602 
603 static gboolean
on_preview_layout_motion_notify(GtkWidget * widget,GdkEvent * event,GeditPrintPreview * preview)604 on_preview_layout_motion_notify (GtkWidget         *widget,
605                                  GdkEvent          *event,
606                                  GeditPrintPreview *preview)
607 {
608 	gint temp_x;
609 	gint temp_y;
610 	gint diff_x;
611 	gint diff_y;
612 
613 	temp_x = ((GdkEventMotion*)event)->x;
614 	temp_y = ((GdkEventMotion*)event)->y;
615 	diff_x = abs (temp_x - preview->cursor_x);
616 	diff_y = abs (temp_y - preview->cursor_y);
617 
618 	if ((diff_x >= TOOLTIP_THRESHOLD) || (diff_y >= TOOLTIP_THRESHOLD))
619 	{
620 		preview->has_tooltip = FALSE;
621 		preview->cursor_x = temp_x;
622 		preview->cursor_y = temp_y;
623 	}
624 	else
625 	{
626 		preview->has_tooltip = TRUE;
627 	}
628 
629 	return GDK_EVENT_STOP;
630 }
631 
632 static gboolean
preview_layout_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip,GeditPrintPreview * preview)633 preview_layout_query_tooltip (GtkWidget         *widget,
634 			      gint               x,
635 			      gint               y,
636 			      gboolean           keyboard_tip,
637 			      GtkTooltip        *tooltip,
638 			      GeditPrintPreview *preview)
639 {
640 	if (preview->has_tooltip)
641 	{
642 		gint page;
643 		gchar *tip;
644 
645 		page = get_page_at_coords (preview, x, y);
646 		if (page < 0)
647 		{
648 			return FALSE;
649 		}
650 
651 		tip = g_strdup_printf (_("Page %d of %d"),
652 				       page + 1,
653 				       get_n_pages (preview));
654 
655 		gtk_tooltip_set_text (tooltip, tip);
656 		g_free (tip);
657 
658 		return TRUE;
659 	}
660 	else
661 	{
662 		preview->has_tooltip = TRUE;
663 		return FALSE;
664 	}
665 }
666 
667 static gint
preview_layout_key_press(GtkWidget * widget,GdkEventKey * event,GeditPrintPreview * preview)668 preview_layout_key_press (GtkWidget         *widget,
669 			  GdkEventKey       *event,
670 			  GeditPrintPreview *preview)
671 {
672 	GtkAdjustment *hadj, *vadj;
673 	gdouble x, y;
674 	gdouble hlower, vlower;
675 	gdouble hupper, vupper;
676 	gdouble visible_width, visible_height;
677 	gdouble hstep, vstep;
678 	gint n_pages;
679 	gboolean do_move = FALSE;
680 
681 	get_adjustments (preview, &hadj, &vadj);
682 
683 	x = gtk_adjustment_get_value (hadj);
684 	y = gtk_adjustment_get_value (vadj);
685 
686 	hlower = gtk_adjustment_get_lower (hadj);
687 	vlower = gtk_adjustment_get_lower (vadj);
688 
689 	hupper = gtk_adjustment_get_upper (hadj);
690 	vupper = gtk_adjustment_get_upper (vadj);
691 
692 	visible_width = gtk_adjustment_get_page_size (hadj);
693 	visible_height = gtk_adjustment_get_page_size (vadj);
694 
695 	hstep = 10;
696 	vstep = 10;
697 
698 	n_pages = get_n_pages (preview);
699 
700 	switch (event->keyval)
701 	{
702 		case '1':
703 			set_zoom_fit_to_size (preview);
704 			break;
705 
706 		case '+':
707 		case '=':
708 		case GDK_KEY_KP_Add:
709 			zoom_in (preview);
710 			break;
711 
712 		case '-':
713 		case '_':
714 		case GDK_KEY_KP_Subtract:
715 			zoom_out (preview);
716 			break;
717 
718 		case GDK_KEY_KP_Right:
719 		case GDK_KEY_Right:
720 			if (event->state & GDK_SHIFT_MASK)
721 				x = hupper - visible_width;
722 			else
723 				x = MIN (hupper - visible_width, x + hstep);
724 			do_move = TRUE;
725 			break;
726 
727 		case GDK_KEY_KP_Left:
728 		case GDK_KEY_Left:
729 			if (event->state & GDK_SHIFT_MASK)
730 				x = hlower;
731 			else
732 				x = MAX (hlower, x - hstep);
733 			do_move = TRUE;
734 			break;
735 
736 		case GDK_KEY_KP_Up:
737 		case GDK_KEY_Up:
738 			if (event->state & GDK_SHIFT_MASK)
739 				goto page_up;
740 
741 			y = MAX (vlower, y - vstep);
742 			do_move = TRUE;
743 			break;
744 
745 		case GDK_KEY_KP_Down:
746 		case GDK_KEY_Down:
747 			if (event->state & GDK_SHIFT_MASK)
748 				goto page_down;
749 
750 			y = MIN (vupper - visible_height, y + vstep);
751 			do_move = TRUE;
752 			break;
753 
754 		case GDK_KEY_KP_Page_Up:
755 		case GDK_KEY_Page_Up:
756 		case GDK_KEY_Delete:
757 		case GDK_KEY_KP_Delete:
758 		case GDK_KEY_BackSpace:
759 		page_up:
760 			if (y <= vlower)
761 			{
762 				if (preview->cur_page > 0)
763 				{
764 					goto_page (preview, preview->cur_page - 1);
765 					y = (vupper - visible_height);
766 				}
767 			}
768 			else
769 			{
770 				y = vlower;
771 			}
772 			do_move = TRUE;
773 			break;
774 
775 		case GDK_KEY_KP_Page_Down:
776 		case GDK_KEY_Page_Down:
777 		case ' ':
778 		page_down:
779 			if (y >= (vupper - visible_height))
780 			{
781 				if (preview->cur_page < n_pages - 1)
782 				{
783 					goto_page (preview, preview->cur_page + 1);
784 					y = vlower;
785 				}
786 			}
787 			else
788 			{
789 				y = (vupper - visible_height);
790 			}
791 			do_move = TRUE;
792 			break;
793 
794 		case GDK_KEY_KP_Home:
795 		case GDK_KEY_Home:
796 			goto_page (preview, 0);
797 			y = vlower;
798 			do_move = TRUE;
799 			break;
800 
801 		case GDK_KEY_KP_End:
802 		case GDK_KEY_End:
803 			goto_page (preview, n_pages - 1);
804 			y = vlower;
805 			do_move = TRUE;
806 			break;
807 
808 		case GDK_KEY_Escape:
809 			gtk_widget_destroy (GTK_WIDGET (preview));
810 			break;
811 
812 		case 'p':
813 			if (event->state & GDK_MOD1_MASK)
814 			{
815 				gtk_widget_grab_focus (GTK_WIDGET (preview->page_entry));
816 			}
817 			break;
818 
819 		default:
820 			return GDK_EVENT_PROPAGATE;
821 	}
822 
823 	if (do_move)
824 	{
825 		gtk_adjustment_set_value (hadj, x);
826 		gtk_adjustment_set_value (vadj, y);
827 	}
828 
829 	return GDK_EVENT_STOP;
830 }
831 
832 static void
gedit_print_preview_init(GeditPrintPreview * preview)833 gedit_print_preview_init (GeditPrintPreview *preview)
834 {
835 	preview->cur_page = 0;
836 	preview->scale = 1.0;
837 	preview->n_columns = 1;
838 	preview->cursor_x = 0;
839 	preview->cursor_y = 0;
840 	preview->has_tooltip = TRUE;
841 
842 	gtk_widget_init_template (GTK_WIDGET (preview));
843 
844 	g_signal_connect (preview->prev_button,
845 			  "clicked",
846 			  G_CALLBACK (prev_button_clicked),
847 			  preview);
848 
849 	g_signal_connect (preview->next_button,
850 			  "clicked",
851 			  G_CALLBACK (next_button_clicked),
852 			  preview);
853 
854 	g_signal_connect (preview->page_entry,
855 			  "activate",
856 			  G_CALLBACK (page_entry_activated),
857 			  preview);
858 
859 	g_signal_connect (preview->page_entry,
860 			  "insert-text",
861 			  G_CALLBACK (page_entry_insert_text),
862 			  NULL);
863 
864 	g_signal_connect (preview->page_entry,
865 			  "focus-out-event",
866 			  G_CALLBACK (page_entry_focus_out),
867 			  preview);
868 
869 	g_signal_connect (preview->multi_pages_button,
870 			  "clicked",
871 			  G_CALLBACK (multi_pages_button_clicked),
872 			  preview);
873 
874 	g_signal_connect (preview->zoom_one_button,
875 			  "clicked",
876 			  G_CALLBACK (zoom_one_button_clicked),
877 			  preview);
878 
879 	g_signal_connect (preview->zoom_fit_button,
880 			  "clicked",
881 			  G_CALLBACK (zoom_fit_button_clicked),
882 			  preview);
883 
884 	g_signal_connect (preview->zoom_in_button,
885 			  "clicked",
886 			  G_CALLBACK (zoom_in_button_clicked),
887 			  preview);
888 
889 	g_signal_connect (preview->zoom_out_button,
890 			  "clicked",
891 			  G_CALLBACK (zoom_out_button_clicked),
892 			  preview);
893 
894 	g_signal_connect (preview->close_button,
895 			  "clicked",
896 			  G_CALLBACK (close_button_clicked),
897 			  preview);
898 
899 	g_signal_connect (preview->layout,
900 			  "query-tooltip",
901 			  G_CALLBACK (preview_layout_query_tooltip),
902 			  preview);
903 
904 	g_signal_connect (preview->layout,
905 			  "key-press-event",
906 			  G_CALLBACK (preview_layout_key_press),
907 			  preview);
908 
909 	g_signal_connect (preview->layout,
910 			  "scroll-event",
911 			  G_CALLBACK (scroll_event_activated),
912 			  preview);
913 
914 	/* hide the tooltip once we move the cursor, since gtk does not do it for us */
915 	g_signal_connect (preview->layout,
916 			  "motion-notify-event",
917 			  G_CALLBACK (on_preview_layout_motion_notify),
918 			  preview);
919 
920 	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
921 }
922 
923 static void
draw_page_content(cairo_t * cr,gint page_number,GeditPrintPreview * preview)924 draw_page_content (cairo_t           *cr,
925 		   gint               page_number,
926 		   GeditPrintPreview *preview)
927 {
928 	gdouble dpi;
929 
930 	/* scale to the desired size */
931 	cairo_scale (cr, preview->scale, preview->scale);
932 
933 	dpi = get_screen_dpi (preview);
934 	gtk_print_context_set_cairo_context (preview->context, cr, dpi, dpi);
935 
936 	gtk_print_operation_preview_render_page (preview->gtk_preview,
937 	                                         page_number);
938 }
939 
940 /* For the frame, we scale and rotate manually, since
941  * the line width should not depend on the zoom and
942  * the drop shadow should be on the bottom right no matter
943  * the orientation.
944  */
945 static void
draw_page_frame(cairo_t * cr,GeditPrintPreview * preview)946 draw_page_frame (cairo_t           *cr,
947 		 GeditPrintPreview *preview)
948 {
949 	gdouble width;
950 	gdouble height;
951 
952 	width = get_paper_width (preview) * preview->scale;
953 	height = get_paper_height (preview) * preview->scale;
954 
955 	/* drop shadow */
956 	cairo_set_source_rgb (cr, 0, 0, 0);
957 	cairo_rectangle (cr,
958 			 PAGE_SHADOW_OFFSET, PAGE_SHADOW_OFFSET,
959 			 width, height);
960 	cairo_fill (cr);
961 
962 	/* page frame */
963 	cairo_set_source_rgb (cr, 1, 1, 1);
964 	cairo_rectangle (cr,
965 			 0, 0,
966 			 width, height);
967 	cairo_fill_preserve (cr);
968 	cairo_set_source_rgb (cr, 0, 0, 0);
969 	cairo_set_line_width (cr, 1);
970 	cairo_stroke (cr);
971 }
972 
973 static void
draw_page(cairo_t * cr,gdouble x,gdouble y,gint page_number,GeditPrintPreview * preview)974 draw_page (cairo_t           *cr,
975 	   gdouble            x,
976 	   gdouble            y,
977 	   gint               page_number,
978 	   GeditPrintPreview *preview)
979 {
980 	cairo_save (cr);
981 
982 	/* move to the page top left corner */
983 	cairo_translate (cr, x + PAGE_PAD, y + PAGE_PAD);
984 
985 	draw_page_frame (cr, preview);
986 	draw_page_content (cr, page_number, preview);
987 
988 	cairo_restore (cr);
989 }
990 
991 static gboolean
preview_draw(GtkWidget * widget,cairo_t * cr,GeditPrintPreview * preview)992 preview_draw (GtkWidget         *widget,
993 	      cairo_t           *cr,
994 	      GeditPrintPreview *preview)
995 {
996 	GdkWindow *bin_window;
997 	gint tile_width;
998 	gint page_num;
999 	gint n_pages;
1000 	gint col;
1001 
1002 	bin_window = gtk_layout_get_bin_window (preview->layout);
1003 
1004 	if (!gtk_cairo_should_draw_window (cr, bin_window))
1005 	{
1006 		return GDK_EVENT_STOP;
1007 	}
1008 
1009 	cairo_save (cr);
1010 
1011 	gtk_cairo_transform_to_window (cr, widget, bin_window);
1012 
1013 	get_tile_size (preview, &tile_width, NULL);
1014 	n_pages = get_n_pages (preview);
1015 
1016 	col = 0;
1017 	page_num = get_first_page_displayed (preview);
1018 
1019 	while (col < preview->n_columns && page_num < n_pages)
1020 	{
1021 		if (!gtk_print_operation_preview_is_selected (preview->gtk_preview, page_num))
1022 		{
1023 			page_num++;
1024 			continue;
1025 		}
1026 
1027 		draw_page (cr,
1028 			   col * tile_width,
1029 			   0,
1030 			   page_num,
1031 			   preview);
1032 
1033 		col++;
1034 		page_num++;
1035 	}
1036 
1037 	cairo_restore (cr);
1038 
1039 	return GDK_EVENT_STOP;
1040 }
1041 
1042 static void
init_last_page_label(GeditPrintPreview * preview)1043 init_last_page_label (GeditPrintPreview *preview)
1044 {
1045 	gchar *str;
1046 
1047 	str = g_strdup_printf ("%d", get_n_pages (preview));
1048 	gtk_label_set_text (preview->last_page_label, str);
1049 	g_free (str);
1050 }
1051 
1052 static void
preview_ready(GtkPrintOperationPreview * gtk_preview,GtkPrintContext * context,GeditPrintPreview * preview)1053 preview_ready (GtkPrintOperationPreview *gtk_preview,
1054 	       GtkPrintContext          *context,
1055 	       GeditPrintPreview        *preview)
1056 {
1057 	init_last_page_label (preview);
1058 	goto_page (preview, 0);
1059 
1060 	set_zoom_factor (preview, 1.0);
1061 
1062 	/* let the default gtklayout handler clear the background */
1063 	g_signal_connect_after (preview->layout,
1064 				"draw",
1065 				G_CALLBACK (preview_draw),
1066 				preview);
1067 
1068 	gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
1069 }
1070 
1071 /* HACK: we need a dummy surface to paginate... can we use something simpler? */
1072 
1073 static cairo_status_t
dummy_write_func(G_GNUC_UNUSED gpointer closure,G_GNUC_UNUSED const guchar * data,G_GNUC_UNUSED guint length)1074 dummy_write_func (G_GNUC_UNUSED gpointer      closure,
1075 		  G_GNUC_UNUSED const guchar *data,
1076 		  G_GNUC_UNUSED guint         length)
1077 {
1078 	return CAIRO_STATUS_SUCCESS;
1079 }
1080 
1081 static cairo_surface_t *
create_preview_surface_platform(GtkPaperSize * paper_size,gdouble * dpi_x,gdouble * dpi_y)1082 create_preview_surface_platform (GtkPaperSize *paper_size,
1083 				 gdouble      *dpi_x,
1084 				 gdouble      *dpi_y)
1085 {
1086 	gdouble width, height;
1087 
1088 	width = gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS);
1089 	height = gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS);
1090 
1091 	*dpi_x = *dpi_y = PRINTER_DPI;
1092 
1093 	return cairo_pdf_surface_create_for_stream (dummy_write_func, NULL,
1094 						    width, height);
1095 }
1096 
1097 static cairo_surface_t *
create_preview_surface(GeditPrintPreview * preview,gdouble * dpi_x,gdouble * dpi_y)1098 create_preview_surface (GeditPrintPreview *preview,
1099 			gdouble           *dpi_x,
1100 			gdouble           *dpi_y)
1101 {
1102 	GtkPageSetup *page_setup;
1103 	GtkPaperSize *paper_size;
1104 
1105 	page_setup = gtk_print_context_get_page_setup (preview->context);
1106 
1107 	/* Note: gtk_page_setup_get_paper_size() swaps width and height for
1108 	 * landscape.
1109 	 */
1110 	paper_size = gtk_page_setup_get_paper_size (page_setup);
1111 
1112 	return create_preview_surface_platform (paper_size, dpi_x, dpi_y);
1113 }
1114 
1115 GtkWidget *
gedit_print_preview_new(GtkPrintOperation * operation,GtkPrintOperationPreview * gtk_preview,GtkPrintContext * context)1116 gedit_print_preview_new (GtkPrintOperation        *operation,
1117 			 GtkPrintOperationPreview *gtk_preview,
1118 			 GtkPrintContext          *context)
1119 {
1120 	GeditPrintPreview *preview;
1121 	cairo_surface_t *surface;
1122 	cairo_t *cr;
1123 	gdouble dpi_x, dpi_y;
1124 
1125 	g_return_val_if_fail (GTK_IS_PRINT_OPERATION (operation), NULL);
1126 	g_return_val_if_fail (GTK_IS_PRINT_OPERATION_PREVIEW (gtk_preview), NULL);
1127 
1128 	preview = g_object_new (GEDIT_TYPE_PRINT_PREVIEW, NULL);
1129 
1130 	preview->operation = g_object_ref (operation);
1131 	preview->gtk_preview = g_object_ref (gtk_preview);
1132 	preview->context = g_object_ref (context);
1133 
1134 	/* FIXME: is this legal?? */
1135 	gtk_print_operation_set_unit (operation, GTK_UNIT_POINTS);
1136 
1137 	g_signal_connect_object (gtk_preview,
1138 				 "ready",
1139 				 G_CALLBACK (preview_ready),
1140 				 preview,
1141 				 0);
1142 
1143 	/* FIXME: we need a cr to paginate... but we can't get the drawing
1144 	 * area surface because it's not there yet... for now I create
1145 	 * a dummy pdf surface.
1146 	 * gtk_print_context_set_cairo_context() should be called in the
1147 	 * got-page-size handler.
1148 	 */
1149 	surface = create_preview_surface (preview, &dpi_x, &dpi_y);
1150 	cr = cairo_create (surface);
1151 	gtk_print_context_set_cairo_context (context, cr, dpi_x, dpi_y);
1152 	cairo_destroy (cr);
1153 	cairo_surface_destroy (surface);
1154 
1155 	return GTK_WIDGET (preview);
1156 }
1157 
1158 /* ex:set ts=8 noet: */
1159