gedit-print-preview.c 26.3 KB
Newer Older
1 2 3 4
/*
 * gedit-print-preview.c
 *
 * Copyright (C) 2008 Paolo Borelli
5
 * Copyright (C) 2015 Sébastien Wilmet
6 7 8 9 10 11 12 13 14 15 16 17
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 20
 */

21 22
#include "gedit-print-preview.h"

23 24 25 26 27 28
#include <math.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>
#include <cairo-pdf.h>

29
#define PRINTER_DPI (72.0)
30
#define TOOLTIP_THRESHOLD 20
31 32 33 34
#define PAGE_PAD 12
#define PAGE_SHADOW_OFFSET 5
#define ZOOM_IN_FACTOR (1.2)
#define ZOOM_OUT_FACTOR (1.0 / ZOOM_IN_FACTOR)
35

36
struct _GeditPrintPreview
37
{
38 39
	GtkGrid parent_instance;

40 41 42 43
	GtkPrintOperation *operation;
	GtkPrintContext *context;
	GtkPrintOperationPreview *gtk_preview;

44 45 46 47 48 49 50 51 52 53 54
	GtkButton *prev_button;
	GtkButton *next_button;
	GtkEntry *page_entry;
	GtkLabel *last_page_label;
	GtkButton *multi_pages_button;
	GtkButton *zoom_one_button;
	GtkButton *zoom_fit_button;
	GtkButton *zoom_in_button;
	GtkButton *zoom_out_button;
	GtkButton *close_button;

55 56 57 58 59 60 61
	/* The GtkLayout is where the pages are drawn. The layout should have
	 * the focus, because key-press-events and scroll-events are handled on
	 * the layout. It is AFAIK not easily possible to handle those events on
	 * the GeditPrintPreview itself because when a toolbar item has the
	 * focus, some key presses (like the arrows) moves the focus to a
	 * sibling toolbar item instead.
	 */
62
	GtkLayout *layout;
63

64
	gdouble scale;
65 66

	/* multipage support */
67
	gint n_columns;
68

69 70 71
	/* FIXME: handle correctly page selection (e.g. print only
	 * page 1-3, 7 and 12.
	 */
72
	guint cur_page; /* starts at 0 */
73

74 75 76
	gint cursor_x;
	gint cursor_y;

77
	guint has_tooltip : 1;
78 79
};

80
G_DEFINE_TYPE (GeditPrintPreview, gedit_print_preview, GTK_TYPE_GRID)
81

82 83 84 85 86
static void
gedit_print_preview_dispose (GObject *object)
{
	GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (object);

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
	if (preview->gtk_preview != NULL)
	{
		GtkPrintOperationPreview *gtk_preview;

		/* Set preview->gtk_preview to NULL because when calling
		 * end_preview() this dispose() function can be run a second
		 * time.
		 */
		gtk_preview = preview->gtk_preview;
		preview->gtk_preview = NULL;

		gtk_print_operation_preview_end_preview (gtk_preview);

		g_object_unref (gtk_preview);
	}

103 104 105 106 107 108
	g_clear_object (&preview->operation);
	g_clear_object (&preview->context);

	G_OBJECT_CLASS (gedit_print_preview_parent_class)->dispose (object);
}

109 110 111
static void
gedit_print_preview_grab_focus (GtkWidget *widget)
{
112
	GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (widget);
113

114
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
115 116
}

117
static void
118 119
gedit_print_preview_class_init (GeditPrintPreviewClass *klass)
{
120
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
121
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
122

123 124
	object_class->dispose = gedit_print_preview_dispose;

125 126
	widget_class->grab_focus = gedit_print_preview_grab_focus;

127 128 129
	/* Bind class to template */
	gtk_widget_class_set_template_from_resource (widget_class,
	                                             "/org/gnome/gedit/ui/gedit-print-preview.ui");
130 131
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, prev_button);
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, next_button);
132
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, page_entry);
133 134 135 136 137 138 139
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, last_page_label);
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, multi_pages_button);
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_one_button);
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_fit_button);
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_in_button);
	gtk_widget_class_bind_template_child (widget_class, GeditPrintPreview, zoom_out_button);
	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 144 145 146 147 148 149 150 151 152
