]> www.fi.muni.cz Git - evince.git/blob - shell/ev-view.c
8998a7f161fb1050df1086836526d6c00b244243
[evince.git] / shell / ev-view.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /* this file is part of evince, a gnome document viewer
3  *
4  *  Copyright (C) 2004 Red Hat, Inc
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19  */
20
21 #include <gtk/gtkalignment.h>
22 #include <glib/gi18n.h>
23 #include <gtk/gtkbindings.h>
24 #include <gtk/gtkselection.h>
25 #include <gtk/gtkclipboard.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <libgnomevfs/gnome-vfs-utils.h>
28
29 #include "ev-marshal.h"
30 #include "ev-view.h"
31 #include "ev-document-find.h"
32 #include "ev-document-misc.h"
33 #include "ev-debug.h"
34
35 #define EV_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_VIEW, EvViewClass))
36 #define EV_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_VIEW))
37 #define EV_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EV_TYPE_VIEW, EvViewClass))
38
39 enum {
40         PROP_0,
41         PROP_STATUS,
42         PROP_FIND_STATUS
43 };
44
45 enum {
46   TARGET_STRING,
47   TARGET_TEXT,
48   TARGET_COMPOUND_TEXT,
49   TARGET_UTF8_STRING,
50   TARGET_TEXT_BUFFER_CONTENTS
51 };
52
53 static const GtkTargetEntry targets[] = {
54         { "STRING", 0, TARGET_STRING },
55         { "TEXT",   0, TARGET_TEXT },
56         { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
57         { "UTF8_STRING", 0, TARGET_UTF8_STRING },
58 };
59
60 typedef enum {
61         EV_VIEW_CURSOR_NORMAL,
62         EV_VIEW_CURSOR_LINK,
63         EV_VIEW_CURSOR_WAIT,
64         EV_VIEW_CURSOR_HIDDEN
65 } EvViewCursor;
66
67 #define ZOOM_IN_FACTOR  1.2
68 #define ZOOM_OUT_FACTOR (1.0/ZOOM_IN_FACTOR)
69
70 #define MIN_SCALE 0.05409
71 #define MAX_SCALE 18.4884
72
73 /* FIXME: temporarily setting the epsilon very high until we figure out how to
74  * constrain the size of the window to a pixel width, instead of to the zoom
75  * level */
76 #define ZOOM_EPSILON 1e-2
77
78
79 struct _EvView {
80         GtkWidget parent_instance;
81
82         EvDocument *document;
83         
84         GdkWindow *bin_window;
85
86         char *status;
87         char *find_status;
88         
89         int scroll_x;
90         int scroll_y;
91
92         gboolean pressed_button;
93         gboolean has_selection;
94         GdkPoint selection_start;
95         GdkRectangle selection;
96         EvViewCursor cursor;
97
98         GtkAdjustment *hadjustment;
99         GtkAdjustment *vadjustment;
100
101         int find_page;
102         int find_result;
103         int spacing;
104
105         double scale;
106         EvSizingMode sizing_mode;
107 };
108
109 struct _EvViewClass {
110         GtkWidgetClass parent_class;
111
112         void    (*set_scroll_adjustments) (EvView         *view,
113                                            GtkAdjustment  *hadjustment,
114                                            GtkAdjustment  *vadjustment);
115         void    (*scroll_view)            (EvView         *view,
116                                            GtkScrollType   scroll,
117                                            gboolean        horizontal);
118         
119         /* Should this be notify::page? */
120         void    (*page_changed)           (EvView         *view);
121 };
122
123 static guint page_changed_signal = 0;
124
125 static void ev_view_set_scroll_adjustments (EvView         *view,
126                                             GtkAdjustment  *hadjustment,
127                                             GtkAdjustment  *vadjustment);
128     
129 G_DEFINE_TYPE (EvView, ev_view, GTK_TYPE_WIDGET)
130
131 /*** Helper functions ***/       
132      
133 static void
134 view_update_adjustments (EvView *view)
135 {
136         int old_x = view->scroll_x;
137         int old_y = view->scroll_y;
138   
139         if (view->hadjustment)
140                 view->scroll_x = view->hadjustment->value;
141         else
142                 view->scroll_x = 0;
143
144         if (view->vadjustment)
145                 view->scroll_y = view->vadjustment->value;
146         else
147                 view->scroll_y = 0;
148   
149         if (GTK_WIDGET_REALIZED (view) &&
150             (view->scroll_x != old_x || view->scroll_y != old_y)) {
151                 gdk_window_move (view->bin_window, - view->scroll_x, - view->scroll_y);
152                 gdk_window_process_updates (view->bin_window, TRUE);
153         }
154 }
155
156 static void
157 view_set_adjustment_values (EvView         *view,
158                             GtkOrientation  orientation)
159 {
160         GtkWidget *widget = GTK_WIDGET (view);
161         GtkAdjustment *adjustment;
162         gboolean value_changed = FALSE;
163         int requisition;
164         int allocation;
165
166         if (orientation == GTK_ORIENTATION_HORIZONTAL)  {
167                 requisition = widget->requisition.width;
168                 allocation = widget->allocation.width;
169                 adjustment = view->hadjustment;
170         } else {
171                 requisition = widget->requisition.height;
172                 allocation = widget->allocation.height;
173                 adjustment = view->vadjustment;
174         }
175
176         if (!adjustment)
177                 return;
178   
179         adjustment->page_size = allocation;
180         adjustment->step_increment = allocation * 0.1;
181         adjustment->page_increment = allocation * 0.9;
182         adjustment->lower = 0;
183         adjustment->upper = MAX (allocation, requisition);
184
185         if (adjustment->value > adjustment->upper - adjustment->page_size) {
186                 adjustment->value = adjustment->upper - adjustment->page_size;
187                 value_changed = TRUE;
188         }
189
190         gtk_adjustment_changed (adjustment);
191         if (value_changed)
192                 gtk_adjustment_value_changed (adjustment);
193 }
194
195 /*** Virtual function implementations ***/       
196      
197 static void
198 ev_view_finalize (GObject *object)
199 {
200         EvView *view = EV_VIEW (object);
201
202         LOG ("Finalize");
203
204         if (view->document)
205                 g_object_unref (view->document);
206
207         ev_view_set_scroll_adjustments (view, NULL, NULL);
208
209         G_OBJECT_CLASS (ev_view_parent_class)->finalize (object);
210 }
211
212 static void
213 ev_view_destroy (GtkObject *object)
214 {
215         EvView *view = EV_VIEW (object);
216
217         ev_view_set_scroll_adjustments (view, NULL, NULL);
218   
219         GTK_OBJECT_CLASS (ev_view_parent_class)->destroy (object);
220 }
221
222 static void
223 ev_view_size_request (GtkWidget      *widget,
224                       GtkRequisition *requisition)
225 {
226         EvView *view = EV_VIEW (widget);
227         GtkBorder border;
228         gint width, height;
229
230         if (! GTK_WIDGET_REALIZED (widget))
231                 return;
232
233         if (! view->document) {
234                 requisition->width = 1;
235                 requisition->height = 1;
236                 return;
237         }
238
239         ev_document_get_page_size (view->document, -1,
240                                    &width, &height);
241         ev_document_misc_get_page_border_size (width, height, &border);
242
243         switch (view->sizing_mode) {
244         case EV_SIZING_BEST_FIT:
245                 requisition->width = MIN_SCALE * ((float) width) / view->scale;
246                 requisition->height = MIN_SCALE * ((float) height) / view->scale;
247                 break;
248         case EV_SIZING_FIT_WIDTH:
249                 requisition->width = MIN_SCALE * ((float) width) / view->scale;
250                 requisition->height = height + border.top + border.bottom;
251                 requisition->height += view->spacing * 2;
252                 break;
253         case EV_SIZING_FREE:
254                 requisition->width = width + border.left + border.right;
255                 requisition->height = height + border.top + border.bottom;
256                 requisition->width += view->spacing * 2;
257                 requisition->height += view->spacing * 2;
258                 break;
259         }
260 }
261
262 static void
263 ev_view_size_allocate (GtkWidget      *widget,
264                        GtkAllocation  *allocation)
265 {
266         EvView *view = EV_VIEW (widget);
267
268         GTK_WIDGET_CLASS (ev_view_parent_class)->size_allocate (widget, allocation);
269
270         view_set_adjustment_values (view, GTK_ORIENTATION_HORIZONTAL);
271         view_set_adjustment_values (view, GTK_ORIENTATION_VERTICAL);
272
273         if (GTK_WIDGET_REALIZED (widget)) {
274                 gdk_window_resize (view->bin_window,
275                                    MAX (widget->allocation.width, widget->requisition.width),
276                                    MAX (widget->allocation.height, widget->requisition.height));
277         }
278 }
279
280 static void
281 ev_view_realize (GtkWidget *widget)
282 {
283         EvView *view = EV_VIEW (widget);
284         GdkWindowAttr attributes;
285
286         GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
287   
288
289         attributes.window_type = GDK_WINDOW_CHILD;
290         attributes.wclass = GDK_INPUT_OUTPUT;
291         attributes.visual = gtk_widget_get_visual (widget);
292         attributes.colormap = gtk_widget_get_colormap (widget);
293   
294         attributes.x = widget->allocation.x;
295         attributes.y = widget->allocation.y;
296         attributes.width = widget->allocation.width;
297         attributes.height = widget->allocation.height;
298         attributes.event_mask = 0;
299   
300         widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
301                                          &attributes,
302                                          GDK_WA_X | GDK_WA_Y |
303                                          GDK_WA_COLORMAP |
304                                          GDK_WA_VISUAL);
305         gdk_window_set_user_data (widget->window, widget);
306         widget->style = gtk_style_attach (widget->style, widget->window);
307         gdk_window_set_background (widget->window, &widget->style->mid[widget->state]);
308   
309         attributes.x = 0;
310         attributes.y = 0;
311         attributes.width = MAX (widget->allocation.width, widget->requisition.width);
312         attributes.height = MAX (widget->allocation.height, widget->requisition.height);
313         attributes.event_mask = GDK_EXPOSURE_MASK |
314                                 GDK_BUTTON_PRESS_MASK |
315                                 GDK_BUTTON_RELEASE_MASK |
316                                 GDK_SCROLL_MASK |
317                                 GDK_KEY_PRESS_MASK |
318                                 GDK_POINTER_MOTION_MASK |
319                                 GDK_LEAVE_NOTIFY_MASK;
320   
321         view->bin_window = gdk_window_new (widget->window,
322                                            &attributes,
323                                            GDK_WA_X | GDK_WA_Y |
324                                            GDK_WA_COLORMAP |
325                                            GDK_WA_VISUAL);
326         gdk_window_set_user_data (view->bin_window, widget);
327         gdk_window_show (view->bin_window);
328
329         widget->style = gtk_style_attach (widget->style, view->bin_window);
330         gdk_window_set_background (view->bin_window, &widget->style->mid[widget->state]);
331
332         if (view->document) {
333                 ev_document_set_target (view->document, view->bin_window);
334
335                 /* We can't get page size without a target, so we have to
336                  * queue a size request at realization. Could be fixed
337                  * with EvDocument changes to allow setting a GdkScreen
338                  * without setting a target.
339                  */
340                 gtk_widget_queue_resize (widget);
341         }
342 }
343
344 static void
345 ev_view_unrealize (GtkWidget *widget)
346 {
347         EvView *view = EV_VIEW (widget);
348
349         if (view->document)
350                 ev_document_set_target (view->document, NULL);
351
352         gdk_window_set_user_data (view->bin_window, NULL);
353         gdk_window_destroy (view->bin_window);
354         view->bin_window = NULL;
355
356         GTK_WIDGET_CLASS (ev_view_parent_class)->unrealize (widget);
357 }
358
359 static guint32
360 ev_gdk_color_to_rgb (const GdkColor *color)
361 {
362   guint32 result;
363   result = (0xff0000 | (color->red & 0xff00));
364   result <<= 8;
365   result |= ((color->green & 0xff00) | (color->blue >> 8));
366   return result;
367 }
368
369 static void
370 draw_rubberband (GtkWidget *widget, GdkWindow *window,
371                  const GdkRectangle *rect, guchar alpha)
372 {
373         GdkGC *gc;
374         GdkPixbuf *pixbuf;
375         GdkColor *fill_color_gdk;
376         guint fill_color;
377
378         fill_color_gdk = gdk_color_copy (&GTK_WIDGET (widget)->style->base[GTK_STATE_SELECTED]);
379         fill_color = ev_gdk_color_to_rgb (fill_color_gdk) << 8 | alpha;
380
381         pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
382                                  rect->width, rect->height);
383         gdk_pixbuf_fill (pixbuf, fill_color);
384
385         gdk_draw_pixbuf (window, NULL, pixbuf,
386                          0, 0,
387                          rect->x,rect->y,
388                          rect->width, rect->height,
389                          GDK_RGB_DITHER_NONE,
390                          0, 0);
391
392         g_object_unref (pixbuf);
393
394         gc = gdk_gc_new (window);
395         gdk_gc_set_rgb_fg_color (gc, fill_color_gdk);
396         gdk_draw_rectangle (window, gc, FALSE,
397                             rect->x, rect->y,
398                             rect->width - 1,
399                             rect->height - 1);
400         g_object_unref (gc);
401
402         gdk_color_free (fill_color_gdk);
403 }
404
405 static void
406 highlight_find_results (EvView *view)
407 {
408         EvDocumentFind *find;
409         int i, results;
410
411         g_return_if_fail (EV_IS_DOCUMENT_FIND (view->document));
412
413         find = EV_DOCUMENT_FIND (view->document);
414
415         results = ev_document_find_get_n_results (find);
416
417         for (i = 0; i < results; i++) {
418                 GdkRectangle rectangle;
419                 guchar alpha;
420
421                 alpha = (i == view->find_result) ? 0x90 : 0x20;
422                 ev_document_find_get_result (find, i, &rectangle);
423                 draw_rubberband (GTK_WIDGET (view), view->bin_window,
424                                  &rectangle, alpha);
425         }
426 }
427
428
429 static void
430 expose_bin_window (GtkWidget      *widget,
431                    GdkEventExpose *event)
432 {
433         EvView *view = EV_VIEW (widget);
434         int x_offset, y_offset;
435         GtkBorder border;
436         gint width, height;
437         GdkRectangle area;
438         int target_width, target_height;
439                         
440         if (view->document == NULL)
441                 return;
442
443         ev_document_get_page_size (view->document, -1,
444                                    &width, &height);
445         ev_document_misc_get_page_border_size (width, height, &border);
446         
447         x_offset = view->spacing;
448         y_offset = view->spacing;
449         target_width = width + border.left + border.right + view->spacing * 2;
450         target_height = height + border.top + border.bottom + view->spacing * 2;
451
452         x_offset += MAX (0, (widget->allocation.width - target_width) / 2);
453         y_offset += MAX (0, (widget->allocation.height - target_height) / 2);
454
455         /* Paint the frame */
456         area.x = x_offset;
457         area.y = y_offset;
458         area.width = width + border.left + border.right;
459         area.height = height + border.top + border.bottom;
460         ev_document_misc_paint_one_page (view->bin_window, widget, &area, &border);
461
462         /* Render the document itself */
463         ev_document_set_page_offset (view->document,
464                                      x_offset + border.left,
465                                      y_offset + border.top);
466
467         LOG ("Render area %d %d %d %d", event->area.x, event->area.y,
468              event->area.width, event->area.height);
469
470         ev_document_render (view->document,
471                             event->area.x, event->area.y,
472                             event->area.width, event->area.height);
473
474         if (EV_IS_DOCUMENT_FIND (view->document)) {
475                 highlight_find_results (view);
476         }
477
478         if (view->has_selection) {
479                 draw_rubberband (widget, view->bin_window,
480                                  &view->selection, 0x40);
481         }
482 }
483
484 static gboolean
485 ev_view_expose_event (GtkWidget      *widget,
486                       GdkEventExpose *event)
487 {
488         EvView *view = EV_VIEW (widget);
489
490         if (event->window == view->bin_window)
491                 expose_bin_window (widget, event);
492         else
493                 return GTK_WIDGET_CLASS (ev_view_parent_class)->expose_event (widget, event);
494
495         return FALSE;
496 }
497
498 void
499 ev_view_select_all (EvView *ev_view)
500 {
501         GtkWidget *widget = GTK_WIDGET (ev_view);
502
503         g_return_if_fail (EV_IS_VIEW (ev_view));
504
505         ev_view->has_selection = TRUE;
506         ev_view->selection.x = ev_view->selection.y = 0;
507         ev_view->selection.width = widget->requisition.width;
508         ev_view->selection.height = widget->requisition.height;
509
510         gtk_widget_queue_draw (widget);
511 }
512
513 void
514 ev_view_copy (EvView *ev_view)
515 {
516         GtkClipboard *clipboard;
517         char *text;
518
519         text = ev_document_get_text (ev_view->document, &ev_view->selection);
520         clipboard = gtk_widget_get_clipboard (GTK_WIDGET (ev_view),
521                                               GDK_SELECTION_CLIPBOARD);
522         gtk_clipboard_set_text (clipboard, text, -1);
523         g_free (text);
524 }
525
526 static void
527 ev_view_primary_get_cb (GtkClipboard     *clipboard,
528                         GtkSelectionData *selection_data,
529                         guint             info,
530                         gpointer          data)
531 {
532         EvView *ev_view = EV_VIEW (data);
533         char *text;
534
535         text = ev_document_get_text (ev_view->document, &ev_view->selection);
536         gtk_selection_data_set_text (selection_data, text, -1);
537 }
538
539 static void
540 ev_view_primary_clear_cb (GtkClipboard *clipboard,
541                           gpointer      data)
542 {
543         EvView *ev_view = EV_VIEW (data);
544
545         ev_view->has_selection = FALSE;
546 }
547
548 static void
549 ev_view_update_primary_selection (EvView *ev_view)
550 {
551         GtkClipboard *clipboard;
552
553         clipboard = gtk_widget_get_clipboard (GTK_WIDGET (ev_view),
554                                               GDK_SELECTION_PRIMARY);
555
556         if (ev_view->has_selection) {
557                 if (!gtk_clipboard_set_with_owner (clipboard,
558                                                    targets,
559                                                    G_N_ELEMENTS (targets),
560                                                    ev_view_primary_get_cb,
561                                                    ev_view_primary_clear_cb,
562                                                    G_OBJECT (ev_view)))
563                         ev_view_primary_clear_cb (clipboard, ev_view);
564         } else {
565                 if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (ev_view))
566                         gtk_clipboard_clear (clipboard);
567         }
568 }
569
570 static gboolean
571 ev_view_button_press_event (GtkWidget      *widget,
572                             GdkEventButton *event)
573 {
574         EvView *view = EV_VIEW (widget);
575
576         if (!GTK_WIDGET_HAS_FOCUS (widget)) {
577                 gtk_widget_grab_focus (widget);
578         }
579
580         view->pressed_button = event->button;
581
582         switch (event->button) {
583                 case 1:
584                         if (view->has_selection) {
585                                 view->has_selection = FALSE;
586                                 gtk_widget_queue_draw (widget);
587                         }
588
589                         view->selection_start.x = event->x;
590                         view->selection_start.y = event->y;
591                         break;
592         }
593
594         return TRUE;
595 }
596
597 static char *
598 status_message_from_link (EvLink *link)
599 {
600         EvLinkType type;
601         char *msg;
602         int page;
603
604         type = ev_link_get_link_type (link);
605         
606         switch (type) {
607                 case EV_LINK_TYPE_TITLE:
608                         msg = g_strdup (ev_link_get_title (link));
609                         break;
610                 case EV_LINK_TYPE_PAGE:
611                         page = ev_link_get_page (link);
612                         msg = g_strdup_printf (_("Go to page %d"), page);
613                         break;
614                 case EV_LINK_TYPE_EXTERNAL_URI:
615                         msg = g_strdup (ev_link_get_uri (link));
616                         break;
617                 default:
618                         msg = NULL;
619         }
620
621         return msg;
622 }
623
624 static void
625 ev_view_set_status (EvView *view, const char *message)
626 {
627         g_return_if_fail (EV_IS_VIEW (view));
628
629         if (message != view->status) {
630                 g_free (view->status);
631                 view->status = g_strdup (message);
632                 g_object_notify (G_OBJECT (view), "status");
633         }
634 }
635
636 static void
637 ev_view_set_find_status (EvView *view, const char *message)
638 {
639         g_return_if_fail (EV_IS_VIEW (view));
640         
641         g_free (view->find_status);
642         view->find_status = g_strdup (message);
643         g_object_notify (G_OBJECT (view), "find-status");
644 }
645
646 static GdkCursor *
647 ev_view_create_invisible_cursor(void)
648 {
649        GdkBitmap *empty;
650        GdkColor black = { 0, 0, 0, 0 };
651        static unsigned char bits[] = { 0x00 };
652
653        empty = gdk_bitmap_create_from_data (NULL, bits, 1, 1);
654
655        return gdk_cursor_new_from_pixmap (empty, empty, &black, &black, 0, 0);
656 }
657
658 static void
659 ev_view_set_cursor (EvView *view, EvViewCursor new_cursor)
660 {
661         GdkCursor *cursor = NULL;
662         GdkDisplay *display;
663         GtkWidget *widget;
664
665         if (view->cursor == new_cursor) {
666                 return;
667         }
668
669         widget = gtk_widget_get_toplevel (GTK_WIDGET (view));
670         display = gtk_widget_get_display (widget);
671         view->cursor = new_cursor;
672
673         switch (new_cursor) {
674                 case EV_VIEW_CURSOR_NORMAL:
675                         gdk_window_set_cursor (widget->window, NULL);
676                         break;
677                 case EV_VIEW_CURSOR_LINK:
678                         cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
679                         break;
680                 case EV_VIEW_CURSOR_WAIT:
681                         cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
682                         break;
683                 case EV_VIEW_CURSOR_HIDDEN:
684                         cursor = ev_view_create_invisible_cursor ();
685                         break;
686
687         }
688
689         if (cursor) {
690                 gdk_window_set_cursor (widget->window, cursor);
691                 gdk_cursor_unref (cursor);
692                 gdk_flush();
693         }
694 }
695
696 static gboolean
697 ev_view_motion_notify_event (GtkWidget      *widget,
698                              GdkEventMotion *event)
699 {
700         EvView *view = EV_VIEW (widget);
701
702         if (view->pressed_button > 0) {
703                 view->has_selection = TRUE;
704                 view->selection.x = MIN (view->selection_start.x, event->x);
705                 view->selection.y = MIN (view->selection_start.y, event->y);
706                 view->selection.width = ABS (view->selection_start.x - event->x) + 1;
707                 view->selection.height = ABS (view->selection_start.y - event->y) + 1;
708
709                 gtk_widget_queue_draw (widget);
710         } else if (view->document) {
711                 EvLink *link;
712
713                 link = ev_document_get_link (view->document, event->x, event->y);
714                 if (link) {
715                         char *msg;
716
717                         msg = status_message_from_link (link);
718                         ev_view_set_status (view, msg);
719                         ev_view_set_cursor (view, EV_VIEW_CURSOR_LINK);
720                         g_free (msg);
721
722                         g_object_unref (link);
723                 } else {
724                         ev_view_set_status (view, NULL);
725                         if (view->cursor == EV_VIEW_CURSOR_LINK) {
726                                 ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
727                         }
728                 }
729         }
730
731         return TRUE;
732 }
733
734 static gboolean
735 ev_view_button_release_event (GtkWidget      *widget,
736                               GdkEventButton *event)
737 {
738         EvView *view = EV_VIEW (widget);
739
740         view->pressed_button = -1;
741
742         if (view->has_selection) {
743                 ev_view_update_primary_selection (view);
744         } else if (view->document) {
745                 EvLink *link;
746
747                 link = ev_document_get_link (view->document,
748                                              event->x,
749                                              event->y);
750                 if (link) {
751                         ev_view_go_to_link (view, link);
752                         g_object_unref (link);
753                 }
754         }
755
756         return FALSE;
757 }
758
759 static void
760 on_adjustment_value_changed (GtkAdjustment  *adjustment,
761                              EvView *view)
762 {
763         view_update_adjustments (view);
764 }
765
766 static void
767 set_scroll_adjustment (EvView *view,
768                        GtkOrientation  orientation,
769                        GtkAdjustment  *adjustment)
770 {
771         GtkAdjustment **to_set;
772
773         if (orientation == GTK_ORIENTATION_HORIZONTAL)
774                 to_set = &view->hadjustment;
775         else
776                 to_set = &view->vadjustment;
777   
778         if (*to_set != adjustment) {
779                 if (*to_set) {
780                         g_signal_handlers_disconnect_by_func (*to_set,
781                                                               (gpointer) on_adjustment_value_changed,
782                                                               view);
783                         g_object_unref (*to_set);
784                 }
785
786                 *to_set = adjustment;
787                 view_set_adjustment_values (view, orientation);
788
789                 if (*to_set) {
790                         g_object_ref (*to_set);
791                         g_signal_connect (*to_set, "value_changed",
792                                           G_CALLBACK (on_adjustment_value_changed), view);
793                 }
794         }
795 }
796
797 static void
798 ev_view_set_scroll_adjustments (EvView *view,
799                                 GtkAdjustment  *hadjustment,
800                                 GtkAdjustment  *vadjustment)
801 {
802         set_scroll_adjustment (view, GTK_ORIENTATION_HORIZONTAL, hadjustment);
803         set_scroll_adjustment (view, GTK_ORIENTATION_VERTICAL, vadjustment);
804
805         view_update_adjustments (view);
806 }
807
808 static void
809 add_scroll_binding (GtkBindingSet  *binding_set,
810                     guint           keyval,
811                     GtkScrollType   scroll,
812                     gboolean        horizontal)
813 {
814   guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
815   
816   gtk_binding_entry_add_signal (binding_set, keyval, 0,
817                                 "scroll_view", 2,
818                                 GTK_TYPE_SCROLL_TYPE, scroll,
819                                 G_TYPE_BOOLEAN, horizontal);
820   gtk_binding_entry_add_signal (binding_set, keypad_keyval, 0,
821                                 "scroll_view", 2,
822                                 GTK_TYPE_SCROLL_TYPE, scroll,
823                                 G_TYPE_BOOLEAN, horizontal);
824 }
825
826 static void
827 ev_view_scroll_view (EvView *view,
828                      GtkScrollType scroll,
829                      gboolean horizontal)
830 {
831         if (scroll == GTK_SCROLL_PAGE_BACKWARD) {
832                 ev_view_set_page (view, ev_view_get_page (view) - 1);
833         } else if (scroll == GTK_SCROLL_PAGE_FORWARD) {
834                 ev_view_set_page (view, ev_view_get_page (view) + 1);
835         } else {
836                 GtkAdjustment *adjustment;
837                 double value;
838
839                 if (horizontal) {
840                         adjustment = view->hadjustment; 
841                 } else {
842                         adjustment = view->vadjustment;
843                 }
844
845                 value = adjustment->value;
846
847                 switch (scroll) {
848                         case GTK_SCROLL_STEP_BACKWARD:  
849                                 value -= adjustment->step_increment; 
850                                 break;
851                         case GTK_SCROLL_STEP_FORWARD:
852                                 value += adjustment->step_increment; 
853                                 break;
854                         default:
855                                 break;
856                 }
857
858                 value = CLAMP (value, adjustment->lower,
859                                adjustment->upper - adjustment->page_size);
860
861                 gtk_adjustment_set_value (adjustment, value);
862         }
863 }
864
865 static void
866 ev_view_set_property (GObject *object,
867                       guint prop_id,
868                       const GValue *value,
869                       GParamSpec *pspec)
870 {
871         switch (prop_id)
872         {
873                 /* Read only */
874                 case PROP_STATUS:
875                 case PROP_FIND_STATUS:
876                         break;
877         }
878 }
879
880 static void
881 ev_view_get_property (GObject *object,
882                       guint prop_id,
883                       GValue *value,
884                       GParamSpec *pspec)
885 {
886         EvView *view = EV_VIEW (object);
887
888         switch (prop_id)
889         {
890                 case PROP_STATUS:
891                         g_value_set_string (value, view->status);
892                         break;
893                 case PROP_FIND_STATUS:
894                         g_value_set_string (value, view->status);
895                         break;
896         }
897 }
898
899 static void
900 ev_view_class_init (EvViewClass *class)
901 {
902         GObjectClass *object_class = G_OBJECT_CLASS (class);
903         GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
904         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
905         GtkBindingSet *binding_set;
906
907         object_class->finalize = ev_view_finalize;
908         object_class->set_property = ev_view_set_property;
909         object_class->get_property = ev_view_get_property;
910
911         widget_class->expose_event = ev_view_expose_event;
912         widget_class->button_press_event = ev_view_button_press_event;
913         widget_class->motion_notify_event = ev_view_motion_notify_event;
914         widget_class->button_release_event = ev_view_button_release_event;
915         widget_class->size_request = ev_view_size_request;
916         widget_class->size_allocate = ev_view_size_allocate;
917         widget_class->realize = ev_view_realize;
918         widget_class->unrealize = ev_view_unrealize;
919         gtk_object_class->destroy = ev_view_destroy;
920
921         class->set_scroll_adjustments = ev_view_set_scroll_adjustments;
922         class->scroll_view = ev_view_scroll_view;
923
924         widget_class->set_scroll_adjustments_signal =  g_signal_new ("set-scroll-adjustments",
925                                                                      G_OBJECT_CLASS_TYPE (object_class),
926                                                                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
927                                                                      G_STRUCT_OFFSET (EvViewClass, set_scroll_adjustments),
928                                                                      NULL, NULL,
929                                                                      ev_marshal_VOID__OBJECT_OBJECT,
930                                                                      G_TYPE_NONE, 2,
931                                                                      GTK_TYPE_ADJUSTMENT,
932                                                                      GTK_TYPE_ADJUSTMENT);
933         page_changed_signal = g_signal_new ("page-changed",
934                                             G_OBJECT_CLASS_TYPE (object_class),
935                                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
936                                             G_STRUCT_OFFSET (EvViewClass, page_changed),
937                                             NULL, NULL,
938                                             ev_marshal_VOID__NONE,
939                                             G_TYPE_NONE, 0);
940
941         g_signal_new ("scroll_view",
942                       G_TYPE_FROM_CLASS (object_class),
943                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
944                       G_STRUCT_OFFSET (EvViewClass, scroll_view),
945                       NULL, NULL,
946                       ev_marshal_VOID__ENUM_BOOLEAN,
947                       G_TYPE_NONE, 2,
948                       GTK_TYPE_SCROLL_TYPE,
949                       G_TYPE_BOOLEAN);
950
951         g_object_class_install_property (object_class,
952                                          PROP_STATUS,
953                                          g_param_spec_string ("status",
954                                                               "Status Message",
955                                                               "The status message",
956                                                               NULL,
957                                                               G_PARAM_READABLE));
958
959         g_object_class_install_property (object_class,
960                                          PROP_STATUS,
961                                          g_param_spec_string ("find-status",
962                                                               "Find Status Message",
963                                                               "The find status message",
964                                                               NULL,
965                                                               G_PARAM_READABLE));
966
967         binding_set = gtk_binding_set_by_class (class);
968
969         add_scroll_binding (binding_set, GDK_Left,  GTK_SCROLL_STEP_BACKWARD, TRUE);
970         add_scroll_binding (binding_set, GDK_Right, GTK_SCROLL_STEP_FORWARD,  TRUE);
971         add_scroll_binding (binding_set, GDK_Up,    GTK_SCROLL_STEP_BACKWARD, FALSE);
972         add_scroll_binding (binding_set, GDK_Down,  GTK_SCROLL_STEP_FORWARD,  FALSE);
973
974         add_scroll_binding (binding_set, GDK_Page_Up,   GTK_SCROLL_PAGE_BACKWARD, FALSE);
975         add_scroll_binding (binding_set, GDK_Page_Down, GTK_SCROLL_PAGE_FORWARD,  FALSE);
976 }
977
978 static void
979 ev_view_init (EvView *view)
980 {
981         GTK_WIDGET_SET_FLAGS (view, GTK_CAN_FOCUS);
982
983         view->spacing = 10;
984         view->scale = 1.0;
985         view->pressed_button = -1;
986         view->cursor = EV_VIEW_CURSOR_NORMAL;
987         view->sizing_mode = EV_SIZING_BEST_FIT;
988 }
989
990 static void
991 update_find_status_message (EvView *view)
992 {
993         char *message;
994
995         if (ev_document_get_page (view->document) == view->find_page) {
996                 int results;
997
998                 results = ev_document_find_get_n_results
999                                 (EV_DOCUMENT_FIND (view->document));
1000
1001                 message = g_strdup_printf (_("%d found on this page"),
1002                                            results);
1003         } else {
1004                 double percent;
1005                 
1006                 percent = ev_document_find_get_progress
1007                                 (EV_DOCUMENT_FIND (view->document));
1008
1009                 if (percent >= (1.0 - 1e-10)) {
1010                         message = g_strdup (_("Not found"));
1011                 } else {
1012                         message = g_strdup_printf (_("%3d%% remaining to search"),
1013                                                    (int) ((1.0 - percent) * 100));
1014                 }
1015                 
1016         }
1017
1018         ev_view_set_find_status (view, message);
1019         g_free (message);
1020 }
1021
1022 static void
1023 set_document_page (EvView *view, int new_page)
1024 {
1025         int page;
1026         int pages;
1027
1028         pages = ev_document_get_n_pages (view->document);
1029         page = CLAMP (new_page, 1, pages);
1030
1031         if (view->document) {
1032                 int old_page = ev_document_get_page (view->document);
1033                 int old_width, old_height;
1034
1035                 ev_document_get_page_size (view->document,
1036                                            -1, 
1037                                            &old_width, &old_height);
1038
1039                 if (old_page != page) {
1040                         if (view->cursor != EV_VIEW_CURSOR_HIDDEN) {
1041                                 ev_view_set_cursor (view, EV_VIEW_CURSOR_WAIT);
1042                         }
1043                         ev_document_set_page (view->document, page);
1044                 }
1045
1046                 if (old_page != ev_document_get_page (view->document)) {
1047                         int width, height;
1048                         
1049                         g_signal_emit (view, page_changed_signal, 0);
1050
1051                         view->has_selection = FALSE;
1052                         ev_document_get_page_size (view->document,
1053                                                    -1, 
1054                                                    &width, &height);
1055                         if (width != old_width || height != old_height)
1056                                 gtk_widget_queue_resize (GTK_WIDGET (view));
1057
1058                         gtk_adjustment_set_value (view->vadjustment,
1059                                                   view->vadjustment->lower);
1060                 }
1061
1062                 if (EV_IS_DOCUMENT_FIND (view->document)) {
1063                         view->find_page = page;
1064                         view->find_result = 0;
1065                         update_find_status_message (view);
1066                 }
1067         }
1068 }
1069
1070 #define MARGIN 5
1071
1072 static void
1073 ensure_rectangle_is_visible (EvView *view, GdkRectangle *rect)
1074 {
1075         GtkWidget *widget = GTK_WIDGET (view);
1076         GtkAdjustment *adjustment;
1077         int value;
1078
1079         adjustment = view->vadjustment;
1080
1081         if (rect->y < adjustment->value) {
1082                 value = MAX (adjustment->lower, rect->y - MARGIN);
1083                 gtk_adjustment_set_value (view->vadjustment, value);
1084         } else if (rect->y + rect->height >
1085                    adjustment->value + widget->allocation.height) {
1086                 value = MIN (adjustment->upper, rect->y + rect->height -
1087                              widget->allocation.height + MARGIN);
1088                 gtk_adjustment_set_value (view->vadjustment, value);
1089         }
1090
1091         adjustment = view->hadjustment;
1092
1093         if (rect->x < adjustment->value) {
1094                 value = MAX (adjustment->lower, rect->x - MARGIN);
1095                 gtk_adjustment_set_value (view->hadjustment, value);
1096         } else if (rect->x + rect->height >
1097                    adjustment->value + widget->allocation.width) {
1098                 value = MIN (adjustment->upper, rect->x + rect->width -
1099                              widget->allocation.width + MARGIN);
1100                 gtk_adjustment_set_value (view->hadjustment, value);
1101         }
1102 }
1103
1104 static void
1105 jump_to_find_result (EvView *view)
1106 {
1107         EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
1108         GdkRectangle rect;
1109         int n_results;
1110
1111         n_results = ev_document_find_get_n_results (find);
1112
1113         if (n_results > view->find_result) {
1114                 ev_document_find_get_result
1115                         (find, view->find_result, &rect);
1116                 ensure_rectangle_is_visible (view, &rect);
1117         }
1118 }
1119
1120 static void
1121 jump_to_find_page (EvView *view)
1122 {
1123         int n_pages, i;
1124
1125         n_pages = ev_document_get_n_pages (view->document);
1126
1127         for (i = 0; i <= n_pages; i++) {
1128                 int has_results;
1129                 int page;
1130
1131                 page = i + view->find_page;
1132                 if (page > n_pages) {
1133                         page = page - n_pages;
1134                 }
1135
1136                 has_results = ev_document_find_page_has_results
1137                                 (EV_DOCUMENT_FIND (view->document), page);
1138                 if (has_results == -1) {
1139                         view->find_page = page;
1140                         break;
1141                 } else if (has_results == 1) {
1142                         set_document_page (view, page);
1143                         jump_to_find_result (view);
1144                         break;
1145                 }
1146         }
1147 }
1148
1149 static void
1150 find_changed_cb (EvDocument *document, int page, EvView *view)
1151 {
1152         jump_to_find_page (view);
1153         jump_to_find_result (view);
1154         update_find_status_message (view);
1155
1156         if (ev_document_get_page (document) == page) {
1157                 gtk_widget_queue_draw (GTK_WIDGET (view));
1158         }
1159 }
1160
1161 static void
1162 document_changed_callback (EvDocument *document,
1163                            EvView     *view)
1164 {
1165         gtk_widget_queue_draw (GTK_WIDGET (view));
1166
1167         if (view->cursor != EV_VIEW_CURSOR_HIDDEN) {
1168                 ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
1169         }
1170 }
1171
1172 /*** Public API ***/       
1173      
1174 GtkWidget*
1175 ev_view_new (void)
1176 {
1177         return g_object_new (EV_TYPE_VIEW, NULL);
1178 }
1179
1180 void
1181 ev_view_set_document (EvView     *view,
1182                       EvDocument *document)
1183 {
1184         g_return_if_fail (EV_IS_VIEW (view));
1185
1186         if (document != view->document) {
1187                 if (view->document) {
1188                         g_signal_handlers_disconnect_by_func (view->document,
1189                                                               find_changed_cb,
1190                                                               view);
1191                         g_object_unref (view->document);
1192                 }
1193
1194                 view->document = document;
1195                 view->find_page = 1;
1196                 view->find_result = 0;
1197
1198                 if (view->document) {
1199                         g_object_ref (view->document);
1200                         if (EV_IS_DOCUMENT_FIND (view->document)) {
1201                                 g_signal_connect (view->document,
1202                                                   "find_changed",
1203                                                   G_CALLBACK (find_changed_cb),
1204                                                   view);
1205                         }
1206                         g_signal_connect (view->document,
1207                                           "changed",
1208                                           G_CALLBACK (document_changed_callback),
1209                                           view);
1210                 }
1211
1212                 if (GTK_WIDGET_REALIZED (view))
1213                         ev_document_set_target (view->document, view->bin_window);
1214                 
1215                 gtk_widget_queue_resize (GTK_WIDGET (view));
1216                 
1217                 g_signal_emit (view, page_changed_signal, 0);
1218         }
1219 }
1220
1221 void
1222 ev_view_set_mode (EvView       *view,
1223                   EvSizingMode  sizing_mode)
1224 {
1225         if (view->sizing_mode == sizing_mode)
1226                 return;
1227
1228         view->sizing_mode = sizing_mode;
1229         gtk_widget_queue_resize (GTK_WIDGET (view));
1230 }
1231
1232 static void
1233 go_to_link (EvView *view, EvLink *link)
1234 {
1235         EvLinkType type;
1236         const char *uri;
1237         int page;
1238
1239         type = ev_link_get_link_type (link);
1240         
1241         switch (type) {
1242                 case EV_LINK_TYPE_TITLE:
1243                         break;
1244                 case EV_LINK_TYPE_PAGE:
1245                         page = ev_link_get_page (link);
1246                         set_document_page (view, page);
1247                         break;
1248                 case EV_LINK_TYPE_EXTERNAL_URI:
1249                         uri = ev_link_get_uri (link);
1250                         gnome_vfs_url_show (uri);
1251                         break;
1252         }
1253 }
1254
1255 void
1256 ev_view_go_to_link (EvView *view, EvLink *link)
1257 {
1258         go_to_link (view, link);
1259 }
1260
1261 void
1262 ev_view_set_page (EvView *view,
1263                   int     page)
1264 {
1265         g_return_if_fail (EV_IS_VIEW (view));
1266
1267         set_document_page (view, page);
1268 }
1269
1270 int
1271 ev_view_get_page (EvView *view)
1272 {
1273         if (view->document)
1274                 return ev_document_get_page (view->document);
1275         else
1276                 return 1;
1277 }
1278
1279 static void
1280 ev_view_zoom (EvView   *view,
1281               double    factor,
1282               gboolean  relative)
1283 {
1284         double scale;
1285
1286         if (relative)
1287                 scale = view->scale * factor;
1288         else
1289                 scale = factor;
1290
1291         scale = CLAMP (scale, MIN_SCALE, MAX_SCALE);
1292
1293         if (ABS (scale - view->scale) < ZOOM_EPSILON)
1294                 return;
1295
1296         view->scale = scale;
1297
1298         ev_document_set_scale (view->document, view->scale);
1299
1300         gtk_widget_queue_resize (GTK_WIDGET (view));
1301 }
1302
1303 void
1304 ev_view_zoom_in (EvView *view)
1305 {
1306         ev_view_zoom (view, ZOOM_IN_FACTOR, TRUE);
1307 }
1308
1309 void
1310 ev_view_zoom_out (EvView *view)
1311 {
1312         ev_view_zoom (view, ZOOM_OUT_FACTOR, TRUE);
1313 }
1314
1315 void
1316 ev_view_normal_size (EvView *view)
1317 {
1318         ev_view_zoom (view, 1.0, FALSE);
1319 }
1320
1321 /* Unfortunately this is not idempotent (!) (numerical stability
1322  * issues because width and height are rounded) */
1323 void
1324 ev_view_best_fit (EvView *view, int allocation_width, int allocation_height)
1325 {
1326         int target_width, target_height;
1327         int width, height;
1328         GtkBorder border;
1329
1330         if (!GTK_WIDGET_REALIZED (view) || view->document == NULL)
1331                 return;
1332
1333         width = height = 0;
1334         /* This is the bad part. You could make it stable by doing
1335          * ev_document_set_scale 1.0. But at least with pdf this means
1336          * redrawing the whole page */
1337         ev_document_get_page_size (view->document, -1, &width, &height);
1338         /* FIXME: The border size isn't constant.  Ugh.  Still, if we have extra
1339          * space, we just cut it from the border */
1340         ev_document_misc_get_page_border_size (width, height, &border);
1341
1342         target_width = allocation_width - (view->spacing * 2 + border.left + border.right);
1343         target_height = allocation_height - (view->spacing * 2 + border.top + border.bottom);
1344
1345         LOG ("Best fit %d %d", allocation_width, allocation_height);
1346
1347         if (width != 0 && height != 0) {
1348                 double scale;
1349                 double scale_w, scale_h;
1350
1351                 scale_w = (double)target_width * view->scale / width;
1352                 scale_h = (double)target_height * view->scale / height;
1353
1354                 scale = (scale_w < scale_h) ? scale_w : scale_h;
1355
1356                 ev_view_zoom (view, scale, FALSE);
1357         }
1358
1359 }
1360
1361 void
1362 ev_view_fit_width (EvView *view, int allocation_width, int allocation_height,
1363                    int vsb_width)
1364 {
1365         int target_width, target_height;
1366         int width, height;
1367         GtkBorder border;
1368
1369         if (view->document == NULL)
1370                 return;
1371
1372         width = height = 0;
1373         ev_document_get_page_size (view->document, -1, &width, &height);
1374         ev_document_misc_get_page_border_size (width, height, &border);
1375
1376         target_width = allocation_width - (view->spacing * 2 + border.left + border.right);
1377         target_height = allocation_height - (view->spacing * 2 + border.top + border.bottom);
1378
1379         if (width) {
1380                 double scale;
1381                 scale = (double)target_width * view->scale / width;
1382
1383                 if (height * scale / view->scale > target_height)
1384                         scale = ((double)(target_width - vsb_width) * view->scale / width);
1385
1386                 ev_view_zoom (view, scale, FALSE);
1387         }
1388 }
1389
1390 const char *
1391 ev_view_get_status (EvView *view)
1392 {
1393         g_return_val_if_fail (EV_IS_VIEW (view), NULL);
1394
1395         return view->status;
1396 }
1397
1398 const char *
1399 ev_view_get_find_status (EvView *view)
1400 {
1401         g_return_val_if_fail (EV_IS_VIEW (view), NULL);
1402
1403         return view->find_status;
1404 }
1405
1406 void
1407 ev_view_find_next (EvView *view)
1408 {
1409         int n_results, n_pages;
1410         EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
1411
1412         n_results = ev_document_find_get_n_results (find);
1413         n_pages = ev_document_get_n_pages (view->document);
1414
1415         view->find_result++;
1416
1417         if (view->find_result >= n_results) {
1418                 view->find_result = 0;
1419                 view->find_page++;
1420
1421                 if (view->find_page > n_pages) {
1422                         view->find_page = 1;
1423                 }
1424
1425                 jump_to_find_page (view);
1426         } else {
1427                 jump_to_find_result (view);
1428                 gtk_widget_queue_draw (GTK_WIDGET (view));
1429         }
1430 }
1431
1432 void
1433 ev_view_find_previous (EvView *view)
1434 {
1435         int n_results, n_pages;
1436         EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
1437
1438         n_results = ev_document_find_get_n_results (find);
1439         n_pages = ev_document_get_n_pages (view->document);
1440
1441         view->find_result--;
1442
1443         if (view->find_result < 0) {
1444                 view->find_result = 0;
1445                 view->find_page--;
1446
1447                 if (view->find_page < 1) {
1448                         view->find_page = n_pages;
1449                 }
1450
1451                 jump_to_find_page (view);
1452         } else {
1453                 jump_to_find_result (view);
1454                 gtk_widget_queue_draw (GTK_WIDGET (view));
1455         }
1456 }
1457 void
1458 ev_view_hide_cursor (EvView *view)
1459 {
1460        ev_view_set_cursor (view, EV_VIEW_CURSOR_HIDDEN);
1461 }
1462
1463 void
1464 ev_view_show_cursor (EvView *view)
1465 {
1466        ev_view_set_cursor (view, EV_VIEW_CURSOR_LINK);
1467 }