Code

gtk-tpdfv/tpdfview: Added a possibility to reload the PDF file.
[tpdfview.git] / src / gtk-tpdfv.c
1 /*
2  * tpdfview - src/gtk-tpdfv.c
3  * Copyright (C) 2011 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 /*
29  * This module provides a Gtk widget to display PDF files.
30  */
32 #include "gtk-tpdfv.h"
34 #include <assert.h>
36 #include <errno.h>
38 #include <cairo.h>
39 #include <gtk/gtk.h>
41 #include <poppler.h>
43 #include <stdlib.h>
44 #include <string.h>
46 #define TPDFV_MIN(a, b) ((a) <= (b) ? (a) : (b))
47 #define TPDFV_MAX(a, b) ((a) >= (b) ? (a) : (b))
49 typedef enum {
50         TPDFV_ZOOM_CUSTOM = 0,
51         TPDFV_ZOOM_WIDTH,
52         TPDFV_ZOOM_HEIGHT,
53         TPDFV_ZOOM_FIT,
54 } tpdfv_zoommode_t;
56 typedef struct {
57         char *filename;
59         PopplerDocument *doc;
60         PopplerPage     *current_page;
62         int current_page_no;
63         int total_pages;
65         tpdfv_zoommode_t zoom_mode;
66         double zoom_factor;
68         double delta_x;
69         double delta_y;
70 } gtk_tpdfv_t;
72 GType gtk_tpdfv_get_type(void);
73 G_DEFINE_TYPE(GtkTPDFV, gtk_tpdfv, GTK_TYPE_DRAWING_AREA);
75 #define GTK_TPDFV_GET_PRIVATE(obj) \
76         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TPDFV, gtk_tpdfv_t))
78 /*
79  * Private helper functions.
80  */
82 static void
83 tpdfv_open(gtk_tpdfv_t *pdf)
84 {
85         GError *err = NULL;
87         if ((! pdf) || (! pdf->filename))
88                 return;
90         pdf->doc = poppler_document_new_from_file(pdf->filename,
91                         /* password = */ NULL, &err);
92         if (! pdf->doc) {
93                 fprintf(stderr, "Failed to open PDF: %s.\n", err->message);
94                 return;
95         }
97         pdf->total_pages = poppler_document_get_n_pages(pdf->doc);
98         if (pdf->current_page_no >= pdf->total_pages)
99                 pdf->current_page_no = pdf->total_pages - 1;
100 } /* tpdfv_open */
102 static void
103 tpdfv_close(gtk_tpdfv_t *pdf)
105         if (! pdf)
106                 return;
108         g_object_unref(pdf->doc);
109         pdf->doc = NULL;
110         pdf->current_page = NULL;
111 } /* tpdfv_close */
113 static gboolean
114 tpdfv_init(gtk_tpdfv_t *pdf, const char *filename)
116         if (strstr(filename, "://"))
117                 pdf->filename = strdup(filename);
118         else {
119                 size_t len = strlen("file://") + strlen(filename);
120                 pdf->filename = (char *)malloc(len + 1);
121                 if (pdf->filename) {
122                         *pdf->filename = '\0';
123                         strncat(pdf->filename, "file://", len);
124                         strncat(pdf->filename, filename, len - strlen("file://"));
125                 }
126         }
128         /* XXX: error reporting mechanism */
130         if (! pdf->filename) {
131                 char errbuf[1024];
132                 strerror_r(errno, errbuf, sizeof(errbuf));
133                 fprintf(stderr, "Failed to allocate string: %s.\n", errbuf);
134                 return FALSE;
135         }
137         pdf->doc = NULL;
138         tpdfv_open(pdf);
140         pdf->current_page_no = 0;
141         pdf->current_page = poppler_document_get_page(pdf->doc,
142                         pdf->current_page_no);
143         if (! pdf->current_page) {
144                 fprintf(stderr, "Failed to open first page of the document.\n");
145                 return FALSE;
146         }
148         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
149         pdf->zoom_factor = 1.0;
150         pdf->delta_x = pdf->delta_y = 0.0;
151         return TRUE;
152 } /* tpdfv_init */
154 static void
155 tpdfv_clean(gtk_tpdfv_t *pdf)
157         if (! pdf)
158                 return;
160         tpdfv_close(pdf);
161         pdf->current_page = NULL;
162         free(pdf->filename);
163         pdf->filename = NULL;
164         return;
165 } /* tpdfv_clean */
167 static void
168 do_redraw(GtkWidget *widget)
170         GdkRegion *region;
171         gtk_tpdfv_t *pdf;
173         pdf = GTK_TPDFV_GET_PRIVATE(widget);
175         if ((! pdf->current_page) || (poppler_page_get_index(pdf->current_page)
176                         != pdf->current_page_no)) {
177                 pdf->current_page = poppler_document_get_page(pdf->doc,
178                 pdf->current_page_no);
180                 if (! pdf->current_page) {
181                         fprintf(stderr, "Failed to open page %i of the document.\n",
182                                         pdf->current_page_no + 1);
183                         return;
184                 }
185         }
187         region = gdk_drawable_get_clip_region(widget->window);
188         gdk_window_invalidate_region(widget->window, region, TRUE);
189         gdk_window_process_updates(widget->window, TRUE);
191         gdk_region_destroy(region);
192 } /* do_redraw */
194 /*
195  * Gtk+ class definition.
196  */
198 static gboolean
199 gtk_tpdfv_expose(GtkWidget *tpdfv, GdkEventExpose *event)
201         gtk_tpdfv_t *pdf;
202         cairo_t *cr;
204         gdouble width, height;
206         gdouble page_width  = 0.0;
207         gdouble page_height = 0.0;
209         double zoom_factor  = 1.0;
210         double x, y;
212         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
214         if (! pdf)
215                 return FALSE;
217         cr = gdk_cairo_create(tpdfv->window);
218         cairo_rectangle(cr, event->area.x, event->area.y,
219                         event->area.width, event->area.height);
220         cairo_clip(cr);
222         poppler_page_get_size(pdf->current_page, &page_width, &page_height);
223         width  = (gdouble)tpdfv->allocation.width;
224         height = (gdouble)tpdfv->allocation.height;
226         /* zoom */
227         if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
228                 zoom_factor = pdf->zoom_factor;
229         }
230         else {
231                 if (pdf->zoom_mode == TPDFV_ZOOM_WIDTH) {
232                         zoom_factor = (double)(width / page_width);
233                 }
234                 else if (pdf->zoom_mode == TPDFV_ZOOM_HEIGHT) {
235                         zoom_factor = (double)(height / page_height);
236                 }
237                 else if (pdf->zoom_mode == TPDFV_ZOOM_FIT) {
238                         zoom_factor = (double)TPDFV_MIN(width / page_width,
239                                         height / page_height);
240                 }
241                 else {
242                         assert(0);
243                 }
244         }
245         cairo_scale(cr, zoom_factor, zoom_factor);
247         /* centered position / scrolling */
248         x = (double)(width - page_width * zoom_factor) / 2.0 / zoom_factor;
249         y = (double)(height - page_height * zoom_factor) / 2.0 / zoom_factor;
251         cairo_translate(cr, x + pdf->delta_x, y + pdf->delta_y);
253         poppler_page_render(pdf->current_page, cr);
255         cairo_destroy(cr);
256         return FALSE;
257 } /* gtk_tpdfv_expose */
259 static void
260 gtk_tpdfv_destroy(GtkObject *obj)
262         GtkTPDFV    *tpdfv = GTK_TPDFV(obj);
263         gtk_tpdfv_t *pdf;
265         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
266         tpdfv_clean(pdf);
268         GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
269 } /* gtk_tpdfv_destroy */
271 static void
272 gtk_tpdfv_class_init(GtkTPDFVClass *class)
274         GObjectClass   *goclass;
275         GtkObjectClass *oclass;
276         GtkWidgetClass *wclass;
278         goclass = G_OBJECT_CLASS(class);
279         g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
281         oclass = GTK_OBJECT_CLASS(class);
282         oclass->destroy = gtk_tpdfv_destroy;
284         wclass = GTK_WIDGET_CLASS(class);
285         wclass->expose_event = gtk_tpdfv_expose;
286 } /* gtk_tpdfv_class_init */
288 static void
289 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
291         /* nothing to do for now */
292 } /* gtk_tpdfv_init */
294 /*
295  * Public API.
296  */
298 GtkWidget *
299 gtk_tpdfv_new(const char *filename)
301         GtkTPDFV    *tpdfv;
302         gtk_tpdfv_t *pdf;
304         tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
305         pdf   = GTK_TPDFV_GET_PRIVATE(tpdfv);
307         if (TRUE != tpdfv_init(pdf, filename))
308                 return NULL;
309         return GTK_WIDGET(tpdfv);
310 } /* gtk_tpdfv_new */
312 void
313 gtk_tpdfv_reload(GtkWidget *widget)
315         gtk_tpdfv_t *pdf;
317         pdf = GTK_TPDFV_GET_PRIVATE(widget);
318         tpdfv_close(pdf);
319         tpdfv_open(pdf);
320         do_redraw(widget);
321 } /* gtk_tpdfv_reload */
323 void
324 gtk_tpdfv_page_up(GtkWidget *widget)
326         gtk_tpdfv_t *pdf;
328         pdf = GTK_TPDFV_GET_PRIVATE(widget);
330         if (pdf->current_page_no)
331                 --pdf->current_page_no;
332         do_redraw(widget);
333 } /* gtk_tpdfv_page_up */
335 void
336 gtk_tpdfv_page_down(GtkWidget *widget)
338         gtk_tpdfv_t *pdf;
340         pdf = GTK_TPDFV_GET_PRIVATE(widget);
342         if (pdf->current_page_no < pdf->total_pages - 1)
343                 ++pdf->current_page_no;
344         do_redraw(widget);
345 } /* gtk_tpdfv_page_down */
347 void
348 gtk_tpdfv_first_page(GtkWidget *widget)
350         gtk_tpdfv_t *pdf;
352         pdf = GTK_TPDFV_GET_PRIVATE(widget);
353         pdf->current_page_no = 0;
354         do_redraw(widget);
355 } /* gtk_tpdfv_first_page */
357 void
358 gtk_tpdfv_last_page(GtkWidget *widget)
360         gtk_tpdfv_t *pdf;
362         pdf = GTK_TPDFV_GET_PRIVATE(widget);
363         pdf->current_page_no = pdf->total_pages - 1;
364         do_redraw(widget);
365 } /* gtk_tpdfv_last_page */
367 void
368 gtk_tpdfv_zoom_in(GtkWidget *widget)
370         gtk_tpdfv_t *pdf;
372         pdf = GTK_TPDFV_GET_PRIVATE(widget);
373         pdf->zoom_factor *= 1.2;
374         pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
375         do_redraw(widget);
376 } /* gtk_tpdfv_zoom_in */
378 void
379 gtk_tpdfv_zoom_out(GtkWidget *widget)
381         gtk_tpdfv_t *pdf;
383         pdf = GTK_TPDFV_GET_PRIVATE(widget);
385         if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
386                 pdf->zoom_factor /= 1.2;
387                 pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
388                 do_redraw(widget);
389         }
390 } /* gtk_tpdfv_zoom_out */
392 void
393 gtk_tpdfv_zoom_1(GtkWidget *widget)
395         gtk_tpdfv_t *pdf;
397         pdf = GTK_TPDFV_GET_PRIVATE(widget);
398         pdf->zoom_factor = 1.0;
399         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
400         do_redraw(widget);
401 } /* gtk_tpdfv_zoom_1 */
403 void
404 gtk_tpdfv_zoom_width(GtkWidget *widget)
406         gtk_tpdfv_t *pdf;
408         pdf = GTK_TPDFV_GET_PRIVATE(widget);
409         pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
410         do_redraw(widget);
411 } /* gtk_tpdfv_zoom_width */
413 void
414 gtk_tpdfv_zoom_height(GtkWidget *widget)
416         gtk_tpdfv_t *pdf;
418         pdf = GTK_TPDFV_GET_PRIVATE(widget);
419         pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
420         do_redraw(widget);
421 } /* gtk_tpdfv_zoom_width */
423 void
424 gtk_tpdfv_zoom_fit(GtkWidget *widget)
426         gtk_tpdfv_t *pdf;
428         pdf = GTK_TPDFV_GET_PRIVATE(widget);
429         pdf->zoom_mode = TPDFV_ZOOM_FIT;
430         do_redraw(widget);
431 } /* gtk_tpdfv_zoom_width */
433 void
434 gtk_tpdfv_scroll_up(GtkWidget *widget)
436         gtk_tpdfv_t *pdf;
438         pdf = GTK_TPDFV_GET_PRIVATE(widget);
439         pdf->delta_y += 10.0;
440         do_redraw(widget);
441 } /* gtk_tpdfv_scroll_up */
443 void
444 gtk_tpdfv_scroll_down(GtkWidget *widget)
446         gtk_tpdfv_t *pdf;
448         pdf = GTK_TPDFV_GET_PRIVATE(widget);
449         pdf->delta_y -= 10.0;
450         do_redraw(widget);
451 } /* gtk_tpdfv_scroll_down */
453 void
454 gtk_tpdfv_scroll_left(GtkWidget *widget)
456         gtk_tpdfv_t *pdf;
458         pdf = GTK_TPDFV_GET_PRIVATE(widget);
459         pdf->delta_x += 10.0;
460         do_redraw(widget);
461 } /* gtk_tpdfv_scroll_left */
463 void
464 gtk_tpdfv_scroll_right(GtkWidget *widget)
466         gtk_tpdfv_t *pdf;
468         pdf = GTK_TPDFV_GET_PRIVATE(widget);
469         pdf->delta_x -= 10.0;
470         do_redraw(widget);
471 } /* gtk_tpdfv_scroll_right */
473 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */