]> www.fi.muni.cz Git - evince.git/blob - shell/ev-view.c
Enanche the find interface to be really able to do multi page find.
[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
33 #define EV_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_VIEW, EvViewClass))
34 #define EV_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_VIEW))
35 #define EV_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EV_TYPE_VIEW, EvViewClass))
36
37 enum {
38         PROP_0,
39         PROP_STATUS,
40         PROP_FIND_STATUS
41 };
42
43 enum {
44   TARGET_STRING,
45   TARGET_TEXT,
46   TARGET_COMPOUND_TEXT,
47   TARGET_UTF8_STRING,
48   TARGET_TEXT_BUFFER_CONTENTS
49 };
50
51 static const GtkTargetEntry targets[] = {
52         { "STRING", 0, TARGET_STRING },
53         { "TEXT",   0, TARGET_TEXT },
54         { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
55         { "UTF8_STRING", 0, TARGET_UTF8_STRING },
56 };
57
58 typedef enum {
59         EV_VIEW_CURSOR_NORMAL,
60         EV_VIEW_CURSOR_LINK,
61         EV_VIEW_CURSOR_WAIT
62 } EvViewCursor;
63
64 struct _EvView {
65         GtkWidget parent_instance;
66
67         EvDocument *document;
68         
69         GdkWindow *bin_window;
70
71         char *status;
72         char *find_status;
73         
74         int scroll_x;
75         int scroll_y;
76
77         gboolean pressed_button;
78         gboolean has_selection;
79         GdkPoint selection_start;
80         GdkRectangle selection;
81         EvViewCursor cursor;
82
83         GtkAdjustment *hadjustment;
84         GtkAdjustment *vadjustment;
85
86         int results_on_this_page;
87         int next_page_with_result;
88         double find_percent_complete;
89
90         double scale;
91 };
92
93 struct _EvViewClass {
94         GtkWidgetClass parent_class;
95
96         void    (*set_scroll_adjustments) (EvView         *view,
97                                            GtkAdjustment  *hadjustment,
98                                            GtkAdjustment  *vadjustment);
99         void    (*scroll_view)            (EvView         *view,
100                                            GtkScrollType   scroll,
101                                            gboolean        horizontal);
102         
103         /* Should this be notify::page? */
104         void    (*page_changed)           (EvView         *view);
105 };
106
107 static guint page_changed_signal = 0;
108
109 static void ev_view_set_scroll_adjustments (EvView         *view,
110                                             GtkAdjustment  *hadjustment,
111                                             GtkAdjustment  *vadjustment);
112     
113 G_DEFINE_TYPE (EvView, ev_view, GTK_TYPE_WIDGET)
114
115 /*** Helper functions ***/       
116      
117 static void
118 view_update_adjustments (EvView *view)
119 {
120         int old_x = view->scroll_x;
121         int old_y = view->scroll_y;
122   
123         if (view->hadjustment)
124                 view->scroll_x = view->hadjustment->value;
125         else
126                 view->scroll_x = 0;
127
128         if (view->vadjustment)
129                 view->scroll_y = view->vadjustment->value;
130         else
131                 view->scroll_y = 0;
132   
133         if (GTK_WIDGET_REALIZED (view) &&
134             (view->scroll_x != old_x || view->scroll_y != old_y)) {
135                 gdk_window_move (view->bin_window, - view->scroll_x, - view->scroll_y);
136                 gdk_window_process_updates (view->bin_window, TRUE);
137         }
138 }
139
140 static void
141 view_set_adjustment_values (EvView         *view,
142                             GtkOrientation  orientation)
143 {
144         GtkWidget *widget = GTK_WIDGET (view);
145         GtkAdjustment *adjustment;
146         gboolean value_changed = FALSE;
147         int requisition;
148         int allocation;
149
150         if (orientation == GTK_ORIENTATION_HORIZONTAL)  {
151                 requisition = widget->requisition.width;
152                 allocation = widget->allocation.width;
153                 adjustment = view->hadjustment;
154         } else {
155                 requisition = widget->requisition.height;
156                 allocation = widget->allocation.height;
157                 adjustment = view->vadjustment;
158         }
159
160         if (!adjustment)
161                 return;
162   
163         adjustment->page_size = allocation;
164         adjustment->step_increment = allocation * 0.1;
165         adjustment->page_increment = allocation * 0.9;
166         adjustment->lower = 0;
167         adjustment->upper = MAX (allocation, requisition);
168
169         if (adjustment->value > adjustment->upper - adjustment->page_size) {
170                 adjustment->value = adjustment->upper - adjustment->page_size;
171                 value_changed = TRUE;
172         }
173
174         gtk_adjustment_changed (adjustment);
175         if (value_changed)
176                 gtk_adjustment_value_changed (adjustment);
177 }
178
179 /*** Virtual function implementations ***/       
180      
181 static void
182 ev_view_finalize (GObject *object)
183 {
184         EvView *view = EV_VIEW (object);
185
186         if (view->document)
187                 g_object_unref (view->document);
188
189         ev_view_set_scroll_adjustments (view, NULL, NULL);
190
191         G_OBJECT_CLASS (ev_view_parent_class)->finalize (object);
192 }
193
194 static void
195 ev_view_destroy (GtkObject *object)
196 {
197         EvView *view = EV_VIEW (object);
198
199         ev_view_set_scroll_adjustments (view, NULL, NULL);
200   
201         GTK_OBJECT_CLASS (ev_view_parent_class)->destroy (object);
202 }
203
204 static void
205 ev_view_size_request (GtkWidget      *widget,
206                       GtkRequisition *requisition)
207 {
208         EvView *view = EV_VIEW (widget);
209
210         if (GTK_WIDGET_REALIZED (widget)) {
211                 if (view->document) {
212                         ev_document_get_page_size (view->document,
213                                                    &requisition->width,
214                                                    &requisition->height);
215                 } else {
216                         requisition->width = 10;
217                         requisition->height = 10;
218                 }
219
220                 requisition->width += 2;
221                 requisition->height += 2;
222         }
223   
224 }
225
226 static void
227 ev_view_size_allocate (GtkWidget      *widget,
228                        GtkAllocation  *allocation)
229 {
230         EvView *view = EV_VIEW (widget);
231
232         GTK_WIDGET_CLASS (ev_view_parent_class)->size_allocate (widget, allocation);
233
234         view_set_adjustment_values (view, GTK_ORIENTATION_HORIZONTAL);
235         view_set_adjustment_values (view, GTK_ORIENTATION_VERTICAL);
236
237         if (GTK_WIDGET_REALIZED (widget)) {
238                 gdk_window_resize (view->bin_window,
239                                    MAX (widget->allocation.width, widget->requisition.width),
240                                    MAX (widget->allocation.height, widget->requisition.height));
241         }
242 }
243
244 static void
245 ev_view_realize (GtkWidget *widget)
246 {
247         EvView *view = EV_VIEW (widget);
248         GdkWindowAttr attributes;
249
250         GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
251   
252         attributes.window_type = GDK_WINDOW_CHILD;
253         attributes.wclass = GDK_INPUT_OUTPUT;
254         attributes.visual = gtk_widget_get_visual (widget);
255         attributes.colormap = gtk_widget_get_colormap (widget);
256   
257         attributes.x = widget->allocation.x;
258         attributes.y = widget->allocation.y;
259         attributes.width = widget->allocation.width;
260         attributes.height = widget->allocation.height;
261         attributes.event_mask = 0;
262   
263         widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
264                                          &attributes,
265                                          GDK_WA_X | GDK_WA_Y |
266                                          GDK_WA_COLORMAP |
267                                          GDK_WA_VISUAL);
268         gdk_window_set_user_data (widget->window, widget);
269         widget->style = gtk_style_attach (widget->style, widget->window);
270   
271         attributes.x = 0;
272         attributes.y = 0;
273         attributes.width = MAX (widget->allocation.width, widget->requisition.width);
274         attributes.height = MAX (widget->allocation.height, widget->requisition.height);
275         attributes.event_mask = GDK_EXPOSURE_MASK |
276                                 GDK_BUTTON_PRESS_MASK |
277                                 GDK_BUTTON_RELEASE_MASK |
278                                 GDK_SCROLL_MASK |
279                                 GDK_KEY_PRESS_MASK |
280                                 GDK_POINTER_MOTION_MASK;
281   
282         view->bin_window = gdk_window_new (widget->window,
283                                            &attributes,
284                                            GDK_WA_X | GDK_WA_Y |
285                                            GDK_WA_COLORMAP |
286                                            GDK_WA_VISUAL);
287         gdk_window_set_user_data (view->bin_window, widget);
288         gdk_window_show (view->bin_window);
289
290         widget->style = gtk_style_attach (widget->style, view->bin_window);
291         gdk_window_set_background (view->bin_window, &widget->style->mid[widget->state]);
292
293         if (view->document) {
294                 ev_document_set_target (view->document, view->bin_window);
295
296                 /* We can't get page size without a target, so we have to
297                  * queue a size request at realization. Could be fixed
298                  * with EvDocument changes to allow setting a GdkScreen
299                  * without setting a target.
300                  */
301                 gtk_widget_queue_resize (widget);
302         }
303 }
304
305 static void
306 ev_view_unrealize (GtkWidget *widget)
307 {
308         EvView *view = EV_VIEW (widget);
309
310         if (view->document)
311                 ev_document_set_target (view->document, NULL);
312
313         gdk_window_set_user_data (view->bin_window, NULL);
314         gdk_window_destroy (view->bin_window);
315         view->bin_window = NULL;
316
317         GTK_WIDGET_CLASS (ev_view_parent_class)->unrealize (widget);
318 }
319
320 static guint32
321 ev_gdk_color_to_rgb (const GdkColor *color)
322 {
323   guint32 result;
324   result = (0xff0000 | (color->red & 0xff00));
325   result <<= 8;
326   result |= ((color->green & 0xff00) | (color->blue >> 8));
327   return result;
328 }
329
330 static void
331 draw_rubberband (GtkWidget *widget, GdkWindow *window, const GdkRectangle *rect)
332 {
333         GdkGC *gc;
334         GdkPixbuf *pixbuf;
335         GdkColor *fill_color_gdk;
336         guint fill_color;
337
338         fill_color_gdk = gdk_color_copy (&GTK_WIDGET (widget)->style->base[GTK_STATE_SELECTED]);
339         fill_color = ev_gdk_color_to_rgb (fill_color_gdk) << 8 | 0x40;
340
341         pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
342                                  rect->width, rect->height);
343         gdk_pixbuf_fill (pixbuf, fill_color);
344
345         gdk_draw_pixbuf (window, NULL, pixbuf,
346                          0, 0,
347                          rect->x,rect->y,
348                          rect->width, rect->height,
349                          GDK_RGB_DITHER_NONE,
350                          0, 0);
351
352         g_object_unref (pixbuf);
353
354         gc = gdk_gc_new (window);
355         gdk_gc_set_rgb_fg_color (gc, fill_color_gdk);
356         gdk_draw_rectangle (window, gc, FALSE,
357                             rect->x, rect->y,
358                             rect->width - 1,
359                             rect->height - 1);
360         g_object_unref (gc);
361
362         gdk_color_free (fill_color_gdk);
363 }
364
365 static void
366 highlight_find_results (EvView *view)
367 {
368         EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
369         int i, results;
370
371         results = ev_document_find_get_n_results (find);
372
373         for (i = 0; i < results; i++) {
374                 GdkRectangle rectangle;
375
376                 ev_document_find_get_result (find, i, &rectangle);
377                 draw_rubberband (GTK_WIDGET (view),
378                                  view->bin_window, &rectangle);
379         }
380 }
381
382 static void
383 expose_bin_window (GtkWidget      *widget,
384                    GdkEventExpose *event)
385 {
386         EvView *view = EV_VIEW (widget);
387         int x_offset, y_offset;
388
389         if (view->document == NULL)
390                 return;
391
392         x_offset = MAX (0, (widget->allocation.width -
393                             widget->requisition.width) / 2);
394         y_offset = MAX (0, (widget->allocation.height -
395                             widget->requisition.height) / 2);
396         gdk_draw_rectangle (view->bin_window,
397                             widget->style->black_gc,
398                             FALSE,
399                             x_offset,
400                             y_offset,
401                             widget->requisition.width - 1,
402                             widget->requisition.height - 1);
403
404         ev_document_set_page_offset (view->document,
405                                      x_offset + 1,
406                                      y_offset + 1);
407
408         ev_document_render (view->document,
409                             event->area.x, event->area.y,
410                             event->area.width, event->area.height);
411
412         highlight_find_results (view);
413
414         if (view->has_selection) {
415                 draw_rubberband (widget, view->bin_window, &view->selection);
416         }
417 }
418
419 static gboolean
420 ev_view_expose_event (GtkWidget      *widget,
421                       GdkEventExpose *event)
422 {
423         EvView *view = EV_VIEW (widget);
424
425         if (event->window == view->bin_window)
426                 expose_bin_window (widget, event);
427         else
428                 return GTK_WIDGET_CLASS (ev_view_parent_class)->expose_event (widget, event);
429
430         return FALSE;
431 }
432
433 void
434 ev_view_select_all (EvView *ev_view)
435 {
436         GtkWidget *widget = GTK_WIDGET (ev_view);
437
438         g_return_if_fail (EV_IS_VIEW (ev_view));
439
440         ev_view->has_selection = TRUE;
441         ev_view->selection.x = ev_view->selection.y = 0;
442         ev_view->selection.width = widget->requisition.width;
443         ev_view->selection.height = widget->requisition.height;
444
445         gtk_widget_queue_draw (widget);
446 }
447
448 void
449 ev_view_copy (EvView *ev_view)
450 {
451         GtkClipboard *clipboard;
452         char *text;
453
454         text = ev_document_get_text (ev_view->document, &ev_view->selection);
455         clipboard = gtk_widget_get_clipboard (GTK_WIDGET (ev_view),
456                                               GDK_SELECTION_CLIPBOARD);
457         gtk_clipboard_set_text (clipboard, text, -1);
458         g_free (text);
459 }
460
461 static void
462 ev_view_primary_get_cb (GtkClipboard     *clipboard,
463                         GtkSelectionData *selection_data,
464                         guint             info,
465                         gpointer          data)
466 {
467         EvView *ev_view = EV_VIEW (data);
468         char *text;
469
470         text = ev_document_get_text (ev_view->document, &ev_view->selection);
471         gtk_selection_data_set_text (selection_data, text, -1);
472 }
473
474 static void
475 ev_view_primary_clear_cb (GtkClipboard *clipboard,
476                           gpointer      data)
477 {
478         EvView *ev_view = EV_VIEW (data);
479
480         ev_view->has_selection = FALSE;
481 }
482
483 static void
484 ev_view_update_primary_selection (EvView *ev_view)
485 {
486         GtkClipboard *clipboard;
487
488         clipboard = gtk_widget_get_clipboard (GTK_WIDGET (ev_view),
489                                               GDK_SELECTION_PRIMARY);
490
491         if (ev_view->has_selection) {
492                 if (!gtk_clipboard_set_with_owner (clipboard,
493                                                    targets,
494                                                    G_N_ELEMENTS (targets),
495                                                    ev_view_primary_get_cb,
496                                                    ev_view_primary_clear_cb,
497                                                    G_OBJECT (ev_view)))
498                         ev_view_primary_clear_cb (clipboard, ev_view);
499         } else {
500                 if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (ev_view))
501                         gtk_clipboard_clear (clipboard);
502         }
503 }
504
505 static gboolean
506 ev_view_button_press_event (GtkWidget      *widget,
507                             GdkEventButton *event)
508 {
509         EvView *view = EV_VIEW (widget);
510
511         if (!GTK_WIDGET_HAS_FOCUS (widget)) {
512                 gtk_widget_grab_focus (widget);
513         }
514
515         view->pressed_button = event->button;
516
517         switch (event->button) {
518                 case 1:
519                         if (view->has_selection) {
520                                 view->has_selection = FALSE;
521                                 gtk_widget_queue_draw (widget);
522                         }
523
524                         view->selection_start.x = event->x;
525                         view->selection_start.y = event->y;
526                         break;
527         }
528
529         return TRUE;
530 }
531
532 static char *
533 status_message_from_link (EvLink *link)
534 {
535         EvLinkType type;
536         char *msg;
537         int page;
538
539         type = ev_link_get_link_type (link);
540         
541         switch (type) {
542                 case EV_LINK_TYPE_TITLE:
543                         msg = g_strdup (ev_link_get_title (link));
544                         break;
545                 case EV_LINK_TYPE_PAGE:
546                         page = ev_link_get_page (link);
547                         msg = g_strdup_printf (_("Go to page %d"), page);
548                         break;
549                 case EV_LINK_TYPE_EXTERNAL_URI:
550                         msg = g_strdup (ev_link_get_uri (link));
551                         break;
552                 default:
553                         msg = NULL;
554         }
555
556         return msg;
557 }
558
559 static void
560 ev_view_set_status (EvView *view, const char *message)
561 {
562         g_return_if_fail (EV_IS_VIEW (view));
563
564         if (message != view->status) {
565                 g_free (view->status);
566                 view->status = g_strdup (message);
567                 g_object_notify (G_OBJECT (view), "status");
568         }
569 }
570
571 static void
572 ev_view_set_find_status (EvView *view, const char *message)
573 {
574         g_return_if_fail (EV_IS_VIEW (view));
575         
576         g_free (view->find_status);
577         view->find_status = g_strdup (message);
578         g_object_notify (G_OBJECT (view), "find-status");
579 }
580
581
582 static void
583 ev_view_set_cursor (EvView *view, EvViewCursor new_cursor)
584 {
585         GdkCursor *cursor = NULL;
586         GdkDisplay *display;
587         GtkWidget *widget;
588
589         if (view->cursor == new_cursor) {
590                 return;
591         }
592
593         widget = gtk_widget_get_toplevel (GTK_WIDGET (view));
594         display = gtk_widget_get_display (widget);
595         view->cursor = new_cursor;
596
597         switch (new_cursor) {
598                 case EV_VIEW_CURSOR_NORMAL:
599                         gdk_window_set_cursor (widget->window, NULL);
600                         break;
601                 case EV_VIEW_CURSOR_LINK:
602                         cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
603                         break;
604                 case EV_VIEW_CURSOR_WAIT:
605                         cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
606                         break;
607         }
608
609         if (cursor) {
610                 gdk_window_set_cursor (widget->window, cursor);
611                 gdk_cursor_unref (cursor);
612                 gdk_flush();
613         }
614 }
615
616 static gboolean
617 ev_view_motion_notify_event (GtkWidget      *widget,
618                              GdkEventMotion *event)
619 {
620         EvView *view = EV_VIEW (widget);
621
622         if (view->pressed_button > 0) {
623                 view->has_selection = TRUE;
624                 view->selection.x = MIN (view->selection_start.x, event->x);
625                 view->selection.y = MIN (view->selection_start.y, event->y);
626                 view->selection.width = ABS (view->selection_start.x - event->x) + 1;
627                 view->selection.height = ABS (view->selection_start.y - event->y) + 1;
628         } else if (view->document) {
629                 EvLink *link;
630
631                 link = ev_document_get_link (view->document, event->x, event->y);
632                 if (link) {
633                         char *msg;
634
635                         msg = status_message_from_link (link);
636                         ev_view_set_status (view, msg);
637                         ev_view_set_cursor (view, EV_VIEW_CURSOR_LINK);
638                         g_free (msg);
639
640                         g_object_unref (link);
641                 } else {
642                         ev_view_set_status (view, NULL);
643                         if (view->cursor == EV_VIEW_CURSOR_LINK) {
644                                 ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
645                         }
646                 }
647         }
648
649         gtk_widget_queue_draw (widget);
650
651         return TRUE;
652 }
653
654 static gboolean
655 ev_view_button_release_event (GtkWidget      *widget,
656                               GdkEventButton *event)
657 {
658         EvView *view = EV_VIEW (widget);
659
660         view->pressed_button = -1;
661
662         if (view->has_selection) {
663                 ev_view_update_primary_selection (view);
664         } else if (view->document) {
665                 EvLink *link;
666
667                 link = ev_document_get_link (view->document,
668                                              event->x,
669                                              event->y);
670                 if (link) {
671                         ev_view_go_to_link (view, link);
672                         g_object_unref (link);
673                 }
674         }
675
676         return FALSE;
677 }
678
679 static void
680 on_adjustment_value_changed (GtkAdjustment  *adjustment,
681                              EvView *view)
682 {
683         view_update_adjustments (view);
684 }
685
686 static void
687 set_scroll_adjustment (EvView *view,
688                        GtkOrientation  orientation,
689                        GtkAdjustment  *adjustment)
690 {
691         GtkAdjustment **to_set;
692
693         if (orientation == GTK_ORIENTATION_HORIZONTAL)
694                 to_set = &view->hadjustment;
695         else
696                 to_set = &view->vadjustment;
697   
698         if (*to_set != adjustment) {
699                 if (*to_set) {
700                         g_signal_handlers_disconnect_by_func (*to_set,
701                                                               (gpointer) on_adjustment_value_changed,
702                                                               view);
703                         g_object_unref (*to_set);
704                 }
705
706                 *to_set = adjustment;
707                 view_set_adjustment_values (view, orientation);
708
709                 if (*to_set) {
710                         g_object_ref (*to_set);
711                         g_signal_connect (*to_set, "value_changed",
712                                           G_CALLBACK (on_adjustment_value_changed), view);
713                 }
714         }
715 }
716
717 static void
718 ev_view_set_scroll_adjustments (EvView *view,
719                                 GtkAdjustment  *hadjustment,
720                                 GtkAdjustment  *vadjustment)
721 {
722         set_scroll_adjustment (view, GTK_ORIENTATION_HORIZONTAL, hadjustment);
723         set_scroll_adjustment (view, GTK_ORIENTATION_VERTICAL, vadjustment);
724
725         view_update_adjustments (view);
726 }
727
728 static void
729 add_scroll_binding (GtkBindingSet  *binding_set,
730                     guint           keyval,
731                     GtkScrollType   scroll,
732                     gboolean        horizontal)
733 {
734   guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
735   
736   gtk_binding_entry_add_signal (binding_set, keyval, 0,
737                                 "scroll_view", 2,
738                                 GTK_TYPE_SCROLL_TYPE, scroll,
739                                 G_TYPE_BOOLEAN, horizontal);
740   gtk_binding_entry_add_signal (binding_set, keypad_keyval, 0,
741                                 "scroll_view", 2,
742                                 GTK_TYPE_SCROLL_TYPE, scroll,
743                                 G_TYPE_BOOLEAN, horizontal);
744 }
745
746 static void
747 ev_view_scroll_view (EvView *view,
748                      GtkScrollType scroll,
749                      gboolean horizontal)
750 {
751         if (scroll == GTK_SCROLL_PAGE_BACKWARD) {
752                 ev_view_set_page (view, ev_view_get_page (view) - 1);
753         } else if (scroll == GTK_SCROLL_PAGE_FORWARD) {
754                 ev_view_set_page (view, ev_view_get_page (view) + 1);
755         } else {
756                 GtkAdjustment *adjustment;
757                 double value;
758
759                 if (horizontal) {
760                         adjustment = view->hadjustment; 
761                 } else {
762                         adjustment = view->vadjustment;
763                 }
764
765                 value = adjustment->value;
766
767                 switch (scroll) {
768                         case GTK_SCROLL_STEP_BACKWARD:  
769                                 value -= adjustment->step_increment; 
770                                 break;
771                         case GTK_SCROLL_STEP_FORWARD:
772                                 value += adjustment->step_increment; 
773                                 break;
774                         default:
775                                 break;
776                 }
777
778                 value = CLAMP (value, adjustment->lower,
779                                adjustment->upper - adjustment->page_size);
780
781                 gtk_adjustment_set_value (adjustment, value);
782         }
783 }
784
785 static void
786 ev_view_set_property (GObject *object,
787                       guint prop_id,
788                       const GValue *value,
789                       GParamSpec *pspec)
790 {
791         switch (prop_id)
792         {
793                 /* Read only */
794                 case PROP_STATUS:
795                 case PROP_FIND_STATUS:
796                         break;
797         }
798 }
799
800 static void
801 ev_view_get_property (GObject *object,
802                       guint prop_id,
803                       GValue *value,
804                       GParamSpec *pspec)
805 {
806         EvView *view = EV_VIEW (object);
807
808         switch (prop_id)
809         {
810                 case PROP_STATUS:
811                         g_value_set_string (value, view->status);
812                         break;
813                 case PROP_FIND_STATUS:
814                         g_value_set_string (value, view->status);
815                         break;
816         }
817 }
818
819 static void
820 ev_view_class_init (EvViewClass *class)
821 {
822         GObjectClass *object_class = G_OBJECT_CLASS (class);
823         GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
824         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
825         GtkBindingSet *binding_set;
826
827         object_class->finalize = ev_view_finalize;
828         object_class->set_property = ev_view_set_property;
829         object_class->get_property = ev_view_get_property;
830
831         widget_class->expose_event = ev_view_expose_event;
832         widget_class->button_press_event = ev_view_button_press_event;
833         widget_class->motion_notify_event = ev_view_motion_notify_event;
834         widget_class->button_release_event = ev_view_button_release_event;
835         widget_class->size_request = ev_view_size_request;
836         widget_class->size_allocate = ev_view_size_allocate;
837         widget_class->realize = ev_view_realize;
838         widget_class->unrealize = ev_view_unrealize;
839         gtk_object_class->destroy = ev_view_destroy;
840
841         class->set_scroll_adjustments = ev_view_set_scroll_adjustments;
842         class->scroll_view = ev_view_scroll_view;
843
844         widget_class->set_scroll_adjustments_signal =  g_signal_new ("set-scroll-adjustments",
845                                                                      G_OBJECT_CLASS_TYPE (object_class),
846                                                                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
847                                                                      G_STRUCT_OFFSET (EvViewClass, set_scroll_adjustments),
848                                                                      NULL, NULL,
849                                                                      ev_marshal_VOID__OBJECT_OBJECT,
850                                                                      G_TYPE_NONE, 2,
851                                                                      GTK_TYPE_ADJUSTMENT,
852                                                                      GTK_TYPE_ADJUSTMENT);
853         page_changed_signal = g_signal_new ("page-changed",
854                                             G_OBJECT_CLASS_TYPE (object_class),
855                                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
856                                             G_STRUCT_OFFSET (EvViewClass, page_changed),
857                                             NULL, NULL,
858                                             ev_marshal_VOID__NONE,
859                                             G_TYPE_NONE, 0);
860
861         g_signal_new ("scroll_view",
862                       G_TYPE_FROM_CLASS (object_class),
863                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
864                       G_STRUCT_OFFSET (EvViewClass, scroll_view),
865                       NULL, NULL,
866                       ev_marshal_VOID__ENUM_BOOLEAN,
867                       G_TYPE_NONE, 2,
868                       GTK_TYPE_SCROLL_TYPE,
869                       G_TYPE_BOOLEAN);
870
871         g_object_class_install_property (object_class,
872                                          PROP_STATUS,
873                                          g_param_spec_string ("status",
874                                                               "Status Message",
875                                                               "The status message",
876                                                               NULL,
877                                                               G_PARAM_READABLE));
878
879         g_object_class_install_property (object_class,
880                                          PROP_STATUS,
881                                          g_param_spec_string ("find-status",
882                                                               "Find Status Message",
883                                                               "The find status message",
884                                                               NULL,
885                                                               G_PARAM_READABLE));
886
887         binding_set = gtk_binding_set_by_class (class);
888
889         add_scroll_binding (binding_set, GDK_Left,  GTK_SCROLL_STEP_BACKWARD, TRUE);
890         add_scroll_binding (binding_set, GDK_Right, GTK_SCROLL_STEP_FORWARD,  TRUE);
891         add_scroll_binding (binding_set, GDK_Up,    GTK_SCROLL_STEP_BACKWARD, FALSE);
892         add_scroll_binding (binding_set, GDK_Down,  GTK_SCROLL_STEP_FORWARD,  FALSE);
893
894         add_scroll_binding (binding_set, GDK_Page_Up,   GTK_SCROLL_PAGE_BACKWARD, FALSE);
895         add_scroll_binding (binding_set, GDK_Page_Down, GTK_SCROLL_PAGE_FORWARD,  FALSE);
896 }
897
898 static void
899 ev_view_init (EvView *view)
900 {
901         GTK_WIDGET_SET_FLAGS (view, GTK_CAN_FOCUS);
902
903         view->scale = 1.0;
904         view->pressed_button = -1;
905         view->cursor = EV_VIEW_CURSOR_NORMAL;
906         view->results_on_this_page = 0;
907         view->next_page_with_result = 0;
908 }
909
910 static char *
911 ev_view_get_find_status_message (EvView *view)
912 {
913 /*
914         if (view->find_results->len == 0) {
915                 if (view->find_percent_complete >= (1.0 - 1e-10)) {
916                         return g_strdup (_("Not found"));
917                 } else {
918                         return g_strdup_printf (_("%3d%% remaining to search"),
919                                                 (int) ((1.0 - view->find_percent_complete) * 100));
920                 }
921         } else if (view->results_on_this_page == 0) {
922                 g_assert (view->next_page_with_result != 0);
923                 return g_strdup_printf (_("Found on page %d"),
924                                         view->next_page_with_result);
925         } else {
926                 return g_strdup_printf (_("%d found on this page"),
927                                         view->results_on_this_page);
928         }
929 */
930 }
931
932 static void
933 find_changed_cb (EvDocument *document, EvView *view)
934 {
935         gtk_widget_queue_draw (GTK_WIDGET (view));
936 }
937
938 static void
939 document_changed_callback (EvDocument *document,
940                            EvView     *view)
941 {
942         gtk_widget_queue_draw (GTK_WIDGET (view));
943         ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
944 }
945
946 /*** Public API ***/       
947      
948 GtkWidget*
949 ev_view_new (void)
950 {
951         return g_object_new (EV_TYPE_VIEW, NULL);
952 }
953
954 void
955 ev_view_set_document (EvView     *view,
956                       EvDocument *document)
957 {
958         g_return_if_fail (EV_IS_VIEW (view));
959
960         if (document != view->document) {
961                 if (view->document) {
962                         g_signal_handlers_disconnect_by_func (view->document,
963                                                               find_changed_cb,
964                                                               view);
965                         g_object_unref (view->document);
966                 }
967
968                 view->document = document;
969
970                 if (view->document) {
971                         g_object_ref (view->document);
972                         if (EV_IS_DOCUMENT_FIND (view->document))
973                                 g_signal_connect (view->document,
974                                                   "find_changed",
975                                                   G_CALLBACK (find_changed_cb),
976                                                   view);
977                         g_signal_connect (view->document,
978                                           "changed",
979                                           G_CALLBACK (document_changed_callback),
980                                           view);
981                 }
982
983                 if (GTK_WIDGET_REALIZED (view))
984                         ev_document_set_target (view->document, view->bin_window);
985                 
986                 gtk_widget_queue_resize (GTK_WIDGET (view));
987                 
988                 g_signal_emit (view, page_changed_signal, 0);
989         }
990 }
991
992 static void
993 set_document_page (EvView *view, int page)
994 {
995         if (view->document) {
996                 int old_page = ev_document_get_page (view->document);
997                 int old_width, old_height;
998
999                 ev_document_get_page_size (view->document,
1000                                            &old_width, &old_height);
1001
1002                 if (old_page != page) {
1003                         ev_view_set_cursor (view, EV_VIEW_CURSOR_WAIT);
1004                         ev_document_set_page (view->document, page);
1005                 }
1006
1007                 if (old_page != ev_document_get_page (view->document)) {
1008                         int width, height;
1009                         
1010                         g_signal_emit (view, page_changed_signal, 0);
1011
1012                         view->has_selection = FALSE;
1013                         ev_document_get_page_size (view->document,
1014                                                    &width, &height);
1015                         if (width != old_width || height != old_height)
1016                                 gtk_widget_queue_resize (GTK_WIDGET (view));
1017                 }
1018         }
1019 }
1020
1021 static void
1022 go_to_link (EvView *view, EvLink *link)
1023 {
1024         EvLinkType type;
1025         const char *uri;
1026         int page;
1027
1028         type = ev_link_get_link_type (link);
1029         
1030         switch (type) {
1031                 case EV_LINK_TYPE_TITLE:
1032                         break;
1033                 case EV_LINK_TYPE_PAGE:
1034                         page = ev_link_get_page (link);
1035                         set_document_page (view, page);
1036                         break;
1037                 case EV_LINK_TYPE_EXTERNAL_URI:
1038                         uri = ev_link_get_uri (link);
1039                         gnome_vfs_url_show (uri);
1040                         break;
1041         }
1042 }
1043
1044 void
1045 ev_view_go_to_link (EvView *view, EvLink *link)
1046 {
1047         go_to_link (view, link);
1048 }
1049
1050 void
1051 ev_view_set_page (EvView *view,
1052                   int     page)
1053 {
1054         g_return_if_fail (EV_IS_VIEW (view));
1055
1056         set_document_page (view, page);
1057 }
1058
1059 int
1060 ev_view_get_page (EvView *view)
1061 {
1062         if (view->document)
1063                 return ev_document_get_page (view->document);
1064         else
1065                 return 1;
1066 }
1067
1068 #define ZOOM_IN_FACTOR  1.2
1069 #define ZOOM_OUT_FACTOR (1.0/ZOOM_IN_FACTOR)
1070
1071 #define MIN_SCALE 0.05409
1072 #define MAX_SCALE 18.4884
1073
1074 static void
1075 ev_view_zoom (EvView   *view,
1076               double    factor,
1077               gboolean  relative)
1078 {
1079         double scale;
1080
1081         if (relative)
1082                 scale = view->scale * factor;
1083         else
1084                 scale = factor;
1085
1086         view->scale = CLAMP (scale, MIN_SCALE, MAX_SCALE);
1087
1088         ev_document_set_scale (view->document, view->scale);
1089
1090         gtk_widget_queue_resize (GTK_WIDGET (view));
1091 }
1092
1093 void
1094 ev_view_zoom_in (EvView *view)
1095 {
1096         ev_view_zoom (view, ZOOM_IN_FACTOR, TRUE);
1097 }
1098
1099 void
1100 ev_view_zoom_out (EvView *view)
1101 {
1102         ev_view_zoom (view, ZOOM_OUT_FACTOR, TRUE);
1103 }
1104
1105 void
1106 ev_view_normal_size (EvView *view)
1107 {
1108         ev_view_zoom (view, 1.0, FALSE);
1109 }
1110
1111 void
1112 ev_view_best_fit (EvView *view)
1113 {
1114         double scale;
1115         int width, height;
1116
1117         width = height = 0;
1118         ev_document_get_page_size (view->document, &width, &height);
1119
1120         scale = 1.0;
1121         if (width != 0 && height != 0) {
1122                 double scale_w, scale_h;
1123
1124                 scale_w = (double)GTK_WIDGET (view)->allocation.width * view->scale / width;
1125                 scale_h = (double)GTK_WIDGET (view)->allocation.height * view->scale / height;
1126
1127                 scale = (scale_w < scale_h) ? scale_w : scale_h;
1128         }
1129
1130         ev_view_zoom (view, scale, FALSE);
1131 }
1132
1133 void
1134 ev_view_fit_width (EvView *view)
1135 {
1136         double scale = 1.0;
1137         int width;
1138
1139         width = 0;
1140         ev_document_get_page_size (view->document, &width, NULL);
1141
1142         scale = 1.0;
1143         if (width != 0)
1144                 scale = (double)GTK_WIDGET (view)->allocation.width * view->scale / width;
1145
1146         ev_view_zoom (view, scale, FALSE);
1147 }
1148
1149 const char *
1150 ev_view_get_status (EvView *view)
1151 {
1152         g_return_val_if_fail (EV_IS_VIEW (view), NULL);
1153
1154         return view->status;
1155 }
1156
1157 const char *
1158 ev_view_get_find_status (EvView *view)
1159 {
1160         g_return_val_if_fail (EV_IS_VIEW (view), NULL);
1161
1162         return view->find_status;
1163 }
1164
1165