]> www.fi.muni.cz Git - evince.git/blob - libview/ev-view-presentation.c
libview: Fix goto window in presentation mode
[evince.git] / libview / ev-view-presentation.c
1 /* ev-view-presentation.c
2  *  this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
5  *
6  * Evince is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Evince is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20
21 #include "config.h"
22
23 #include <stdlib.h>
24 #include <glib/gi18n-lib.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "ev-view-presentation.h"
28 #include "ev-jobs.h"
29 #include "ev-job-scheduler.h"
30 #include "ev-transition-animation.h"
31 #include "ev-view-cursor.h"
32 #include "ev-page-cache.h"
33
34 enum {
35         PROP_0,
36         PROP_DOCUMENT,
37         PROP_CURRENT_PAGE,
38         PROP_ROTATION,
39         PROP_INVERTED_COLORS
40 };
41
42 enum {
43         CHANGE_PAGE,
44         FINISHED,
45         SIGNAL_EXTERNAL_LINK,
46         N_SIGNALS
47 };
48
49 typedef enum {
50         EV_PRESENTATION_NORMAL,
51         EV_PRESENTATION_BLACK,
52         EV_PRESENTATION_WHITE,
53         EV_PRESENTATION_END
54 } EvPresentationState;
55
56 struct _EvViewPresentation
57 {
58         GtkWidget base;
59
60         guint                  is_constructing : 1;
61
62         guint                  current_page;
63         cairo_surface_t       *current_surface;
64         EvDocument            *document;
65         guint                  rotation;
66         gboolean               inverted_colors;
67         EvPresentationState    state;
68         gdouble                scale;
69         gint                   monitor_width;
70         gint                   monitor_height;
71
72         /* Cursors */
73         EvViewCursor           cursor;
74         guint                  hide_cursor_timeout_id;
75
76         /* Goto Window */
77         GtkWidget             *goto_window;
78         GtkWidget             *goto_entry;
79
80         /* Page Transition */
81         guint                  trans_timeout_id;
82
83         /* Animations */
84         gboolean               enable_animations;
85         EvTransitionAnimation *animation;
86
87         /* Links */
88         EvPageCache           *page_cache;
89
90         EvJob *prev_job;
91         EvJob *curr_job;
92         EvJob *next_job;
93 };
94
95 struct _EvViewPresentationClass
96 {
97         GtkWidgetClass base_class;
98
99         /* signals */
100         void (* change_page)   (EvViewPresentation *pview,
101                                 GtkScrollType       scroll);
102         void (* finished)      (EvViewPresentation *pview);
103         void (* external_link) (EvViewPresentation *pview,
104                                 EvLinkAction       *action);
105 };
106
107 static guint signals[N_SIGNALS] = { 0 };
108
109 static void ev_view_presentation_set_cursor_for_location (EvViewPresentation *pview,
110                                                           gdouble             x,
111                                                           gdouble             y);
112
113 #define HIDE_CURSOR_TIMEOUT 5
114
115 G_DEFINE_TYPE (EvViewPresentation, ev_view_presentation, GTK_TYPE_WIDGET)
116
117 static GdkRGBA black = { 0., 0., 0., 1. };
118 static GdkRGBA white = { 1., 1., 1., 1. };
119
120 static void
121 ev_view_presentation_set_normal (EvViewPresentation *pview)
122 {
123         GtkWidget *widget = GTK_WIDGET (pview);
124
125         if (pview->state == EV_PRESENTATION_NORMAL)
126                 return;
127
128         pview->state = EV_PRESENTATION_NORMAL;
129         gdk_window_set_background_rgba (gtk_widget_get_window (widget), &black);
130         gtk_widget_queue_draw (widget);
131 }
132
133 static void
134 ev_view_presentation_set_black (EvViewPresentation *pview)
135 {
136         GtkWidget *widget = GTK_WIDGET (pview);
137
138         if (pview->state == EV_PRESENTATION_BLACK)
139                 return;
140
141         pview->state = EV_PRESENTATION_BLACK;
142         gdk_window_set_background_rgba (gtk_widget_get_window (widget), &black);
143         gtk_widget_queue_draw (widget);
144 }
145
146 static void
147 ev_view_presentation_set_white (EvViewPresentation *pview)
148 {
149         GtkWidget *widget = GTK_WIDGET (pview);
150
151         if (pview->state == EV_PRESENTATION_WHITE)
152                 return;
153
154         pview->state = EV_PRESENTATION_WHITE;
155         gdk_window_set_background_rgba (gtk_widget_get_window (widget), &white);
156         gtk_widget_queue_draw (widget);
157 }
158
159 static void
160 ev_view_presentation_set_end (EvViewPresentation *pview)
161 {
162         GtkWidget *widget = GTK_WIDGET (pview);
163
164         if (pview->state == EV_PRESENTATION_END)
165                 return;
166
167         pview->state = EV_PRESENTATION_END;
168         gtk_widget_queue_draw (widget);
169 }
170
171 static gdouble
172 ev_view_presentation_get_scale_for_page (EvViewPresentation *pview,
173                                          guint               page)
174 {
175         if (!ev_document_is_page_size_uniform (pview->document) || pview->scale == 0) {
176                 gdouble width, height;
177
178                 ev_document_get_page_size (pview->document, page, &width, &height);
179                 if (pview->rotation == 90 || pview->rotation == 270) {
180                         gdouble tmp;
181
182                         tmp = width;
183                         width = height;
184                         height = tmp;
185                 }
186                 pview->scale = MIN (pview->monitor_width / width, pview->monitor_height / height);
187         }
188
189         return pview->scale;
190 }
191
192 static void
193 ev_view_presentation_get_page_area (EvViewPresentation *pview,
194                                     GdkRectangle       *area)
195 {
196         GtkWidget    *widget = GTK_WIDGET (pview);
197         GtkAllocation allocation;
198         gdouble       doc_width, doc_height;
199         gint          view_width, view_height;
200         gdouble       scale;
201
202         ev_document_get_page_size (pview->document,
203                                    pview->current_page,
204                                    &doc_width, &doc_height);
205         scale = ev_view_presentation_get_scale_for_page (pview, pview->current_page);
206
207         if (pview->rotation == 90 || pview->rotation == 270) {
208                 view_width = (gint)((doc_height * scale) + 0.5);
209                 view_height = (gint)((doc_width * scale) + 0.5);
210         } else {
211                 view_width = (gint)((doc_width * scale) + 0.5);
212                 view_height = (gint)((doc_height * scale) + 0.5);
213         }
214
215         gtk_widget_get_allocation (widget, &allocation);
216
217         area->x = (MAX (0, allocation.width - view_width)) / 2;
218         area->y = (MAX (0, allocation.height - view_height)) / 2;
219         area->width = view_width;
220         area->height = view_height;
221 }
222
223 /* Page Transition */
224 static gboolean
225 transition_next_page (EvViewPresentation *pview)
226 {
227         ev_view_presentation_next_page (pview);
228
229         return FALSE;
230 }
231
232 static void
233 ev_view_presentation_transition_stop (EvViewPresentation *pview)
234 {
235         if (pview->trans_timeout_id > 0)
236                 g_source_remove (pview->trans_timeout_id);
237         pview->trans_timeout_id = 0;
238 }
239
240 static void
241 ev_view_presentation_transition_start (EvViewPresentation *pview)
242 {
243         gdouble duration;
244
245         if (!EV_IS_DOCUMENT_TRANSITION (pview->document))
246                 return;
247
248         ev_view_presentation_transition_stop (pview);
249
250         duration = ev_document_transition_get_page_duration (EV_DOCUMENT_TRANSITION (pview->document),
251                                                              pview->current_page);
252         if (duration >= 0) {
253                         pview->trans_timeout_id =
254                                 g_timeout_add_seconds (duration,
255                                                        (GSourceFunc) transition_next_page,
256                                                        pview);
257         }
258 }
259
260 /* Animations */
261 static void
262 ev_view_presentation_animation_cancel (EvViewPresentation *pview)
263 {
264         if (pview->animation) {
265                 g_object_unref (pview->animation);
266                 pview->animation = NULL;
267         }
268 }
269
270 static void
271 ev_view_presentation_transition_animation_finish (EvViewPresentation *pview)
272 {
273         ev_view_presentation_animation_cancel (pview);
274         ev_view_presentation_transition_start (pview);
275         gtk_widget_queue_draw (GTK_WIDGET (pview));
276 }
277
278 static void
279 ev_view_presentation_transition_animation_frame (EvViewPresentation *pview,
280                                                  gdouble             progress)
281 {
282         gtk_widget_queue_draw (GTK_WIDGET (pview));
283 }
284
285 static void
286 ev_view_presentation_animation_start (EvViewPresentation *pview,
287                                       gint                new_page)
288 {
289         EvTransitionEffect *effect = NULL;
290         cairo_surface_t    *surface;
291         gint                jump;
292
293         if (!pview->enable_animations)
294                 return;
295
296         if (pview->current_page == new_page)
297                 return;
298
299         effect = ev_document_transition_get_effect (EV_DOCUMENT_TRANSITION (pview->document),
300                                                     new_page);
301         if (!effect)
302                 return;
303
304         pview->animation = ev_transition_animation_new (effect);
305
306         surface = pview->curr_job ? EV_JOB_RENDER (pview->curr_job)->surface : NULL;
307         ev_transition_animation_set_origin_surface (pview->animation,
308                                                     surface != NULL ?
309                                                     surface : pview->current_surface);
310
311         jump = new_page - pview->current_page;
312         if (jump == -1)
313                 surface = pview->prev_job ? EV_JOB_RENDER (pview->prev_job)->surface : NULL;
314         else if (jump == 1)
315                 surface = pview->next_job ? EV_JOB_RENDER (pview->next_job)->surface : NULL;
316         else
317                 surface = NULL;
318         if (surface)
319                 ev_transition_animation_set_dest_surface (pview->animation, surface);
320
321         g_signal_connect_swapped (pview->animation, "frame",
322                                   G_CALLBACK (ev_view_presentation_transition_animation_frame),
323                                   pview);
324         g_signal_connect_swapped (pview->animation, "finished",
325                                   G_CALLBACK (ev_view_presentation_transition_animation_finish),
326                                   pview);
327 }
328
329 /* Page Navigation */
330 static void
331 job_finished_cb (EvJob              *job,
332                  EvViewPresentation *pview)
333 {
334         EvJobRender *job_render = EV_JOB_RENDER (job);
335
336         if (pview->inverted_colors)
337                 ev_document_misc_invert_surface (job_render->surface);
338
339         if (job != pview->curr_job)
340                 return;
341
342         if (pview->animation) {
343                 ev_transition_animation_set_dest_surface (pview->animation,
344                                                           job_render->surface);
345         } else {
346                 ev_view_presentation_transition_start (pview);
347                 gtk_widget_queue_draw (GTK_WIDGET (pview));
348         }
349 }
350
351 static EvJob *
352 ev_view_presentation_schedule_new_job (EvViewPresentation *pview,
353                                        gint                page,
354                                        EvJobPriority       priority)
355 {
356         EvJob  *job;
357         gdouble scale;
358
359         if (page < 0 || page >= ev_document_get_n_pages (pview->document))
360                 return NULL;
361
362         scale = ev_view_presentation_get_scale_for_page (pview, page);
363         job = ev_job_render_new (pview->document, page, pview->rotation, scale, 0, 0);
364         g_signal_connect (job, "finished",
365                           G_CALLBACK (job_finished_cb),
366                           pview);
367         ev_job_scheduler_push_job (job, priority);
368
369         return job;
370 }
371
372 static void
373 ev_view_presentation_delete_job (EvViewPresentation *pview,
374                                  EvJob              *job)
375 {
376         if (!job)
377                 return;
378
379         g_signal_handlers_disconnect_by_func (job, job_finished_cb, pview);
380         ev_job_cancel (job);
381         g_object_unref (job);
382 }
383
384 static void
385 ev_view_presentation_reset_jobs (EvViewPresentation *pview)
386 {
387         if (pview->curr_job) {
388                 ev_view_presentation_delete_job (pview, pview->curr_job);
389                 pview->curr_job = NULL;
390         }
391
392         if (pview->prev_job) {
393                 ev_view_presentation_delete_job (pview, pview->prev_job);
394                 pview->prev_job = NULL;
395         }
396
397         if (pview->next_job) {
398                 ev_view_presentation_delete_job (pview, pview->next_job);
399                 pview->next_job = NULL;
400         }
401 }
402
403 static void
404 ev_view_presentation_update_current_page (EvViewPresentation *pview,
405                                           guint               page)
406 {
407         gint jump;
408
409         if (page < 0 || page >= ev_document_get_n_pages (pview->document))
410                 return;
411
412         ev_view_presentation_animation_cancel (pview);
413         ev_view_presentation_animation_start (pview, page);
414
415         jump = page - pview->current_page;
416
417         switch (jump) {
418         case 0:
419                 if (!pview->curr_job)
420                         pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
421                 if (!pview->next_job)
422                         pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
423                 if (!pview->prev_job)
424                         pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_LOW);
425                 break;
426         case -1:
427                 ev_view_presentation_delete_job (pview, pview->next_job);
428                 pview->next_job = pview->curr_job;
429                 pview->curr_job = pview->prev_job;
430
431                 if (!pview->curr_job)
432                         pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
433                 else
434                         ev_job_scheduler_update_job (pview->curr_job, EV_JOB_PRIORITY_URGENT);
435                 pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_HIGH);
436                 ev_job_scheduler_update_job (pview->next_job, EV_JOB_PRIORITY_LOW);
437
438                 break;
439         case 1:
440                 ev_view_presentation_delete_job (pview, pview->prev_job);
441                 pview->prev_job = pview->curr_job;
442                 pview->curr_job = pview->next_job;
443
444                 if (!pview->curr_job)
445                         pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
446                 else
447                         ev_job_scheduler_update_job (pview->curr_job, EV_JOB_PRIORITY_URGENT);
448                 pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
449                 ev_job_scheduler_update_job (pview->prev_job, EV_JOB_PRIORITY_LOW);
450
451                 break;
452         case -2:
453                 ev_view_presentation_delete_job (pview, pview->next_job);
454                 ev_view_presentation_delete_job (pview, pview->curr_job);
455                 pview->next_job = pview->prev_job;
456
457                 pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
458                 pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_HIGH);
459                 if (!pview->next_job)
460                         pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_LOW);
461                 else
462                         ev_job_scheduler_update_job (pview->next_job, EV_JOB_PRIORITY_LOW);
463                 break;
464         case 2:
465                 ev_view_presentation_delete_job (pview, pview->prev_job);
466                 ev_view_presentation_delete_job (pview, pview->curr_job);
467                 pview->prev_job = pview->next_job;
468
469                 pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
470                 pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
471                 if (!pview->prev_job)
472                         pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_LOW);
473                 else
474                         ev_job_scheduler_update_job (pview->prev_job, EV_JOB_PRIORITY_LOW);
475                 break;
476         default:
477                 ev_view_presentation_delete_job (pview, pview->prev_job);
478                 ev_view_presentation_delete_job (pview, pview->curr_job);
479                 ev_view_presentation_delete_job (pview, pview->next_job);
480
481                 pview->curr_job = ev_view_presentation_schedule_new_job (pview, page, EV_JOB_PRIORITY_URGENT);
482                 if (jump > 0) {
483                         pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_HIGH);
484                         pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_LOW);
485                 } else {
486                         pview->prev_job = ev_view_presentation_schedule_new_job (pview, page - 1, EV_JOB_PRIORITY_HIGH);
487                         pview->next_job = ev_view_presentation_schedule_new_job (pview, page + 1, EV_JOB_PRIORITY_LOW);
488                 }
489         }
490
491         pview->current_page = page;
492
493         if (pview->page_cache)
494                 ev_page_cache_set_page_range (pview->page_cache, page, page);
495
496         if (pview->cursor != EV_VIEW_CURSOR_HIDDEN) {
497                 gint x, y;
498
499                 gtk_widget_get_pointer (GTK_WIDGET (pview), &x, &y);
500                 ev_view_presentation_set_cursor_for_location (pview, x, y);
501         }
502
503         if (EV_JOB_RENDER (pview->curr_job)->surface)
504                 gtk_widget_queue_draw (GTK_WIDGET (pview));
505 }
506
507 void
508 ev_view_presentation_next_page (EvViewPresentation *pview)
509 {
510         guint n_pages;
511         gint  new_page;
512
513         switch (pview->state) {
514         case EV_PRESENTATION_BLACK:
515         case EV_PRESENTATION_WHITE:
516                 ev_view_presentation_set_normal (pview);
517         case EV_PRESENTATION_END:
518                 return;
519         case EV_PRESENTATION_NORMAL:
520                 break;
521         }
522
523         n_pages = ev_document_get_n_pages (pview->document);
524         new_page = pview->current_page + 1;
525
526         if (new_page == n_pages)
527                 ev_view_presentation_set_end (pview);
528         else
529                 ev_view_presentation_update_current_page (pview, new_page);
530 }
531
532 void
533 ev_view_presentation_previous_page (EvViewPresentation *pview)
534 {
535         gint new_page = 0;
536
537         switch (pview->state) {
538         case EV_PRESENTATION_BLACK:
539         case EV_PRESENTATION_WHITE:
540                 ev_view_presentation_set_normal (pview);
541                 return;
542         case EV_PRESENTATION_END:
543                 pview->state = EV_PRESENTATION_NORMAL;
544                 new_page = pview->current_page;
545                 break;
546         case EV_PRESENTATION_NORMAL:
547                 new_page = pview->current_page - 1;
548                 break;
549         }
550
551         ev_view_presentation_update_current_page (pview, new_page);
552 }
553
554 /* Goto Window */
555 #define KEY_IS_NUMERIC(keyval) \
556         ((keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9) || (keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9))
557
558 /* Cut and paste from gtkwindow.c */
559 static void
560 send_focus_change (GtkWidget *widget,
561                    gboolean   in)
562 {
563         GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
564
565         fevent->focus_change.type = GDK_FOCUS_CHANGE;
566         fevent->focus_change.window = gtk_widget_get_window (widget);
567         fevent->focus_change.in = in;
568         if (fevent->focus_change.window)
569                 g_object_ref (fevent->focus_change.window);
570
571         gtk_widget_send_focus_change (widget, fevent);
572
573         gdk_event_free (fevent);
574 }
575
576 static void
577 ev_view_presentation_goto_window_hide (EvViewPresentation *pview)
578 {
579         /* send focus-in event */
580         send_focus_change (pview->goto_entry, FALSE);
581         gtk_widget_hide (pview->goto_window);
582         gtk_entry_set_text (GTK_ENTRY (pview->goto_entry), "");
583 }
584
585 static gboolean
586 ev_view_presentation_goto_window_delete_event (GtkWidget          *widget,
587                                                GdkEventAny        *event,
588                                                EvViewPresentation *pview)
589 {
590         ev_view_presentation_goto_window_hide (pview);
591
592         return TRUE;
593 }
594
595 static gboolean
596 ev_view_presentation_goto_window_key_press_event (GtkWidget          *widget,
597                                                   GdkEventKey        *event,
598                                                   EvViewPresentation *pview)
599 {
600         switch (event->keyval) {
601         case GDK_KEY_Escape:
602         case GDK_KEY_Tab:
603         case GDK_KEY_KP_Tab:
604         case GDK_KEY_ISO_Left_Tab:
605                 ev_view_presentation_goto_window_hide (pview);
606                 return TRUE;
607         case GDK_KEY_Return:
608         case GDK_KEY_KP_Enter:
609         case GDK_KEY_ISO_Enter:
610         case GDK_KEY_BackSpace:
611         case GDK_KEY_Delete:
612                 return FALSE;
613         default:
614                 if (!KEY_IS_NUMERIC (event->keyval))
615                         return TRUE;
616         }
617
618         return FALSE;
619 }
620
621 static gboolean
622 ev_view_presentation_goto_window_button_press_event (GtkWidget          *widget,
623                                                      GdkEventButton     *event,
624                                                      EvViewPresentation *pview)
625 {
626         ev_view_presentation_goto_window_hide (pview);
627
628         return TRUE;
629 }
630
631 static void
632 ev_view_presentation_goto_entry_activate (GtkEntry           *entry,
633                                           EvViewPresentation *pview)
634 {
635         const gchar *text;
636         gint         page;
637
638         text = gtk_entry_get_text (entry);
639         page = atoi (text) - 1;
640
641         ev_view_presentation_goto_window_hide (pview);
642         ev_view_presentation_update_current_page (pview, page);
643 }
644
645 static void
646 ev_view_presentation_goto_window_create (EvViewPresentation *pview)
647 {
648         GtkWidget *frame, *hbox, *label;
649         GtkWindow *toplevel, *goto_window;
650
651         toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (pview)));
652
653         if (pview->goto_window) {
654                 goto_window = GTK_WINDOW (pview->goto_window);
655                 if (gtk_window_has_group (toplevel))
656                         gtk_window_group_add_window (gtk_window_get_group (toplevel), goto_window);
657                 else if (gtk_window_has_group (goto_window))
658                         gtk_window_group_remove_window (gtk_window_get_group (goto_window), goto_window);
659
660                 return;
661         }
662
663         pview->goto_window = gtk_window_new (GTK_WINDOW_POPUP);
664         goto_window = GTK_WINDOW (pview->goto_window);
665         gtk_window_set_screen (goto_window, gtk_widget_get_screen (GTK_WIDGET (pview)));
666
667         if (gtk_window_has_group (toplevel))
668                 gtk_window_group_add_window (gtk_window_get_group (toplevel), goto_window);
669
670         gtk_window_set_modal (goto_window, TRUE);
671
672         g_signal_connect (pview->goto_window, "delete_event",
673                           G_CALLBACK (ev_view_presentation_goto_window_delete_event),
674                           pview);
675         g_signal_connect (pview->goto_window, "key_press_event",
676                           G_CALLBACK (ev_view_presentation_goto_window_key_press_event),
677                           pview);
678         g_signal_connect (pview->goto_window, "button_press_event",
679                           G_CALLBACK (ev_view_presentation_goto_window_button_press_event),
680                           pview);
681
682         frame = gtk_frame_new (NULL);
683         gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
684         gtk_container_add (GTK_CONTAINER (pview->goto_window), frame);
685         gtk_widget_show (frame);
686
687         hbox = gtk_hbox_new (FALSE, 0);
688         gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
689         gtk_container_add (GTK_CONTAINER (frame), hbox);
690         gtk_widget_show (hbox);
691
692         label = gtk_label_new (_("Jump to page:"));
693         gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 3);
694         gtk_widget_show (label);
695         gtk_widget_realize (label);
696
697         pview->goto_entry = gtk_entry_new ();
698         g_signal_connect (pview->goto_entry, "activate",
699                           G_CALLBACK (ev_view_presentation_goto_entry_activate),
700                           pview);
701         gtk_box_pack_start (GTK_BOX (hbox), pview->goto_entry, TRUE, TRUE, 0);
702         gtk_widget_show (pview->goto_entry);
703         gtk_widget_realize (pview->goto_entry);
704 }
705
706 static void
707 ev_view_presentation_goto_entry_grab_focus (EvViewPresentation *pview)
708 {
709         GtkWidgetClass *entry_parent_class;
710
711         entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (pview->goto_entry));
712         (entry_parent_class->grab_focus) (pview->goto_entry);
713
714         send_focus_change (pview->goto_entry, TRUE);
715 }
716
717 static void
718 ev_view_presentation_goto_window_send_key_event (EvViewPresentation *pview,
719                                                  GdkEvent           *event)
720 {
721         GdkEventKey *new_event;
722         GdkScreen   *screen;
723
724         /* Move goto window off screen */
725         screen = gtk_widget_get_screen (GTK_WIDGET (pview));
726         gtk_window_move (GTK_WINDOW (pview->goto_window),
727                          gdk_screen_get_width (screen) + 1,
728                          gdk_screen_get_height (screen) + 1);
729         gtk_widget_show (pview->goto_window);
730
731         new_event = (GdkEventKey *) gdk_event_copy (event);
732         g_object_unref (new_event->window);
733         new_event->window = gtk_widget_get_window (pview->goto_window);
734         if (new_event->window)
735                 g_object_ref (new_event->window);
736         gtk_widget_realize (pview->goto_window);
737
738         gtk_widget_event (pview->goto_window, (GdkEvent *)new_event);
739         gdk_event_free ((GdkEvent *)new_event);
740         gtk_widget_hide (pview->goto_window);
741 }
742
743 /* Links */
744 static gboolean
745 ev_view_presentation_link_is_supported (EvViewPresentation *pview,
746                                         EvLink             *link)
747 {
748         EvLinkAction *action;
749
750         action = ev_link_get_action (link);
751         if (!action)
752                 return FALSE;
753
754         switch (ev_link_action_get_action_type (action)) {
755         case EV_LINK_ACTION_TYPE_GOTO_DEST:
756                 return ev_link_action_get_dest (action) != NULL;
757         case EV_LINK_ACTION_TYPE_NAMED:
758         case EV_LINK_ACTION_TYPE_GOTO_REMOTE:
759         case EV_LINK_ACTION_TYPE_EXTERNAL_URI:
760         case EV_LINK_ACTION_TYPE_LAUNCH:
761                 return TRUE;
762         default:
763                 return FALSE;
764         }
765
766         return FALSE;
767 }
768
769 static EvLink *
770 ev_view_presentation_get_link_at_location (EvViewPresentation *pview,
771                                            gdouble             x,
772                                            gdouble             y)
773 {
774         GdkRectangle   page_area;
775         EvMappingList *link_mapping;
776         EvLink        *link;
777         gdouble        width, height;
778         gdouble        new_x, new_y;
779         gdouble        scale;
780
781         if (!pview->page_cache)
782                 return NULL;
783
784         ev_document_get_page_size (pview->document, pview->current_page, &width, &height);
785         ev_view_presentation_get_page_area (pview, &page_area);
786         scale = ev_view_presentation_get_scale_for_page (pview, pview->current_page);
787         x = (x - page_area.x) / scale;
788         y = (y - page_area.y) / scale;
789         switch (pview->rotation) {
790         case 0:
791         case 360:
792                 new_x = x;
793                 new_y = y;
794                 break;
795         case 90:
796                 new_x = y;
797                 new_y = height - x;
798                 break;
799         case 180:
800                 new_x = width - x;
801                 new_y = height - y;
802                 break;
803         case 270:
804                 new_x = width - y;
805                 new_y = x;
806                 break;
807         default:
808                 g_assert_not_reached ();
809         }
810
811         link_mapping = ev_page_cache_get_link_mapping (pview->page_cache, pview->current_page);
812
813         link = link_mapping ? ev_mapping_list_get_data (link_mapping, new_x, new_y) : NULL;
814
815         return link && ev_view_presentation_link_is_supported (pview, link) ? link : NULL;
816 }
817
818 static void
819 ev_vew_presentation_handle_link (EvViewPresentation *pview,
820                                  EvLink             *link)
821 {
822         EvLinkAction *action;
823
824         action = ev_link_get_action (link);
825
826         switch (ev_link_action_get_action_type (action)) {
827         case EV_LINK_ACTION_TYPE_NAMED: {
828                 const gchar *name = ev_link_action_get_name (action);
829
830                 if (g_ascii_strcasecmp (name, "FirstPage") == 0) {
831                         ev_view_presentation_update_current_page (pview, 0);
832                 } else if (g_ascii_strcasecmp (name, "PrevPage") == 0) {
833                         ev_view_presentation_update_current_page (pview, pview->current_page - 1);
834                 } else if (g_ascii_strcasecmp (name, "NextPage") == 0) {
835                         ev_view_presentation_update_current_page (pview, pview->current_page + 1);
836                 } else if (g_ascii_strcasecmp (name, "LastPage") == 0) {
837                         gint n_pages;
838
839                         n_pages = ev_document_get_n_pages (pview->document);
840                         ev_view_presentation_update_current_page (pview, n_pages - 1);
841                 }
842         }
843                 break;
844
845         case EV_LINK_ACTION_TYPE_GOTO_DEST: {
846                 EvLinkDest *dest;
847                 gint        page;
848
849                 dest = ev_link_action_get_dest (action);
850                 page = ev_document_links_get_dest_page (EV_DOCUMENT_LINKS (pview->document), dest);
851                 ev_view_presentation_update_current_page (pview, page);
852         }
853                 break;
854         case EV_LINK_ACTION_TYPE_GOTO_REMOTE:
855         case EV_LINK_ACTION_TYPE_EXTERNAL_URI:
856         case EV_LINK_ACTION_TYPE_LAUNCH:
857                 g_signal_emit (pview, signals[SIGNAL_EXTERNAL_LINK], 0, action);
858                 break;
859         default:
860                 break;
861         }
862 }
863
864 /* Cursors */
865 static void
866 ev_view_presentation_set_cursor (EvViewPresentation *pview,
867                                  EvViewCursor        view_cursor)
868 {
869         GtkWidget  *widget;
870         GdkCursor  *cursor;
871
872         if (pview->cursor == view_cursor)
873                 return;
874
875         widget = GTK_WIDGET (pview);
876         if (!gtk_widget_get_realized (widget))
877                 gtk_widget_realize (widget);
878
879         pview->cursor = view_cursor;
880
881         cursor = ev_view_cursor_new (gtk_widget_get_display (widget), view_cursor);
882         gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
883         gdk_flush ();
884         if (cursor)
885                 g_object_unref (cursor);
886 }
887
888 static void
889 ev_view_presentation_set_cursor_for_location (EvViewPresentation *pview,
890                                               gdouble             x,
891                                               gdouble             y)
892 {
893         if (ev_view_presentation_get_link_at_location (pview, x, y))
894                 ev_view_presentation_set_cursor (pview, EV_VIEW_CURSOR_LINK);
895         else
896                 ev_view_presentation_set_cursor (pview, EV_VIEW_CURSOR_NORMAL);
897 }
898
899 static gboolean
900 hide_cursor_timeout_cb (EvViewPresentation *pview)
901 {
902         ev_view_presentation_set_cursor (pview, EV_VIEW_CURSOR_HIDDEN);
903         pview->hide_cursor_timeout_id = 0;
904
905         return FALSE;
906 }
907
908 static void
909 ev_view_presentation_hide_cursor_timeout_stop (EvViewPresentation *pview)
910 {
911         if (pview->hide_cursor_timeout_id > 0)
912                 g_source_remove (pview->hide_cursor_timeout_id);
913         pview->hide_cursor_timeout_id = 0;
914 }
915
916 static void
917 ev_view_presentation_hide_cursor_timeout_start (EvViewPresentation *pview)
918 {
919         ev_view_presentation_hide_cursor_timeout_stop (pview);
920         pview->hide_cursor_timeout_id =
921                 g_timeout_add_seconds (HIDE_CURSOR_TIMEOUT,
922                                        (GSourceFunc)hide_cursor_timeout_cb,
923                                        pview);
924 }
925
926 static void
927 ev_view_presentation_update_current_surface (EvViewPresentation *pview,
928                                              cairo_surface_t    *surface)
929 {
930         if (!surface || pview->current_surface == surface)
931                 return;
932
933         cairo_surface_reference (surface);
934         if (pview->current_surface)
935                 cairo_surface_destroy (pview->current_surface);
936         pview->current_surface = surface;
937 }
938
939 static void
940 ev_view_presentation_dispose (GObject *object)
941 {
942         EvViewPresentation *pview = EV_VIEW_PRESENTATION (object);
943
944         if (pview->document) {
945                 g_object_unref (pview->document);
946                 pview->document = NULL;
947         }
948
949         ev_view_presentation_animation_cancel (pview);
950         ev_view_presentation_transition_stop (pview);
951         ev_view_presentation_hide_cursor_timeout_stop (pview);
952         ev_view_presentation_reset_jobs (pview);
953
954         if (pview->current_surface) {
955                 cairo_surface_destroy (pview->current_surface);
956                 pview->current_surface = NULL;
957         }
958
959         if (pview->page_cache) {
960                 g_object_unref (pview->page_cache);
961                 pview->page_cache = NULL;
962         }
963
964         if (pview->goto_window) {
965                 gtk_widget_destroy (pview->goto_window);
966                 pview->goto_window = NULL;
967                 pview->goto_entry = NULL;
968         }
969
970         G_OBJECT_CLASS (ev_view_presentation_parent_class)->dispose (object);
971 }
972
973 static void
974 ev_view_presentation_get_preferred_width (GtkWidget *widget,
975                                           gint      *minimum,
976                                           gint      *natural)
977 {
978         *minimum = *natural = 0;
979 }
980
981 static void
982 ev_view_presentation_get_preferred_height (GtkWidget *widget,
983                                            gint      *minimum,
984                                            gint      *natural)
985 {
986         *minimum = *natural = 0;
987 }
988
989 static void
990 ev_view_presentation_draw_end_page (EvViewPresentation *pview,
991                                     cairo_t *cr)
992 {
993         GtkWidget *widget = GTK_WIDGET (pview);
994         PangoLayout *layout;
995         PangoFontDescription *font_desc;
996         gchar *markup;
997         GtkAllocation allocation;
998         GdkRectangle area = {0};
999         const gchar *text = _("End of presentation. Click to exit.");
1000
1001         if (pview->state != EV_PRESENTATION_END)
1002                 return;
1003
1004         layout = gtk_widget_create_pango_layout (widget, NULL);
1005         markup = g_strdup_printf ("<span foreground=\"white\">%s</span>", text);
1006         pango_layout_set_markup (layout, markup, -1);
1007         g_free (markup);
1008
1009         font_desc = pango_font_description_new ();
1010         pango_font_description_set_size (font_desc, 16 * PANGO_SCALE);
1011         pango_layout_set_font_description (layout, font_desc);
1012
1013         gtk_widget_get_allocation (widget, &allocation);
1014         area.width = allocation.width;
1015         area.height = allocation.height;
1016
1017         gtk_render_layout (gtk_widget_get_style_context (widget),
1018                            cr, 15, 15, layout);
1019
1020         pango_font_description_free (font_desc);
1021         g_object_unref (layout);
1022 }
1023
1024 static gboolean
1025 ev_view_presentation_draw (GtkWidget *widget,
1026                            cairo_t   *cr)
1027 {
1028         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1029         GdkRectangle        page_area;
1030         GdkRectangle        overlap;
1031         cairo_surface_t    *surface;
1032         GdkRectangle        clip_rect;
1033
1034         if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
1035                 return FALSE;
1036
1037         switch (pview->state) {
1038         case EV_PRESENTATION_END:
1039                 ev_view_presentation_draw_end_page (pview, cr);
1040                 return FALSE;
1041         case EV_PRESENTATION_BLACK:
1042         case EV_PRESENTATION_WHITE:
1043                 return FALSE;
1044         case EV_PRESENTATION_NORMAL:
1045                 break;
1046         }
1047
1048         if (pview->animation) {
1049                 if (ev_transition_animation_ready (pview->animation)) {
1050                         ev_view_presentation_get_page_area (pview, &page_area);
1051
1052                         cairo_save (cr);
1053
1054                         /* normalize to x=0, y=0 */
1055                         cairo_translate (cr, page_area.x, page_area.y);
1056                         page_area.x = page_area.y = 0;
1057
1058                         /* Try to fix rounding errors */
1059                         page_area.width--;
1060
1061                         ev_transition_animation_paint (pview->animation, cr, page_area);
1062
1063                         cairo_restore (cr);
1064                 }
1065
1066                 return TRUE;
1067         }
1068
1069         surface = pview->curr_job ? EV_JOB_RENDER (pview->curr_job)->surface : NULL;
1070         if (surface) {
1071                 ev_view_presentation_update_current_surface (pview, surface);
1072         } else if (pview->current_surface) {
1073                 surface = pview->current_surface;
1074         } else {
1075                 return FALSE;
1076         }
1077
1078         ev_view_presentation_get_page_area (pview, &page_area);
1079         if (gdk_rectangle_intersect (&page_area, &clip_rect, &overlap)) {
1080                 cairo_save (cr);
1081
1082                 /* Try to fix rounding errors. See bug #438760 */
1083                 if (overlap.width == page_area.width)
1084                         overlap.width--;
1085
1086                 cairo_rectangle (cr, overlap.x, overlap.y, overlap.width, overlap.height);
1087                 cairo_set_source_surface (cr, surface, page_area.x, page_area.y);
1088                 cairo_fill (cr);
1089
1090                 cairo_restore (cr);
1091         }
1092
1093         return FALSE;
1094 }
1095
1096 static gboolean
1097 ev_view_presentation_key_press_event (GtkWidget   *widget,
1098                                       GdkEventKey *event)
1099 {
1100         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1101
1102         if (pview->state == EV_PRESENTATION_END)
1103                 return gtk_bindings_activate_event (G_OBJECT (widget), event);
1104
1105         switch (event->keyval) {
1106         case GDK_KEY_b:
1107         case GDK_KEY_B:
1108         case GDK_KEY_period:
1109         case GDK_KEY_KP_Decimal:
1110                 if (pview->state == EV_PRESENTATION_BLACK)
1111                         ev_view_presentation_set_normal (pview);
1112                 else
1113                         ev_view_presentation_set_black (pview);
1114
1115                 return TRUE;
1116         case GDK_KEY_w:
1117         case GDK_KEY_W:
1118                 if (pview->state == EV_PRESENTATION_WHITE)
1119                         ev_view_presentation_set_normal (pview);
1120                 else
1121                         ev_view_presentation_set_white (pview);
1122
1123                 return TRUE;
1124         case GDK_KEY_Home:
1125                 if (pview->state == EV_PRESENTATION_NORMAL) {
1126                         ev_view_presentation_update_current_page (pview, 0);
1127                         return TRUE;
1128                 }
1129                 break;
1130         case GDK_KEY_End:
1131                 if (pview->state == EV_PRESENTATION_NORMAL) {
1132                         gint page;
1133
1134                         page = ev_document_get_n_pages (pview->document) - 1;
1135                         ev_view_presentation_update_current_page (pview, page);
1136
1137                         return TRUE;
1138                 }
1139                 break;
1140         default:
1141                 break;
1142         }
1143
1144         ev_view_presentation_set_normal (pview);
1145
1146         if (ev_document_get_n_pages (pview->document) > 1 && KEY_IS_NUMERIC (event->keyval)) {
1147                 gint x, y;
1148
1149                 ev_view_presentation_goto_window_create (pview);
1150                 ev_view_presentation_goto_window_send_key_event (pview, (GdkEvent *)event);
1151                 gtk_widget_get_pointer (GTK_WIDGET (pview), &x, &y);
1152                 gtk_window_move (GTK_WINDOW (pview->goto_window), x, y);
1153                 gtk_widget_show (pview->goto_window);
1154                 ev_view_presentation_goto_entry_grab_focus (pview);
1155
1156                 return TRUE;
1157         }
1158
1159         return gtk_bindings_activate_event (G_OBJECT (widget), event);
1160 }
1161
1162 static gboolean
1163 ev_view_presentation_button_release_event (GtkWidget      *widget,
1164                                            GdkEventButton *event)
1165 {
1166         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1167
1168         switch (event->button) {
1169         case 1: {
1170                 EvLink *link;
1171
1172                 if (pview->state == EV_PRESENTATION_END) {
1173                         g_signal_emit (pview, signals[FINISHED], 0, NULL);
1174
1175                         return FALSE;
1176                 }
1177
1178                 link = ev_view_presentation_get_link_at_location (pview,
1179                                                                   event->x,
1180                                                                   event->y);
1181                 if (link)
1182                         ev_vew_presentation_handle_link (pview, link);
1183                 else
1184                         ev_view_presentation_next_page (pview);
1185         }
1186                 break;
1187         case 3:
1188                 ev_view_presentation_previous_page (pview);
1189                 break;
1190         default:
1191                 break;
1192         }
1193
1194         return FALSE;
1195 }
1196
1197 static gint
1198 ev_view_presentation_focus_out (GtkWidget     *widget,
1199                                 GdkEventFocus *event)
1200 {
1201         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1202
1203         if (pview->goto_window)
1204                 ev_view_presentation_goto_window_hide (pview);
1205
1206         return FALSE;
1207 }
1208
1209 static gboolean
1210 ev_view_presentation_motion_notify_event (GtkWidget      *widget,
1211                                           GdkEventMotion *event)
1212 {
1213         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1214
1215         ev_view_presentation_hide_cursor_timeout_start (pview);
1216         ev_view_presentation_set_cursor_for_location (pview, event->x, event->y);
1217
1218         return FALSE;
1219 }
1220
1221 static gboolean
1222 init_presentation (GtkWidget *widget)
1223 {
1224         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1225         GdkScreen          *screen = gtk_widget_get_screen (widget);
1226         GdkRectangle        monitor;
1227         gint                monitor_num;
1228
1229         monitor_num = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (widget));
1230         gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1231         pview->monitor_width = monitor.width;
1232         pview->monitor_height = monitor.height;
1233
1234         ev_view_presentation_update_current_page (pview, pview->current_page);
1235         ev_view_presentation_hide_cursor_timeout_start (pview);
1236
1237         return FALSE;
1238 }
1239
1240 static void
1241 ev_view_presentation_realize (GtkWidget *widget)
1242 {
1243         GdkWindow     *window;
1244         GdkWindowAttr  attributes;
1245         GtkAllocation  allocation;
1246
1247         gtk_widget_set_realized (widget, TRUE);
1248
1249         attributes.window_type = GDK_WINDOW_CHILD;
1250         attributes.wclass = GDK_INPUT_OUTPUT;
1251         attributes.visual = gtk_widget_get_visual (widget);
1252
1253         gtk_widget_get_allocation (widget, &allocation);
1254         attributes.x = allocation.x;
1255         attributes.y = allocation.y;
1256         attributes.width = allocation.width;
1257         attributes.height = allocation.height;
1258         attributes.event_mask = GDK_EXPOSURE_MASK |
1259                 GDK_BUTTON_PRESS_MASK |
1260                 GDK_BUTTON_RELEASE_MASK |
1261                 GDK_SCROLL_MASK |
1262                 GDK_KEY_PRESS_MASK |
1263                 GDK_POINTER_MOTION_MASK |
1264                 GDK_POINTER_MOTION_HINT_MASK |
1265                 GDK_ENTER_NOTIFY_MASK |
1266                 GDK_LEAVE_NOTIFY_MASK;
1267
1268         window = gdk_window_new (gtk_widget_get_parent_window (widget),
1269                                  &attributes,
1270                                  GDK_WA_X | GDK_WA_Y |
1271                                  GDK_WA_VISUAL);
1272
1273         gdk_window_set_user_data (window, widget);
1274         gtk_widget_set_window (widget, window);
1275         gtk_style_context_set_background (gtk_widget_get_style_context (widget),
1276                                           window);
1277
1278         g_idle_add ((GSourceFunc)init_presentation, widget);
1279 }
1280
1281 static void
1282 ev_view_presentation_change_page (EvViewPresentation *pview,
1283                                   GtkScrollType       scroll)
1284 {
1285         switch (scroll) {
1286         case GTK_SCROLL_PAGE_FORWARD:
1287                 ev_view_presentation_next_page (pview);
1288                 break;
1289         case GTK_SCROLL_PAGE_BACKWARD:
1290                 ev_view_presentation_previous_page (pview);
1291                 break;
1292         default:
1293                 g_assert_not_reached ();
1294         }
1295 }
1296
1297 static gboolean
1298 ev_view_presentation_scroll_event (GtkWidget      *widget,
1299                                    GdkEventScroll *event)
1300 {
1301         EvViewPresentation *pview = EV_VIEW_PRESENTATION (widget);
1302         guint               state;
1303
1304         state = event->state & gtk_accelerator_get_default_mod_mask ();
1305         if (state != 0)
1306                 return FALSE;
1307
1308         switch (event->direction) {
1309         case GDK_SCROLL_DOWN:
1310         case GDK_SCROLL_RIGHT:
1311                 ev_view_presentation_change_page (pview, GTK_SCROLL_PAGE_FORWARD);
1312                 break;
1313         case GDK_SCROLL_UP:
1314         case GDK_SCROLL_LEFT:
1315                 ev_view_presentation_change_page (pview, GTK_SCROLL_PAGE_BACKWARD);
1316                 break;
1317         }
1318
1319         return TRUE;
1320 }
1321
1322
1323 static void
1324 add_change_page_binding_keypad (GtkBindingSet  *binding_set,
1325                                 guint           keyval,
1326                                 GdkModifierType modifiers,
1327                                 GtkScrollType   scroll)
1328 {
1329         guint keypad_keyval = keyval - GDK_KEY_Left + GDK_KEY_KP_Left;
1330
1331         gtk_binding_entry_add_signal (binding_set, keyval, modifiers,
1332                                       "change_page", 1,
1333                                       GTK_TYPE_SCROLL_TYPE, scroll);
1334         gtk_binding_entry_add_signal (binding_set, keypad_keyval, modifiers,
1335                                       "change_page", 1,
1336                                       GTK_TYPE_SCROLL_TYPE, scroll);
1337 }
1338
1339 static void
1340 ev_view_presentation_set_property (GObject      *object,
1341                                    guint         prop_id,
1342                                    const GValue *value,
1343                                    GParamSpec   *pspec)
1344 {
1345         EvViewPresentation *pview = EV_VIEW_PRESENTATION (object);
1346
1347         switch (prop_id) {
1348         case PROP_DOCUMENT:
1349                 pview->document = g_value_dup_object (value);
1350                 pview->enable_animations = EV_IS_DOCUMENT_TRANSITION (pview->document);
1351                 break;
1352         case PROP_CURRENT_PAGE:
1353                 pview->current_page = g_value_get_uint (value);
1354                 break;
1355         case PROP_ROTATION:
1356                 ev_view_presentation_set_rotation (pview, g_value_get_uint (value));
1357                 break;
1358         case PROP_INVERTED_COLORS:
1359                 pview->inverted_colors = g_value_get_boolean (value);
1360                 break;
1361         default:
1362                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1363         }
1364 }
1365
1366 static void
1367 ev_view_presentation_get_property (GObject    *object,
1368                                    guint       prop_id,
1369                                    GValue     *value,
1370                                    GParamSpec *pspec)
1371 {
1372         EvViewPresentation *pview = EV_VIEW_PRESENTATION (object);
1373
1374         switch (prop_id) {
1375         case PROP_ROTATION:
1376                 g_value_set_uint (value, ev_view_presentation_get_rotation (pview));
1377                 break;
1378         default:
1379                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1380         }
1381 }
1382
1383 static GObject *
1384 ev_view_presentation_constructor (GType                  type,
1385                                   guint                  n_construct_properties,
1386                                   GObjectConstructParam *construct_params)
1387 {
1388         GObject            *object;
1389         EvViewPresentation *pview;
1390
1391         object = G_OBJECT_CLASS (ev_view_presentation_parent_class)->constructor (type,
1392                                                                                   n_construct_properties,
1393                                                                                   construct_params);
1394         pview = EV_VIEW_PRESENTATION (object);
1395         pview->is_constructing = FALSE;
1396
1397         if (EV_IS_DOCUMENT_LINKS (pview->document)) {
1398                 pview->page_cache = ev_page_cache_new (pview->document);
1399                 ev_page_cache_set_flags (pview->page_cache, EV_PAGE_DATA_INCLUDE_LINKS);
1400         }
1401
1402         return object;
1403 }
1404
1405 static void
1406 ev_view_presentation_class_init (EvViewPresentationClass *klass)
1407 {
1408         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1409         GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
1410         GtkBindingSet  *binding_set;
1411         GtkCssProvider *provider;
1412
1413         klass->change_page = ev_view_presentation_change_page;
1414
1415         gobject_class->dispose = ev_view_presentation_dispose;
1416
1417         widget_class->get_preferred_width = ev_view_presentation_get_preferred_width;
1418         widget_class->get_preferred_height = ev_view_presentation_get_preferred_height;
1419         widget_class->realize = ev_view_presentation_realize;
1420         widget_class->draw = ev_view_presentation_draw;
1421         widget_class->key_press_event = ev_view_presentation_key_press_event;
1422         widget_class->button_release_event = ev_view_presentation_button_release_event;
1423         widget_class->focus_out_event = ev_view_presentation_focus_out;
1424         widget_class->motion_notify_event = ev_view_presentation_motion_notify_event;
1425         widget_class->scroll_event = ev_view_presentation_scroll_event;
1426
1427         gobject_class->constructor = ev_view_presentation_constructor;
1428         gobject_class->set_property = ev_view_presentation_set_property;
1429         gobject_class->get_property = ev_view_presentation_get_property;
1430
1431         g_object_class_install_property (gobject_class,
1432                                          PROP_DOCUMENT,
1433                                          g_param_spec_object ("document",
1434                                                               "Document",
1435                                                               "Document",
1436                                                               EV_TYPE_DOCUMENT,
1437                                                               G_PARAM_WRITABLE |
1438                                                               G_PARAM_CONSTRUCT_ONLY));
1439         g_object_class_install_property (gobject_class,
1440                                          PROP_CURRENT_PAGE,
1441                                          g_param_spec_uint ("current_page",
1442                                                             "Current Page",
1443                                                             "The current page",
1444                                                             0, G_MAXUINT, 0,
1445                                                             G_PARAM_WRITABLE |
1446                                                             G_PARAM_CONSTRUCT_ONLY));
1447         g_object_class_install_property (gobject_class,
1448                                          PROP_ROTATION,
1449                                          g_param_spec_uint ("rotation",
1450                                                             "Rotation",
1451                                                             "Current rotation angle",
1452                                                             0, 360, 0,
1453                                                             G_PARAM_READWRITE |
1454                                                             G_PARAM_CONSTRUCT));
1455         g_object_class_install_property (gobject_class,
1456                                          PROP_INVERTED_COLORS,
1457                                          g_param_spec_boolean ("inverted_colors",
1458                                                                "Inverted Colors",
1459                                                                "Whether presentation is displayed with inverted colors",
1460                                                                FALSE,
1461                                                                G_PARAM_WRITABLE |
1462                                                                G_PARAM_CONSTRUCT_ONLY));
1463
1464         signals[CHANGE_PAGE] =
1465                 g_signal_new ("change_page",
1466                               G_OBJECT_CLASS_TYPE (gobject_class),
1467                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1468                               G_STRUCT_OFFSET (EvViewPresentationClass, change_page),
1469                               NULL, NULL,
1470                               g_cclosure_marshal_VOID__ENUM,
1471                               G_TYPE_NONE, 1,
1472                               GTK_TYPE_SCROLL_TYPE);
1473         signals[FINISHED] =
1474                 g_signal_new ("finished",
1475                               G_OBJECT_CLASS_TYPE (gobject_class),
1476                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1477                               G_STRUCT_OFFSET (EvViewPresentationClass, finished),
1478                               NULL, NULL,
1479                               g_cclosure_marshal_VOID__VOID,
1480                               G_TYPE_NONE, 0,
1481                               G_TYPE_NONE);
1482         signals[SIGNAL_EXTERNAL_LINK] =
1483                 g_signal_new ("external-link",
1484                               G_TYPE_FROM_CLASS (gobject_class),
1485                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1486                               G_STRUCT_OFFSET (EvViewPresentationClass, external_link),
1487                               NULL, NULL,
1488                               g_cclosure_marshal_VOID__OBJECT,
1489                               G_TYPE_NONE, 1,
1490                               G_TYPE_OBJECT);
1491
1492         binding_set = gtk_binding_set_by_class (klass);
1493         add_change_page_binding_keypad (binding_set, GDK_KEY_Left,  0, GTK_SCROLL_PAGE_BACKWARD);
1494         add_change_page_binding_keypad (binding_set, GDK_KEY_Right, 0, GTK_SCROLL_PAGE_FORWARD);
1495         add_change_page_binding_keypad (binding_set, GDK_KEY_Up,    0, GTK_SCROLL_PAGE_BACKWARD);
1496         add_change_page_binding_keypad (binding_set, GDK_KEY_Down,  0, GTK_SCROLL_PAGE_FORWARD);
1497         gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
1498                                       "change_page", 1,
1499                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
1500         gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, 0,
1501                                       "change_page", 1,
1502                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
1503         gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Down, 0,
1504                                       "change_page", 1,
1505                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
1506         gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Up, 0,
1507                                       "change_page", 1,
1508                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
1509         gtk_binding_entry_add_signal (binding_set, GDK_KEY_J, 0,
1510                                       "change_page", 1,
1511                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
1512         gtk_binding_entry_add_signal (binding_set, GDK_KEY_H, 0,
1513                                       "change_page", 1,
1514                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
1515         gtk_binding_entry_add_signal (binding_set, GDK_KEY_L, 0,
1516                                       "change_page", 1,
1517                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
1518         gtk_binding_entry_add_signal (binding_set, GDK_KEY_K, 0,
1519                                       "change_page", 1,
1520                                       GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
1521
1522         provider = gtk_css_provider_new ();
1523         gtk_css_provider_load_from_data (provider,
1524                                          "EvViewPresentation {\n"
1525                                          " background-color: black; }",
1526                                          -1, NULL);
1527         gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
1528                                                    GTK_STYLE_PROVIDER (provider),
1529                                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1530         g_object_unref (provider);
1531 }
1532
1533 static void
1534 ev_view_presentation_init (EvViewPresentation *pview)
1535 {
1536         gtk_widget_set_can_focus (GTK_WIDGET (pview), TRUE);
1537         pview->is_constructing = TRUE;
1538 }
1539
1540 GtkWidget *
1541 ev_view_presentation_new (EvDocument *document,
1542                           guint       current_page,
1543                           guint       rotation,
1544                           gboolean    inverted_colors)
1545 {
1546         g_return_val_if_fail (EV_IS_DOCUMENT (document), NULL);
1547         g_return_val_if_fail (current_page < ev_document_get_n_pages (document), NULL);
1548
1549         return GTK_WIDGET (g_object_new (EV_TYPE_VIEW_PRESENTATION,
1550                                          "document", document,
1551                                          "current_page", current_page,
1552                                          "rotation", rotation,
1553                                          "inverted_colors", inverted_colors,
1554                                          NULL));
1555 }
1556
1557 guint
1558 ev_view_presentation_get_current_page (EvViewPresentation *pview)
1559 {
1560         return pview->current_page;
1561 }
1562
1563 void
1564 ev_view_presentation_set_rotation (EvViewPresentation *pview,
1565                                    gint                rotation)
1566 {
1567         if (rotation >= 360)
1568                 rotation -= 360;
1569         else if (rotation < 0)
1570                 rotation += 360;
1571
1572         if (pview->rotation == rotation)
1573                 return;
1574
1575         pview->rotation = rotation;
1576         g_object_notify (G_OBJECT (pview), "rotation");
1577         if (pview->is_constructing)
1578                 return;
1579
1580         pview->scale = 0;
1581         ev_view_presentation_reset_jobs (pview);
1582         ev_view_presentation_update_current_page (pview, pview->current_page);
1583 }
1584
1585 guint
1586 ev_view_presentation_get_rotation (EvViewPresentation *pview)
1587 {
1588         return pview->rotation;
1589 }