From 6d690c345c434c074333fa0971af0406eeb2719e Mon Sep 17 00:00:00 2001 From: Carlos Garcia Campos Date: Sun, 3 Jan 2010 18:27:30 +0100 Subject: [PATCH] [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(-) create mode 100644 libview/ev-view-presentation.c create mode 100644 libview/ev-view-presentation.h diff --git a/libview/Makefile.am b/libview/Makefile.am index 95d9f29d..b4edc579 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 00000000..8f918697 --- /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 + * + * 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 +#include +#include + +#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 ("%s", 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 00000000..9394da6a --- /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 + * + * 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 can be included directly." +#endif + +#ifndef __EV_VIEW_PRESENTATION_H__ +#define __EV_VIEW_PRESENTATION_H__ + +#include + +#include + +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__ */ -- 2.43.5