static gint
get_n_pages (GeditPrintPreview *preview)
{
	gint n_pages;

	g_object_get (preview->operation, "n-pages", &n_pages, NULL);

	return n_pages;
}

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
static gdouble
get_screen_dpi (GeditPrintPreview *preview)
{
	GdkScreen *screen;
	gdouble dpi;
	static gboolean warning_shown = FALSE;

	screen = gtk_widget_get_screen (GTK_WIDGET (preview));

	if (screen == NULL)
	{
		return PRINTER_DPI;
	}

	dpi = gdk_screen_get_resolution (screen);
	if (dpi < 30.0 || 600.0 < dpi)
	{
		if (!warning_shown)
		{
			g_warning ("Invalid the x-resolution for the screen, assuming 96dpi");
			warning_shown = TRUE;
		}

		dpi = 96.0;
	}

	return dpi;
}

182 183 184
/* Get the paper size in points: these must be used only
 * after the widget has been mapped and the dpi is known.
 */
185
static gdouble
186 187
get_paper_width (GeditPrintPreview *preview)
{
188 189 190 191 192 193
	GtkPageSetup *page_setup;
	gdouble paper_width;

	page_setup = gtk_print_context_get_page_setup (preview->context);
	paper_width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_INCH);

194
	return paper_width * get_screen_dpi (preview);
195 196
}

197
static gdouble
198 199
get_paper_height (GeditPrintPreview *preview)
{
200 201 202 203 204 205
	GtkPageSetup *page_setup;
	gdouble paper_height;

	page_setup = gtk_print_context_get_page_setup (preview->context);
	paper_height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_INCH);

206
	return paper_height * get_screen_dpi (preview);
207 208
}

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
/* The tile size is the size in pixels of the area where a page will be
 * drawn, including the padding. The size is independent of the
 * orientation.
 */
static void
get_tile_size (GeditPrintPreview *preview,
	       gint              *tile_width,
	       gint              *tile_height)
{
	if (tile_width != NULL)
	{
		*tile_width = 2 * PAGE_PAD + round (preview->scale * get_paper_width (preview));
	}

	if (tile_height != NULL)
	{
		*tile_height = 2 * PAGE_PAD + round (preview->scale * get_paper_height (preview));
	}
}

229
static void
230 231 232
get_adjustments (GeditPrintPreview  *preview,
		 GtkAdjustment     **hadj,
		 GtkAdjustment     **vadj)
233
{
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
	*hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (preview->layout));
	*vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (preview->layout));
}

static void
update_layout_size (GeditPrintPreview *preview)
{
	gint tile_width;
	gint tile_height;

	get_tile_size (preview, &tile_width, &tile_height);

	/* force size of the drawing area to make the scrolled window work */
	gtk_layout_set_size (preview->layout,
	                     tile_width * preview->n_columns,
	                     tile_height);

	gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
252 253 254
}

/* Zoom should always be set with one of these two function
255 256
 * so that the tile size is properly updated.
 */
257 258 259

static void
set_zoom_factor (GeditPrintPreview *preview,
260
		 gdouble            zoom)
261
{
262
	preview->scale = zoom;
263 264 265 266 267 268
	update_layout_size (preview);
}

static void
set_zoom_fit_to_size (GeditPrintPreview *preview)
{
269
	GtkAdjustment *hadj, *vadj;
270
	gdouble width, height;
271
	gdouble paper_width, paper_height;
272
	gdouble zoomx, zoomy;
273

274 275
	get_adjustments (preview, &hadj, &vadj);

276 277
	width = gtk_adjustment_get_page_size (hadj);
	height = gtk_adjustment_get_page_size (vadj);
278

279
	width /= preview->n_columns;
280

281 282
	paper_width = get_paper_width (preview);
	paper_height = get_paper_height (preview);
283

284 285 286 287
	zoomx = MAX (1, width - 2 * PAGE_PAD) / paper_width;
	zoomy = MAX (1, height - 2 * PAGE_PAD) / paper_height;

	set_zoom_factor (preview, zoomx <= zoomy ? zoomx : zoomy);
288 289 290 291 292
}

static void
zoom_in (GeditPrintPreview *preview)
{
293
	set_zoom_factor (preview, preview->scale * ZOOM_IN_FACTOR);
294 295 296 297 298
}

static void
zoom_out (GeditPrintPreview *preview)
{
299
	set_zoom_factor (preview, preview->scale * ZOOM_OUT_FACTOR);
300 301 302
}

static void
Garrett Regier's avatar
Garrett Regier committed
303
goto_page (GeditPrintPreview *preview,
304
           gint               page)
