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