]> git.donarmstrong.com Git - xournal.git/blob - src/xo-file.c
Release 0.3.3 (minor enhancements)
[xournal.git] / src / xo-file.c
1 #ifdef HAVE_CONFIG_H
2 #  include <config.h>
3 #endif
4
5 #include <signal.h>
6 #include <memory.h>
7 #include <string.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <gtk/gtk.h>
11 #include <libgnomecanvas/libgnomecanvas.h>
12 #include <zlib.h>
13 #include <math.h>
14 #include <gdk/gdkx.h>
15 #include <X11/Xlib.h>
16 #include <locale.h>
17
18 #include "xournal.h"
19 #include "xo-interface.h"
20 #include "xo-support.h"
21 #include "xo-callbacks.h"
22 #include "xo-misc.h"
23 #include "xo-file.h"
24
25 const char *tool_names[NUM_TOOLS] = {"pen", "eraser", "highlighter", "", "", "selectrect", "vertspace", "hand"};
26 const char *color_names[COLOR_MAX] = {"black", "blue", "red", "green",
27    "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white"};
28 const char *bgtype_names[3] = {"solid", "pixmap", "pdf"};
29 const char *bgcolor_names[COLOR_MAX] = {"", "blue", "pink", "green",
30    "", "", "", "", "orange", "yellow", "white"};
31 const char *bgstyle_names[4] = {"plain", "lined", "ruled", "graph"};
32 const char *file_domain_names[3] = {"absolute", "attach", "clone"};
33 const char *unit_names[4] = {"cm", "in", "px", "pt"};
34 int PDFTOPPM_PRINTING_DPI, GS_BITMAP_DPI;
35
36 // creates a new empty journal
37
38 void new_journal(void)
39 {
40   journal.npages = 1;
41   journal.pages = g_list_append(NULL, new_page(&ui.default_page));
42   journal.last_attach_no = 0;
43   ui.pageno = 0;
44   ui.layerno = 0;
45   ui.cur_page = (struct Page *) journal.pages->data;
46   ui.cur_layer = (struct Layer *) ui.cur_page->layers->data;
47   ui.saved = TRUE;
48   ui.filename = NULL;
49   update_file_name(NULL);
50 }
51
52 // check attachment names
53
54 void chk_attach_names(void)
55 {
56   GList *list;
57   struct Background *bg;
58   
59   for (list = journal.pages; list!=NULL; list = list->next) {
60     bg = ((struct Page *)list->data)->bg;
61     if (bg->type == BG_SOLID || bg->file_domain != DOMAIN_ATTACH ||
62         bg->filename->s != NULL) continue;
63     bg->filename->s = g_strdup_printf("bg_%d.png", ++journal.last_attach_no);
64   }
65 }
66
67 // saves the journal to a file: returns true on success, false on error
68
69 gboolean save_journal(const char *filename)
70 {
71   gzFile f;
72   struct Page *pg, *tmppg;
73   struct Layer *layer;
74   struct Item *item;
75   int i, is_clone;
76   char *tmpfn;
77   gchar *pdfbuf;
78   gsize pdflen;
79   gboolean success;
80   FILE *tmpf;
81   GList *pagelist, *layerlist, *itemlist, *list;
82   GtkWidget *dialog;
83   
84   f = gzopen(filename, "w");
85   if (f==NULL) return FALSE;
86   chk_attach_names();
87
88   setlocale(LC_NUMERIC, "C");
89   
90   gzprintf(f, "<?xml version=\"1.0\" standalone=\"no\"?>\n"
91      "<title>Xournal document - see http://math.mit.edu/~auroux/software/xournal/</title>\n"
92      "<xournal version=\"" VERSION "\"/>\n");
93   for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next) {
94     pg = (struct Page *)pagelist->data;
95     gzprintf(f, "<page width=\"%.2f\" height=\"%.2f\">\n", pg->width, pg->height);
96     gzprintf(f, "<background type=\"%s\" ", bgtype_names[pg->bg->type]); 
97     if (pg->bg->type == BG_SOLID) {
98       gzputs(f, "color=\"");
99       if (pg->bg->color_no >= 0) gzputs(f, bgcolor_names[pg->bg->color_no]);
100       else gzprintf(f, "#%08x", pg->bg->color_rgba);
101       gzprintf(f, "\" style=\"%s\" ", bgstyle_names[pg->bg->ruling]);
102     }
103     else if (pg->bg->type == BG_PIXMAP) {
104       is_clone = -1;
105       for (list = journal.pages, i = 0; list!=pagelist; list = list->next, i++) {
106         tmppg = (struct Page *)list->data;
107         if (tmppg->bg->type == BG_PIXMAP && 
108             tmppg->bg->pixbuf == pg->bg->pixbuf &&
109             tmppg->bg->filename == pg->bg->filename)
110           { is_clone = i; break; }
111       }
112       if (is_clone >= 0)
113         gzprintf(f, "domain=\"clone\" filename=\"%d\" ", is_clone);
114       else {
115         if (pg->bg->file_domain == DOMAIN_ATTACH) {
116           tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
117           if (!gdk_pixbuf_save(pg->bg->pixbuf, tmpfn, "png", NULL, NULL)) {
118             dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
119               GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, 
120               "Could not write background '%s'. Continuing anyway.", tmpfn);
121             gtk_dialog_run(GTK_DIALOG(dialog));
122             gtk_widget_destroy(dialog);
123           }
124           g_free(tmpfn);
125         }
126         gzprintf(f, "domain=\"%s\" filename=\"%s\" ", 
127           file_domain_names[pg->bg->file_domain], pg->bg->filename->s);
128       }
129     }
130     else if (pg->bg->type == BG_PDF) {
131       is_clone = 0;
132       for (list = journal.pages; list!=pagelist; list = list->next) {
133         tmppg = (struct Page *)list->data;
134         if (tmppg->bg->type == BG_PDF) { is_clone = 1; break; }
135       }
136       if (!is_clone) {
137         if (pg->bg->file_domain == DOMAIN_ATTACH) {
138           tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
139           success = FALSE;
140           if (bgpdf.status != STATUS_NOT_INIT &&
141               g_file_get_contents(bgpdf.tmpfile_copy, &pdfbuf, &pdflen, NULL))
142           {
143             tmpf = fopen(tmpfn, "w");
144             if (tmpf != NULL && fwrite(pdfbuf, 1, pdflen, tmpf) == pdflen)
145               success = TRUE;
146             g_free(pdfbuf);
147             fclose(tmpf);
148           }
149           if (!success) {
150             dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
151               GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, 
152               "Could not write background '%s'. Continuing anyway.", tmpfn);
153             gtk_dialog_run(GTK_DIALOG(dialog));
154             gtk_widget_destroy(dialog);
155           }
156           g_free(tmpfn);
157         }
158         gzprintf(f, "domain=\"%s\" filename=\"%s\" ", 
159           file_domain_names[pg->bg->file_domain], pg->bg->filename->s);
160       }
161       gzprintf(f, "pageno=\"%d\" ", pg->bg->file_page_seq);
162     }
163     gzprintf(f, "/>\n");
164     for (layerlist = pg->layers; layerlist!=NULL; layerlist = layerlist->next) {
165       layer = (struct Layer *)layerlist->data;
166       gzprintf(f, "<layer>\n");
167       for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
168         item = (struct Item *)itemlist->data;
169         if (item->type == ITEM_STROKE) {
170           gzprintf(f, "<stroke tool=\"%s\" color=\"", 
171                           tool_names[item->brush.tool_type]);
172           if (item->brush.color_no >= 0)
173             gzputs(f, color_names[item->brush.color_no]);
174           else
175             gzprintf(f, "#%08x", item->brush.color_rgba);
176           gzprintf(f, "\" width=\"%.2f\">\n", item->brush.thickness);
177           for (i=0;i<2*item->path->num_points;i++)
178             gzprintf(f, "%.2f ", item->path->coords[i]);
179           gzprintf(f, "\n</stroke>\n");
180         }
181       }
182       gzprintf(f, "</layer>\n");
183     }
184     gzprintf(f, "</page>\n");
185   }
186   gzclose(f);
187   setlocale(LC_NUMERIC, "");
188
189   return TRUE;
190 }
191
192 // closes a journal: returns true on success, false on abort
193
194 gboolean close_journal(void)
195 {
196   if (!ok_to_close()) return FALSE;
197   
198   // free everything...
199   reset_selection();
200   clear_redo_stack();
201   clear_undo_stack();
202
203   shutdown_bgpdf();
204   delete_journal(&journal);
205   
206   return TRUE;
207   /* note: various members of ui and journal are now in invalid states,
208      use new_journal() to reinitialize them */
209 }
210
211 // sanitize a string containing floats, in case it may have , instead of .
212
213 void cleanup_numeric(char *s)
214 {
215   while (*s!=0) { if (*s==',') *s='.'; s++; }
216 }
217
218 // the XML parser functions for open_journal()
219
220 struct Journal tmpJournal;
221 struct Page *tmpPage;
222 struct Layer *tmpLayer;
223 struct Item *tmpItem;
224 char *tmpFilename;
225 struct Background *tmpBg_pdf;
226
227 GError *xoj_invalid(void)
228 {
229   return g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Invalid file contents");
230 }
231
232 void xoj_parser_start_element(GMarkupParseContext *context,
233    const gchar *element_name, const gchar **attribute_names, 
234    const gchar **attribute_values, gpointer user_data, GError **error)
235 {
236   int has_attr, i;
237   char *ptr;
238   struct Background *tmpbg;
239   char *tmpbg_filename;
240   GtkWidget *dialog;
241   
242   if (!strcmp(element_name, "title") || !strcmp(element_name, "xournal")) {
243     if (tmpPage != NULL) {
244       *error = xoj_invalid();
245       return;
246     }
247     // nothing special to do
248   }
249   else if (!strcmp(element_name, "page")) { // start of a page
250     if (tmpPage != NULL) {
251       *error = xoj_invalid();
252       return;
253     }
254     tmpPage = (struct Page *)g_malloc(sizeof(struct Page));
255     tmpPage->layers = NULL;
256     tmpPage->nlayers = 0;
257     tmpPage->group = NULL;
258     tmpPage->bg = g_new(struct Background, 1);
259     tmpPage->bg->type = -1;
260     tmpPage->bg->canvas_item = NULL;
261     tmpPage->bg->pixbuf = NULL;
262     tmpPage->bg->filename = NULL;
263     tmpJournal.pages = g_list_append(tmpJournal.pages, tmpPage);
264     tmpJournal.npages++;
265     // scan for height and width attributes
266     has_attr = 0;
267     while (*attribute_names!=NULL) {
268       if (!strcmp(*attribute_names, "width")) {
269         if (has_attr & 1) *error = xoj_invalid();
270         cleanup_numeric((gchar *)*attribute_values);
271         tmpPage->width = g_ascii_strtod(*attribute_values, &ptr);
272         if (ptr == *attribute_values) *error = xoj_invalid();
273         has_attr |= 1;
274       }
275       else if (!strcmp(*attribute_names, "height")) {
276         if (has_attr & 2) *error = xoj_invalid();
277         cleanup_numeric((gchar *)*attribute_values);
278         tmpPage->height = g_ascii_strtod(*attribute_values, &ptr);
279         if (ptr == *attribute_values) *error = xoj_invalid();
280         has_attr |= 2;
281       }
282       else *error = xoj_invalid();
283       attribute_names++;
284       attribute_values++;
285     }
286     if (has_attr!=3) *error = xoj_invalid();
287   }
288   else if (!strcmp(element_name, "background")) {
289     if (tmpPage == NULL || tmpLayer !=NULL || tmpPage->bg->type >= 0) {
290       *error = xoj_invalid();
291       return;
292     }
293     has_attr = 0;
294     while (*attribute_names!=NULL) {
295       if (!strcmp(*attribute_names, "type")) {
296         if (has_attr) *error = xoj_invalid();
297         for (i=0; i<3; i++)
298           if (!strcmp(*attribute_values, bgtype_names[i]))
299             tmpPage->bg->type = i;
300         if (tmpPage->bg->type < 0) *error = xoj_invalid();
301         has_attr |= 1;
302         if (tmpPage->bg->type == BG_PDF) {
303           if (tmpBg_pdf == NULL) tmpBg_pdf = tmpPage->bg;
304           else {
305             has_attr |= 24;
306             tmpPage->bg->filename = refstring_ref(tmpBg_pdf->filename);
307             tmpPage->bg->file_domain = tmpBg_pdf->file_domain;
308           }
309         }
310       }
311       else if (!strcmp(*attribute_names, "color")) {
312         if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
313         if (has_attr & 2) *error = xoj_invalid();
314         tmpPage->bg->color_no = COLOR_OTHER;
315         for (i=0; i<COLOR_MAX; i++)
316           if (!strcmp(*attribute_values, bgcolor_names[i])) {
317             tmpPage->bg->color_no = i;
318             tmpPage->bg->color_rgba = predef_bgcolors_rgba[i];
319           }
320         // there's also the case of hex (#rrggbbaa) colors
321         if (tmpPage->bg->color_no == COLOR_OTHER && **attribute_values == '#') {
322           tmpPage->bg->color_rgba = strtol(*attribute_values + 1, &ptr, 16);
323           if (*ptr!=0) *error = xoj_invalid();
324         }
325         has_attr |= 2;
326       }
327       else if (!strcmp(*attribute_names, "style")) {
328         if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
329         if (has_attr & 4) *error = xoj_invalid();
330         tmpPage->bg->ruling = -1;
331         for (i=0; i<4; i++)
332           if (!strcmp(*attribute_values, bgstyle_names[i]))
333             tmpPage->bg->ruling = i;
334         if (tmpPage->bg->ruling < 0) *error = xoj_invalid();
335         has_attr |= 4;
336       }
337       else if (!strcmp(*attribute_names, "domain")) {
338         if (tmpPage->bg->type <= BG_SOLID || (has_attr & 8))
339           { *error = xoj_invalid(); return; }
340         tmpPage->bg->file_domain = -1;
341         for (i=0; i<3; i++)
342           if (!strcmp(*attribute_values, file_domain_names[i]))
343             tmpPage->bg->file_domain = i;
344         if (tmpPage->bg->file_domain < 0)
345           { *error = xoj_invalid(); return; }
346         has_attr |= 8;
347       }
348       else if (!strcmp(*attribute_names, "filename")) {
349         if (tmpPage->bg->type <= BG_SOLID || (has_attr != 9)) 
350           { *error = xoj_invalid(); return; }
351         if (tmpPage->bg->file_domain == DOMAIN_CLONE) {
352           // filename is a page number
353           i = strtol(*attribute_values, &ptr, 10);
354           if (ptr == *attribute_values || i < 0 || i > tmpJournal.npages-2)
355             { *error = xoj_invalid(); return; }
356           tmpbg = ((struct Page *)g_list_nth_data(tmpJournal.pages, i))->bg;
357           if (tmpbg->type != tmpPage->bg->type)
358             { *error = xoj_invalid(); return; }
359           tmpPage->bg->filename = refstring_ref(tmpbg->filename);
360           tmpPage->bg->pixbuf = tmpbg->pixbuf;
361           if (tmpbg->pixbuf!=NULL) gdk_pixbuf_ref(tmpbg->pixbuf);
362           tmpPage->bg->file_domain = tmpbg->file_domain;
363         }
364         else {
365           tmpPage->bg->filename = new_refstring(*attribute_values);
366           if (tmpPage->bg->type == BG_PIXMAP) {
367             if (tmpPage->bg->file_domain == DOMAIN_ATTACH) {
368               tmpbg_filename = g_strdup_printf("%s.%s", tmpFilename, *attribute_values);
369               if (sscanf(*attribute_values, "bg_%d.png", &i) == 1)
370                 if (i > tmpJournal.last_attach_no) 
371                   tmpJournal.last_attach_no = i;
372             }
373             else tmpbg_filename = g_strdup(*attribute_values);
374             tmpPage->bg->pixbuf = gdk_pixbuf_new_from_file(tmpbg_filename, NULL);
375             if (tmpPage->bg->pixbuf == NULL) {
376               dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
377                 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, 
378                 "Could not open background '%s'. Setting background to white.",
379                 tmpbg_filename);
380               gtk_dialog_run(GTK_DIALOG(dialog));
381               gtk_widget_destroy(dialog);
382               tmpPage->bg->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
383               gdk_pixbuf_fill(tmpPage->bg->pixbuf, 0xffffffff); // solid white
384             }
385             g_free(tmpbg_filename);
386           }
387         }
388         has_attr |= 16;
389       }
390       else if (!strcmp(*attribute_names, "pageno")) {
391         if (tmpPage->bg->type != BG_PDF || (has_attr & 32))
392           { *error = xoj_invalid(); return; }
393         tmpPage->bg->file_page_seq = strtol(*attribute_values, &ptr, 10);
394         if (ptr == *attribute_values) *error = xoj_invalid();
395         has_attr |= 32;
396       }
397       else *error = xoj_invalid();
398       attribute_names++;
399       attribute_values++;
400     }
401     if (tmpPage->bg->type < 0) *error = xoj_invalid();
402     if (tmpPage->bg->type == BG_SOLID && has_attr != 7) *error = xoj_invalid();
403     if (tmpPage->bg->type == BG_PIXMAP && has_attr != 25) *error = xoj_invalid();
404     if (tmpPage->bg->type == BG_PDF && has_attr != 57) *error = xoj_invalid();
405   }
406   else if (!strcmp(element_name, "layer")) { // start of a layer
407     if (tmpPage == NULL || tmpLayer != NULL) {
408       *error = xoj_invalid();
409       return;
410     }
411     tmpLayer = (struct Layer *)g_malloc(sizeof(struct Layer));
412     tmpLayer->items = NULL;
413     tmpLayer->nitems = 0;
414     tmpLayer->group = NULL;
415     tmpPage->layers = g_list_append(tmpPage->layers, tmpLayer);
416     tmpPage->nlayers++;
417   }
418   else if (!strcmp(element_name, "stroke")) { // start of a stroke
419     if (tmpLayer == NULL || tmpItem != NULL) {
420       *error = xoj_invalid();
421       return;
422     }
423     tmpItem = (struct Item *)g_malloc(sizeof(struct Item));
424     tmpItem->type = ITEM_STROKE;
425     tmpItem->path = NULL;
426     tmpItem->canvas_item = NULL;
427     tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
428     tmpLayer->nitems++;
429     // scan for tool, color, and width attributes
430     has_attr = 0;
431     while (*attribute_names!=NULL) {
432       if (!strcmp(*attribute_names, "width")) {
433         if (has_attr & 1) *error = xoj_invalid();
434         cleanup_numeric((gchar *)*attribute_values);
435         tmpItem->brush.thickness = g_ascii_strtod(*attribute_values, &ptr);
436         if (ptr == *attribute_values) *error = xoj_invalid();
437         has_attr |= 1;
438       }
439       else if (!strcmp(*attribute_names, "color")) {
440         if (has_attr & 2) *error = xoj_invalid();
441         tmpItem->brush.color_no = COLOR_OTHER;
442         for (i=0; i<COLOR_MAX; i++)
443           if (!strcmp(*attribute_values, color_names[i])) {
444             tmpItem->brush.color_no = i;
445             tmpItem->brush.color_rgba = predef_colors_rgba[i];
446           }
447         // there's also the case of hex (#rrggbbaa) colors
448         if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
449           tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
450           if (*ptr!=0) *error = xoj_invalid();
451         }
452         has_attr |= 2;
453       }
454       else if (!strcmp(*attribute_names, "tool")) {
455         if (has_attr & 4) *error = xoj_invalid();
456         tmpItem->brush.tool_type = -1;
457         for (i=0; i<NUM_STROKE_TOOLS; i++)
458           if (!strcmp(*attribute_values, tool_names[i])) {
459             tmpItem->brush.tool_type = i;
460           }
461         if (tmpItem->brush.tool_type == -1) *error = xoj_invalid();
462         has_attr |= 4;
463       }
464       else *error = xoj_invalid();
465       attribute_names++;
466       attribute_values++;
467     }
468     if (has_attr!=7) *error = xoj_invalid();
469     // finish filling the brush info
470     tmpItem->brush.thickness_no = 0;  // who cares ?
471     tmpItem->brush.tool_options = 0;  // who cares ?
472     if (tmpItem->brush.tool_type == TOOL_HIGHLIGHTER) {
473       if (tmpItem->brush.color_no >= 0)
474         tmpItem->brush.color_rgba &= HILITER_ALPHA_MASK;
475     }
476   }
477 }
478
479 void xoj_parser_end_element(GMarkupParseContext *context,
480    const gchar *element_name, gpointer user_data, GError **error)
481 {
482   if (!strcmp(element_name, "page")) {
483     if (tmpPage == NULL || tmpLayer != NULL) {
484       *error = xoj_invalid();
485       return;
486     }
487     if (tmpPage->nlayers == 0 || tmpPage->bg->type < 0) *error = xoj_invalid();
488     tmpPage = NULL;
489   }
490   if (!strcmp(element_name, "layer")) {
491     if (tmpLayer == NULL || tmpItem != NULL) {
492       *error = xoj_invalid();
493       return;
494     }
495     tmpLayer = NULL;
496   }
497   if (!strcmp(element_name, "stroke")) {
498     if (tmpItem == NULL) {
499       *error = xoj_invalid();
500       return;
501     }
502     update_item_bbox(tmpItem);
503     tmpItem = NULL;
504   }
505 }
506
507 void xoj_parser_text(GMarkupParseContext *context,
508    const gchar *text, gsize text_len, gpointer user_data, GError **error)
509 {
510   const gchar *element_name, *ptr;
511   int n;
512   
513   element_name = g_markup_parse_context_get_element(context);
514   if (element_name == NULL) return;
515   if (!strcmp(element_name, "stroke")) {
516     cleanup_numeric((gchar *)text);
517     ptr = text;
518     n = 0;
519     while (text_len > 0) {
520       realloc_cur_path(n/2 + 1);
521       ui.cur_path.coords[n] = g_ascii_strtod(text, (char **)(&ptr));
522       if (ptr == text) break;
523       text_len -= (ptr - text);
524       text = ptr;
525       n++;
526     }
527     if (n<4 || n&1) { *error = xoj_invalid(); return; }
528     tmpItem->path = gnome_canvas_points_new(n/2);
529     g_memmove(tmpItem->path->coords, ui.cur_path.coords, n*sizeof(double));
530   }
531 }
532
533 gboolean user_wants_second_chance(char **filename)
534 {
535   GtkWidget *dialog;
536   GtkFileFilter *filt_all, *filt_pdf;
537   GtkResponseType response;
538
539   dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
540     GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, 
541     "Could not open background '%s'.\nSelect another file?",
542     *filename);
543   response = gtk_dialog_run(GTK_DIALOG(dialog));
544   gtk_widget_destroy(dialog);
545   if (response != GTK_RESPONSE_YES) return FALSE;
546   dialog = gtk_file_chooser_dialog_new("Open PDF", GTK_WINDOW (winMain),
547      GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
548      GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
549
550   filt_all = gtk_file_filter_new();
551   gtk_file_filter_set_name(filt_all, "All files");
552   gtk_file_filter_add_pattern(filt_all, "*");
553   filt_pdf = gtk_file_filter_new();
554   gtk_file_filter_set_name(filt_pdf, "PDF files");
555   gtk_file_filter_add_pattern(filt_pdf, "*.pdf");
556   gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_pdf);
557   gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_all);
558
559   if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) {
560     gtk_widget_destroy(dialog);
561     return FALSE;
562   }
563   g_free(*filename);
564   *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
565   gtk_widget_destroy(dialog);
566   return TRUE;    
567 }
568
569 gboolean open_journal(char *filename)
570 {
571   const GMarkupParser parser = { xoj_parser_start_element, 
572                                  xoj_parser_end_element, 
573                                  xoj_parser_text, NULL, NULL};
574   GMarkupParseContext *context;
575   GError *error;
576   GtkWidget *dialog;
577   gboolean valid;
578   gzFile f;
579   char buffer[1000];
580   int len;
581   gchar *tmpfn;
582   gboolean maybe_pdf;
583   
584   f = gzopen(filename, "r");
585   if (f==NULL) return FALSE;
586   
587   context = g_markup_parse_context_new(&parser, 0, NULL, NULL);
588   valid = TRUE;
589   tmpJournal.npages = 0;
590   tmpJournal.pages = NULL;
591   tmpJournal.last_attach_no = 0;
592   tmpPage = NULL;
593   tmpLayer = NULL;
594   tmpItem = NULL;
595   tmpFilename = filename;
596   error = NULL;
597   tmpBg_pdf = NULL;
598   maybe_pdf = TRUE;
599
600   while (valid && !gzeof(f)) {
601     len = gzread(f, buffer, 1000);
602     if (len<0) valid = FALSE;
603     if (maybe_pdf && len>=4 && !strncmp(buffer, "%PDF", 4))
604       { valid = FALSE; break; } // most likely pdf
605     else maybe_pdf = FALSE;
606     if (len<=0) break;
607     valid = g_markup_parse_context_parse(context, buffer, len, &error);
608   }
609   gzclose(f);
610   if (valid) valid = g_markup_parse_context_end_parse(context, &error);
611   if (tmpJournal.npages == 0) valid = FALSE;
612   g_markup_parse_context_free(context);
613   
614   if (!valid) {
615     delete_journal(&tmpJournal);
616     if (!maybe_pdf) return FALSE;
617     // essentially same as on_fileNewBackground from here on
618     ui.saved = TRUE;
619     close_journal();
620     while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
621     new_journal();
622     ui.zoom = ui.startup_zoom;
623     gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
624     update_page_stuff();
625     return init_bgpdf(filename, TRUE, DOMAIN_ABSOLUTE);
626   }
627   
628   ui.saved = TRUE; // force close_journal() to do its job
629   close_journal();
630   g_memmove(&journal, &tmpJournal, sizeof(struct Journal));
631   
632   // if we need to initialize a fresh pdf loader
633   if (tmpBg_pdf!=NULL) { 
634     while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
635     if (tmpBg_pdf->file_domain == DOMAIN_ATTACH)
636       tmpfn = g_strdup_printf("%s.%s", filename, tmpBg_pdf->filename->s);
637     else
638       tmpfn = g_strdup(tmpBg_pdf->filename->s);
639     valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
640     // in case the file name became invalid
641     if (!valid && tmpBg_pdf->file_domain != DOMAIN_ATTACH)
642       if (user_wants_second_chance(&tmpfn)) {
643         valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
644         if (valid) { // change the file name...
645           g_free(tmpBg_pdf->filename->s);
646           tmpBg_pdf->filename->s = g_strdup(tmpfn);
647         }
648       }
649     if (valid) {
650       refstring_unref(bgpdf.filename);
651       bgpdf.filename = refstring_ref(tmpBg_pdf->filename);
652     } else {
653       dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
654         GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open background '%s'.",
655         tmpfn);
656       gtk_dialog_run(GTK_DIALOG(dialog));
657       gtk_widget_destroy(dialog);
658     }
659     g_free(tmpfn);
660   }
661   
662   ui.pageno = 0;
663   ui.cur_page = (struct Page *)journal.pages->data;
664   ui.layerno = ui.cur_page->nlayers-1;
665   ui.cur_layer = (struct Layer *)(g_list_last(ui.cur_page->layers)->data);
666   ui.saved = TRUE;
667   ui.zoom = ui.startup_zoom;
668   update_file_name(g_strdup(filename));
669   gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
670   make_canvas_items();
671   update_page_stuff();
672   gtk_adjustment_set_value(gtk_layout_get_vadjustment(GTK_LAYOUT(canvas)), 0);
673   return TRUE;
674 }
675
676 /************ file backgrounds *************/
677
678 struct Background *attempt_load_pix_bg(char *filename, gboolean attach)
679 {
680   struct Background *bg;
681   GdkPixbuf *pix;
682   
683   pix = gdk_pixbuf_new_from_file(filename, NULL);
684   if (pix == NULL) return NULL;
685   
686   bg = g_new(struct Background, 1);
687   bg->type = BG_PIXMAP;
688   bg->canvas_item = NULL;
689   bg->pixbuf = pix;
690   bg->pixbuf_scale = DEFAULT_ZOOM;
691   if (attach) {
692     bg->filename = new_refstring(NULL);
693     bg->file_domain = DOMAIN_ATTACH;
694   } else {
695     bg->filename = new_refstring(filename);
696     bg->file_domain = DOMAIN_ABSOLUTE;
697   }
698   return bg;
699 }
700
701 #define BUFSIZE 65536 // a reasonable buffer size for reads from gs pipe
702
703 GList *attempt_load_gv_bg(char *filename)
704 {
705   struct Background *bg;
706   GList *bg_list;
707   GdkPixbuf *pix;
708   GdkPixbufLoader *loader;
709   FILE *gs_pipe, *f;
710   unsigned char *buf;
711   char *pipename;
712   int buflen, remnlen, file_pageno;
713   
714   buf = g_malloc(BUFSIZE); // a reasonable buffer size
715   f = fopen(filename, "r");
716   if (fread(buf, 1, 4, f) !=4 ||
717         (strncmp((char *)buf, "%!PS", 4) && strncmp((char *)buf, "%PDF", 4))) {
718     fclose(f);
719     g_free(buf);
720     return NULL;
721   }
722   
723   fclose(f);
724   pipename = g_strdup_printf(GS_CMDLINE, (double)GS_BITMAP_DPI, filename);
725   gs_pipe = popen(pipename, "r");
726   g_free(pipename);
727   
728   bg_list = NULL;
729   remnlen = 0;
730   file_pageno = 0;
731   loader = NULL;
732   if (gs_pipe!=NULL)
733   while (!feof(gs_pipe)) {
734     if (!remnlen) { // new page: get a BMP header ?
735       buflen = fread(buf, 1, 54, gs_pipe);
736       if (buflen < 6) buflen += fread(buf, 1, 54-buflen, gs_pipe);
737       if (buflen < 6 || buf[0]!='B' || buf[1]!='M') break; // fatal: abort
738       remnlen = (int)(buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + (buf[2]);
739       loader = gdk_pixbuf_loader_new();
740     }
741     else buflen = fread(buf, 1, (remnlen < BUFSIZE)?remnlen:BUFSIZE, gs_pipe);
742     remnlen -= buflen;
743     if (buflen == 0) break;
744     if (!gdk_pixbuf_loader_write(loader, buf, buflen, NULL)) break;
745     if (remnlen == 0) { // make a new bg
746       pix = gdk_pixbuf_loader_get_pixbuf(loader);
747       if (pix == NULL) break;
748       gdk_pixbuf_ref(pix);
749       gdk_pixbuf_loader_close(loader, NULL);
750       g_object_unref(loader);
751       loader = NULL;
752       bg = g_new(struct Background, 1);
753       bg->canvas_item = NULL;
754       bg->pixbuf = pix;
755       bg->pixbuf_scale = (GS_BITMAP_DPI/72.0);
756       bg->type = BG_PIXMAP;
757       bg->filename = new_refstring(NULL);
758       bg->file_domain = DOMAIN_ATTACH;
759       file_pageno++;
760       bg_list = g_list_append(bg_list, bg);
761     }
762   }
763   if (loader != NULL) gdk_pixbuf_loader_close(loader, NULL);
764   pclose(gs_pipe);
765   g_free(buf);
766   return bg_list;
767 }
768
769 struct Background *attempt_screenshot_bg(void)
770 {
771   struct Background *bg;
772   GdkPixbuf *pix;
773   XEvent x_event;
774   GError *error = NULL;
775   GdkWindow *window;
776   int x,y,w,h, status;
777   unsigned int tmp;
778   Window x_root, x_win;
779
780   x_root = gdk_x11_get_default_root_xwindow();
781   
782   if (!XGrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root, 
783       False, ButtonReleaseMask, GrabModeAsync, GrabModeSync, None, None))
784     return NULL;
785
786   XWindowEvent (GDK_DISPLAY(), x_root, ButtonReleaseMask, &x_event);
787   XUngrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root);
788
789   x_win = x_event.xbutton.subwindow;
790   if (x_win == None) x_win = x_root;
791
792   window = gdk_window_foreign_new_for_display(gdk_display_get_default(), x_win);
793     
794   gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
795   
796   pix = gdk_pixbuf_get_from_drawable(NULL, window,
797     gdk_colormap_get_system(), 0, 0, 0, 0, w, h);
798     
799   if (pix == NULL) return NULL;
800   
801   bg = g_new(struct Background, 1);
802   bg->type = BG_PIXMAP;
803   bg->canvas_item = NULL;
804   bg->pixbuf = pix;
805   bg->pixbuf_scale = DEFAULT_ZOOM;
806   bg->filename = new_refstring(NULL);
807   bg->file_domain = DOMAIN_ATTACH;
808   return bg;
809 }
810
811 /************** pdf annotation ***************/
812
813 /* free tmp directory */
814
815 void end_bgpdf_shutdown(void)
816 {
817   if (bgpdf.tmpdir!=NULL) {
818     if (bgpdf.tmpfile_copy!=NULL) {
819       g_unlink(bgpdf.tmpfile_copy);
820       g_free(bgpdf.tmpfile_copy);
821       bgpdf.tmpfile_copy = NULL;
822     }
823     g_rmdir(bgpdf.tmpdir);  
824     g_free(bgpdf.tmpdir);
825     bgpdf.tmpdir = NULL;
826   }
827   bgpdf.status = STATUS_NOT_INIT;
828 }
829
830 /* cancel a request */
831
832 void cancel_bgpdf_request(struct BgPdfRequest *req)
833 {
834   GList *list_link;
835   
836   list_link = g_list_find(bgpdf.requests, req);
837   if (list_link == NULL) return;
838   if (list_link->prev == NULL && bgpdf.pid > 0) {
839     // this is being processed: kill the child but don't remove the request yet
840     if (bgpdf.status == STATUS_RUNNING) bgpdf.status = STATUS_ABORTED;
841     kill(bgpdf.pid, SIGHUP);
842 //    printf("Cancelling a request - killing %d\n", bgpdf.pid);
843   }
844   else {
845     // remove the request
846     bgpdf.requests = g_list_delete_link(bgpdf.requests, list_link);
847     g_free(req);
848 //    printf("Cancelling a request - no kill needed\n");
849   }
850 }
851
852 /* sigchld callback */
853
854 void bgpdf_child_handler(GPid pid, gint status, gpointer data)
855 {
856   struct BgPdfRequest *req;
857   struct BgPdfPage *bgpg;
858   gchar *ppm_name;
859   GdkPixbuf *pixbuf;
860   
861   if (bgpdf.requests == NULL) return;
862   req = (struct BgPdfRequest *)bgpdf.requests->data;
863   
864   ppm_name = g_strdup_printf("%s/p-%06d.ppm", bgpdf.tmpdir, req->pageno);
865 //  printf("Child %d finished, should look for %s... \n", pid, ppm_name);
866   
867   if (bgpdf.status == STATUS_ABORTED || bgpdf.status == STATUS_SHUTDOWN)
868      pixbuf = NULL;
869   else
870      pixbuf = gdk_pixbuf_new_from_file(ppm_name, NULL);
871
872   unlink(ppm_name);
873   g_free(ppm_name);
874
875   if (pixbuf != NULL) { // success
876 //    printf("success\n");
877     while (req->pageno > bgpdf.npages) {
878       bgpg = g_new(struct BgPdfPage, 1);
879       bgpg->pixbuf = NULL;
880       bgpdf.pages = g_list_append(bgpdf.pages, bgpg);
881       bgpdf.npages++;
882     }
883     bgpg = g_list_nth_data(bgpdf.pages, req->pageno-1);
884     if (bgpg->pixbuf!=NULL) gdk_pixbuf_unref(bgpg->pixbuf);
885     bgpg->pixbuf = pixbuf;
886     bgpg->dpi = req->dpi;
887     if (req->initial_request && bgpdf.create_pages) {
888       bgpdf_create_page_with_bg(req->pageno, bgpg);
889       // create page n, resize it, set its bg - all without any undo effect
890     } else {
891       if (!req->is_printing) bgpdf_update_bg(req->pageno, bgpg);
892       // look for all pages with this bg, and update their bg pixmaps
893     }
894   }
895   else {
896 //    printf("failed or aborted\n");
897     bgpdf.create_pages = FALSE;
898     req->initial_request = FALSE;
899   }
900
901   bgpdf.pid = 0;
902   g_spawn_close_pid(pid);
903   
904   if (req->initial_request)
905     req->pageno++; // try for next page
906   else
907     bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
908   
909   if (bgpdf.status == STATUS_SHUTDOWN) {
910     end_bgpdf_shutdown();
911     return;
912   }
913   
914   bgpdf.status = STATUS_IDLE;
915   if (bgpdf.requests != NULL) bgpdf_spawn_child();
916 }
917
918 /* spawn a child to process the head request */
919
920 void bgpdf_spawn_child(void)
921 {
922   struct BgPdfRequest *req;
923   GPid pid;
924   gchar pageno_str[10], dpi_str[10];
925   gchar *pdf_filename = bgpdf.tmpfile_copy;
926   gchar *ppm_root = g_strdup_printf("%s/p", bgpdf.tmpdir);
927   gchar *argv[]= PDFTOPPM_ARGV;
928   GtkWidget *dialog;
929
930   if (bgpdf.requests == NULL) return;
931   req = (struct BgPdfRequest *)bgpdf.requests->data;
932   if (req->pageno > bgpdf.npages+1 || 
933       (!req->initial_request && req->pageno <= bgpdf.npages && 
934        req->dpi == ((struct BgPdfPage *)g_list_nth_data(bgpdf.pages, req->pageno-1))->dpi))
935   { // ignore this request - it's redundant, or in outer space
936     bgpdf.pid = 0;
937     bgpdf.status = STATUS_IDLE;
938     bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
939     g_free(ppm_root);
940     if (bgpdf.requests != NULL) bgpdf_spawn_child();
941     return;
942   }
943   g_snprintf(pageno_str, 10, "%d", req->pageno);
944   g_snprintf(dpi_str, 10, "%d", req->dpi);
945 /*  printf("Processing request for page %d at %d dpi -- in %s\n", 
946     req->pageno, req->dpi, ppm_root); */
947   if (!g_spawn_async(NULL, argv, NULL,
948                      G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, 
949                      NULL, NULL, &pid, NULL))
950   {
951     // couldn't spawn... abort this request, try next one maybe ?
952 //    printf("Couldn't spawn\n");
953     bgpdf.pid = 0;
954     bgpdf.status = STATUS_IDLE;
955     bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
956     g_free(ppm_root);
957     if (!bgpdf.has_failed) {
958       dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
959         GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Unable to start PDF loader %s.", argv[0]);
960       gtk_dialog_run(GTK_DIALOG(dialog));
961       gtk_widget_destroy(dialog);
962     }
963     bgpdf.has_failed = TRUE;
964     if (bgpdf.requests != NULL) bgpdf_spawn_child();
965     return;
966   }  
967
968 //  printf("Spawned process %d\n", pid);
969   bgpdf.pid = pid;
970   bgpdf.status = STATUS_RUNNING;
971   g_child_watch_add(pid, bgpdf_child_handler, NULL);
972   g_free(ppm_root);
973 }
974
975 /* make a request */
976
977 void add_bgpdf_request(int pageno, double zoom, gboolean printing)
978 {
979   struct BgPdfRequest *req, *cmp_req;
980   GList *list;
981   
982   if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN)
983     return; // don't accept requests in those modes...
984   req = g_new(struct BgPdfRequest, 1);
985   req->is_printing = printing;
986   if (printing) req->dpi = PDFTOPPM_PRINTING_DPI;
987   else req->dpi = (int)floor(72*zoom+0.5);
988 //  printf("Enqueuing request for page %d at %d dpi\n", pageno, req->dpi);
989   if (pageno >= 1) {
990     // cancel any request this may supersede
991     for (list = bgpdf.requests; list != NULL; ) {
992       cmp_req = (struct BgPdfRequest *)list->data;
993       list = list->next;
994       if (!cmp_req->initial_request && cmp_req->pageno == pageno &&
995              cmp_req->is_printing == printing)
996         cancel_bgpdf_request(cmp_req);
997     }
998     req->pageno = pageno;
999     req->initial_request = FALSE;
1000   } else {
1001     req->pageno = 1;
1002     req->initial_request = TRUE;
1003   }
1004   bgpdf.requests = g_list_append(bgpdf.requests, req);
1005   if (!bgpdf.pid) bgpdf_spawn_child();
1006 }
1007
1008 /* shutdown the PDF reader */
1009
1010 void shutdown_bgpdf(void)
1011 {
1012   GList *list;
1013   struct BgPdfPage *pdfpg;
1014   struct BgPdfRequest *req;
1015   
1016   if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN) return;
1017   refstring_unref(bgpdf.filename);
1018   for (list = bgpdf.pages; list != NULL; list = list->next) {
1019     pdfpg = (struct BgPdfPage *)list->data;
1020     if (pdfpg->pixbuf!=NULL) gdk_pixbuf_unref(pdfpg->pixbuf);
1021   }
1022   g_list_free(bgpdf.pages);
1023   bgpdf.status = STATUS_SHUTDOWN;
1024   for (list = g_list_last(bgpdf.requests); list != NULL; ) {
1025     req = (struct BgPdfRequest *)list->data;
1026     list = list->prev;
1027     cancel_bgpdf_request(req);
1028   }
1029   if (!bgpdf.pid) end_bgpdf_shutdown();
1030   /* The above will ultimately remove all requests and kill the child if needed.
1031      The child will set status to STATUS_NOT_INIT, clear the requests list,
1032      empty tmpdir, ... except if there's no child! */
1033   /* note: it could look like there's a race condition here - if a child
1034      terminates and a new request is enqueued while we are destroying the
1035      queue - but actually the child handler callback is NOT a signal
1036      callback, so execution of this function is atomic */
1037 }
1038
1039 gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain)
1040 {
1041   FILE *f;
1042   gchar *filebuf;
1043   gsize filelen;
1044   
1045   if (bgpdf.status != STATUS_NOT_INIT) return FALSE;
1046   bgpdf.tmpfile_copy = NULL;
1047   bgpdf.tmpdir = mkdtemp(g_strdup(TMPDIR_TEMPLATE));
1048   if (!bgpdf.tmpdir) return FALSE;
1049   // make a local copy and check if it's a PDF
1050   if (!g_file_get_contents(pdfname, &filebuf, &filelen, NULL))
1051     { end_bgpdf_shutdown(); return FALSE; }
1052   if (filelen < 4 || strncmp(filebuf, "%PDF", 4))
1053     { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1054   bgpdf.tmpfile_copy = g_strdup_printf("%s/bg.pdf", bgpdf.tmpdir);
1055   f = fopen(bgpdf.tmpfile_copy, "w");
1056   if (f == NULL || fwrite(filebuf, 1, filelen, f) != filelen) 
1057     { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1058   fclose(f);
1059   g_free(filebuf);
1060   bgpdf.status = STATUS_IDLE;
1061   bgpdf.pid = 0;
1062   bgpdf.filename = new_refstring((file_domain == DOMAIN_ATTACH) ? "bg.pdf" : pdfname);
1063   bgpdf.file_domain = file_domain;
1064   bgpdf.npages = 0;
1065   bgpdf.pages = NULL;
1066   bgpdf.requests = NULL;
1067   bgpdf.create_pages = create_pages;
1068   bgpdf.has_failed = FALSE;
1069   add_bgpdf_request(-1, ui.startup_zoom, FALSE); // request all pages
1070   return TRUE;
1071 }
1072
1073 // create page n, resize it, set its bg
1074 void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg)
1075 {
1076   struct Page *pg;
1077   struct Background *bg;
1078
1079   if (journal.npages < pageno) {
1080     bg = g_new(struct Background, 1);
1081     bg->canvas_item = NULL;
1082   } else {
1083     pg = (struct Page *)g_list_nth_data(journal.pages, pageno-1);
1084     bg = pg->bg;
1085     if (bg->type != BG_SOLID) return;
1086       // don't mess with a page the user has modified significantly...
1087   }
1088   
1089   bg->type = BG_PDF;
1090   bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1091   bg->filename = refstring_ref(bgpdf.filename);
1092   bg->file_domain = bgpdf.file_domain;
1093   bg->file_page_seq = pageno;
1094   bg->pixbuf_scale = ui.startup_zoom;
1095   bg->pixbuf_dpi = bgpg->dpi;
1096
1097   if (journal.npages < pageno) {
1098     pg = new_page_with_bg(bg, 
1099             gdk_pixbuf_get_width(bg->pixbuf)*72.0/bg->pixbuf_dpi,
1100             gdk_pixbuf_get_height(bg->pixbuf)*72.0/bg->pixbuf_dpi);
1101     journal.pages = g_list_append(journal.pages, pg);
1102     journal.npages++;
1103   } else {
1104     pg->width = gdk_pixbuf_get_width(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1105     pg->height = gdk_pixbuf_get_height(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1106     make_page_clipbox(pg);
1107     update_canvas_bg(pg);
1108   }
1109   update_page_stuff();
1110 }
1111
1112 // look for all journal pages with given pdf bg, and update their bg pixmaps
1113 void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg)
1114 {
1115   GList *list;
1116   struct Page *pg;
1117   
1118   for (list = journal.pages; list!= NULL; list = list->next) {
1119     pg = (struct Page *)list->data;
1120     if (pg->bg->type == BG_PDF && pg->bg->file_page_seq == pageno) {
1121       if (pg->bg->pixbuf!=NULL) gdk_pixbuf_unref(pg->bg->pixbuf);
1122       pg->bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1123       pg->bg->pixbuf_dpi = bgpg->dpi;
1124       update_canvas_bg(pg);
1125     }
1126   }
1127 }
1128
1129 // initialize the recent files list
1130 void init_mru(void)
1131 {
1132   int i;
1133   gsize lfptr;
1134   char s[5];
1135   GIOChannel *f;
1136   gchar *str;
1137   GIOStatus status;
1138   
1139   g_strlcpy(s, "mru0", 5);
1140   for (s[3]='0', i=0; i<MRU_SIZE; s[3]++, i++) {
1141     ui.mrumenu[i] = GET_COMPONENT(s);
1142     ui.mru[i] = NULL;
1143   }
1144   f = g_io_channel_new_file(ui.mrufile, "r", NULL);
1145   if (f) status = G_IO_STATUS_NORMAL;
1146   else status = G_IO_STATUS_ERROR;
1147   i = 0;
1148   while (status == G_IO_STATUS_NORMAL && i<MRU_SIZE) {
1149     lfptr = 0;
1150     status = g_io_channel_read_line(f, &str, NULL, &lfptr, NULL);
1151     if (status == G_IO_STATUS_NORMAL && lfptr>0) {
1152       str[lfptr] = 0;
1153       ui.mru[i] = str;
1154       i++;
1155     }
1156   }
1157   if (f) {
1158     g_io_channel_shutdown(f, FALSE, NULL);
1159     g_io_channel_unref(f);
1160   }
1161   update_mru_menu();
1162 }
1163
1164 void update_mru_menu(void)
1165 {
1166   int i;
1167   gboolean anyone = FALSE;
1168   
1169   for (i=0; i<MRU_SIZE; i++) {
1170     if (ui.mru[i]!=NULL) {
1171       gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(ui.mrumenu[i]))),
1172           g_basename(ui.mru[i]));
1173       gtk_widget_show(ui.mrumenu[i]);
1174       anyone = TRUE;
1175     }
1176     else gtk_widget_hide(ui.mrumenu[i]);
1177   }
1178   gtk_widget_set_sensitive(GET_COMPONENT("fileRecentFiles"), anyone);
1179 }
1180
1181 void new_mru_entry(char *name)
1182 {
1183   int i, j;
1184   
1185   for (i=0;i<MRU_SIZE;i++) 
1186     if (ui.mru[i]!=NULL && !strcmp(ui.mru[i], name)) {
1187       g_free(ui.mru[i]);
1188       for (j=i+1; j<MRU_SIZE; j++) ui.mru[j-1] = ui.mru[j];
1189       ui.mru[MRU_SIZE-1]=NULL;
1190     }
1191   if (ui.mru[MRU_SIZE-1]!=NULL) g_free(ui.mru[MRU_SIZE-1]);
1192   for (j=MRU_SIZE-1; j>=1; j--) ui.mru[j] = ui.mru[j-1];
1193   ui.mru[0] = g_strdup(name);
1194   update_mru_menu();
1195 }
1196
1197 void delete_mru_entry(int which)
1198 {
1199   int i;
1200   
1201   if (ui.mru[which]!=NULL) g_free(ui.mru[which]);
1202   for (i=which+1;i<MRU_SIZE;i++) 
1203     ui.mru[i-1] = ui.mru[i];
1204   ui.mru[MRU_SIZE-1] = NULL;
1205   update_mru_menu();
1206 }
1207
1208 void save_mru_list(void)
1209 {
1210   FILE *f;
1211   int i;
1212   
1213   f = fopen(ui.mrufile, "w");
1214   if (f==NULL) return;
1215   for (i=0; i<MRU_SIZE; i++)
1216     if (ui.mru[i]!=NULL) fprintf(f, "%s\n", ui.mru[i]);
1217   fclose(f);
1218 }
1219
1220 void init_config_default(void)
1221 {
1222   int i, j;
1223
1224   DEFAULT_ZOOM = DISPLAY_DPI_DEFAULT/72.0;
1225   ui.zoom = ui.startup_zoom = 1.0*DEFAULT_ZOOM;
1226   ui.default_page.height = 792.0;
1227   ui.default_page.width = 612.0;
1228   ui.default_page.bg->type = BG_SOLID;
1229   ui.default_page.bg->color_no = COLOR_WHITE;
1230   ui.default_page.bg->color_rgba = predef_bgcolors_rgba[COLOR_WHITE];
1231   ui.default_page.bg->ruling = RULING_LINED;
1232   ui.view_continuous = TRUE;
1233   ui.allow_xinput = TRUE;
1234   ui.discard_corepointer = TRUE;
1235   ui.bg_apply_all_pages = FALSE;
1236   ui.use_erasertip = FALSE;
1237   ui.window_default_width = 720;
1238   ui.window_default_height = 480;
1239   ui.maximize_at_start = FALSE;
1240   ui.fullscreen = FALSE;
1241   ui.scrollbar_step_increment = 30;
1242   ui.zoom_step_increment = 1;
1243   ui.zoom_step_factor = 1.5;
1244   ui.antialias_bg = TRUE;
1245   ui.progressive_bg = TRUE;
1246   ui.print_ruling = TRUE;
1247   ui.default_unit = UNIT_CM;
1248   ui.default_path = NULL;
1249   
1250   // the default UI vertical order
1251   ui.vertical_order[0][0] = 1; 
1252   ui.vertical_order[0][1] = 2; 
1253   ui.vertical_order[0][2] = 3; 
1254   ui.vertical_order[0][3] = 0; 
1255   ui.vertical_order[0][4] = 4;
1256   ui.vertical_order[1][0] = 2;
1257   ui.vertical_order[1][1] = 3;
1258   ui.vertical_order[1][2] = 0;
1259   ui.vertical_order[1][3] = ui.vertical_order[1][4] = -1;
1260
1261   ui.toolno[0] = ui.startuptool = TOOL_PEN;
1262   ui.ruler[0] = ui.startupruler = FALSE;
1263   for (i=1; i<=NUM_BUTTONS; i++) {
1264     ui.toolno[i] = TOOL_ERASER;
1265     ui.ruler[i] = FALSE;
1266   }
1267   for (i=0; i<=NUM_BUTTONS; i++)
1268     ui.linked_brush[i] = BRUSH_LINKED;
1269   ui.brushes[0][TOOL_PEN].color_no = COLOR_BLACK;
1270   ui.brushes[0][TOOL_ERASER].color_no = COLOR_WHITE;
1271   ui.brushes[0][TOOL_HIGHLIGHTER].color_no = COLOR_YELLOW;
1272   for (i=0; i < NUM_STROKE_TOOLS; i++) {
1273     ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM;
1274     ui.brushes[0][i].tool_options = 0;
1275   }
1276   for (i=0; i< NUM_STROKE_TOOLS; i++)
1277     for (j=1; j<=NUM_BUTTONS; j++)
1278       g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1279
1280   // predef_thickness is already initialized as a global variable
1281   GS_BITMAP_DPI = 144;
1282   PDFTOPPM_PRINTING_DPI = 150;
1283 }
1284
1285 #if GLIB_CHECK_VERSION(2,6,0)
1286
1287 void update_keyval(const gchar *group_name, const gchar *key,
1288                 const gchar *comment, gchar *value)
1289 {
1290   gboolean has_it = g_key_file_has_key(ui.config_data, group_name, key, NULL);
1291   cleanup_numeric(value);
1292   g_key_file_set_value(ui.config_data, group_name, key, value);
1293   g_free(value);
1294   if (!has_it) g_key_file_set_comment(ui.config_data, group_name, key, comment, NULL);
1295 }
1296
1297 #endif
1298
1299 const char *vorder_usernames[VBOX_MAIN_NITEMS+1] = 
1300   {"drawarea", "menu", "main_toolbar", "pen_toolbar", "statusbar", NULL};
1301   
1302 gchar *verbose_vertical_order(int *order)
1303 {
1304   gchar buf[80], *p; // longer than needed
1305   int i;
1306
1307   p = buf;  
1308   for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1309     if (order[i]<0 || order[i]>=VBOX_MAIN_NITEMS) continue;
1310     if (p!=buf) *(p++) = ' ';
1311     p = g_stpcpy(p, vorder_usernames[order[i]]);
1312   }
1313   return g_strdup(buf);
1314 }
1315
1316 void save_config_to_file(void)
1317 {
1318   gchar *buf;
1319   FILE *f;
1320
1321 #if GLIB_CHECK_VERSION(2,6,0)
1322   // no support for keyval files before Glib 2.6.0
1323   if (glib_minor_version<6) return; 
1324
1325   // save some data...
1326   ui.maximize_at_start = (gdk_window_get_state(winMain->window) & GDK_WINDOW_STATE_MAXIMIZED);
1327   if (!ui.maximize_at_start && !ui.fullscreen)
1328     gdk_drawable_get_size(winMain->window, 
1329       &ui.window_default_width, &ui.window_default_height);
1330
1331   update_keyval("general", "display_dpi",
1332     " the display resolution, in pixels per inch",
1333     g_strdup_printf("%.2f", DEFAULT_ZOOM*72));
1334   update_keyval("general", "initial_zoom",
1335     " the initial zoom level, in percent",
1336     g_strdup_printf("%.2f", 100*ui.zoom/DEFAULT_ZOOM));
1337   update_keyval("general", "window_maximize",
1338     " maximize the window at startup (true/false)",
1339     g_strdup(ui.maximize_at_start?"true":"false"));
1340   update_keyval("general", "window_fullscreen",
1341     " start in full screen mode (true/false)",
1342     g_strdup(ui.fullscreen?"true":"false"));
1343   update_keyval("general", "window_width",
1344     " the window width in pixels (when not maximized)",
1345     g_strdup_printf("%d", ui.window_default_width));
1346   update_keyval("general", "window_height",
1347     " the window height in pixels",
1348     g_strdup_printf("%d", ui.window_default_height));
1349   update_keyval("general", "scrollbar_speed",
1350     " scrollbar step increment (in pixels)",
1351     g_strdup_printf("%d", ui.scrollbar_step_increment));
1352   update_keyval("general", "zoom_dialog_increment",
1353     " the step increment in the zoom dialog box",
1354     g_strdup_printf("%d", ui.zoom_step_increment));
1355   update_keyval("general", "zoom_step_factor",
1356     " the multiplicative factor for zoom in/out",
1357     g_strdup_printf("%.3f", ui.zoom_step_factor));
1358   update_keyval("general", "view_continuous",
1359     " document view (true = continuous, false = single page)",
1360     g_strdup(ui.view_continuous?"true":"false"));
1361   update_keyval("general", "use_xinput",
1362     " use XInput extensions (true/false)",
1363     g_strdup(ui.allow_xinput?"true":"false"));
1364   update_keyval("general", "discard_corepointer",
1365     " discard Core Pointer events in XInput mode (true/false)",
1366     g_strdup(ui.discard_corepointer?"true":"false"));
1367   update_keyval("general", "use_erasertip",
1368     " always map eraser tip to eraser (true/false)",
1369     g_strdup(ui.use_erasertip?"true":"false"));
1370   update_keyval("general", "default_path",
1371     " default path for open/save (leave blank for current directory)",
1372     g_strdup((ui.default_path!=NULL)?ui.default_path:""));
1373   update_keyval("general", "interface_order",
1374     " interface components from top to bottom\n valid values: drawarea menu main_toolbar pen_toolbar statusbar",
1375     verbose_vertical_order(ui.vertical_order[0]));
1376   update_keyval("general", "interface_fullscreen",
1377     " interface components in fullscreen mode, from top to bottom",
1378     verbose_vertical_order(ui.vertical_order[1]));
1379
1380   update_keyval("paper", "width",
1381     " the default page width, in points (1/72 in)",
1382     g_strdup_printf("%.2f", ui.default_page.width));
1383   update_keyval("paper", "height",
1384     " the default page height, in points (1/72 in)",
1385     g_strdup_printf("%.2f", ui.default_page.height));
1386   update_keyval("paper", "color",
1387     " the default paper color",
1388     g_strdup(bgcolor_names[ui.default_page.bg->color_no]));
1389   update_keyval("paper", "style",
1390     " the default paper style (plain, lined, ruled, or graph)",
1391     g_strdup(bgstyle_names[ui.default_page.bg->ruling]));
1392   update_keyval("paper", "apply_all",
1393     " apply paper style changes to all pages (true/false)",
1394     g_strdup(ui.bg_apply_all_pages?"true":"false"));
1395   update_keyval("paper", "default_unit",
1396     " preferred unit (cm, in, px, pt)",
1397     g_strdup(unit_names[ui.default_unit]));
1398   update_keyval("paper", "print_ruling",
1399     " include paper ruling when printing or exporting to PDF (true/false)",
1400     g_strdup(ui.print_ruling?"true":"false"));
1401   update_keyval("paper", "antialias_bg",
1402     " antialiased bitmap backgrounds (true/false)",
1403     g_strdup(ui.antialias_bg?"true":"false"));
1404   update_keyval("paper", "progressive_bg",
1405     " progressive scaling of bitmap backgrounds (true/false)",
1406     g_strdup(ui.progressive_bg?"true":"false"));
1407   update_keyval("paper", "gs_bitmap_dpi",
1408     " bitmap resolution of PS/PDF backgrounds rendered using ghostscript (dpi)",
1409     g_strdup_printf("%d", GS_BITMAP_DPI));
1410   update_keyval("paper", "pdftoppm_printing_dpi",
1411     " bitmap resolution of PDF backgrounds when printing with libgnomeprint (dpi)",
1412     g_strdup_printf("%d", PDFTOPPM_PRINTING_DPI));
1413
1414   update_keyval("tools", "startup_tool",
1415     " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)",
1416     g_strdup(tool_names[ui.startuptool]));
1417   update_keyval("tools", "startup_ruler",
1418     " ruler mode at startup (true/false) (for pen or highlighter only)",
1419     g_strdup(ui.startupruler?"true":"false"));
1420   update_keyval("tools", "pen_color",
1421     " default pen color",
1422     g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no]));
1423   update_keyval("tools", "pen_thickness",
1424     " default pen thickness (fine = 1, medium = 2, thick = 3)",
1425     g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no));
1426   update_keyval("tools", "eraser_thickness",
1427     " default eraser thickness (fine = 1, medium = 2, thick = 3)",
1428     g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no));
1429   update_keyval("tools", "eraser_mode",
1430     " default eraser mode (standard = 0, whiteout = 1, strokes = 2)",
1431     g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].tool_options));
1432   update_keyval("tools", "highlighter_color",
1433     " default highlighter color",
1434     g_strdup(color_names[ui.default_brushes[TOOL_HIGHLIGHTER].color_no]));
1435   update_keyval("tools", "highlighter_thickness",
1436     " default highlighter thickness (fine = 1, medium = 2, thick = 3)",
1437     g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no));
1438   update_keyval("tools", "btn2_tool",
1439     " button 2 tool (pen, eraser, highlighter, selectrect, vertspace, hand)",
1440     g_strdup(tool_names[ui.toolno[1]]));
1441   update_keyval("tools", "btn2_linked",
1442     " button 2 brush linked to primary brush (true/false) (overrides all other settings)",
1443     g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false"));
1444   update_keyval("tools", "btn2_ruler",
1445     " button 2 ruler mode (true/false) (for pen or highlighter only)",
1446     g_strdup(ui.ruler[1]?"true":"false"));
1447   update_keyval("tools", "btn2_color",
1448     " button 2 brush color (for pen or highlighter only)",
1449     g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)?
1450                color_names[ui.brushes[1][ui.toolno[1]].color_no]:"white"));
1451   update_keyval("tools", "btn2_thickness",
1452     " button 2 brush thickness (pen, eraser, or highlighter only)",
1453     g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)?
1454                             ui.brushes[1][ui.toolno[1]].thickness_no:0));
1455   update_keyval("tools", "btn2_erasermode",
1456     " button 2 eraser mode (eraser only)",
1457     g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options));
1458   update_keyval("tools", "btn3_tool",
1459     " button 3 tool (pen, eraser, highlighter, selectrect, vertspace, hand)",
1460     g_strdup(tool_names[ui.toolno[2]]));
1461   update_keyval("tools", "btn3_linked",
1462     " button 3 brush linked to primary brush (true/false) (overrides all other settings)",
1463     g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false"));
1464   update_keyval("tools", "btn3_ruler",
1465     " button 3 ruler mode (true/false) (for pen or highlighter only)",
1466     g_strdup(ui.ruler[2]?"true":"false"));
1467   update_keyval("tools", "btn3_color",
1468     " button 3 brush color (for pen or highlighter only)",
1469     g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)?
1470                color_names[ui.brushes[2][ui.toolno[2]].color_no]:"white"));
1471   update_keyval("tools", "btn3_thickness",
1472     " button 3 brush thickness (pen, eraser, or highlighter only)",
1473     g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)?
1474                             ui.brushes[2][ui.toolno[2]].thickness_no:0));
1475   update_keyval("tools", "btn3_erasermode",
1476     " button 3 eraser mode (eraser only)",
1477     g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options));
1478
1479   update_keyval("tools", "pen_thicknesses",
1480     " thickness of the various pens (in points, 1 pt = 1/72 in)",
1481     g_strdup_printf("%.2f;%.2f;%.2f;%.2f;%.2f", 
1482       predef_thickness[TOOL_PEN][0], predef_thickness[TOOL_PEN][1],
1483       predef_thickness[TOOL_PEN][2], predef_thickness[TOOL_PEN][3],
1484       predef_thickness[TOOL_PEN][4]));
1485   update_keyval("tools", "eraser_thicknesses",
1486     " thickness of the various erasers (in points, 1 pt = 1/72 in)",
1487     g_strdup_printf("%.2f;%.2f;%.2f", 
1488       predef_thickness[TOOL_ERASER][1], predef_thickness[TOOL_ERASER][2],
1489       predef_thickness[TOOL_ERASER][3]));
1490   update_keyval("tools", "highlighter_thicknesses",
1491     " thickness of the various highlighters (in points, 1 pt = 1/72 in)",
1492     g_strdup_printf("%.2f;%.2f;%.2f", 
1493       predef_thickness[TOOL_HIGHLIGHTER][1], predef_thickness[TOOL_HIGHLIGHTER][2],
1494       predef_thickness[TOOL_HIGHLIGHTER][3]));
1495   // set comments for keys / groups that don't exist
1496   // set keyvals to current options
1497
1498   buf = g_key_file_to_data(ui.config_data, NULL, NULL);
1499   if (buf == NULL) return;
1500   f = fopen(ui.configfile, "w");
1501   if (f==NULL) { g_free(buf); return; }
1502   fputs(buf, f);
1503   fclose(f);
1504   g_free(buf);
1505 #endif
1506 }
1507
1508 #if GLIB_CHECK_VERSION(2,6,0)
1509 gboolean parse_keyval_float(const gchar *group, const gchar *key, double *val, double inf, double sup)
1510 {
1511   gchar *ret, *end;
1512   double conv;
1513   
1514   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1515   if (ret==NULL) return FALSE;
1516   conv = g_ascii_strtod(ret, &end);
1517   if (*end!=0) { g_free(ret); return FALSE; }
1518   g_free(ret);
1519   if (conv < inf || conv > sup) return FALSE;
1520   *val = conv;
1521   return TRUE;
1522 }
1523
1524 gboolean parse_keyval_floatlist(const gchar *group, const gchar *key, double *val, int n, double inf, double sup)
1525 {
1526   gchar *ret, *end;
1527   double conv[5];
1528   int i;
1529
1530   if (n>5) return FALSE;
1531   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1532   if (ret==NULL) return FALSE;
1533   end = ret;
1534   for (i=0; i<n; i++) {
1535     conv[i] = g_ascii_strtod(end, &end);
1536     if ((i==n-1 && *end!=0) || (i<n-1 && *end!=';') ||
1537         (conv[i] < inf) || (conv[i] > sup)) { g_free(ret); return FALSE; }
1538     end++;
1539   }
1540   g_free(ret);
1541   for (i=0; i<n; i++) val[i] = conv[i];
1542   return TRUE;
1543 }
1544
1545 gboolean parse_keyval_int(const gchar *group, const gchar *key, int *val, int inf, int sup)
1546 {
1547   gchar *ret, *end;
1548   int conv;
1549   
1550   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1551   if (ret==NULL) return FALSE;
1552   conv = strtol(ret, &end, 10);
1553   if (*end!=0) { g_free(ret); return FALSE; }
1554   g_free(ret);
1555   if (conv < inf || conv > sup) return FALSE;
1556   *val = conv;
1557   return TRUE;
1558 }
1559
1560 gboolean parse_keyval_enum(const gchar *group, const gchar *key, int *val, const char **names, int n)
1561 {
1562   gchar *ret;
1563   int i;
1564   
1565   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1566   if (ret==NULL) return FALSE;
1567   for (i=0; i<n; i++) {
1568     if (!names[i][0]) continue; // "" is for invalid values
1569     if (!g_ascii_strcasecmp(ret, names[i]))
1570       { *val = i; g_free(ret); return TRUE; }
1571   }
1572   return FALSE;
1573 }
1574
1575 gboolean parse_keyval_boolean(const gchar *group, const gchar *key, gboolean *val)
1576 {
1577   gchar *ret;
1578   
1579   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1580   if (ret==NULL) return FALSE;
1581   if (!g_ascii_strcasecmp(ret, "true")) 
1582     { *val = TRUE; g_free(ret); return TRUE; }
1583   if (!g_ascii_strcasecmp(ret, "false")) 
1584     { *val = FALSE; g_free(ret); return TRUE; }
1585   g_free(ret);
1586   return FALSE;
1587 }
1588
1589 gboolean parse_keyval_string(const gchar *group, const gchar *key, gchar **val)
1590 {
1591   gchar *ret;
1592   
1593   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1594   if (ret==NULL) return FALSE;
1595   if (strlen(ret) == 0) {
1596     *val = NULL;
1597     g_free(ret);
1598   } 
1599   else *val = ret;
1600   return TRUE;
1601 }
1602
1603 gboolean parse_keyval_vorderlist(const gchar *group, const gchar *key, int *order)
1604 {
1605   gchar *ret, *p;
1606   int tmp[VBOX_MAIN_NITEMS];
1607   int i, n, found, l;
1608
1609   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1610   if (ret==NULL) return FALSE;
1611   
1612   for (i=0; i<VBOX_MAIN_NITEMS; i++) tmp[i] = -1;
1613   n = 0; p = ret;
1614   while (*p==' ') p++;
1615   while (*p!=0) {
1616     if (n>VBOX_MAIN_NITEMS) return FALSE; // too many items
1617     for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1618       if (!g_str_has_prefix(p, vorder_usernames[i])) continue;
1619       l = strlen(vorder_usernames[i]);
1620       if (p[l]==' '||p[l]==0) { p+=l; break; }
1621     }
1622     if (i>=VBOX_MAIN_NITEMS) { g_free(ret); return FALSE; } // parse error
1623     // we found item #i
1624     tmp[n++] = i;
1625     while (*p==' ') p++;
1626   }
1627   
1628   for (n=0; n<VBOX_MAIN_NITEMS; n++) order[n] = tmp[n];
1629   g_free(ret);
1630   return TRUE;
1631 }
1632
1633 #endif
1634
1635 void load_config_from_file(void)
1636 {
1637   double f;
1638   gboolean b;
1639   int i, j;
1640   
1641 #if GLIB_CHECK_VERSION(2,6,0)
1642   // no support for keyval files before Glib 2.6.0
1643   if (glib_minor_version<6) return; 
1644   ui.config_data = g_key_file_new();
1645   if (!g_key_file_load_from_file(ui.config_data, ui.configfile, 
1646          G_KEY_FILE_KEEP_COMMENTS, NULL)) {
1647     g_key_file_free(ui.config_data);
1648     ui.config_data = g_key_file_new();
1649     g_key_file_set_comment(ui.config_data, NULL, NULL, 
1650            " Xournal configuration file.\n"
1651            " This file is generated automatically upon saving preferences.\n"
1652            " Use caution when editing this file manually.\n", NULL);
1653     return;
1654   }
1655
1656   // parse keys from the keyfile to set defaults
1657   if (parse_keyval_float("general", "display_dpi", &f, 10., 500.))
1658     DEFAULT_ZOOM = f/72.0;
1659   if (parse_keyval_float("general", "initial_zoom", &f, 
1660               MIN_ZOOM*100/DEFAULT_ZOOM, MAX_ZOOM*100/DEFAULT_ZOOM))
1661     ui.zoom = ui.startup_zoom = DEFAULT_ZOOM*f/100.0;
1662   parse_keyval_boolean("general", "window_maximize", &ui.maximize_at_start);
1663   parse_keyval_boolean("general", "window_fullscreen", &ui.fullscreen);
1664   parse_keyval_int("general", "window_width", &ui.window_default_width, 10, 5000);
1665   parse_keyval_int("general", "window_height", &ui.window_default_height, 10, 5000);
1666   parse_keyval_int("general", "scrollbar_speed", &ui.scrollbar_step_increment, 1, 5000);
1667   parse_keyval_int("general", "zoom_dialog_increment", &ui.zoom_step_increment, 1, 500);
1668   parse_keyval_float("general", "zoom_step_factor", &ui.zoom_step_factor, 1., 5.);
1669   parse_keyval_boolean("general", "view_continuous", &ui.view_continuous);
1670   parse_keyval_boolean("general", "use_xinput", &ui.allow_xinput);
1671   parse_keyval_boolean("general", "discard_corepointer", &ui.discard_corepointer);
1672   parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip);
1673   parse_keyval_string("general", "default_path", &ui.default_path);
1674   parse_keyval_vorderlist("general", "interface_order", ui.vertical_order[0]);
1675   parse_keyval_vorderlist("general", "interface_fullscreen", ui.vertical_order[1]);
1676   
1677   parse_keyval_float("paper", "width", &ui.default_page.width, 1., 5000.);
1678   parse_keyval_float("paper", "height", &ui.default_page.height, 1., 5000.);
1679   parse_keyval_enum("paper", "color", &(ui.default_page.bg->color_no), bgcolor_names, COLOR_MAX);
1680   ui.default_page.bg->color_rgba = predef_bgcolors_rgba[ui.default_page.bg->color_no];
1681   parse_keyval_enum("paper", "style", &(ui.default_page.bg->ruling), bgstyle_names, 4);
1682   parse_keyval_boolean("paper", "apply_all", &ui.bg_apply_all_pages);
1683   parse_keyval_enum("paper", "default_unit", &ui.default_unit, unit_names, 4);
1684   parse_keyval_boolean("paper", "antialias_bg", &ui.antialias_bg);
1685   parse_keyval_boolean("paper", "progressive_bg", &ui.progressive_bg);
1686   parse_keyval_boolean("paper", "print_ruling", &ui.print_ruling);
1687   parse_keyval_int("paper", "gs_bitmap_dpi", &GS_BITMAP_DPI, 1, 1200);
1688   parse_keyval_int("paper", "pdftoppm_printing_dpi", &PDFTOPPM_PRINTING_DPI, 1, 1200);
1689
1690   parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS);
1691   ui.toolno[0] = ui.startuptool;
1692   if (ui.startuptool == TOOL_PEN || ui.startuptool == TOOL_HIGHLIGHTER) {
1693     parse_keyval_boolean("tools", "startup_ruler", &ui.startupruler);
1694     ui.ruler[0] = ui.startupruler;
1695   }
1696   parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX);
1697   parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4);
1698   parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3);
1699   parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2);
1700   parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX);
1701   parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4);
1702   for (i=0; i< NUM_STROKE_TOOLS; i++)
1703     for (j=1; j<=NUM_BUTTONS; j++)
1704       g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1705
1706   parse_keyval_enum("tools", "btn2_tool", &(ui.toolno[1]), tool_names, NUM_TOOLS);
1707   if (parse_keyval_boolean("tools", "btn2_linked", &b))
1708     ui.linked_brush[1] = b?BRUSH_LINKED:BRUSH_STATIC;
1709   parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS);
1710   if (parse_keyval_boolean("tools", "btn3_linked", &b))
1711     ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC;
1712   for (i=1; i<=NUM_BUTTONS; i++)
1713     if (ui.toolno[i]==TOOL_PEN || ui.toolno[i]==TOOL_HIGHLIGHTER)
1714       ui.ruler[i] = ui.ruler[0];
1715   if (ui.linked_brush[1]!=BRUSH_LINKED) {
1716     if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) {
1717       parse_keyval_boolean("tools", "btn2_ruler", &(ui.ruler[1]));
1718       parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX);
1719     }
1720     if (ui.toolno[1]<NUM_STROKE_TOOLS)
1721       parse_keyval_int("tools", "btn2_thickness", &(ui.brushes[1][ui.toolno[1]].thickness_no), 0, 4);
1722     if (ui.toolno[1]==TOOL_ERASER)
1723       parse_keyval_int("tools", "btn2_erasermode", &(ui.brushes[1][TOOL_ERASER].tool_options), 0, 2);
1724   }
1725   if (ui.linked_brush[2]!=BRUSH_LINKED) {
1726     if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) {
1727       parse_keyval_boolean("tools", "btn3_ruler", &(ui.ruler[2]));
1728       parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX);
1729     }
1730     if (ui.toolno[2]<NUM_STROKE_TOOLS)
1731       parse_keyval_int("tools", "btn3_thickness", &(ui.brushes[2][ui.toolno[2]].thickness_no), 0, 4);
1732     if (ui.toolno[2]==TOOL_ERASER)
1733       parse_keyval_int("tools", "btn3_erasermode", &(ui.brushes[2][TOOL_ERASER].tool_options), 0, 2);
1734   }
1735   parse_keyval_floatlist("tools", "pen_thicknesses", predef_thickness[TOOL_PEN], 5, 0.01, 1000.0);
1736   parse_keyval_floatlist("tools", "eraser_thicknesses", predef_thickness[TOOL_ERASER]+1, 3, 0.01, 1000.0);
1737   parse_keyval_floatlist("tools", "highlighter_thicknesses", predef_thickness[TOOL_HIGHLIGHTER]+1, 3, 0.01, 1000.0);
1738 #endif
1739 }