305
{
306
	gchar *page_str;
307
	gint n_pages;
308

309 310 311
	page_str = g_strdup_printf ("%d", page + 1);
	gtk_entry_set_text (preview->page_entry, page_str);
	g_free (page_str);
312

313 314
	n_pages = get_n_pages (preview);

315
	gtk_widget_set_sensitive (GTK_WIDGET (preview->prev_button),
316 317 318
	                          page > 0 &&
				  n_pages > 1);

319
	gtk_widget_set_sensitive (GTK_WIDGET (preview->next_button),
320 321
	                          page < (n_pages - 1) &&
	                          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 334 335 336 337 338 339 340 341 342
	}
}

static void
prev_button_clicked (GtkWidget         *button,
		     GeditPrintPreview *preview)
{
	GdkEvent *event;
	gint page;

	event = gtk_get_current_event ();

	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 354
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));

355 356 357 358 359 360 361 362 363
	gdk_event_free (event);
}

static void
next_button_clicked (GtkWidget         *button,
		     GeditPrintPreview *preview)
{
	GdkEvent *event;
	gint page;
364
	gint n_pages = get_n_pages (preview);
365 366 367 368

	event = gtk_get_current_event ();

	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 380
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));

381 382 383 384 385 386 387 388 389
	gdk_event_free (event);
}

static void
page_entry_activated (GtkEntry          *entry,
		      GeditPrintPreview *preview)
{
	const gchar *text;
	gint page;
390
	gint n_pages = get_n_pages (preview);
391 392 393

	text = gtk_entry_get_text (entry);

394
	page = CLAMP (atoi (text), 1, n_pages) - 1;
395 396
	goto_page (preview, page);

397
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
398 399 400 401 402 403 404 405
}

static void
page_entry_insert_text (GtkEditable *editable,
			const gchar *text,
			gint         length,
			gint        *position)
{
406
	const gchar *end;
407
	const gchar *p;
408 409 410

	end = text + length;

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 416 417 418 419 420
		{
			g_signal_stop_emission_by_name (editable, "insert-text");
			break;
		}
	}
}

421
static gboolean
422
page_entry_focus_out (GtkEntry          *entry,
423 424 425 426 427 428
		      GdkEventFocus     *event,
		      GeditPrintPreview *preview)
{
	const gchar *text;
	gint page;

429
	text = gtk_entry_get_text (entry);
430 431 432
	page = atoi (text) - 1;

	/* Reset the page number only if really needed */
433
	if (page != preview->cur_page)
434 435 436
	{
		gchar *str;

437
		str = g_strdup_printf ("%d", preview->cur_page + 1);
438
		gtk_entry_set_text (entry, str);
439 440 441
		g_free (str);
	}

442
	return GDK_EVENT_PROPAGATE;
443 444 445
}

static void
446
on_1x1_clicked (GtkMenuItem       *item,
Garrett Regier's avatar
Garrett Regier committed
447
		GeditPrintPreview *preview)
448
{
449 450
	preview->n_columns = 1;
	update_layout_size (preview);
451
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
452 453 454
}

