[evince] [libview] Add EvViewPresentation to implement presentation mode



commit 6d690c345c434c074333fa0971af0406eeb2719e
Author: Carlos Garcia Campos <carlosgc gnome org>
Date:   Sun Jan 3 18:27:30 2010 +0100

    [libview] Add EvViewPresentation to implement presentation mode
    
    It's a new view that contains all the EvView features that are specific
    to presentation mode.

 libview/Makefile.am            |    6 +-
 libview/ev-view-presentation.c | 1415 ++++++++++++++++++++++++++++++++++++++++
 libview/ev-view-presentation.h |   50 ++
 3 files changed, 1469 insertions(+), 2 deletions(-)
---
diff --git a/libview/Makefile.am b/libview/Makefile.am
index 95d9f29..b4edc57 100644
--- a/libview/Makefile.am
+++ b/libview/Makefile.am
@@ -15,9 +15,10 @@ INST_H_FILES = 				\
 	ev-document-model.h		\
 	ev-jobs.h			\
 	ev-job-scheduler.h		\
-	ev-print-operation.h	\
+	ev-print-operation.h	        \
 	ev-stock-icons.h		\
 	ev-view.h			\
+	ev-view-presentation.h		\
 	ev-view-type-builtins.h
 
 headerdir = $(includedir)/evince/$(EV_API_VERSION)/libview
@@ -30,7 +31,7 @@ libevview_la_SOURCES = 			\
 	ev-job-scheduler.c		\
 	ev-page-cache.c			\
 	ev-pixbuf-cache.c		\
-	ev-print-operation.c	\
+	ev-print-operation.c	        \
 	ev-stock-icons.c		\
 	ev-timeline.c			\
 	ev-transition-animation.c	\
@@ -38,6 +39,7 @@ libevview_la_SOURCES = 			\
 	ev-view-accessible.c		\
 	ev-view-marshal.c		\
 	ev-view-cursor.c		\
+	ev-view-presentation.c		\
 	ev-view-type-builtins.c		\
 	$(NOINST_H_FILES)		\
 	$(INST_H_FILES)
diff --git a/libview/ev-view-presentation.c b/libview/ev-view-presentation.c
new file mode 100644
index 0000000..8f91869
--- /dev/null
+++ b/libview/ev-view-presentation.c
@@ -0,0 +1,1415 @@
+/* ev-view-presentation.c
+ *  this file is part of evince, a gnome document viewer
+ *
+ * Copyright (C) 2010 Carlos Garcia Campos <carlosgc gnome org>
+ *
+ * Evince 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.
+ *
+ * Evince 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "ev-view-presentation.h"
+#include "ev-jobs.h"
+#include "ev-job-scheduler.h"
+#include "ev-transition-animation.h"
+#include "ev-view-cursor.h"
+#include "ev-page-cache.h"
+#include "ev-mapping.h"
+
+enum {
+	PROP_0,
+	PROP_DOCUMENT,
+	PROP_CURRENT_PAGE,
+	PROP_ROTATION
+};
+
+enum {
+	CHANGE_PAGE,
+	N_SIGNALS
+};
+
+typedef enum {
+	EV_PRESENTATION_NORMAL,
+	EV_PRESENTATION_BLACK,
+	EV_PRESENTATION_WHITE,
+	EV_PRESENTATION_END
+} EvPresentationState;
+
+struct _EvViewPresentation
+{
+	GtkWidget base;
+
+	guint                  current_page;
+	EvDocument            *document;
+	guint                  rotation;
+	EvPresentationState    state;
+	gdouble                scale;
+
+	/* Cursors */
+	EvViewCursor           cursor;
+	guint                  hide_cursor_timeout_id;
+
+	/* Goto Window */
+	GtkWidget             *goto_window;
+	GtkWidget             *goto_entry;
+
+	/* Page Transition */
+	guint                  trans_timeout_id;
+
+	/* Animations */
+	gboolean               enable_animations;
+	EvTransitionAnimation *animation;
+
+	/* Links */
+	EvPageCache           *page_cache;
+
+	EvJob *prev_job;
+	EvJob *curr_job;
+	EvJob *next_job;
+};
+
+struct _EvViewPresentationClass
+{
+	GtkWidgetClass base_class;
+
+	/* signals */
+	void (* change_page) (EvViewPresentation *pview,
+			      GtkScrollType       scroll);
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+static void ev_view_presentation_next_page               (EvViewPresentation *pview);
+static void ev_view_presentation_previous_page           (EvViewPresentation *pview);
+static void ev_view_presentation_set_cursor_for_location (EvViewPresentation *pview,
+							  gdouble             x,
+							  gdouble             y);
+
+#define HIDE_CURSOR_TIMEOUT 5
+
+G_DEFINE_TYPE (EvViewPresentation, ev_view_presentation, GTK_TYPE_WIDGET)
+
+static void
+ev_view_presentation_set_normal (EvViewPresentation *pview)
+{
+	GtkWidget *widget = GTK_WIDGET (pview);
+
+	if (pview->state == EV_PRESENTATION_NORMAL)
+		return;
+
+	pview->state = EV_PRESENTATION_NORMAL;
+	gdk_window_set_background (widget->window, &widget->style->black);
+	gtk_widget_queue_draw (widget);
+}
+
+static void
+ev_view_presentation_set_black (EvViewPresentation *pview)
+{
+	GtkWidget *widget = GTK_WIDGET (pview);
+
+	if (pview->state == EV_PRESENTATION_BLACK)
+		return;
+
+	pview->state = EV_PRESENTATION_BLACK;
+	gdk_window_set_background (widget->window, &widget->style->black);
+	gtk_widget_queue_draw (widget);
+}
+
+static void
+ev_view_presentation_set_white (EvViewPresentation *pview)
+{
+	GtkWidget *widget = GTK_WIDGET (pview);
+
+	if (pview->state == EV_PRESENTATION_WHITE)
+		return;
+
+	pview->state = EV_PRESENTATION_WHITE;
+	gdk_window_set_background (widget->window, &widget->style->white);
+	gtk_widget_queue_draw (widget);
+}
+
+static void
+ev_view_presentation_set_end (EvViewPresentation *pview)
+{
+	GtkWidget *widget = GTK_WIDGET (pview);
+
+	if (pview->state == EV_PRESENTATION_END)
+		return;
+
+	pview->state = EV_PRESENTATION_END;
+	gtk_widget_queue_draw (widget);
+}
+
+static gdouble
+ev_view_presentation_get_scale_for_page (EvViewPresentation *pview,
+					 guint               page)
+{
+	gdouble width, height;
+
+	ev_document_get_page_size (pview->document, page, &width, &height);
+
+	if (pview->rotation == 90 || pview->rotation == 270)
+		return GTK_WIDGET (pview)->allocation.height / width;
+	else
+		return GTK_WIDGET (pview)->allocation.height / height;
+}
+
+static void
+ev_view_presentation_update_scale (EvViewPresentation *pview)
+{
+	if (ev_document_is_page_size_uniform (pview->document) && pview->scale != 0)
+		return;
+
+	pview->scale = ev_view_presentation_get_scale_for_page (pview, pview->current_page);
+}
+
+static void
+ev_view_presentation_get_page_area (EvViewPresentation *pview,
+				    GdkRectangle       *area)
+{
+	GtkWidget *widget = GTK_WIDGET (pview);
+	gdouble    width, height;
+
+	ev_document_get_page_size (pview->document,
+				   pview->current_page,
+				   &width, &height);
+
+	if (pview->rotation == 90 || pview->rotation == 270) {
+		gdouble tmp;
+
+		tmp = width;
+		width = height;
+		height = tmp;
+	}
+
+	width *= pview->scale;
+	height *= pview->scale;
+
+	area->x = (MAX (0, widget->allocation.width - width)) / 2;
+	area->y = (MAX (0, widget->allocation.height - height)) / 2;
+	area->width = width;
+	area->height = height;
+}
+
+/* Page Transition */
+static gboolean
+transition_next_page (EvViewPresentation *pview)
+{
+	ev_view_presentation_next_page (pview);
+
+	return FALSE;
+}
+
+static void
+ev_view_presentation_transition_stop (EvViewPresentation *pview)
+{
+	if (pview->trans_timeout_id > 0)
+		g_source_remove (pview->trans_timeout_id);
+	pview->trans_timeout_id = 0;
+}
+
+static void
+ev_view_presentation_transition_start (EvViewPresentation *pview)
+{
+	gdouble duration;
+
+	if (!EV_IS_DOCUMENT_TRANSITION (pview->document))
+		return;
+
+	ev_view_presentation_transition_stop (pview);
+
+	duration = ev_document_transition_get_page_duration (EV_DOCUMENT_TRANSITION (pview->document),
+							     pview->current_page);
+	if (duration > 0) {
+		        pview->trans_timeout_id =
+				g_timeout_add_seconds (duration,
+						       (GSourceFunc) transition_next_page,
+						       pview);
+	}
+}
+
+/* Animations */
+static void
+ev_view_presentation_animation_cancel (EvViewPresentation *pview)
+{
+	if (pview->animation) {
+		g_object_unref (pview->animation);
+		pview->animation = NULL;
+	}
+}
+
+static void
+ev_view_presentation_transition_animation_finish (EvViewPresentation *pview)
+{
+	ev_view_presentation_animation_cancel (pview);
+	ev_view_presentation_transition_start (pview);
+	gtk_widget_queue_draw (GTK_WIDGET (pview));
+}
+
+static void
+ev_view_presentation_transition_animation_frame (EvViewPresentation *pview,
+						 gdouble             progress)
+{
+	gtk_widget_queue_draw (GTK_WIDGET (pview));
+}
+
+static void
+ev_view_presentation_animation_start (EvViewPresentation *pview,
+				      gint                new_page)
+{
+	EvTransitionEffect *effect = NULL;
+	cairo_surface_t    *surface;
+	gint                jump;
+
+	if (!pview->enable_animations)
+		return;
+
+	if (pview->current_page == new_page)
+		return;
+
+	effect = ev_document_transition_get_effect (EV_DOCUMENT_TRANSITION (pview->document),
+						    new_page);
+	if (!effect)
+		return;
+
+	pview->animation = ev_transition_animation_new (effect);
+
+	surface = EV_JOB_RENDER (pview->curr_job)->surface;
+	ev_transition_animation_set_origin_surface (pview->animation, surface);
+
+	jump = new_page - pview->current_page;
+	if (jump == -1)
+		surface = EV_JOB_RENDER (pview->prev_job)->surface;
+	else if (jump == 1)
+		surface = EV_JOB_RENDER (pview->next_job)->surface;
+	else
+		surface = NULL;
+	if (surface)
+		ev_transition_animation_set_dest_surface (pview->animation, surface);
+
+	g_signal_connect_swapped (pview->animation, "frame",
+				  G_CALLBACK (ev_view_presentation_transition_animation_frame),
+				  pview);
+	g_signal_connect_swapped (pview->animation, "finished",
+				  G_CALLBACK (ev_view_presentation_transition_animation_finish),
+				  pview);
+}
+
+/* Page Navigation */
+static void
+job_finished_cb (EvJob              *job,
+		 EvViewPresentation *pview)
+{
+	EvJobRender *job_render = EV_JOB_RENDER (job);
+
+	if (job != pview->curr_job)
+		return;
+
+	if (pview->animation) {
+		ev_transition_animation_set_dest_surface (pview->animation,
+							  job_render->surface);
+	} else {
+		ev_view_presentation_transition_start (pview);
+		gtk_widget_queue_draw (GTK_WIDGET (pview));
+	}
+}
+
+static EvJob *
+ev_view_presentation_schedule_new_job (EvViewPresentation *pview,
+				       gint                page,
+				       EvJobPriority       priority)
+{
+	EvJob  *job;
+	gdouble scale;
+
+	if (page < 0 || page >= ev_document_get_n_pages (pview->document))
+		return NULL;
+
+	if (ev_document_is_page_size_uniform (pview->document))
+		scale = pview->scale;
+	else
+		scale = ev_view_presentation_get_scale_for_page (pview, page);
+	job = ev_job_render_new (pview->document, page, pview->rotation, pview->scale, 0, 0);
+	g_signal_connect (job, "finished",
+			  G_CALLBACK (job_finished_cb),
+			  pview);
+	ev_job_scheduler_push_job (job, priority);
+
+	return job;
+}
+
+static void
+ev_view_presentation_delete_job (EvViewPresentation *pview,
+				 EvJob              *job)
+{
+	if (!job)
+		return;
+
+	g_signal_handlers_disconnect_by_func (job, job_finished_cb, pview);
+	ev_job_cancel (job);
+	g_object_unref (job);
+}
+
+static void
+ev_view_presentation_update_current_page (EvViewPresentation *pview,
+					  guint               page)
+{
+	gint jump;
+
+	if (page < 0 || page >= ev_document_get_n_pages (pview->document))
+		return;
+
+	ev_view_presentation_animation_cancel (pview);
+	ev_view_presentation_animation_start (pview, page);
+
+	jump = page - pview->current_page;
+
+	switch (jump) {
+	case 0:
+		if (!pview->curr_job)
+			pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
+		if (!pview->next_job)
+			pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
+		if (!pview->prev_job)
+			pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_LOW);
+		break;
+	case -1:
+		ev_view_presentation_delete_job (pview, pview->next_job);
+		pview->next_job = pview->curr_job;
+		pview->curr_job = pview->prev_job;
+
+		if (!pview->curr_job)
+			pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
+		else
+			ev_job_scheduler_update_job (pview->curr_job, EV_JOB_PRIORITY_URGENT);
+		pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_HIGH);
+		ev_job_scheduler_update_job (pview->next_job, EV_JOB_PRIORITY_LOW);
+
+		break;
+	case 1:
+		ev_view_presentation_delete_job (pview, pview->prev_job);
+		pview->prev_job = pview->curr_job;
+		pview->curr_job = pview->next_job;
+
+		if (!pview->curr_job)
+			pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
+		else
+			ev_job_scheduler_update_job (pview->curr_job, EV_JOB_PRIORITY_URGENT);
+		pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
+		ev_job_scheduler_update_job (pview->prev_job, EV_JOB_PRIORITY_LOW);
+
+		break;
+	case -2:
+		ev_view_presentation_delete_job (pview, pview->next_job);
+		ev_view_presentation_delete_job (pview, pview->curr_job);
+		pview->next_job = pview->prev_job;
+
+		pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
+		pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_HIGH);
+		if (!pview->next_job)
+			pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_LOW);
+		else
+			ev_job_scheduler_update_job (pview->next_job, EV_JOB_PRIORITY_LOW);
+		break;
+	case 2:
+		ev_view_presentation_delete_job (pview, pview->prev_job);
+		ev_view_presentation_delete_job (pview, pview->curr_job);
+		pview->prev_job = pview->next_job;
+
+		pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
+		pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
+		if (!pview->prev_job)
+			pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_LOW);
+		else
+			ev_job_scheduler_update_job (pview->prev_job, EV_JOB_PRIORITY_LOW);
+		break;
+	default:
+		ev_view_presentation_delete_job (pview, pview->prev_job);
+		ev_view_presentation_delete_job (pview, pview->curr_job);
+		ev_view_presentation_delete_job (pview, pview->next_job);
+
+		pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
+		if (jump > 0) {
+			pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
+			pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_LOW);
+		} else {
+			pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_HIGH);
+			pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_LOW);
+		}
+	}
+
+	pview->current_page = page;
+	ev_view_presentation_update_scale (pview);
+	if (pview->page_cache)
+		ev_page_cache_set_page_range (pview->page_cache, page, page);
+
+	if (pview->cursor != EV_VIEW_CURSOR_HIDDEN) {
+		gint x, y;
+
+		gtk_widget_get_pointer (GTK_WIDGET (pview), &x, &y);
+		ev_view_presentation_set_cursor_for_location (pview, x, y);
+	}
+
+	if (EV_JOB_RENDER (pview->curr_job)->surface)
+		gtk_widget_queue_draw (GTK_WIDGET (pview));
+}
+
+static void
+ev_view_presentation_next_page (EvViewPresentation *pview)
+{
+	guint n_pages;
+	gint  new_page;
+
+	switch (pview->state) {
+	case EV_PRESENTATION_BLACK:
+	case EV_PRESENTATION_WHITE:
+		ev_view_presentation_set_normal (pview);
+	case EV_PRESENTATION_END:
+		return;
+	case EV_PRESENTATION_NORMAL:
+		break;
+	}
+
+	n_pages = ev_document_get_n_pages (pview->document);
+	new_page = pview->current_page + 1;
+
+	if (new_page == n_pages)
+		ev_view_presentation_set_end (pview);
+	else
+		ev_view_presentation_update_current_page (pview, new_page);
+}
+
+static void
+ev_view_presentation_previous_page (EvViewPresentation *pview)
+{
+	gint new_page = 0;
+
+	switch (pview->state) {
+	case EV_PRESENTATION_BLACK:
+	case EV_PRESENTATION_WHITE:
+		ev_view_presentation_set_normal (pview);
+		return;
+	case EV_PRESENTATION_END:
+		pview->state = EV_PRESENTATION_NORMAL;
+		new_page = pview->current_page;
+		break;
+	case EV_PRESENTATION_NORMAL:
+		new_page = pview->current_page - 1;
+		break;
+	}
+
+	ev_view_presentation_update_current_page (pview, new_page);
+}
+
+/* Goto Window */
+#define KEY_IS_NUMERIC(keyval) \
+	((keyval >= GDK_0 && keyval <= GDK_9) || (keyval >= GDK_KP_0 && keyval <= GDK_KP_9))
+
+/* Cut and paste from gtkwindow.c */
+static void
+send_focus_change (GtkWidget *widget,
+		   gboolean   in)
+{
+	GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
+
+	g_object_ref (widget);
+
+	if (in)
+		GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
+	else
+		GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
+
+	fevent->focus_change.type = GDK_FOCUS_CHANGE;
+	fevent->focus_change.window = g_object_ref (widget->window);
+	fevent->focus_change.in = in;
+
+	gtk_widget_event (widget, fevent);
+
+	g_object_notify (G_OBJECT (widget), "has-focus");
+
+	g_object_unref (widget);
+	gdk_event_free (fevent);
+}
+
+static void
+ev_view_presentation_goto_window_hide (EvViewPresentation *pview)
+{
+	/* send focus-in event */
+	send_focus_change (pview->goto_entry, FALSE);
+	gtk_widget_hide (pview->goto_window);
+	gtk_entry_set_text (GTK_ENTRY (pview->goto_entry), "");
+}
+
+static gboolean
+ev_view_presentation_goto_window_delete_event (GtkWidget          *widget,
+					       GdkEventAny        *event,
+					       EvViewPresentation *pview)
+{
+	ev_view_presentation_goto_window_hide (pview);
+
+	return TRUE;
+}
+
+static gboolean
+ev_view_presentation_goto_window_key_press_event (GtkWidget          *widget,
+						  GdkEventKey        *event,
+						  EvViewPresentation *pview)
+{
+	switch (event->keyval) {
+	case GDK_Escape:
+	case GDK_Tab:
+	case GDK_KP_Tab:
+	case GDK_ISO_Left_Tab:
+		ev_view_presentation_goto_window_hide (pview);
+		return TRUE;
+	case GDK_Return:
+	case GDK_KP_Enter:
+	case GDK_ISO_Enter:
+	case GDK_BackSpace:
+	case GDK_Delete:
+		return FALSE;
+	default:
+		if (!KEY_IS_NUMERIC (event->keyval))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+ev_view_presentation_goto_window_button_press_event (GtkWidget          *widget,
+						     GdkEventButton     *event,
+						     EvViewPresentation *pview)
+{
+	ev_view_presentation_goto_window_hide (pview);
+
+	return TRUE;
+}
+
+static void
+ev_view_presentation_goto_entry_activate (GtkEntry           *entry,
+					  EvViewPresentation *pview)
+{
+	const gchar *text;
+	gint         page;
+
+	text = gtk_entry_get_text (entry);
+	page = atoi (text) - 1;
+
+	ev_view_presentation_goto_window_hide (pview);
+	ev_view_presentation_update_current_page (pview, page);
+}
+
+static void
+ev_view_presentation_goto_window_create (EvViewPresentation *pview)
+{
+	GtkWidget *frame, *hbox, *toplevel, *label;
+
+	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (pview));
+
+	if (pview->goto_window) {
+		if (GTK_WINDOW (toplevel)->group)
+			gtk_window_group_add_window (GTK_WINDOW (toplevel)->group,
+						     GTK_WINDOW (pview->goto_window));
+		else if (GTK_WINDOW (pview->goto_window)->group)
+			gtk_window_group_remove_window (GTK_WINDOW (pview->goto_window)->group,
+							GTK_WINDOW (pview->goto_window));
+		return;
+	}
+
+	pview->goto_window = gtk_window_new (GTK_WINDOW_POPUP);
+	gtk_window_set_screen (GTK_WINDOW (pview->goto_window),
+			       gtk_widget_get_screen (GTK_WIDGET (pview)));
+
+	if (GTK_WINDOW (toplevel)->group)
+		gtk_window_group_add_window (GTK_WINDOW (toplevel)->group,
+					     GTK_WINDOW (pview->goto_window));
+
+	gtk_window_set_modal (GTK_WINDOW (pview->goto_window), TRUE);
+
+	g_signal_connect (pview->goto_window, "delete_event",
+			  G_CALLBACK (ev_view_presentation_goto_window_delete_event),
+			  pview);
+	g_signal_connect (pview->goto_window, "key_press_event",
+			  G_CALLBACK (ev_view_presentation_goto_window_key_press_event),
+			  pview);
+	g_signal_connect (pview->goto_window, "button_press_event",
+			  G_CALLBACK (ev_view_presentation_goto_window_button_press_event),
+			  pview);
+
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+	gtk_container_add (GTK_CONTAINER (pview->goto_window), frame);
+	gtk_widget_show (frame);
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
+	gtk_container_add (GTK_CONTAINER (frame), hbox);
+	gtk_widget_show (hbox);
+
+	label = gtk_label_new (_("Jump to page:"));
+	gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 3);
+	gtk_widget_show (label);
+	gtk_widget_realize (label);
+
+	pview->goto_entry = gtk_entry_new ();
+	g_signal_connect (pview->goto_entry, "activate",
+			  G_CALLBACK (ev_view_presentation_goto_entry_activate),
+			  pview);
+	gtk_box_pack_start (GTK_BOX (hbox), pview->goto_entry, TRUE, TRUE, 0);
+	gtk_widget_show (pview->goto_entry);
+	gtk_widget_realize (pview->goto_entry);
+}
+
+static void
+ev_view_presentation_goto_entry_grab_focus (EvViewPresentation *pview)
+{
+	GtkWidgetClass *entry_parent_class;
+
+	entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (pview->goto_entry));
+	(entry_parent_class->grab_focus) (pview->goto_entry);
+
+	send_focus_change (pview->goto_entry, TRUE);
+}
+
+static void
+ev_view_presentation_goto_window_send_key_event (EvViewPresentation *pview,
+						 GdkEvent           *event)
+{
+	GdkEventKey *new_event;
+	GdkScreen   *screen;
+
+	/* Move goto window off screen */
+	screen = gtk_widget_get_screen (GTK_WIDGET (pview));
+	gtk_window_move (GTK_WINDOW (pview->goto_window),
+			 gdk_screen_get_width (screen) + 1,
+			 gdk_screen_get_height (screen) + 1);
+	gtk_widget_show (pview->goto_window);
+
+	new_event = (GdkEventKey *) gdk_event_copy (event);
+	g_object_unref (new_event->window);
+	new_event->window = g_object_ref (pview->goto_window->window);
+	gtk_widget_realize (pview->goto_window);
+
+	gtk_widget_event (pview->goto_window, (GdkEvent *)new_event);
+	gdk_event_free ((GdkEvent *)new_event);
+	gtk_widget_hide (pview->goto_window);
+}
+
+/* Links */
+static gboolean
+ev_view_presentation_link_is_supported (EvViewPresentation *pview,
+					EvLink             *link)
+{
+	EvLinkAction *action;
+
+	action = ev_link_get_action (link);
+	if (!action)
+		return FALSE;
+
+	switch (ev_link_action_get_action_type (action)) {
+	case EV_LINK_ACTION_TYPE_GOTO_DEST:
+		return ev_link_action_get_dest (action) != NULL;
+	case EV_LINK_ACTION_TYPE_NAMED:
+		return TRUE;
+	default:
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static EvLink *
+ev_view_presentation_get_link_at_location (EvViewPresentation *pview,
+					   gdouble             x,
+					   gdouble             y)
+{
+	GdkRectangle page_area;
+	GList       *link_mapping;
+	EvLink      *link;
+	gdouble      width, height;
+	gdouble      new_x, new_y;
+
+	if (!pview->page_cache)
+		return NULL;
+
+	ev_document_get_page_size (pview->document, pview->current_page, &width, &height);
+	ev_view_presentation_get_page_area (pview, &page_area);
+	x = (x - page_area.x) / pview->scale;
+	y = (y - page_area.y) / pview->scale;
+	switch (pview->rotation) {
+	case 0:
+	case 360:
+		new_x = x;
+		new_y = y;
+		break;
+	case 90:
+		new_x = y;
+		new_y = height - x;
+		break;
+	case 180:
+		new_x = width - x;
+		new_y = height - y;
+		break;
+	case 270:
+		new_x = width - y;
+		new_y = x;
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	link_mapping = ev_page_cache_get_link_mapping (pview->page_cache, pview->current_page);
+
+	link = link_mapping ? ev_mapping_list_get_data (link_mapping, new_x, new_y) : NULL;
+
+	return link && ev_view_presentation_link_is_supported (pview, link) ? link : NULL;
+}
+
+static void
+ev_vew_presentation_goto_link_dest (EvViewPresentation *pview,
+				    EvLink             *link)
+{
+	EvLinkAction *action;
+
+	action = ev_link_get_action (link);
+
+	if (ev_link_action_get_action_type (action) == EV_LINK_ACTION_TYPE_NAMED) {
+		const gchar *name = ev_link_action_get_name (action);
+
+		if (g_ascii_strcasecmp (name, "FirstPage") == 0) {
+			ev_view_presentation_update_current_page (pview, 0);
+		} else if (g_ascii_strcasecmp (name, "PrevPage") == 0) {
+			ev_view_presentation_update_current_page (pview, pview->current_page - 1);
+		} else if (g_ascii_strcasecmp (name, "NextPage") == 0) {
+			ev_view_presentation_update_current_page (pview, pview->current_page + 1);
+		} else if (g_ascii_strcasecmp (name, "LastPage") == 0) {
+			gint n_pages;
+
+			n_pages = ev_document_get_n_pages (pview->document);
+			ev_view_presentation_update_current_page (pview, n_pages - 1);
+		}
+	} else {
+		EvLinkDest *dest;
+		gint        page;
+
+		dest = ev_link_action_get_dest (action);
+		page = ev_document_links_get_dest_page (EV_DOCUMENT_LINKS (pview->document), dest);
+		ev_view_presentation_update_current_page (pview, page);
+	}
+}
+
+/* Cursors */
+static void
+ev_view_presentation_set_cursor (EvViewPresentation *pview,
+				 EvViewCursor        view_cursor)
+{
+	GtkWidget  *widget;
+	GdkCursor  *cursor;
+
+	if (pview->cursor == view_cursor)
+		return;
+
+	widget = GTK_WIDGET (pview);
+	if (!GTK_WIDGET_REALIZED (widget))
+		gtk_widget_realize (widget);
+
+	pview->cursor = view_cursor;
+
+	cursor = ev_view_cursor_new (gtk_widget_get_display (widget), view_cursor);
+	gdk_window_set_cursor (widget->window, cursor);
+	gdk_flush ();
+	if (cursor)
+		gdk_cursor_unref (cursor);
+}
+
+static void
+ev_view_presentation_set_cursor_for_location (EvViewPresentation *pview,
+					      gdouble             x,
+					      gdouble             y)
+{
+	if (ev_view_presentation_get_link_at_location (pview, x, y))
+		ev_view_presentation_set_cursor (pview, EV_VIEW_CURSOR_LINK);
+	else
+		ev_view_presentation_set_cursor (pview, EV_VIEW_CURSOR_NORMAL);
+}
+
+static gboolean
+hide_cursor_timeout_cb (EvViewPresentation *pview)
+{
+	ev_view_presentation_set_cursor (pview, EV_VIEW_CURSOR_HIDDEN);
+	pview->hide_cursor_timeout_id = 0;
+
+	return FALSE;
+}
+
+static void
+ev_view_presentation_hide_cursor_timeout_stop (EvViewPresentation *pview)
+{
+	if (pview->hide_cursor_timeout_id > 0)
+		g_source_remove (pview->hide_cursor_timeout_id);
+	pview->hide_cursor_timeout_id = 0;
+}
+
+static void
+ev_view_presentation_hide_cursor_timeout_start (EvViewPresentation *pview)
+{
+	ev_view_presentation_hide_cursor_timeout_stop (pview);
+	pview->hide_cursor_timeout_id =
+		g_timeout_add_seconds (HIDE_CURSOR_TIMEOUT,
+				       (GSourceFunc)hide_cursor_timeout_cb,
+				       pview);
+}
+
+static void
+ev_view_presentation_destroy (GtkObject *object)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (object);
+
+	if (pview->document) {
+		g_object_unref (pview->document);
+		pview->document = NULL;
+	}
+
+	ev_view_presentation_animation_cancel (pview);
+	ev_view_presentation_transition_stop (pview);
+	ev_view_presentation_hide_cursor_timeout_stop (pview);
+
+	if (pview->curr_job) {
+		ev_view_presentation_delete_job (pview, pview->curr_job);
+		pview->curr_job = NULL;
+	}
+
+	if (pview->prev_job) {
+		ev_view_presentation_delete_job (pview, pview->prev_job);
+		pview->prev_job = NULL;
+	}
+
+	if (pview->next_job) {
+		ev_view_presentation_delete_job (pview, pview->next_job);
+		pview->next_job = NULL;
+	}
+
+	if (pview->page_cache) {
+		g_object_unref (pview->page_cache);
+		pview->page_cache = NULL;
+	}
+
+	if (pview->goto_window) {
+		gtk_widget_destroy (pview->goto_window);
+		pview->goto_window = NULL;
+		pview->goto_entry = NULL;
+	}
+
+	GTK_OBJECT_CLASS (ev_view_presentation_parent_class)->destroy (object);
+}
+
+static void
+ev_view_presentation_size_request (GtkWidget      *widget,
+				   GtkRequisition *requisition)
+{
+	requisition->width = 0;
+	requisition->height = 0;
+}
+
+static void
+ev_view_presentation_size_allocate (GtkWidget     *widget,
+				    GtkAllocation *allocation)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+	GdkScreen          *screen = gtk_widget_get_screen (widget);
+
+	allocation->x = 0;
+	allocation->y = 0;
+	allocation->width = gdk_screen_get_width (screen);
+	allocation->height = gdk_screen_get_height (screen);
+
+	GTK_WIDGET_CLASS (ev_view_presentation_parent_class)->size_allocate (widget, allocation);
+
+	ev_view_presentation_update_scale (pview);
+
+	gtk_widget_queue_draw (widget);
+}
+
+static void
+ev_view_presentation_draw_end_page (EvViewPresentation *pview)
+{
+	GtkWidget *widget = GTK_WIDGET (pview);
+	PangoLayout *layout;
+	PangoFontDescription *font_desc;
+	gchar *markup;
+	GdkRectangle area = {0};
+	const gchar *text = _("End of presentation. Press Escape to exit.");
+
+	if (pview->state != EV_PRESENTATION_END)
+		return;
+
+	layout = gtk_widget_create_pango_layout (widget, NULL);
+	markup = g_strdup_printf ("<span foreground=\"white\">%s</span>", text);
+	pango_layout_set_markup (layout, markup, -1);
+	g_free (markup);
+
+	font_desc = pango_font_description_new ();
+	pango_font_description_set_size (font_desc, 16 * PANGO_SCALE);
+	pango_layout_set_font_description (layout, font_desc);
+
+	area.width = widget->allocation.width;
+	area.height = widget->allocation.height;
+
+	gtk_paint_layout (widget->style,
+			  widget->window,
+			  GTK_WIDGET_STATE (widget),
+			  FALSE,
+			  &area,
+			  widget,
+			  NULL,
+			  15,
+			  15,
+			  layout);
+
+	pango_font_description_free (font_desc);
+	g_object_unref (layout);
+}
+
+static gboolean
+ev_view_presentation_expose_event (GtkWidget      *widget,
+				   GdkEventExpose *event)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+	GdkRectangle        page_area;
+	GdkRectangle        overlap;
+	cairo_surface_t    *surface;
+	cairo_t            *cr;
+
+	switch (pview->state) {
+	case EV_PRESENTATION_END:
+		ev_view_presentation_draw_end_page (pview);
+		return FALSE;
+	case EV_PRESENTATION_BLACK:
+	case EV_PRESENTATION_WHITE:
+		return FALSE;
+	case EV_PRESENTATION_NORMAL:
+		break;
+	}
+
+	if (pview->animation) {
+		if (ev_transition_animation_ready (pview->animation)) {
+			ev_view_presentation_get_page_area (pview, &page_area);
+
+			cr = gdk_cairo_create (widget->window);
+
+			/* normalize to x=0, y=0 */
+			cairo_translate (cr, page_area.x, page_area.y);
+			page_area.x = page_area.y = 0;
+
+			ev_transition_animation_paint (pview->animation, cr, page_area);
+			cairo_destroy (cr);
+		}
+
+		return TRUE;
+	}
+
+	surface = pview->curr_job ? EV_JOB_RENDER (pview->curr_job)->surface : NULL;
+	if (!surface)
+		return FALSE;
+
+	ev_view_presentation_get_page_area (pview, &page_area);
+	if (gdk_rectangle_intersect (&page_area, &(event->area), &overlap)) {
+		cr = gdk_cairo_create (widget->window);
+
+		cairo_translate (cr, overlap.x, overlap.y);
+		cairo_surface_set_device_offset (surface,
+						 overlap.x - page_area.x,
+						 overlap.y - page_area.y);
+		cairo_set_source_surface (cr, surface, 0, 0);
+		cairo_paint (cr);
+		cairo_destroy (cr);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+ev_view_presentation_key_press_event (GtkWidget   *widget,
+				      GdkEventKey *event)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+
+	if (pview->state == EV_PRESENTATION_END)
+		return gtk_bindings_activate_event (GTK_OBJECT (widget), event);
+
+	switch (event->keyval) {
+	case GDK_b:
+	case GDK_B:
+	case GDK_period:
+	case GDK_KP_Decimal:
+		if (pview->state == EV_PRESENTATION_BLACK)
+			ev_view_presentation_set_normal (pview);
+		else
+			ev_view_presentation_set_black (pview);
+
+		return TRUE;
+	case GDK_w:
+	case GDK_W:
+		if (pview->state == EV_PRESENTATION_WHITE)
+			ev_view_presentation_set_normal (pview);
+		else
+			ev_view_presentation_set_white (pview);
+
+		return TRUE;
+	default:
+		break;
+	}
+
+	ev_view_presentation_set_normal (pview);
+
+	if (ev_document_get_n_pages (pview->document) > 1 && KEY_IS_NUMERIC (event->keyval)) {
+		gint x, y;
+
+		ev_view_presentation_goto_window_create (pview);
+		ev_view_presentation_goto_window_send_key_event (pview, (GdkEvent *)event);
+		gtk_widget_get_pointer (GTK_WIDGET (pview), &x, &y);
+		gtk_window_move (GTK_WINDOW (pview->goto_window), x, y);
+		gtk_widget_show (pview->goto_window);
+		ev_view_presentation_goto_entry_grab_focus (pview);
+
+		return TRUE;
+	}
+
+	return gtk_bindings_activate_event (GTK_OBJECT (widget), event);
+}
+
+static gboolean
+ev_view_presentation_button_release_event (GtkWidget      *widget,
+					   GdkEventButton *event)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+
+	switch (event->button) {
+	case 1: {
+		EvLink *link;
+
+		link = ev_view_presentation_get_link_at_location (pview,
+								  event->x,
+								  event->y);
+		if (link)
+			ev_vew_presentation_goto_link_dest (pview, link);
+		else
+			ev_view_presentation_next_page (pview);
+	}
+		break;
+	case 3:
+		ev_view_presentation_previous_page (pview);
+		break;
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+static gint
+ev_view_presentation_focus_out (GtkWidget     *widget,
+				GdkEventFocus *event)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+
+	if (pview->goto_window)
+		ev_view_presentation_goto_window_hide (pview);
+
+	return FALSE;
+}
+
+static gboolean
+ev_view_presentation_motion_notify_event (GtkWidget      *widget,
+					  GdkEventMotion *event)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+
+	ev_view_presentation_hide_cursor_timeout_start (pview);
+	ev_view_presentation_set_cursor_for_location (pview, event->x, event->y);
+
+	return FALSE;
+}
+
+static void
+ev_view_presentation_realize (GtkWidget *widget)
+{
+	GdkWindowAttr attributes;
+
+	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.wclass = GDK_INPUT_OUTPUT;
+	attributes.visual = gtk_widget_get_visual (widget);
+	attributes.colormap = gtk_widget_get_colormap (widget);
+
+	attributes.x = widget->allocation.x;
+	attributes.y = widget->allocation.y;
+	attributes.width = widget->allocation.width;
+	attributes.height = widget->allocation.height;
+	attributes.event_mask = GDK_EXPOSURE_MASK |
+		GDK_BUTTON_PRESS_MASK |
+		GDK_BUTTON_RELEASE_MASK |
+		GDK_SCROLL_MASK |
+		GDK_KEY_PRESS_MASK |
+		GDK_POINTER_MOTION_MASK |
+		GDK_POINTER_MOTION_HINT_MASK |
+		                GDK_ENTER_NOTIFY_MASK |
+		GDK_LEAVE_NOTIFY_MASK;
+
+	widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+					 &attributes,
+					 GDK_WA_X | GDK_WA_Y |
+					 GDK_WA_COLORMAP |
+					 GDK_WA_VISUAL);
+	gdk_window_set_user_data (widget->window, widget);
+	widget->style = gtk_style_attach (widget->style, widget->window);
+
+	gdk_window_set_background (widget->window, &widget->style->black);
+
+	gtk_widget_queue_resize (widget);
+}
+
+static void
+ev_view_presentation_change_page (EvViewPresentation *pview,
+				  GtkScrollType       scroll)
+{
+	switch (scroll) {
+	case GTK_SCROLL_PAGE_FORWARD:
+		ev_view_presentation_next_page (pview);
+		break;
+	case GTK_SCROLL_PAGE_BACKWARD:
+		ev_view_presentation_previous_page (pview);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static gboolean
+ev_view_presentation_scroll_event (GtkWidget      *widget,
+				   GdkEventScroll *event)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
+	guint               state;
+
+	state = event->state & gtk_accelerator_get_default_mod_mask ();
+	if (state != 0)
+		return FALSE;
+
+	switch (event->direction) {
+	case GDK_SCROLL_DOWN:
+	case GDK_SCROLL_RIGHT:
+		ev_view_presentation_change_page (pview, GTK_SCROLL_PAGE_FORWARD);
+		break;
+	case GDK_SCROLL_UP:
+	case GDK_SCROLL_LEFT:
+		ev_view_presentation_change_page (pview, GTK_SCROLL_PAGE_BACKWARD);
+		break;
+	}
+
+	return TRUE;
+}
+
+
+static void
+add_change_page_binding_keypad (GtkBindingSet  *binding_set,
+				guint           keyval,
+				GdkModifierType modifiers,
+				GtkScrollType   scroll)
+{
+	guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
+
+	gtk_binding_entry_add_signal (binding_set, keyval, modifiers,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, scroll);
+	gtk_binding_entry_add_signal (binding_set, keypad_keyval, modifiers,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, scroll);
+}
+
+static void
+ev_view_presentation_set_property (GObject      *object,
+				   guint         prop_id,
+				   const GValue *value,
+				   GParamSpec   *pspec)
+{
+	EvViewPresentation *pview = EV_VIEW_PRESENTATION (object);
+
+	switch (prop_id) {
+	case PROP_DOCUMENT:
+		pview->document = g_value_dup_object (value);
+		pview->enable_animations = EV_IS_DOCUMENT_TRANSITION (pview->document);
+		break;
+	case PROP_CURRENT_PAGE:
+		pview->current_page = g_value_get_uint (value);
+		break;
+	case PROP_ROTATION:
+		pview->rotation = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	}
+}
+
+static GObject *
+ev_view_presentation_constructor (GType                  type,
+				  guint                  n_construct_properties,
+				  GObjectConstructParam *construct_params)
+{
+	GObject            *object;
+	EvViewPresentation *pview;
+	GtkAllocation       a;
+
+	object = G_OBJECT_CLASS (ev_view_presentation_parent_class)->constructor (type,
+										  n_construct_properties,
+										  construct_params);
+	pview = EV_VIEW_PRESENTATION (object);
+
+	if (EV_IS_DOCUMENT_LINKS (pview->document)) {
+		pview->page_cache = ev_page_cache_new (pview->document);
+		ev_page_cache_set_flags (pview->page_cache, EV_PAGE_DATA_INCLUDE_LINKS);
+	}
+
+	/* Call allocate asap to update page scale */
+	ev_view_presentation_size_allocate (GTK_WIDGET (pview), &a);
+	ev_view_presentation_update_current_page (pview, pview->current_page);
+	ev_view_presentation_hide_cursor_timeout_start (pview);
+
+	return object;
+}
+
+static void
+ev_view_presentation_class_init (EvViewPresentationClass *klass)
+{
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+	GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
+	GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (klass);
+	GtkBindingSet  *binding_set;
+
+	klass->change_page = ev_view_presentation_change_page;
+
+	widget_class->size_allocate = ev_view_presentation_size_allocate;
+	widget_class->size_request = ev_view_presentation_size_request;
+	widget_class->realize = ev_view_presentation_realize;
+	widget_class->expose_event = ev_view_presentation_expose_event;
+	widget_class->key_press_event = ev_view_presentation_key_press_event;
+	widget_class->button_release_event = ev_view_presentation_button_release_event;
+	widget_class->focus_out_event = ev_view_presentation_focus_out;
+	widget_class->motion_notify_event = ev_view_presentation_motion_notify_event;
+	widget_class->scroll_event = ev_view_presentation_scroll_event;
+
+	gtk_object_class->destroy = ev_view_presentation_destroy;
+
+	gobject_class->constructor = ev_view_presentation_constructor;
+	gobject_class->set_property = ev_view_presentation_set_property;
+
+	g_object_class_install_property (gobject_class,
+					 PROP_DOCUMENT,
+					 g_param_spec_object ("document",
+							      "Document",
+							      "Document",
+							      EV_TYPE_DOCUMENT,
+							      G_PARAM_WRITABLE |
+							      G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (gobject_class,
+					 PROP_CURRENT_PAGE,
+					 g_param_spec_uint ("current_page",
+							    "Current Page",
+							    "The current page",
+							    0, G_MAXUINT, 0,
+							    G_PARAM_WRITABLE |
+							    G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (gobject_class,
+					 PROP_ROTATION,
+					 g_param_spec_uint ("rotation",
+							    "Rotation",
+							    "Current rotation angle",
+							    0, 360, 0,
+							    G_PARAM_WRITABLE |
+							    G_PARAM_CONSTRUCT_ONLY));
+
+	signals[CHANGE_PAGE] =
+		g_signal_new ("change_page",
+			      G_OBJECT_CLASS_TYPE (gobject_class),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+			      G_STRUCT_OFFSET (EvViewPresentationClass, change_page),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__ENUM,
+			      G_TYPE_NONE, 1,
+			      GTK_TYPE_SCROLL_TYPE);
+
+	binding_set = gtk_binding_set_by_class (klass);
+	add_change_page_binding_keypad (binding_set, GDK_Left,  0, GTK_SCROLL_PAGE_BACKWARD);
+	add_change_page_binding_keypad (binding_set, GDK_Right, 0, GTK_SCROLL_PAGE_FORWARD);
+	add_change_page_binding_keypad (binding_set, GDK_Up,    0, GTK_SCROLL_PAGE_BACKWARD);
+	add_change_page_binding_keypad (binding_set, GDK_Down,  0, GTK_SCROLL_PAGE_FORWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_space, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_J, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_H, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_L, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
+	gtk_binding_entry_add_signal (binding_set, GDK_K, 0,
+				      "change_page", 1,
+				      GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
+}
+
+static void
+ev_view_presentation_init (EvViewPresentation *pview)
+{
+	GTK_WIDGET_SET_FLAGS (pview, GTK_CAN_FOCUS);
+}
+
+GtkWidget *
+ev_view_presentation_new (EvDocument *document,
+			  guint       current_page,
+			  guint       rotation)
+{
+	g_return_val_if_fail (EV_IS_DOCUMENT (document), NULL);
+	g_return_val_if_fail (current_page < ev_document_get_n_pages (document), NULL);
+
+	return GTK_WIDGET (g_object_new (EV_TYPE_VIEW_PRESENTATION,
+					 "document", document,
+					 "current_page", current_page,
+					 "rotation", rotation,
+					 NULL));
+}
+
+guint
+ev_view_presentation_get_current_page (EvViewPresentation *pview)
+{
+	return pview->current_page;
+}
diff --git a/libview/ev-view-presentation.h b/libview/ev-view-presentation.h
new file mode 100644
index 0000000..9394da6
--- /dev/null
+++ b/libview/ev-view-presentation.h
@@ -0,0 +1,50 @@
+/* ev-view-presentation.h
+ *  this file is part of evince, a gnome document viewer
+ *
+ * Copyright (C) 2010 Carlos Garcia Campos <carlosgc gnome org>
+ *
+ * Evince 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.
+ *
+ * Evince 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__EV_EVINCE_VIEW_H_INSIDE__) && !defined (EVINCE_COMPILATION)
+#error "Only <evince-view.h> can be included directly."
+#endif
+
+#ifndef __EV_VIEW_PRESENTATION_H__
+#define __EV_VIEW_PRESENTATION_H__
+
+#include <gtk/gtk.h>
+
+#include <evince-document.h>
+
+G_BEGIN_DECLS
+
+#define EV_TYPE_VIEW_PRESENTATION            (ev_view_presentation_get_type ())
+#define EV_VIEW_PRESENTATION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EV_TYPE_VIEW_PRESENTATION, EvViewPresentation))
+#define EV_IS_VIEW_PRESENTATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EV_TYPE_VIEW_PRESENTATION))
+
+typedef struct _EvViewPresentation       EvViewPresentation;
+typedef struct _EvViewPresentationClass  EvViewPresentationClass;
+
+GType		ev_view_presentation_get_type	      (void) G_GNUC_CONST;
+
+GtkWidget      *ev_view_presentation_new	      (EvDocument         *document,
+						       guint               current_page,
+						       guint               rotation);
+guint           ev_view_presentation_get_current_page (EvViewPresentation *pview);
+
+G_END_DECLS
+
+#endif /* __EV_VIEW_PRESENTATION_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]