]> www.fi.muni.cz Git - evince.git/blob - shell/ev-view.c
45535253d2ad349dfc62655d008ca594b6333f3c
[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
23 #include "ev-marshal.h"
24 #include "ev-view.h"
25
26 #define EV_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_VIEW, EvViewClass))
27 #define EV_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_VIEW))
28 #define EV_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EV_TYPE_VIEW, EvViewClass))
29
30 struct _EvView {
31         GtkWidget parent_instance;
32
33         EvDocument *document;
34         
35         GdkWindow *bin_window;
36         
37         int scroll_x;
38         int scroll_y;
39
40         GtkAdjustment *hadjustment;
41         GtkAdjustment *vadjustment;
42
43         GArray *find_results;
44
45         double scale;
46 };
47
48 struct _EvViewClass {
49         GtkWidgetClass parent_class;
50
51         void    (*set_scroll_adjustments) (EvView         *view,
52                                            GtkAdjustment  *hadjustment,
53                                            GtkAdjustment  *vadjustment);
54         
55         /* Should this be notify::page? */
56         void    (*page_changed)           (EvView         *view);
57 };
58
59 static guint page_changed_signal = 0;
60
61 static void ev_view_set_scroll_adjustments (EvView         *view,
62                                             GtkAdjustment  *hadjustment,
63                                             GtkAdjustment  *vadjustment);
64      
65 G_DEFINE_TYPE (EvView, ev_view, GTK_TYPE_WIDGET)
66
67 /*** Helper functions ***/       
68      
69 static void
70 view_update_adjustments (EvView *view)
71 {
72         int old_x = view->scroll_x;
73         int old_y = view->scroll_y;
74   
75         if (view->hadjustment)
76                 view->scroll_x = view->hadjustment->value;
77         else
78                 view->scroll_x = 0;
79
80         if (view->vadjustment)
81                 view->scroll_y = view->vadjustment->value;
82         else
83                 view->scroll_y = 0;
84   
85         if (GTK_WIDGET_REALIZED (view) &&
86             (view->scroll_x != old_x || view->scroll_y != old_y)) {
87                 gdk_window_move (view->bin_window, - view->scroll_x, - view->scroll_y);
88                 gdk_window_process_updates (view->bin_window, TRUE);
89         }
90 }
91
92 static void
93 view_set_adjustment_values (EvView         *view,
94                             GtkOrientation  orientation)
95 {
96         GtkWidget *widget = GTK_WIDGET (view);
97         GtkAdjustment *adjustment;
98         gboolean value_changed = FALSE;
99         int requisition;
100         int allocation;
101
102         if (orientation == GTK_ORIENTATION_HORIZONTAL)  {
103                 requisition = widget->requisition.width;
104                 allocation = widget->allocation.width;
105                 adjustment = view->hadjustment;
106         } else {
107                 requisition = widget->requisition.height;
108                 allocation = widget->allocation.height;
109                 adjustment = view->vadjustment;
110         }
111
112         if (!adjustment)
113                 return;
114   
115         adjustment->page_size = allocation;
116         adjustment->step_increment = allocation * 0.1;
117         adjustment->page_increment = allocation * 0.9;
118         adjustment->lower = 0;
119         adjustment->upper = MAX (allocation, requisition);
120
121         if (adjustment->value > adjustment->upper - adjustment->page_size) {
122                 adjustment->value = adjustment->upper - adjustment->page_size;
123                 value_changed = TRUE;
124         }
125
126         gtk_adjustment_changed (adjustment);
127         if (value_changed)
128                 gtk_adjustment_value_changed (adjustment);
129 }
130
131 /*** Virtual function implementations ***/       
132      
133 static void
134 ev_view_finalize (GObject *object)
135 {
136         EvView *view = EV_VIEW (object);
137
138         if (view->document)
139                 g_object_unref (view->document);
140
141         ev_view_set_scroll_adjustments (view, NULL, NULL);
142
143         g_array_free (view->find_results, TRUE);
144         view->find_results = NULL;
145         
146         G_OBJECT_CLASS (ev_view_parent_class)->finalize (object);
147 }
148
149 static void
150 ev_view_destroy (GtkObject *object)
151 {
152         EvView *view = EV_VIEW (object);
153
154         ev_view_set_scroll_adjustments (view, NULL, NULL);
155   
156         GTK_OBJECT_CLASS (ev_view_parent_class)->destroy (object);
157 }
158
159 static void
160 ev_view_size_request (GtkWidget      *widget,
161                       GtkRequisition *requisition)
162 {
163         EvView *view = EV_VIEW (widget);
164
165         if (GTK_WIDGET_REALIZED (widget)) {
166                 if (view->document) {
167                         ev_document_get_page_size (view->document,
168                                                    &requisition->width,
169                                                    &requisition->height);
170                 } else {
171                         requisition->width = 10;
172                         requisition->height = 10;
173                 }
174         }
175   
176 }
177
178 static void
179 ev_view_size_allocate (GtkWidget      *widget,
180                        GtkAllocation  *allocation)
181 {
182         EvView *view = EV_VIEW (widget);
183
184         GTK_WIDGET_CLASS (ev_view_parent_class)->size_allocate (widget, allocation);
185
186         view_set_adjustment_values (view, GTK_ORIENTATION_HORIZONTAL);
187         view_set_adjustment_values (view, GTK_ORIENTATION_VERTICAL);
188
189         if (GTK_WIDGET_REALIZED (widget)) {
190                 gdk_window_resize (view->bin_window,
191                                    MAX (widget->allocation.width, widget->requisition.width),
192                                    MAX (widget->allocation.height, widget->requisition.height));
193         }
194 }
195
196 static void
197 update_window_backgrounds (EvView *view)
198 {
199         GtkWidget *widget = GTK_WIDGET (view);
200   
201         if (GTK_WIDGET_REALIZED (view)) {
202                 gdk_window_set_background (view->bin_window,
203                                            &widget->style->base[GTK_WIDGET_STATE (widget)]);
204         }
205 }
206
207 static void
208 ev_view_realize (GtkWidget *widget)
209 {
210         EvView *view = EV_VIEW (widget);
211         GdkWindowAttr attributes;
212
213         GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
214   
215         attributes.window_type = GDK_WINDOW_CHILD;
216         attributes.wclass = GDK_INPUT_OUTPUT;
217         attributes.visual = gtk_widget_get_visual (widget);
218         attributes.colormap = gtk_widget_get_colormap (widget);
219   
220         attributes.x = widget->allocation.x;
221         attributes.y = widget->allocation.y;
222         attributes.width = widget->allocation.width;
223         attributes.height = widget->allocation.height;
224         attributes.event_mask = 0;
225   
226         widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
227                                          &attributes,
228                                          GDK_WA_X | GDK_WA_Y |
229                                          GDK_WA_COLORMAP |
230                                          GDK_WA_VISUAL);
231         gdk_window_set_user_data (widget->window, widget);
232         widget->style = gtk_style_attach (widget->style, widget->window);
233   
234         attributes.x = 0;
235         attributes.y = 0;
236         attributes.width = MAX (widget->allocation.width, widget->requisition.width);
237         attributes.height = MAX (widget->allocation.height, widget->requisition.height);
238         attributes.event_mask = GDK_EXPOSURE_MASK;
239   
240         view->bin_window = gdk_window_new (widget->window,
241                                            &attributes,
242                                            GDK_WA_X | GDK_WA_Y |
243                                            GDK_WA_COLORMAP |
244                                            GDK_WA_VISUAL);
245         gdk_window_set_user_data (view->bin_window, widget);
246         gdk_window_show (view->bin_window);
247
248         if (view->document) {
249                 ev_document_set_target (view->document, view->bin_window);
250
251                 /* We can't get page size without a target, so we have to
252                  * queue a size request at realization. Could be fixed
253                  * with EvDocument changes to allow setting a GdkScreen
254                  * without setting a target.
255                  */
256                 gtk_widget_queue_resize (widget);
257         }
258
259         update_window_backgrounds (view);
260 }
261
262 static void
263 ev_view_unrealize (GtkWidget *widget)
264 {
265         EvView *view = EV_VIEW (widget);
266
267         if (view->document)
268                 ev_document_set_target (view->document, NULL);
269
270         gdk_window_set_user_data (view->bin_window, NULL);
271         gdk_window_destroy (view->bin_window);
272         view->bin_window = NULL;
273
274         GTK_WIDGET_CLASS (ev_view_parent_class)->unrealize (widget);
275 }
276
277 static void
278 ev_view_style_set (GtkWidget      *widget,
279                    GtkStyle       *previous_style)
280 {
281         update_window_backgrounds (EV_VIEW (widget));
282 }
283
284 static void
285 ev_view_state_changed (GtkWidget    *widget,
286                        GtkStateType  previous_state)
287 {
288         update_window_backgrounds (EV_VIEW (widget));
289 }
290
291 static void
292 expose_bin_window (GtkWidget      *widget,
293                    GdkEventExpose *event)
294 {
295         EvView *view = EV_VIEW (widget);
296         int i;
297         const EvFindResult *results;
298
299         if (view->document)
300                 ev_document_render (view->document,
301                                     event->area.x, event->area.y,
302                                     event->area.width, event->area.height);
303
304         results = (EvFindResult*) view->find_results->data;
305         i = 0;
306         while (i < view->find_results->len) {
307 #if 0
308                 g_printerr ("highlighting result %d at %d,%d %dx%d\n",
309                             i,
310                             results[i].highlight_area.x,
311                             results[i].highlight_area.y,
312                             results[i].highlight_area.width,
313                             results[i].highlight_area.height);
314 #endif                       
315                 // if (results[i].page_num == current_page) FIXME
316                 gdk_draw_rectangle (view->bin_window,
317                                     widget->style->base_gc[GTK_STATE_SELECTED],
318                                     FALSE,
319                                     results[i].highlight_area.x,
320                                     results[i].highlight_area.y,
321                                     results[i].highlight_area.width,
322                                     results[i].highlight_area.height);
323                 ++i;
324         }
325 }
326
327 static gboolean
328 ev_view_expose_event (GtkWidget      *widget,
329                       GdkEventExpose *event)
330 {
331         EvView *view = EV_VIEW (widget);
332
333         if (event->window == view->bin_window)
334                 expose_bin_window (widget, event);
335         else
336                 return GTK_WIDGET_CLASS (ev_view_parent_class)->expose_event (widget, event);
337
338         return FALSE;
339 }
340
341 static gboolean
342 ev_view_button_press_event (GtkWidget      *widget,
343                             GdkEventButton *event)
344 {
345         /* EvView *view = EV_VIEW (widget); */
346
347         return FALSE;
348 }
349
350 static gboolean
351 ev_view_motion_notify_event (GtkWidget      *widget,
352                              GdkEventMotion *event)
353 {
354         /* EvView *view = EV_VIEW (widget); */
355   
356         return FALSE;
357 }
358
359 static gboolean
360 ev_view_button_release_event (GtkWidget      *widget,
361                               GdkEventButton *event)
362 {
363         /* EvView *view = EV_VIEW (widget); */
364
365         return FALSE;
366 }
367
368 static void
369 on_adjustment_value_changed (GtkAdjustment  *adjustment,
370                              EvView *view)
371 {
372         view_update_adjustments (view);
373 }
374
375 static void
376 set_scroll_adjustment (EvView *view,
377                        GtkOrientation  orientation,
378                        GtkAdjustment  *adjustment)
379 {
380         GtkAdjustment **to_set;
381
382         if (orientation == GTK_ORIENTATION_HORIZONTAL)
383                 to_set = &view->hadjustment;
384         else
385                 to_set = &view->vadjustment;
386   
387         if (*to_set != adjustment) {
388                 if (*to_set) {
389                         g_signal_handlers_disconnect_by_func (*to_set,
390                                                               (gpointer) on_adjustment_value_changed,
391                                                               view);
392                         g_object_unref (*to_set);
393                 }
394
395                 *to_set = adjustment;
396                 view_set_adjustment_values (view, orientation);
397
398                 if (*to_set) {
399                         g_object_ref (*to_set);
400                         g_signal_connect (*to_set, "value_changed",
401                                           G_CALLBACK (on_adjustment_value_changed), view);
402                 }
403         }
404 }
405
406 static void
407 ev_view_set_scroll_adjustments (EvView *view,
408                                 GtkAdjustment  *hadjustment,
409                                 GtkAdjustment  *vadjustment)
410 {
411         set_scroll_adjustment (view, GTK_ORIENTATION_HORIZONTAL, hadjustment);
412         set_scroll_adjustment (view, GTK_ORIENTATION_VERTICAL, vadjustment);
413
414         view_update_adjustments (view);
415 }
416
417 static void
418 ev_view_class_init (EvViewClass *class)
419 {
420         GObjectClass *object_class = G_OBJECT_CLASS (class);
421         GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
422         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
423
424         object_class->finalize = ev_view_finalize;
425
426         widget_class->expose_event = ev_view_expose_event;
427         widget_class->button_press_event = ev_view_button_press_event;
428         widget_class->motion_notify_event = ev_view_motion_notify_event;
429         widget_class->button_release_event = ev_view_button_release_event;
430         widget_class->size_request = ev_view_size_request;
431         widget_class->size_allocate = ev_view_size_allocate;
432         widget_class->realize = ev_view_realize;
433         widget_class->unrealize = ev_view_unrealize;
434         widget_class->style_set = ev_view_style_set;
435         widget_class->state_changed = ev_view_state_changed;
436         gtk_object_class->destroy = ev_view_destroy;
437   
438         class->set_scroll_adjustments = ev_view_set_scroll_adjustments;
439
440         widget_class->set_scroll_adjustments_signal =  g_signal_new ("set-scroll-adjustments",
441                                                                      G_OBJECT_CLASS_TYPE (object_class),
442                                                                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
443                                                                      G_STRUCT_OFFSET (EvViewClass, set_scroll_adjustments),
444                                                                      NULL, NULL,
445                                                                      ev_marshal_VOID__OBJECT_OBJECT,
446                                                                      G_TYPE_NONE, 2,
447                                                                      GTK_TYPE_ADJUSTMENT,
448                                                                      GTK_TYPE_ADJUSTMENT);
449         page_changed_signal = g_signal_new ("page-changed",
450                                             G_OBJECT_CLASS_TYPE (object_class),
451                                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
452                                             G_STRUCT_OFFSET (EvViewClass, page_changed),
453                                             NULL, NULL,
454                                             ev_marshal_VOID__NONE,
455                                             G_TYPE_NONE, 0);
456 }
457
458 static void
459 ev_view_init (EvView *view)
460 {
461         static const GdkColor white = { 0, 0xffff, 0xffff, 0xffff };
462
463         view->scale = 1.0;
464         
465         gtk_widget_modify_bg (GTK_WIDGET (view), GTK_STATE_NORMAL, &white);
466
467         view->find_results = g_array_new (FALSE,
468                                           FALSE,
469                                           sizeof (EvFindResult));
470 }
471
472
473 static void
474 found_results_callback (EvDocument         *document,
475                         const EvFindResult *results,
476                         int                 n_results,
477                         double              percent_complete,
478                         void               *data)
479 {
480   EvView *view = EV_VIEW (data);
481   
482   g_array_set_size (view->find_results, 0);
483
484   if (n_results > 0)
485           g_array_append_vals (view->find_results,
486                                results, n_results);
487   
488   gtk_widget_queue_draw (GTK_WIDGET (view));
489 }
490
491 /*** Public API ***/       
492      
493 GtkWidget*
494 ev_view_new (void)
495 {
496         return g_object_new (EV_TYPE_VIEW, NULL);
497 }
498
499 void
500 ev_view_set_document (EvView     *view,
501                       EvDocument *document)
502 {
503         g_return_if_fail (EV_IS_VIEW (view));
504
505         if (document != view->document) {
506                 int old_page = ev_view_get_page (view);
507                 
508                 if (view->document) {
509                         g_object_unref (view->document);
510                         g_signal_handlers_disconnect_by_func (view->document,
511                                                               found_results_callback,
512                                                               view);
513                         g_array_set_size (view->find_results, 0);
514                 }
515
516                 view->document = document;
517
518                 if (view->document) {
519                         g_object_ref (view->document);
520                         g_signal_connect (view->document,
521                                           "found",
522                                           G_CALLBACK (found_results_callback),
523                                           view);
524                 }
525
526                 if (GTK_WIDGET_REALIZED (view))
527                         ev_document_set_target (view->document, view->bin_window);
528                 
529                 gtk_widget_queue_resize (GTK_WIDGET (view));
530                 
531                 if (old_page != ev_view_get_page (view))
532                         g_signal_emit (view, page_changed_signal, 0);
533         }
534 }
535
536 void
537 ev_view_set_page (EvView *view,
538                   int     page)
539 {
540         if (view->document) {
541                 int old_page = ev_document_get_page (view->document);
542                 if (old_page != page)
543                         ev_document_set_page (view->document, page);
544                 if (old_page != ev_document_get_page (view->document)) {
545                         g_signal_emit (view, page_changed_signal, 0);
546                         gtk_widget_queue_draw (GTK_WIDGET (view));
547                 }
548         }
549 }
550
551 int
552 ev_view_get_page (EvView *view)
553 {
554         if (view->document)
555                 return ev_document_get_page (view->document);
556         else
557                 return 1;
558 }
559
560 #define ZOOM_IN_FACTOR  1.2
561 #define ZOOM_OUT_FACTOR (1.0/ZOOM_IN_FACTOR)
562
563 #define MIN_SCALE 0.05409
564 #define MAX_SCALE 18.4884
565
566 static void
567 ev_view_zoom (EvView   *view,
568               double    factor,
569               gboolean  relative)
570 {
571         double scale;
572
573         if (relative)
574                 scale = view->scale * factor;
575         else
576                 scale = factor;
577
578         view->scale = CLAMP (scale, MIN_SCALE, MAX_SCALE);
579
580         ev_document_set_scale (view->document, view->scale);
581
582         gtk_widget_queue_draw (GTK_WIDGET (view));
583 }
584
585 void
586 ev_view_zoom_in (EvView *view)
587 {
588         ev_view_zoom (view, ZOOM_IN_FACTOR, TRUE);
589 }
590
591 void
592 ev_view_zoom_out (EvView *view)
593 {
594         ev_view_zoom (view, ZOOM_OUT_FACTOR, TRUE);
595 }
596
597 void
598 ev_view_normal_size (EvView *view)
599 {
600         ev_view_zoom (view, 1.0, FALSE);
601 }
602
603 void
604 ev_view_best_fit (EvView *view)
605 {
606         double scale;
607         int width, height;
608
609         width = height = 0;
610         ev_document_get_page_size (view->document, &width, &height);
611
612         scale = 1.0;
613         if (width != 0 && height != 0) {
614                 double scale_w, scale_h;
615
616                 scale_w = (double)GTK_WIDGET (view)->allocation.width * view->scale / width;
617                 scale_h = (double)GTK_WIDGET (view)->allocation.height * view->scale / height;
618
619                 scale = (scale_w < scale_h) ? scale_w : scale_h;
620         }
621
622         ev_view_zoom (view, scale, FALSE);
623 }
624
625 void
626 ev_view_fit_width (EvView *view)
627 {
628         double scale = 1.0;
629         int width;
630
631         width = 0;
632         ev_document_get_page_size (view->document, &width, NULL);
633
634         scale = 1.0;
635         if (width != 0)
636                 scale = (double)GTK_WIDGET (view)->allocation.width * view->scale / width;
637
638         ev_view_zoom (view, scale, FALSE);
639 }