static void
455
on_1x2_clicked (GtkMenuItem       *item,
Garrett Regier's avatar
Garrett Regier committed
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
464 465
multi_pages_button_clicked (GtkWidget         *button,
			    GeditPrintPreview *preview)
466
{
467 468
	GtkWidget *menu;
	GtkWidget *item;
469

470 471 472 473
	menu = gtk_menu_new ();
	gtk_widget_show (menu);
	g_signal_connect (menu,
			  "selection-done",
474
			  G_CALLBACK (gtk_widget_destroy),
475
			  NULL);
476

477 478 479 480
	item = gtk_menu_item_new_with_label ("1x1");
	gtk_widget_show (item);
	gtk_menu_attach (GTK_MENU (menu), item, 0, 1, 0, 1);
	g_signal_connect (item, "activate", G_CALLBACK (on_1x1_clicked), preview);
481

482 483 484 485
	item = gtk_menu_item_new_with_label ("1x2");
	gtk_widget_show (item);
	gtk_menu_attach (GTK_MENU (menu), item, 1, 2, 0, 1);
	g_signal_connect (item, "activate", G_CALLBACK (on_1x2_clicked), preview);
486

487
	gtk_menu_popup (GTK_MENU (menu),
488 489 490 491 492 493 494 495 496
			NULL, NULL, NULL, preview, 0,
			GDK_CURRENT_TIME);
}

static void
zoom_one_button_clicked (GtkWidget         *button,
			 GeditPrintPreview *preview)
{
	set_zoom_factor (preview, 1);
497
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
498 499 500 501 502 503 504
}

static void
zoom_fit_button_clicked (GtkWidget         *button,
			 GeditPrintPreview *preview)
{
	set_zoom_fit_to_size (preview);
505
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
506 507 508 509 510 511 512
}

static void
zoom_in_button_clicked (GtkWidget         *button,
			GeditPrintPreview *preview)
{
	zoom_in (preview);
513
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
514 515 516 517 518 519 520
}

static void
zoom_out_button_clicked (GtkWidget         *button,
			 GeditPrintPreview *preview)
{
	zoom_out (preview);
521
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
522 523 524 525 526 527 528 529 530
}

static void
close_button_clicked (GtkWidget         *button,
		      GeditPrintPreview *preview)
{
	gtk_widget_destroy (GTK_WIDGET (preview));
}

531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
static gboolean
scroll_event_activated (GtkWidget         *widget,
		        GdkEventScroll    *event,
		        GeditPrintPreview *preview)
{
	if (event->state & GDK_CONTROL_MASK)
	{
		if ((event->direction == GDK_SCROLL_UP) ||
		    (event->direction == GDK_SCROLL_SMOOTH &&
		     event->delta_y < 0))
		{
			zoom_in (preview);
		}
		else if ((event->direction == GDK_SCROLL_DOWN) ||
		         (event->direction == GDK_SCROLL_SMOOTH &&
		          event->delta_y > 0))
		{
			zoom_out (preview);
		}

		return GDK_EVENT_STOP;
	}

	return GDK_EVENT_PROPAGATE;
}

557 558 559
static gint
get_first_page_displayed (GeditPrintPreview *preview)
{
560
	return preview->cur_page - (preview->cur_page % preview->n_columns);
561 562
}

563
/* Returns the page number (starting from 0) or -1 if no page. */
564 565
static gint
get_page_at_coords (GeditPrintPreview *preview,
566 567
                    gint               x,
                    gint               y)
568
{
569
	gint tile_width, tile_height;
570
	GtkAdjustment *hadj, *vadj;
571
	gint col, page;
572

573 574 575
	get_tile_size (preview, &tile_width, &tile_height);

	if (tile_height <= 0 || tile_width <= 0)
576
	{
577
		return -1;
578
	}
579

580
	get_adjustments (preview, &hadj, &vadj);
581 582 583

	x += gtk_adjustment_get_value (hadj);
	y += gtk_adjustment_get_value (vadj);
584

585
	col = x / tile_width;
586

587
	if (col >= preview->n_columns || y > tile_height)
588
	{
589
		return -1;
590
	}
591

592
	page = get_first_page_displayed (preview) + col;
593

594
	if (page >= get_n_pages (preview))
595
	{
596
		return -1;
597
	}
598

599 600 601 602
	/* FIXME: we could try to be picky and check if we actually are inside
	 * the page (i.e. not in the padding or shadow).
	 */
	return page;
603 604
}

605 606 607 608 609 610 611 612 613 614 615 616
static gboolean
on_preview_layout_motion_notify (GtkWidget         *widget,
                                 GdkEvent          *event,
                                 GeditPrintPreview *preview)
{
	gint temp_x;
	gint temp_y;
	gint diff_x;
	gint diff_y;

	temp_x = ((GdkEventMotion*)event)->x;
	temp_y = ((GdkEventMotion*)event)->y;
617 618
	diff_x = abs (temp_x - preview->cursor_x);
	diff_y = abs (temp_y - preview->cursor_y);
619 620 621

	if ((diff_x >= TOOLTIP_THRESHOLD) || (diff_y >= TOOLTIP_THRESHOLD))
	{
622 623 624
		preview->has_tooltip = FALSE;
		preview->cursor_x = temp_x;
		preview->cursor_y = temp_y;
625 626 627
	}
	else
	{
628
		preview->has_tooltip = TRUE;
629 630 631 632 633
	}

	return GDK_EVENT_STOP;
}

634 635 636 637 638 639 640 641
static gboolean
preview_layout_query_tooltip (GtkWidget         *widget,
			      gint               x,
			      gint               y,
			      gboolean           keyboard_tip,
			      GtkTooltip        *tooltip,
			      GeditPrintPreview *preview)
{
642
	if (preview->has_tooltip)
643
	{
644 645 646 647 648 649
		gint page;
		gchar *tip;

		page = get_page_at_coords (preview, x, y);
		if (page < 0)
		{
650
			return FALSE;
651 652 653 654
		}

		tip = g_strdup_printf (_("Page %d of %d"),
				       page + 1,
655
				       get_n_pages (preview));
656

657 658 659 660 661 662 663
		gtk_tooltip_set_text (tooltip, tip);
		g_free (tip);

		return TRUE;
	}
	else
	{
664
		preview->has_tooltip = TRUE;
665 666
		return FALSE;
	}
667 668 669 670 671 672 673
}

static gint
preview_layout_key_press (GtkWidget         *widget,
			  GdkEventKey       *event,
			  GeditPrintPreview *preview)
{
674
	GtkAdjustment *hadj, *vadj;
675
	gdouble x, y;
676 677
	gdouble hlower, vlower;
	gdouble hupper, vupper;
678
	gdouble visible_width, visible_height;
679
	gdouble hstep, vstep;
680
	gint n_pages;
681
	gboolean do_move = FALSE;
682

683
	get_adjustments (preview, &hadj, &vadj);
684

685 686
	x = gtk_adjustment_get_value (hadj);
	y = gtk_adjustment_get_value (vadj);
687

688 689
	hlower = gtk_adjustment_get_lower (hadj);
	vlower = gtk_adjustment_get_lower (vadj);
690

691 692 693
	hupper = gtk_adjustment_get_upper (hadj);
	vupper = gtk_adjustment_get_upper (vadj);

694 695
	visible_width = gtk_adjustment_get_page_size (hadj);
	visible_height = gtk_adjustment_get_page_size (vadj);
696

697 698 699
	hstep = 10;
	vstep = 10;

700 701
	n_pages = get_n_pages (preview);

Garrett Regier's avatar
Garrett Regier committed
702 703 704 705 706
	switch (event->keyval)
	{
		case '1':
			set_zoom_fit_to_size (preview);
			break;
707

Garrett Regier's avatar
Garrett Regier committed
708 709
		case '+':
		case '=':
710
		case GDK_KEY_KP_Add:
Garrett Regier's avatar
Garrett Regier committed
711 712
			zoom_in (preview);
			break;
713

Garrett Regier's avatar
Garrett Regier committed
714 715
		case '-':
		case '_':
716
		case GDK_KEY_KP_Subtract:
Garrett Regier's avatar
Garrett Regier committed
717 718
			zoom_out (preview);
			break;
719

720 721
		case GDK_KEY_KP_Right:
		case GDK_KEY_Right:
Garrett Regier's avatar
Garrett Regier committed
722
			if (event->state & GDK_SHIFT_MASK)
723
				x = hupper - visible_width;
Garrett Regier's avatar
Garrett Regier committed
724
			else
725
				x = MIN (hupper - visible_width, x + hstep);
726
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
727
			break;
728

729 730
		case GDK_KEY_KP_Left:
		case GDK_KEY_Left:
Garrett Regier's avatar
Garrett Regier committed
731 732 733 734
			if (event->state & GDK_SHIFT_MASK)
				x = hlower;
			else
				x = MAX (hlower, x - hstep);
735
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
736
			break;
737

738 739
		case GDK_KEY_KP_Up:
		case GDK_KEY_Up:
Garrett Regier's avatar
Garrett Regier committed
740 741
			if (event->state & GDK_SHIFT_MASK)
				goto page_up;
742

Garrett Regier's avatar
Garrett Regier committed
743
			y = MAX (vlower, y - vstep);
744
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
745
			break;
746

747 748
		case GDK_KEY_KP_Down:
		case GDK_KEY_Down:
Garrett Regier's avatar
Garrett Regier committed
749 750
			if (event->state & GDK_SHIFT_MASK)
				goto page_down;
751

752
			y = MIN (vupper - visible_height, y + vstep);
753
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
754
			break;
755

756 757 758 759 760
		case GDK_KEY_KP_Page_Up:
		case GDK_KEY_Page_Up:
		case GDK_KEY_Delete:
		case GDK_KEY_KP_Delete:
		case GDK_KEY_BackSpace:
Garrett Regier's avatar
Garrett Regier committed
761 762
		page_up:
			if (y <= vlower)
763
			{
764
				if (preview->cur_page > 0)
Garrett Regier's avatar
Garrett Regier committed
765
				{
766
					goto_page (preview, preview->cur_page - 1);
767
					y = (vupper - visible_height);
Garrett Regier's avatar
Garrett Regier committed
768
				}
769
			}
Garrett Regier's avatar
Garrett Regier committed
770
			else
771
			{
772
				y = vlower;
773
			}
774
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
775
			break;
776

777 778
		case GDK_KEY_KP_Page_Down:
		case GDK_KEY_Page_Down:
Garrett Regier's avatar
Garrett Regier committed
779 780
		case ' ':
		page_down:
781
			if (y >= (vupper - visible_height))
Garrett Regier's avatar
Garrett Regier committed
782
			{
783
				if (preview->cur_page < n_pages - 1)
Garrett Regier's avatar
Garrett Regier committed
784
				{
785
					goto_page (preview, preview->cur_page + 1);
Garrett Regier's avatar
Garrett Regier committed
786 787 788 789 790
					y = vlower;
				}
			}
			else
			{
791
				y = (vupper - visible_height);
Garrett Regier's avatar
Garrett Regier committed
792
			}
793
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
794
			break;
795

796 797
		case GDK_KEY_KP_Home:
		case GDK_KEY_Home:
Garrett Regier's avatar
Garrett Regier committed
798
			goto_page (preview, 0);
799 800
			y = vlower;
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
801
			break;
802

803 804
		case GDK_KEY_KP_End:
		case GDK_KEY_End:
805
			goto_page (preview, n_pages - 1);
806 807
			y = vlower;
			do_move = TRUE;
Garrett Regier's avatar
Garrett Regier committed
808
			break;
809

810
		case GDK_KEY_Escape:
811
			gtk_widget_destroy (GTK_WIDGET (preview));
Garrett Regier's avatar
Garrett Regier committed
812
			break;
813

Garrett Regier's avatar
Garrett Regier committed
814 815 816
		case 'p':
			if (event->state & GDK_MOD1_MASK)
			{
817
				gtk_widget_grab_focus (GTK_WIDGET (preview->page_entry));
Garrett Regier's avatar
Garrett Regier committed
818 819
			}
			break;
820

Garrett Regier's avatar
Garrett Regier committed
821
		default:
822
			return GDK_EVENT_PROPAGATE;
823 824
	}

825
	if (do_move)
826 827 828 829 830
	{
		gtk_adjustment_set_value (hadj, x);
		gtk_adjustment_set_value (vadj, y);
	}

831
	return GDK_EVENT_STOP;
832 833 834
}

static void
835
gedit_print_preview_init (GeditPrintPreview *preview)
836
{
837 838
	preview->cur_page = 0;
	preview->scale = 1.0;
839
	preview->n_columns = 1;
840 841 842
	preview->cursor_x = 0;
	preview->cursor_y = 0;
	preview->has_tooltip = TRUE;
843

844
	gtk_widget_init_template (GTK_WIDGET (preview));
845

846
	g_signal_connect (preview->prev_button,
847 848 849
			  "clicked",
			  G_CALLBACK (prev_button_clicked),
			  preview);
850

851
	g_signal_connect (preview->next_button,
852 853 854
			  "clicked",
			  G_CALLBACK (next_button_clicked),
			  preview);
855

856
	g_signal_connect (preview->page_entry,
857 858 859
			  "activate",
			  G_CALLBACK (page_entry_activated),
			  preview);
860

861
	g_signal_connect (preview->page_entry,
862 863 864
			  "insert-text",
			  G_CALLBACK (page_entry_insert_text),
			  NULL);
865

866
	g_signal_connect (preview->page_entry,
867 868 869
			  "focus-out-event",
			  G_CALLBACK (page_entry_focus_out),
			  preview);
870

871
	g_signal_connect (preview->multi_pages_button,
872
			  "clicked",
873
			  G_CALLBACK (multi_pages_button_clicked),
874
			  preview);
875

876
	g_signal_connect (preview->zoom_one_button,
877 878 879
			  "clicked",
			  G_CALLBACK (zoom_one_button_clicked),
			  preview);
880

881
	g_signal_connect (preview->zoom_fit_button,
882 883 884
			  "clicked",
			  G_CALLBACK (zoom_fit_button_clicked),
			  preview);
885

886
	g_signal_connect (preview->zoom_in_button,
887 888 889
			  "clicked",
			  G_CALLBACK (zoom_in_button_clicked),
			  preview);
890

891
	g_signal_connect (preview->zoom_out_button,
892 893 894
			  "clicked",
			  G_CALLBACK (zoom_out_button_clicked),
			  preview);
895

896
	g_signal_connect (preview->close_button,
897 898
			  "clicked",
			  G_CALLBACK (close_button_clicked),
899 900
			  preview);

901
	g_signal_connect (preview->layout,
902 903 904
			  "query-tooltip",
			  G_CALLBACK (preview_layout_query_tooltip),
			  preview);
905

906
	g_signal_connect (preview->layout,
907 908 909
			  "key-press-event",
			  G_CALLBACK (preview_layout_key_press),
			  preview);
910

911
	g_signal_connect (preview->layout,
912 913 914
			  "scroll-event",
			  G_CALLBACK (scroll_event_activated),
			  preview);
915

916
	/* hide the tooltip once we move the cursor, since gtk does not do it for us */
917
	g_signal_connect (preview->layout,
918 919 920
			  "motion-notify-event",
			  G_CALLBACK (on_preview_layout_motion_notify),
			  preview);
921

922
	gtk_widget_grab_focus (GTK_WIDGET (preview->layout));
923 924 925
}

static void
926 927 928
draw_page_content (cairo_t           *cr,
		   gint               page_number,
		   GeditPrintPreview *preview)
929
{
930 931
	gdouble dpi;

932
	/* scale to the desired size */
933
	cairo_scale (cr, preview->scale, preview->scale);
934

935 936
	dpi = get_screen_dpi (preview);
	gtk_print_context_set_cairo_context (preview->context, cr, dpi, dpi);
937

938 939
	gtk_print_operation_preview_render_page (preview->gtk_preview,
	                                         page_number);
940 941 942 943 944
}

/* For the frame, we scale and rotate manually, since
 * the line width should not depend on the zoom and
 * the drop shadow should be on the bottom right no matter
945 946
 * the orientation.
 */
947
static void
948 949
draw_page_frame (cairo_t           *cr,
		 GeditPrintPreview *preview)
950
{
951 952
	gdouble width;
	gdouble height;
953

954 955
	width = get_paper_width (preview) * preview->scale;
	height = get_paper_height (preview) * preview->scale;
956 957 958 959 960

	/* drop shadow */
	cairo_set_source_rgb (cr, 0, 0, 0);
	cairo_rectangle (cr,
			 PAGE_SHADOW_OFFSET, PAGE_SHADOW_OFFSET,
961
			 width, height);
962 963 964 965 966 967
	cairo_fill (cr);

	/* page frame */
	cairo_set_source_rgb (cr, 1, 1, 1);
	cairo_rectangle (cr,
			 0, 0,
968
			 width, height);
969 970 971 972 973 974 975 976
	cairo_fill_preserve (cr);
	cairo_set_source_rgb (cr, 0, 0, 0);
	cairo_set_line_width (cr, 1);
	cairo_stroke (cr);
}

static void
draw_page (cairo_t           *cr,
977 978 979
	   gdouble            x,
	   gdouble            y,
	   gint               page_number,
980 981 982 983 984 985 986 987 988 989 990 991 992 993
	   GeditPrintPreview *preview)
{
	cairo_save (cr);

	/* move to the page top left corner */
	cairo_translate (cr, x + PAGE_PAD, y + PAGE_PAD);

	draw_page_frame (cr, preview);
	draw_page_content (cr, page_number, preview);

	cairo_restore (cr);
}

static gboolean
994 995 996
preview_draw (GtkWidget         *widget,
	      cairo_t           *cr,
	      GeditPrintPreview *preview)
997
{
998
	GdkWindow *bin_window;
999
	gint tile_width;
1000
	gint page_num;
1001
	gint n_pages;
1002
	gint col;
1003

1004
	bin_window = gtk_layout_get_bin_window (preview->layout);
1005

1006
	if (!gtk_cairo_should_draw_window (cr, bin_window))
1007
	{
1008 1009 1010 1011
		return GDK_EVENT_STOP;
	}

	cairo_save (cr);
1012

1013
	gtk_cairo_transform_to_window (cr, widget, bin_window);
1014

1015
	get_tile_size (preview, &tile_width, NULL);
1016
	n_pages = get_n_pages (preview);
1017

1018
	col = 0;
1019
	page_num = get_first_page_displayed (preview);
1020

1021
	while (col < preview->n_columns && page_num < n_pages)
1022
	{
1023
		if (!gtk_print_operation_preview_is_selected (preview->gtk_preview, page_num))
1024
		{
1025
			page_num++;
1026 1027
			continue;
		}
1028

1029
		draw_page (cr,
1030
			   col * tile_width,
1031 1032 1033
			   0,
			   page_num,
			   preview);
1034

1035
		col++;
1036
		page_num++;
1037 1038
	}

1039 1040
	cairo_restore (cr);

1041
	return GDK_EVENT_STOP;
1042 1043 1044
}

static void
1045
init_last_page_label (GeditPrintPreview *preview)
1046 1047 1048
{
	gchar *str;

1049
	str = g_strdup_printf ("%d", get_n_pages (preview));
1050
	gtk_label_set_text (preview->last_page_label, str);
1051 1052 1053 1054 1055 1056 1057 1058
	g_free (str);
}

static void
preview_ready (GtkPrintOperationPreview *gtk_preview,
	       GtkPrintContext          *context,
	       GeditPrintPreview        *preview)
{
1059
	init_last_page_label (preview);
1060 1061 1062 1063 1064
	goto_page (preview, 0);

	set_zoom_factor (preview, 1.0);

	/* let the default gtklayout handler clear the background */
1065
	g_signal_connect_after (preview->layout,
1066 1067
				"draw",
				G_CALLBACK (preview_draw),
1068 1069
				preview);

1070
	gtk_widget_queue_draw (GTK_WIDGET (preview->layout));
1071 1072 1073 1074 1075 1076 1077 1078 1079
}

/* HACK: we need a dummy surface to paginate... can we use something simpler? */

static cairo_status_t
dummy_write_func (G_GNUC_UNUSED gpointer      closure,
		  G_GNUC_UNUSED const guchar *data,
		  G_GNUC_UNUSED guint         length)
{
1080
	return CAIRO_STATUS_SUCCESS;
1081 1082 1083 1084
}

static cairo_surface_t *
create_preview_surface_platform (GtkPaperSize *paper_size,
1085 1086
				 gdouble      *dpi_x,
				 gdouble      *dpi_y)
1087
{
1088
	gdouble width, height;
1089 1090 1091

	width = gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS);
	height = gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS);
