f83cb6e524b7a5b247928c2b0a5f1e56867df0e5
[raktpdf.git] / rakt-window.c
1 /*
2  * Copyright (C) 2007 Tilman Sauerbeck (tilman at code-monkey de)
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <gtk/gtk.h>
25 #include <poppler.h>
26
27 #include <limits.h>
28 #include <stdlib.h>
29 #include <stdbool.h>
30
31 #include "rakt-window.h"
32
33 typedef struct {
34         GtkWidget *content_vbox;
35
36         /* Menu & toolbar */
37         GtkUIManager *ui_manager;
38         GtkAction *action_prev;
39         GtkAction *action_next;
40
41         GtkAction *action_zoom_in;
42         GtkAction *action_zoom_out;
43         GtkAction *action_zoom_100;
44
45         GtkWidget *drawing_area;
46         PopplerDocument *document;
47         PopplerPage *page;
48         gint page_no;
49         gdouble scale;
50 } RaktWindowPriv;
51
52 static void window_finalize (GObject *object);
53
54 static void on_add_widget (GtkUIManager *merge, GtkWidget *widget, RaktWindow *window);
55
56 static void on_action_quit (GtkAction *action, RaktWindow *window);
57 static void on_action_open (GtkAction *action, RaktWindow *window);
58 static void on_action_go_next (GtkAction *action, RaktWindow *window);
59 static void on_action_go_previous (GtkAction *action, RaktWindow *window);
60 static void on_action_zoom (GtkAction *action, RaktWindow *window);
61 static void on_action_about (GtkAction *action, RaktWindow *window);
62
63 static const GtkActionEntry action_entries[] = {
64         {
65                 "FileMenu", NULL, "_File", NULL, NULL, NULL
66         },
67         {
68                 "GoMenu", NULL, "_Go", NULL, NULL, NULL
69         },
70         {
71                 "ViewMenu", NULL, "_View", NULL, NULL, NULL
72         },
73         {
74                 "HelpMenu", NULL, "_Help", NULL, NULL, NULL
75         },
76         {
77                 "Open", GTK_STOCK_OPEN, "_Open",
78                 "<control>O", "Open a PDF file",
79                 G_CALLBACK (on_action_open)
80         },
81         {
82                 "Next", GTK_STOCK_GO_FORWARD, "_Next",
83                 "<control>n", "Next",
84                 G_CALLBACK (on_action_go_next)
85         },
86         {
87                 "Previous", GTK_STOCK_GO_BACK, "_Previous",
88                 "<control>p", "Previous",
89                 G_CALLBACK (on_action_go_previous)
90         },
91         {
92                 "Zoom In", GTK_STOCK_ZOOM_IN, "Zoom _In",
93                 "<control>plus", "Zoom In",
94                 G_CALLBACK (on_action_zoom)
95         },
96         {
97                 "Zoom Out", GTK_STOCK_ZOOM_OUT, "Zoom _Out",
98                 "<control>minus", "Zoom Out",
99                 G_CALLBACK (on_action_zoom)
100         },
101         {
102                 "Zoom 100", GTK_STOCK_ZOOM_100, "Zoom 100%",
103                 NULL, "Zoom 100%",
104                 G_CALLBACK (on_action_zoom)
105         },
106         {
107                 "Quit", GTK_STOCK_QUIT, "_Quit",
108                 "<control>Q", "Quit the application",
109                 G_CALLBACK (on_action_quit)
110         },
111         {
112                 "About", GTK_STOCK_ABOUT, "_About",
113                 NULL, "About this application",
114                 G_CALLBACK (on_action_about)
115         }
116 };
117
118 static const gchar *ui_layout =
119         "<ui>"
120         "       <menubar name='MenuBar'>"
121         "               <menu action='FileMenu'>"
122         "                       <menuitem action='Open'/>"
123         "                       <separator action='Sep1'/>"
124         "                       <menuitem action='Quit'/>"
125         "               </menu>"
126         "               <menu action='GoMenu'>"
127         "                       <menuitem action='Previous'/>"
128         "                       <menuitem action='Next'/>"
129         "               </menu>"
130         "               <menu action='ViewMenu'>"
131         "                       <menuitem action='Zoom In'/>"
132         "                       <menuitem action='Zoom Out'/>"
133         "                       <menuitem action='Zoom 100'/>"
134         "               </menu>"
135         "               <menu action='HelpMenu'>"
136         "                       <menuitem action='About'/>"
137         "               </menu>"
138         "       </menubar>"
139         "       <toolbar name='ToolBar'>"
140         "               <toolitem action='Open'/>"
141         "               <toolitem action='Previous'/>"
142         "               <toolitem action='Next'/>"
143         "               <toolitem action='Zoom In'/>"
144         "               <toolitem action='Zoom Out'/>"
145         "               <toolitem action='Zoom 100'/>"
146         "       </toolbar>"
147         "</ui>";
148
149 G_DEFINE_TYPE (RaktWindow, rakt_window, GTK_TYPE_WINDOW)
150
151 #define GET_PRIV(obj) \
152         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RAKT_TYPE_WINDOW, RaktWindowPriv))
153
154 static void
155 rakt_window_class_init (RaktWindowClass *class)
156 {
157         GObjectClass *object_class = G_OBJECT_CLASS (class);
158
159         object_class->finalize = window_finalize;
160
161         g_type_class_add_private (object_class, sizeof (RaktWindowPriv));
162 }
163
164 static void
165 render_page (RaktWindow *window)
166 {
167         RaktWindowPriv *priv;
168         double tmpw, tmph;
169         int width, height, n_pages;
170
171         priv = GET_PRIV (window);
172
173         n_pages = poppler_document_get_n_pages (priv->document);
174         gtk_action_set_sensitive (priv->action_prev, priv->page_no > 0);
175         gtk_action_set_sensitive (priv->action_next, priv->page_no < n_pages - 1);
176
177         gtk_action_set_sensitive (priv->action_zoom_in, priv->scale < 3.0);
178         gtk_action_set_sensitive (priv->action_zoom_out, priv->scale > 0.4);
179         gtk_action_set_sensitive (priv->action_zoom_100, priv->scale != 1.0);
180
181         priv->page = poppler_document_get_page (priv->document, priv->page_no);
182
183         poppler_page_get_size (priv->page, &tmpw, &tmph);
184
185         width = (int) (tmpw * priv->scale + 0.5);
186         height = (int) (tmph * priv->scale + 0.5);
187
188         gtk_widget_set_size_request (priv->drawing_area, width, height);
189         gtk_widget_queue_draw_area (priv->drawing_area, 0, 0, width, height);
190 }
191
192 static void
193 open_document (RaktWindow *window, gchar *uri)
194 {
195         RaktWindowPriv *priv;
196         GError *error = NULL;
197
198         priv = GET_PRIV (window);
199
200         priv->document = poppler_document_new_from_file (uri, NULL, &error);
201
202         priv->page_no = 0;
203         render_page (window);
204 }
205
206 static void
207 window_create_menu (RaktWindow *window)
208 {
209         RaktWindowPriv *priv;
210         GtkActionGroup *action_group;
211         GtkAccelGroup *accel_group;
212         GError *error = NULL;
213
214         priv = GET_PRIV (window);
215
216         priv->ui_manager = gtk_ui_manager_new ();
217
218         g_signal_connect (priv->ui_manager,
219                           "add_widget",
220                           G_CALLBACK (on_add_widget),
221                           window);
222
223         action_group = gtk_action_group_new ("Actions");
224         gtk_action_group_add_actions (action_group, action_entries,
225                                       G_N_ELEMENTS (action_entries), window);
226         gtk_ui_manager_insert_action_group (priv->ui_manager, action_group, 0);
227
228         accel_group = gtk_ui_manager_get_accel_group (priv->ui_manager);
229         gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
230
231         g_object_unref (action_group);
232
233         gtk_ui_manager_add_ui_from_string (priv->ui_manager, ui_layout, -1, &error);
234
235         if (error)
236                 g_error ("Couldn't create UI: %s\n", error->message);
237
238         gtk_ui_manager_ensure_update (priv->ui_manager);
239
240         priv->action_prev = gtk_ui_manager_get_action (priv->ui_manager,
241                                                        "ui/ToolBar/Previous");
242         priv->action_next = gtk_ui_manager_get_action (priv->ui_manager,
243                                                        "ui/ToolBar/Next");
244         priv->action_zoom_in = gtk_ui_manager_get_action (priv->ui_manager,
245                                                           "ui/ToolBar/Zoom In");
246         priv->action_zoom_out = gtk_ui_manager_get_action (priv->ui_manager,
247                                                            "ui/ToolBar/Zoom Out");
248         priv->action_zoom_100 = gtk_ui_manager_get_action (priv->ui_manager,
249                                                            "ui/ToolBar/Zoom 100");
250
251         gtk_action_set_sensitive (priv->action_prev, false);
252         gtk_action_set_sensitive (priv->action_next, false);
253
254         gtk_action_set_sensitive (priv->action_zoom_in, false);
255         gtk_action_set_sensitive (priv->action_zoom_out, false);
256         gtk_action_set_sensitive (priv->action_zoom_100, false);
257 }
258
259 static gboolean
260 on_expose_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
261 {
262         RaktWindowPriv *priv;
263         cairo_t *cr;
264
265         priv = GET_PRIV (user_data);
266
267         cr = gdk_cairo_create (priv->drawing_area->window);
268
269         cairo_set_source_rgb (cr, 255, 255, 255);
270         cairo_paint (cr);
271
272         cairo_scale (cr, priv->scale, priv->scale);
273
274         if (priv->page)
275                 poppler_page_render (priv->page, cr);
276
277         cairo_destroy (cr);
278
279         return FALSE;
280 }
281
282 static gboolean
283 on_delete_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
284 {
285         gtk_main_quit ();
286
287         return TRUE;
288 }
289
290 static void
291 rakt_window_init (RaktWindow *window)
292 {
293         RaktWindowPriv *priv;
294         GtkWidget *scrolled;
295
296         priv = GET_PRIV (window);
297
298         priv->scale = 1.0;
299
300         gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
301         gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
302
303         priv->content_vbox = gtk_vbox_new (FALSE, 0);
304         gtk_widget_show (priv->content_vbox);
305         gtk_container_add (GTK_CONTAINER (window), priv->content_vbox);
306
307         window_create_menu (window);
308
309         priv->drawing_area = gtk_drawing_area_new ();
310         gtk_widget_show (priv->drawing_area);
311
312         g_signal_connect (priv->drawing_area, "expose-event",
313                           G_CALLBACK (on_expose_event), window);
314
315         scrolled = gtk_scrolled_window_new (NULL, NULL);
316
317         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
318                                         GTK_POLICY_AUTOMATIC,
319                                         GTK_POLICY_AUTOMATIC);
320
321         gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
322                                                priv->drawing_area);
323         gtk_widget_show (scrolled);
324
325         gtk_box_pack_start (GTK_BOX (priv->content_vbox), scrolled,
326                             TRUE, TRUE, 0);
327
328         g_signal_connect (GTK_WINDOW (window), "delete-event",
329                           G_CALLBACK (on_delete_event), NULL);
330 }
331
332 static void
333 window_finalize (GObject *object)
334 {
335         RaktWindowPriv *priv;
336
337         priv = GET_PRIV (object);
338
339         g_object_unref (priv->ui_manager);
340
341         G_OBJECT_CLASS (rakt_window_parent_class)->finalize (object);
342 }
343
344 static void
345 on_add_widget (GtkUIManager *merge, GtkWidget *widget, RaktWindow *window)
346 {
347         RaktWindowPriv *priv;
348
349         priv = GET_PRIV (window);
350
351         gtk_box_pack_start (GTK_BOX (priv->content_vbox), widget, FALSE, FALSE, 0);
352 }
353
354 static void
355 on_action_quit (GtkAction *action, RaktWindow *window)
356 {
357         gtk_main_quit ();
358 }
359
360 static void
361 on_action_open (GtkAction *action, RaktWindow *window)
362 {
363         GtkWidget *dialog;
364         GtkFileFilter *filter;
365         gint n;
366
367         dialog = gtk_file_chooser_dialog_new ("Open PDF",
368                                               GTK_WINDOW (window),
369                                               GTK_FILE_CHOOSER_ACTION_OPEN,
370                                               GTK_STOCK_OPEN,
371                                               GTK_RESPONSE_ACCEPT,
372                                               GTK_STOCK_CANCEL,
373                                               GTK_RESPONSE_CANCEL, NULL);
374
375         filter = gtk_file_filter_new ();
376         gtk_file_filter_set_name (filter, "PDF files");
377         gtk_file_filter_add_pattern (filter, "*.pdf");
378
379         gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
380
381         /* now run the dialog */
382         n = gtk_dialog_run (GTK_DIALOG (dialog));
383
384         if (n == GTK_RESPONSE_ACCEPT) {
385                 gchar *uri;
386
387                 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
388                 open_document (window, uri);
389                 g_free (uri);
390         }
391
392         gtk_widget_destroy (dialog);
393 }
394
395 static void
396 on_action_go_next (GtkAction *action, RaktWindow *window)
397 {
398         RaktWindowPriv *priv;
399
400         priv = GET_PRIV (window);
401
402         priv->page_no++;
403         render_page (window);
404 }
405
406 static void
407 on_action_go_previous (GtkAction *action, RaktWindow *window)
408 {
409         RaktWindowPriv *priv;
410
411         priv = GET_PRIV (window);
412
413         priv->page_no--;
414         render_page (window);
415 }
416
417 static void
418 on_action_zoom (GtkAction *action, RaktWindow *window)
419 {
420         RaktWindowPriv *priv;
421
422         priv = GET_PRIV (window);
423
424         if (action == priv->action_zoom_in)
425                 priv->scale += 0.2;
426         else if (action == priv->action_zoom_out)
427                 priv->scale -= 0.2;
428         else
429                 priv->scale = 1.0;
430
431         render_page (window);
432 }
433
434 static void
435 on_action_about (GtkAction *action, RaktWindow *window)
436 {
437         const gchar *authors[] = {
438                 "Tilman Sauerbeck",
439                 NULL
440         };
441
442         gtk_show_about_dialog (GTK_WINDOW (window),
443                                "name", PACKAGE_NAME,
444                                "copyright", "Copyright (c) 2007 Tilman Sauerbeck",
445                                "version", VERSION,
446                                "authors", authors,
447                                NULL);
448 }
449
450 GtkWidget *
451 rakt_window_new (void)
452 {
453         return g_object_new (RAKT_TYPE_WINDOW, NULL);
454 }