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