1092

1093
	*dpi_x = *dpi_y = PRINTER_DPI;
1094

1095 1096
	return cairo_pdf_surface_create_for_stream (dummy_write_func, NULL,
						    width, height);
1097 1098 1099 1100
}

static cairo_surface_t *
create_preview_surface (GeditPrintPreview *preview,
1101 1102
			gdouble           *dpi_x,
			gdouble           *dpi_y)
1103
{
1104 1105 1106
	GtkPageSetup *page_setup;
	GtkPaperSize *paper_size;

1107
	page_setup = gtk_print_context_get_page_setup (preview->context);
1108

1109 1110 1111
	/* Note: gtk_page_setup_get_paper_size() swaps width and height for
	 * landscape.
	 */
1112
	paper_size = gtk_page_setup_get_paper_size (page_setup);
1113

1114
	return create_preview_surface_platform (paper_size, dpi_x, dpi_y);
1115 1116 1117
}

GtkWidget *
1118
gedit_print_preview_new (GtkPrintOperation        *operation,
1119 1120 1121 1122 1123 1124
			 GtkPrintOperationPreview *gtk_preview,
			 GtkPrintContext          *context)
{
	GeditPrintPreview *preview;
	cairo_surface_t *surface;
	cairo_t *cr;
1125
	gdouble dpi_x, dpi_y;
1126

1127
	g_return_val_if_fail (GTK_IS_PRINT_OPERATION (operation), NULL);
1128 1129 1130 1131
	g_return_val_if_fail (GTK_IS_PRINT_OPERATION_PREVIEW (gtk_preview), NULL);

	preview = g_object_new (GEDIT_TYPE_PRINT_PREVIEW, NULL);

1132
	preview->operation = g_object_ref (operation);
1133 1134
	preview->gtk_preview = g_object_ref (gtk_preview);
	preview->context = g_object_ref (context);
1135 1136

	/* FIXME: is this legal?? */
1137
	gtk_print_operation_set_unit (operation, GTK_UNIT_POINTS);
1138

1139 1140 1141 1142 1143 1144
	g_signal_connect_object (gtk_preview,
				 "ready",
				 G_CALLBACK (preview_ready),
				 preview,
				 0);

1145
	/* FIXME: we need a cr to paginate... but we can't get the drawing
1146
	 * area surface because it's not there yet... for now I create
1147 1148 1149 1150
	 * a dummy pdf surface.
	 * gtk_print_context_set_cairo_context() should be called in the
	 * got-page-size handler.
	 */
1151 1152 1153 1154 1155 1156 1157 1158 1159
	surface = create_preview_surface (preview, &dpi_x, &dpi_y);
	cr = cairo_create (surface);
	gtk_print_context_set_cairo_context (context, cr, dpi_x, dpi_y);
	cairo_destroy (cr);
	cairo_surface_destroy (surface);

	return GTK_WIDGET (preview);
}

1160
/* ex:set ts=8 noet: */