]> git.donarmstrong.com Git - xournal.git/blob - src/xo-file.c
91044b0e491202286720fd52005ed4b7682448d0
[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   
948   if (bgpdf.requests == NULL) return;
949   req = (struct BgPdfRequest *)bgpdf.requests->data;
950   
951   ppm_name = g_strdup_printf("%s/p-%06d.ppm", bgpdf.tmpdir, req->pageno);
952 //  printf("Child %d finished, should look for %s... \n", pid, ppm_name);
953   
954   if (bgpdf.status == STATUS_ABORTED || bgpdf.status == STATUS_SHUTDOWN)
955      pixbuf = NULL;
956   else
957      pixbuf = gdk_pixbuf_new_from_file(ppm_name, NULL);
958
959   unlink(ppm_name);
960   g_free(ppm_name);
961
962   if (pixbuf != NULL) { // success
963 //    printf("success\n");
964     while (req->pageno > bgpdf.npages) {
965       bgpg = g_new(struct BgPdfPage, 1);
966       bgpg->pixbuf = NULL;
967       bgpdf.pages = g_list_append(bgpdf.pages, bgpg);
968       bgpdf.npages++;
969     }
970     bgpg = g_list_nth_data(bgpdf.pages, req->pageno-1);
971     if (bgpg->pixbuf!=NULL) gdk_pixbuf_unref(bgpg->pixbuf);
972     bgpg->pixbuf = pixbuf;
973     bgpg->dpi = req->dpi;
974     if (req->initial_request && bgpdf.create_pages) {
975       bgpdf_create_page_with_bg(req->pageno, bgpg);
976       // create page n, resize it, set its bg - all without any undo effect
977     } else {
978       if (!req->is_printing) bgpdf_update_bg(req->pageno, bgpg);
979       // look for all pages with this bg, and update their bg pixmaps
980     }
981   }
982   else {
983 //    printf("failed or aborted\n");
984     bgpdf.create_pages = FALSE;
985     req->initial_request = FALSE;
986   }
987
988   bgpdf.pid = 0;
989   g_spawn_close_pid(pid);
990   
991   if (req->initial_request)
992     req->pageno++; // try for next page
993   else
994     bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
995   
996   if (bgpdf.status == STATUS_SHUTDOWN) {
997     end_bgpdf_shutdown();
998     return;
999   }
1000   
1001   bgpdf.status = STATUS_IDLE;
1002   if (bgpdf.requests != NULL) bgpdf_spawn_child();
1003 }
1004
1005 /* spawn a child to process the head request */
1006
1007 void bgpdf_spawn_child(void)
1008 {
1009   struct BgPdfRequest *req;
1010   GPid pid;
1011   gchar pageno_str[10], dpi_str[10];
1012   gchar *pdf_filename = bgpdf.tmpfile_copy;
1013   gchar *ppm_root = g_strdup_printf("%s/p", bgpdf.tmpdir);
1014   gchar *argv[]= PDFTOPPM_ARGV;
1015   GtkWidget *dialog;
1016
1017   if (bgpdf.requests == NULL) return;
1018   req = (struct BgPdfRequest *)bgpdf.requests->data;
1019   if (req->pageno > bgpdf.npages+1 || 
1020       (!req->initial_request && req->pageno <= bgpdf.npages && 
1021        req->dpi == ((struct BgPdfPage *)g_list_nth_data(bgpdf.pages, req->pageno-1))->dpi))
1022   { // ignore this request - it's redundant, or in outer space
1023     bgpdf.pid = 0;
1024     bgpdf.status = STATUS_IDLE;
1025     bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1026     g_free(ppm_root);
1027     if (bgpdf.requests != NULL) bgpdf_spawn_child();
1028     return;
1029   }
1030   g_snprintf(pageno_str, 10, "%d", req->pageno);
1031   g_snprintf(dpi_str, 10, "%d", req->dpi);
1032 /*  printf("Processing request for page %d at %d dpi -- in %s\n", 
1033     req->pageno, req->dpi, ppm_root); */
1034   if (!g_spawn_async(NULL, argv, NULL,
1035                      G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, 
1036                      NULL, NULL, &pid, NULL))
1037   {
1038     // couldn't spawn... abort this request, try next one maybe ?
1039 //    printf("Couldn't spawn\n");
1040     bgpdf.pid = 0;
1041     bgpdf.status = STATUS_IDLE;
1042     bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1043     g_free(ppm_root);
1044     if (!bgpdf.has_failed) {
1045       dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
1046         GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Unable to start PDF loader %s.", argv[0]);
1047       gtk_dialog_run(GTK_DIALOG(dialog));
1048       gtk_widget_destroy(dialog);
1049     }
1050     bgpdf.has_failed = TRUE;
1051     if (bgpdf.requests != NULL) bgpdf_spawn_child();
1052     return;
1053   }  
1054
1055 //  printf("Spawned process %d\n", pid);
1056   bgpdf.pid = pid;
1057   bgpdf.status = STATUS_RUNNING;
1058   g_child_watch_add(pid, bgpdf_child_handler, NULL);
1059   g_free(ppm_root);
1060 }
1061
1062 /* make a request */
1063
1064 void add_bgpdf_request(int pageno, double zoom, gboolean printing)
1065 {
1066   struct BgPdfRequest *req, *cmp_req;
1067   GList *list;
1068   
1069   if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN)
1070     return; // don't accept requests in those modes...
1071   req = g_new(struct BgPdfRequest, 1);
1072   req->is_printing = printing;
1073   if (printing) req->dpi = PDFTOPPM_PRINTING_DPI;
1074   else req->dpi = (int)floor(72*zoom+0.5);
1075 //  printf("Enqueuing request for page %d at %d dpi\n", pageno, req->dpi);
1076   if (pageno >= 1) {
1077     // cancel any request this may supersede
1078     for (list = bgpdf.requests; list != NULL; ) {
1079       cmp_req = (struct BgPdfRequest *)list->data;
1080       list = list->next;
1081       if (!cmp_req->initial_request && cmp_req->pageno == pageno &&
1082              cmp_req->is_printing == printing)
1083         cancel_bgpdf_request(cmp_req);
1084     }
1085     req->pageno = pageno;
1086     req->initial_request = FALSE;
1087   } else {
1088     req->pageno = 1;
1089     req->initial_request = TRUE;
1090   }
1091   bgpdf.requests = g_list_append(bgpdf.requests, req);
1092   if (!bgpdf.pid) bgpdf_spawn_child();
1093 }
1094
1095 /* shutdown the PDF reader */
1096
1097 void shutdown_bgpdf(void)
1098 {
1099   GList *list;
1100   struct BgPdfPage *pdfpg;
1101   struct BgPdfRequest *req;
1102   
1103   if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN) return;
1104   refstring_unref(bgpdf.filename);
1105   for (list = bgpdf.pages; list != NULL; list = list->next) {
1106     pdfpg = (struct BgPdfPage *)list->data;
1107     if (pdfpg->pixbuf!=NULL) gdk_pixbuf_unref(pdfpg->pixbuf);
1108   }
1109   g_list_free(bgpdf.pages);
1110   bgpdf.status = STATUS_SHUTDOWN;
1111   for (list = g_list_last(bgpdf.requests); list != NULL; ) {
1112     req = (struct BgPdfRequest *)list->data;
1113     list = list->prev;
1114     cancel_bgpdf_request(req);
1115   }
1116   if (!bgpdf.pid) end_bgpdf_shutdown();
1117   /* The above will ultimately remove all requests and kill the child if needed.
1118      The child will set status to STATUS_NOT_INIT, clear the requests list,
1119      empty tmpdir, ... except if there's no child! */
1120   /* note: it could look like there's a race condition here - if a child
1121      terminates and a new request is enqueued while we are destroying the
1122      queue - but actually the child handler callback is NOT a signal
1123      callback, so execution of this function is atomic */
1124 }
1125
1126 gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain)
1127 {
1128   FILE *f;
1129   gchar *filebuf;
1130   gsize filelen;
1131   
1132   if (bgpdf.status != STATUS_NOT_INIT) return FALSE;
1133   bgpdf.tmpfile_copy = NULL;
1134   bgpdf.tmpdir = mkdtemp(g_strdup(TMPDIR_TEMPLATE));
1135   if (!bgpdf.tmpdir) return FALSE;
1136   // make a local copy and check if it's a PDF
1137   if (!g_file_get_contents(pdfname, &filebuf, &filelen, NULL))
1138     { end_bgpdf_shutdown(); return FALSE; }
1139   if (filelen < 4 || strncmp(filebuf, "%PDF", 4))
1140     { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1141   bgpdf.tmpfile_copy = g_strdup_printf("%s/bg.pdf", bgpdf.tmpdir);
1142   f = fopen(bgpdf.tmpfile_copy, "w");
1143   if (f == NULL || fwrite(filebuf, 1, filelen, f) != filelen) 
1144     { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1145   fclose(f);
1146   g_free(filebuf);
1147   bgpdf.status = STATUS_IDLE;
1148   bgpdf.pid = 0;
1149   bgpdf.filename = new_refstring((file_domain == DOMAIN_ATTACH) ? "bg.pdf" : pdfname);
1150   bgpdf.file_domain = file_domain;
1151   bgpdf.npages = 0;
1152   bgpdf.pages = NULL;
1153   bgpdf.requests = NULL;
1154   bgpdf.create_pages = create_pages;
1155   bgpdf.has_failed = FALSE;
1156   add_bgpdf_request(-1, ui.startup_zoom, FALSE); // request all pages
1157   return TRUE;
1158 }
1159
1160 // create page n, resize it, set its bg
1161 void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg)
1162 {
1163   struct Page *pg;
1164   struct Background *bg;
1165
1166   if (journal.npages < pageno) {
1167     bg = g_new(struct Background, 1);
1168     bg->canvas_item = NULL;
1169   } else {
1170     pg = (struct Page *)g_list_nth_data(journal.pages, pageno-1);
1171     bg = pg->bg;
1172     if (bg->type != BG_SOLID) return;
1173       // don't mess with a page the user has modified significantly...
1174   }
1175   
1176   bg->type = BG_PDF;
1177   bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1178   bg->filename = refstring_ref(bgpdf.filename);
1179   bg->file_domain = bgpdf.file_domain;
1180   bg->file_page_seq = pageno;
1181   bg->pixbuf_scale = ui.startup_zoom;
1182   bg->pixbuf_dpi = bgpg->dpi;
1183
1184   if (journal.npages < pageno) {
1185     pg = new_page_with_bg(bg, 
1186             gdk_pixbuf_get_width(bg->pixbuf)*72.0/bg->pixbuf_dpi,
1187             gdk_pixbuf_get_height(bg->pixbuf)*72.0/bg->pixbuf_dpi);
1188     journal.pages = g_list_append(journal.pages, pg);
1189     journal.npages++;
1190   } else {
1191     pg->width = gdk_pixbuf_get_width(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1192     pg->height = gdk_pixbuf_get_height(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1193     make_page_clipbox(pg);
1194     update_canvas_bg(pg);
1195   }
1196   update_page_stuff();
1197 }
1198
1199 // look for all journal pages with given pdf bg, and update their bg pixmaps
1200 void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg)
1201 {
1202   GList *list;
1203   struct Page *pg;
1204   
1205   for (list = journal.pages; list!= NULL; list = list->next) {
1206     pg = (struct Page *)list->data;
1207     if (pg->bg->type == BG_PDF && pg->bg->file_page_seq == pageno) {
1208       if (pg->bg->pixbuf!=NULL) gdk_pixbuf_unref(pg->bg->pixbuf);
1209       pg->bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1210       pg->bg->pixbuf_dpi = bgpg->dpi;
1211       update_canvas_bg(pg);
1212     }
1213   }
1214 }
1215
1216 // initialize the recent files list
1217 void init_mru(void)
1218 {
1219   int i;
1220   gsize lfptr;
1221   char s[5];
1222   GIOChannel *f;
1223   gchar *str;
1224   GIOStatus status;
1225   
1226   g_strlcpy(s, "mru0", 5);
1227   for (s[3]='0', i=0; i<MRU_SIZE; s[3]++, i++) {
1228     ui.mrumenu[i] = GET_COMPONENT(s);
1229     ui.mru[i] = NULL;
1230   }
1231   f = g_io_channel_new_file(ui.mrufile, "r", NULL);
1232   if (f) status = G_IO_STATUS_NORMAL;
1233   else status = G_IO_STATUS_ERROR;
1234   i = 0;
1235   while (status == G_IO_STATUS_NORMAL && i<MRU_SIZE) {
1236     lfptr = 0;
1237     status = g_io_channel_read_line(f, &str, NULL, &lfptr, NULL);
1238     if (status == G_IO_STATUS_NORMAL && lfptr>0) {
1239       str[lfptr] = 0;
1240       ui.mru[i] = str;
1241       i++;
1242     }
1243   }
1244   if (f) {
1245     g_io_channel_shutdown(f, FALSE, NULL);
1246     g_io_channel_unref(f);
1247   }
1248   update_mru_menu();
1249 }
1250
1251 void update_mru_menu(void)
1252 {
1253   int i;
1254   gboolean anyone = FALSE;
1255   gchar *tmp;
1256   
1257   for (i=0; i<MRU_SIZE; i++) {
1258     if (ui.mru[i]!=NULL) {
1259       tmp = g_strdup_printf("_%d %s", i+1, g_basename(ui.mru[i]));
1260       gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(ui.mrumenu[i]))),
1261           tmp);
1262       g_free(tmp);
1263       gtk_widget_show(ui.mrumenu[i]);
1264       anyone = TRUE;
1265     }
1266     else gtk_widget_hide(ui.mrumenu[i]);
1267   }
1268   gtk_widget_set_sensitive(GET_COMPONENT("fileRecentFiles"), anyone);
1269 }
1270
1271 void new_mru_entry(char *name)
1272 {
1273   int i, j;
1274   
1275   for (i=0;i<MRU_SIZE;i++) 
1276     if (ui.mru[i]!=NULL && !strcmp(ui.mru[i], name)) {
1277       g_free(ui.mru[i]);
1278       for (j=i+1; j<MRU_SIZE; j++) ui.mru[j-1] = ui.mru[j];
1279       ui.mru[MRU_SIZE-1]=NULL;
1280     }
1281   if (ui.mru[MRU_SIZE-1]!=NULL) g_free(ui.mru[MRU_SIZE-1]);
1282   for (j=MRU_SIZE-1; j>=1; j--) ui.mru[j] = ui.mru[j-1];
1283   ui.mru[0] = g_strdup(name);
1284   update_mru_menu();
1285 }
1286
1287 void delete_mru_entry(int which)
1288 {
1289   int i;
1290   
1291   if (ui.mru[which]!=NULL) g_free(ui.mru[which]);
1292   for (i=which+1;i<MRU_SIZE;i++) 
1293     ui.mru[i-1] = ui.mru[i];
1294   ui.mru[MRU_SIZE-1] = NULL;
1295   update_mru_menu();
1296 }
1297
1298 void save_mru_list(void)
1299 {
1300   FILE *f;
1301   int i;
1302   
1303   f = fopen(ui.mrufile, "w");
1304   if (f==NULL) return;
1305   for (i=0; i<MRU_SIZE; i++)
1306     if (ui.mru[i]!=NULL) fprintf(f, "%s\n", ui.mru[i]);
1307   fclose(f);
1308 }
1309
1310 void init_config_default(void)
1311 {
1312   int i, j;
1313
1314   DEFAULT_ZOOM = DISPLAY_DPI_DEFAULT/72.0;
1315   ui.zoom = ui.startup_zoom = 1.0*DEFAULT_ZOOM;
1316   ui.default_page.height = 792.0;
1317   ui.default_page.width = 612.0;
1318   ui.default_page.bg->type = BG_SOLID;
1319   ui.default_page.bg->color_no = COLOR_WHITE;
1320   ui.default_page.bg->color_rgba = predef_bgcolors_rgba[COLOR_WHITE];
1321   ui.default_page.bg->ruling = RULING_LINED;
1322   ui.view_continuous = TRUE;
1323   ui.allow_xinput = TRUE;
1324   ui.discard_corepointer = FALSE;
1325   ui.bg_apply_all_pages = FALSE;
1326   ui.use_erasertip = FALSE;
1327   ui.window_default_width = 720;
1328   ui.window_default_height = 480;
1329   ui.maximize_at_start = FALSE;
1330   ui.fullscreen = FALSE;
1331   ui.scrollbar_step_increment = 30;
1332   ui.zoom_step_increment = 1;
1333   ui.zoom_step_factor = 1.5;
1334   ui.antialias_bg = TRUE;
1335   ui.progressive_bg = TRUE;
1336   ui.print_ruling = TRUE;
1337   ui.default_unit = UNIT_CM;
1338   ui.default_path = NULL;
1339   ui.default_font_name = g_strdup(DEFAULT_FONT);
1340   ui.default_font_size = DEFAULT_FONT_SIZE;
1341   
1342   // the default UI vertical order
1343   ui.vertical_order[0][0] = 1; 
1344   ui.vertical_order[0][1] = 2; 
1345   ui.vertical_order[0][2] = 3; 
1346   ui.vertical_order[0][3] = 0; 
1347   ui.vertical_order[0][4] = 4;
1348   ui.vertical_order[1][0] = 2;
1349   ui.vertical_order[1][1] = 3;
1350   ui.vertical_order[1][2] = 0;
1351   ui.vertical_order[1][3] = ui.vertical_order[1][4] = -1;
1352
1353   ui.toolno[0] = ui.startuptool = TOOL_PEN;
1354   ui.ruler[0] = ui.startupruler = FALSE;
1355   for (i=1; i<=NUM_BUTTONS; i++) {
1356     ui.toolno[i] = TOOL_ERASER;
1357     ui.ruler[i] = FALSE;
1358   }
1359   for (i=0; i<=NUM_BUTTONS; i++)
1360     ui.linked_brush[i] = BRUSH_LINKED;
1361   ui.brushes[0][TOOL_PEN].color_no = COLOR_BLACK;
1362   ui.brushes[0][TOOL_ERASER].color_no = COLOR_WHITE;
1363   ui.brushes[0][TOOL_HIGHLIGHTER].color_no = COLOR_YELLOW;
1364   for (i=0; i < NUM_STROKE_TOOLS; i++) {
1365     ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM;
1366     ui.brushes[0][i].tool_options = 0;
1367   }
1368   for (i=0; i< NUM_STROKE_TOOLS; i++)
1369     for (j=1; j<=NUM_BUTTONS; j++)
1370       g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1371
1372   // predef_thickness is already initialized as a global variable
1373   GS_BITMAP_DPI = 144;
1374   PDFTOPPM_PRINTING_DPI = 150;
1375   
1376   ui.hiliter_opacity = 0.5;
1377 }
1378
1379 #if GLIB_CHECK_VERSION(2,6,0)
1380
1381 void update_keyval(const gchar *group_name, const gchar *key,
1382                 const gchar *comment, gchar *value)
1383 {
1384   gboolean has_it = g_key_file_has_key(ui.config_data, group_name, key, NULL);
1385   cleanup_numeric(value);
1386   g_key_file_set_value(ui.config_data, group_name, key, value);
1387   g_free(value);
1388   if (!has_it) g_key_file_set_comment(ui.config_data, group_name, key, comment, NULL);
1389 }
1390
1391 #endif
1392
1393 const char *vorder_usernames[VBOX_MAIN_NITEMS+1] = 
1394   {"drawarea", "menu", "main_toolbar", "pen_toolbar", "statusbar", NULL};
1395   
1396 gchar *verbose_vertical_order(int *order)
1397 {
1398   gchar buf[80], *p; // longer than needed
1399   int i;
1400
1401   p = buf;  
1402   for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1403     if (order[i]<0 || order[i]>=VBOX_MAIN_NITEMS) continue;
1404     if (p!=buf) *(p++) = ' ';
1405     p = g_stpcpy(p, vorder_usernames[order[i]]);
1406   }
1407   return g_strdup(buf);
1408 }
1409
1410 void save_config_to_file(void)
1411 {
1412   gchar *buf;
1413   FILE *f;
1414
1415 #if GLIB_CHECK_VERSION(2,6,0)
1416   // no support for keyval files before Glib 2.6.0
1417   if (glib_minor_version<6) return; 
1418
1419   // save some data...
1420   ui.maximize_at_start = (gdk_window_get_state(winMain->window) & GDK_WINDOW_STATE_MAXIMIZED);
1421   if (!ui.maximize_at_start && !ui.fullscreen)
1422     gdk_drawable_get_size(winMain->window, 
1423       &ui.window_default_width, &ui.window_default_height);
1424
1425   update_keyval("general", "display_dpi",
1426     " the display resolution, in pixels per inch",
1427     g_strdup_printf("%.2f", DEFAULT_ZOOM*72));
1428   update_keyval("general", "initial_zoom",
1429     " the initial zoom level, in percent",
1430     g_strdup_printf("%.2f", 100*ui.zoom/DEFAULT_ZOOM));
1431   update_keyval("general", "window_maximize",
1432     " maximize the window at startup (true/false)",
1433     g_strdup(ui.maximize_at_start?"true":"false"));
1434   update_keyval("general", "window_fullscreen",
1435     " start in full screen mode (true/false)",
1436     g_strdup(ui.fullscreen?"true":"false"));
1437   update_keyval("general", "window_width",
1438     " the window width in pixels (when not maximized)",
1439     g_strdup_printf("%d", ui.window_default_width));
1440   update_keyval("general", "window_height",
1441     " the window height in pixels",
1442     g_strdup_printf("%d", ui.window_default_height));
1443   update_keyval("general", "scrollbar_speed",
1444     " scrollbar step increment (in pixels)",
1445     g_strdup_printf("%d", ui.scrollbar_step_increment));
1446   update_keyval("general", "zoom_dialog_increment",
1447     " the step increment in the zoom dialog box",
1448     g_strdup_printf("%d", ui.zoom_step_increment));
1449   update_keyval("general", "zoom_step_factor",
1450     " the multiplicative factor for zoom in/out",
1451     g_strdup_printf("%.3f", ui.zoom_step_factor));
1452   update_keyval("general", "view_continuous",
1453     " document view (true = continuous, false = single page)",
1454     g_strdup(ui.view_continuous?"true":"false"));
1455   update_keyval("general", "use_xinput",
1456     " use XInput extensions (true/false)",
1457     g_strdup(ui.allow_xinput?"true":"false"));
1458   update_keyval("general", "discard_corepointer",
1459     " discard Core Pointer events in XInput mode (true/false)",
1460     g_strdup(ui.discard_corepointer?"true":"false"));
1461   update_keyval("general", "use_erasertip",
1462     " always map eraser tip to eraser (true/false)",
1463     g_strdup(ui.use_erasertip?"true":"false"));
1464   update_keyval("general", "default_path",
1465     " default path for open/save (leave blank for current directory)",
1466     g_strdup((ui.default_path!=NULL)?ui.default_path:""));
1467   update_keyval("general", "interface_order",
1468     " interface components from top to bottom\n valid values: drawarea menu main_toolbar pen_toolbar statusbar",
1469     verbose_vertical_order(ui.vertical_order[0]));
1470   update_keyval("general", "interface_fullscreen",
1471     " interface components in fullscreen mode, from top to bottom",
1472     verbose_vertical_order(ui.vertical_order[1]));
1473   update_keyval("general", "highlighter_opacity",
1474     " highlighter opacity (0 to 1, default 0.5)\n warning: opacity level is not saved in xoj files!",
1475     g_strdup_printf("%.2f", ui.hiliter_opacity));
1476
1477   update_keyval("paper", "width",
1478     " the default page width, in points (1/72 in)",
1479     g_strdup_printf("%.2f", ui.default_page.width));
1480   update_keyval("paper", "height",
1481     " the default page height, in points (1/72 in)",
1482     g_strdup_printf("%.2f", ui.default_page.height));
1483   update_keyval("paper", "color",
1484     " the default paper color",
1485     g_strdup(bgcolor_names[ui.default_page.bg->color_no]));
1486   update_keyval("paper", "style",
1487     " the default paper style (plain, lined, ruled, or graph)",
1488     g_strdup(bgstyle_names[ui.default_page.bg->ruling]));
1489   update_keyval("paper", "apply_all",
1490     " apply paper style changes to all pages (true/false)",
1491     g_strdup(ui.bg_apply_all_pages?"true":"false"));
1492   update_keyval("paper", "default_unit",
1493     " preferred unit (cm, in, px, pt)",
1494     g_strdup(unit_names[ui.default_unit]));
1495   update_keyval("paper", "print_ruling",
1496     " include paper ruling when printing or exporting to PDF (true/false)",
1497     g_strdup(ui.print_ruling?"true":"false"));
1498   update_keyval("paper", "antialias_bg",
1499     " antialiased bitmap backgrounds (true/false)",
1500     g_strdup(ui.antialias_bg?"true":"false"));
1501   update_keyval("paper", "progressive_bg",
1502     " progressive scaling of bitmap backgrounds (true/false)",
1503     g_strdup(ui.progressive_bg?"true":"false"));
1504   update_keyval("paper", "gs_bitmap_dpi",
1505     " bitmap resolution of PS/PDF backgrounds rendered using ghostscript (dpi)",
1506     g_strdup_printf("%d", GS_BITMAP_DPI));
1507   update_keyval("paper", "pdftoppm_printing_dpi",
1508     " bitmap resolution of PDF backgrounds when printing with libgnomeprint (dpi)",
1509     g_strdup_printf("%d", PDFTOPPM_PRINTING_DPI));
1510
1511   update_keyval("tools", "startup_tool",
1512     " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)",
1513     g_strdup(tool_names[ui.startuptool]));
1514   update_keyval("tools", "startup_ruler",
1515     " ruler mode at startup (true/false) (for pen or highlighter only)",
1516     g_strdup(ui.startupruler?"true":"false"));
1517   update_keyval("tools", "pen_color",
1518     " default pen color",
1519     g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no]));
1520   update_keyval("tools", "pen_thickness",
1521     " default pen thickness (fine = 1, medium = 2, thick = 3)",
1522     g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no));
1523   update_keyval("tools", "eraser_thickness",
1524     " default eraser thickness (fine = 1, medium = 2, thick = 3)",
1525     g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no));
1526   update_keyval("tools", "eraser_mode",
1527     " default eraser mode (standard = 0, whiteout = 1, strokes = 2)",
1528     g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].tool_options));
1529   update_keyval("tools", "highlighter_color",
1530     " default highlighter color",
1531     g_strdup(color_names[ui.default_brushes[TOOL_HIGHLIGHTER].color_no]));
1532   update_keyval("tools", "highlighter_thickness",
1533     " default highlighter thickness (fine = 1, medium = 2, thick = 3)",
1534     g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no));
1535   update_keyval("tools", "btn2_tool",
1536     " button 2 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)",
1537     g_strdup(tool_names[ui.toolno[1]]));
1538   update_keyval("tools", "btn2_linked",
1539     " button 2 brush linked to primary brush (true/false) (overrides all other settings)",
1540     g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false"));
1541   update_keyval("tools", "btn2_ruler",
1542     " button 2 ruler mode (true/false) (for pen or highlighter only)",
1543     g_strdup(ui.ruler[1]?"true":"false"));
1544   update_keyval("tools", "btn2_color",
1545     " button 2 brush color (for pen or highlighter only)",
1546     g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)?
1547                color_names[ui.brushes[1][ui.toolno[1]].color_no]:"white"));
1548   update_keyval("tools", "btn2_thickness",
1549     " button 2 brush thickness (pen, eraser, or highlighter only)",
1550     g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)?
1551                             ui.brushes[1][ui.toolno[1]].thickness_no:0));
1552   update_keyval("tools", "btn2_erasermode",
1553     " button 2 eraser mode (eraser only)",
1554     g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options));
1555   update_keyval("tools", "btn3_tool",
1556     " button 3 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)",
1557     g_strdup(tool_names[ui.toolno[2]]));
1558   update_keyval("tools", "btn3_linked",
1559     " button 3 brush linked to primary brush (true/false) (overrides all other settings)",
1560     g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false"));
1561   update_keyval("tools", "btn3_ruler",
1562     " button 3 ruler mode (true/false) (for pen or highlighter only)",
1563     g_strdup(ui.ruler[2]?"true":"false"));
1564   update_keyval("tools", "btn3_color",
1565     " button 3 brush color (for pen or highlighter only)",
1566     g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)?
1567                color_names[ui.brushes[2][ui.toolno[2]].color_no]:"white"));
1568   update_keyval("tools", "btn3_thickness",
1569     " button 3 brush thickness (pen, eraser, or highlighter only)",
1570     g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)?
1571                             ui.brushes[2][ui.toolno[2]].thickness_no:0));
1572   update_keyval("tools", "btn3_erasermode",
1573     " button 3 eraser mode (eraser only)",
1574     g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options));
1575
1576   update_keyval("tools", "pen_thicknesses",
1577     " thickness of the various pens (in points, 1 pt = 1/72 in)",
1578     g_strdup_printf("%.2f;%.2f;%.2f;%.2f;%.2f", 
1579       predef_thickness[TOOL_PEN][0], predef_thickness[TOOL_PEN][1],
1580       predef_thickness[TOOL_PEN][2], predef_thickness[TOOL_PEN][3],
1581       predef_thickness[TOOL_PEN][4]));
1582   update_keyval("tools", "eraser_thicknesses",
1583     " thickness of the various erasers (in points, 1 pt = 1/72 in)",
1584     g_strdup_printf("%.2f;%.2f;%.2f", 
1585       predef_thickness[TOOL_ERASER][1], predef_thickness[TOOL_ERASER][2],
1586       predef_thickness[TOOL_ERASER][3]));
1587   update_keyval("tools", "highlighter_thicknesses",
1588     " thickness of the various highlighters (in points, 1 pt = 1/72 in)",
1589     g_strdup_printf("%.2f;%.2f;%.2f", 
1590       predef_thickness[TOOL_HIGHLIGHTER][1], predef_thickness[TOOL_HIGHLIGHTER][2],
1591       predef_thickness[TOOL_HIGHLIGHTER][3]));
1592   update_keyval("tools", "default_font",
1593     " name of the default font",
1594     g_strdup(ui.default_font_name));
1595   update_keyval("tools", "default_font_size",
1596     " default font size",
1597     g_strdup_printf("%.1f", ui.default_font_size));
1598
1599   buf = g_key_file_to_data(ui.config_data, NULL, NULL);
1600   if (buf == NULL) return;
1601   f = fopen(ui.configfile, "w");
1602   if (f==NULL) { g_free(buf); return; }
1603   fputs(buf, f);
1604   fclose(f);
1605   g_free(buf);
1606 #endif
1607 }
1608
1609 #if GLIB_CHECK_VERSION(2,6,0)
1610 gboolean parse_keyval_float(const gchar *group, const gchar *key, double *val, double inf, double sup)
1611 {
1612   gchar *ret, *end;
1613   double conv;
1614   
1615   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1616   if (ret==NULL) return FALSE;
1617   conv = g_ascii_strtod(ret, &end);
1618   if (*end!=0) { g_free(ret); return FALSE; }
1619   g_free(ret);
1620   if (conv < inf || conv > sup) return FALSE;
1621   *val = conv;
1622   return TRUE;
1623 }
1624
1625 gboolean parse_keyval_floatlist(const gchar *group, const gchar *key, double *val, int n, double inf, double sup)
1626 {
1627   gchar *ret, *end;
1628   double conv[5];
1629   int i;
1630
1631   if (n>5) return FALSE;
1632   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1633   if (ret==NULL) return FALSE;
1634   end = ret;
1635   for (i=0; i<n; i++) {
1636     conv[i] = g_ascii_strtod(end, &end);
1637     if ((i==n-1 && *end!=0) || (i<n-1 && *end!=';') ||
1638         (conv[i] < inf) || (conv[i] > sup)) { g_free(ret); return FALSE; }
1639     end++;
1640   }
1641   g_free(ret);
1642   for (i=0; i<n; i++) val[i] = conv[i];
1643   return TRUE;
1644 }
1645
1646 gboolean parse_keyval_int(const gchar *group, const gchar *key, int *val, int inf, int sup)
1647 {
1648   gchar *ret, *end;
1649   int conv;
1650   
1651   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1652   if (ret==NULL) return FALSE;
1653   conv = strtol(ret, &end, 10);
1654   if (*end!=0) { g_free(ret); return FALSE; }
1655   g_free(ret);
1656   if (conv < inf || conv > sup) return FALSE;
1657   *val = conv;
1658   return TRUE;
1659 }
1660
1661 gboolean parse_keyval_enum(const gchar *group, const gchar *key, int *val, const char **names, int n)
1662 {
1663   gchar *ret;
1664   int i;
1665   
1666   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1667   if (ret==NULL) return FALSE;
1668   for (i=0; i<n; i++) {
1669     if (!names[i][0]) continue; // "" is for invalid values
1670     if (!g_ascii_strcasecmp(ret, names[i]))
1671       { *val = i; g_free(ret); return TRUE; }
1672   }
1673   return FALSE;
1674 }
1675
1676 gboolean parse_keyval_boolean(const gchar *group, const gchar *key, gboolean *val)
1677 {
1678   gchar *ret;
1679   
1680   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1681   if (ret==NULL) return FALSE;
1682   if (!g_ascii_strcasecmp(ret, "true")) 
1683     { *val = TRUE; g_free(ret); return TRUE; }
1684   if (!g_ascii_strcasecmp(ret, "false")) 
1685     { *val = FALSE; g_free(ret); return TRUE; }
1686   g_free(ret);
1687   return FALSE;
1688 }
1689
1690 gboolean parse_keyval_string(const gchar *group, const gchar *key, gchar **val)
1691 {
1692   gchar *ret;
1693   
1694   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1695   if (ret==NULL) return FALSE;
1696   if (strlen(ret) == 0) {
1697     *val = NULL;
1698     g_free(ret);
1699   } 
1700   else *val = ret;
1701   return TRUE;
1702 }
1703
1704 gboolean parse_keyval_vorderlist(const gchar *group, const gchar *key, int *order)
1705 {
1706   gchar *ret, *p;
1707   int tmp[VBOX_MAIN_NITEMS];
1708   int i, n, found, l;
1709
1710   ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1711   if (ret==NULL) return FALSE;
1712   
1713   for (i=0; i<VBOX_MAIN_NITEMS; i++) tmp[i] = -1;
1714   n = 0; p = ret;
1715   while (*p==' ') p++;
1716   while (*p!=0) {
1717     if (n>VBOX_MAIN_NITEMS) return FALSE; // too many items
1718     for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1719       if (!g_str_has_prefix(p, vorder_usernames[i])) continue;
1720       l = strlen(vorder_usernames[i]);
1721       if (p[l]==' '||p[l]==0) { p+=l; break; }
1722     }
1723     if (i>=VBOX_MAIN_NITEMS) { g_free(ret); return FALSE; } // parse error
1724     // we found item #i
1725     tmp[n++] = i;
1726     while (*p==' ') p++;
1727   }
1728   
1729   for (n=0; n<VBOX_MAIN_NITEMS; n++) order[n] = tmp[n];
1730   g_free(ret);
1731   return TRUE;
1732 }
1733
1734 #endif
1735
1736 void load_config_from_file(void)
1737 {
1738   double f;
1739   gboolean b;
1740   int i, j;
1741   gchar *str;
1742   
1743 #if GLIB_CHECK_VERSION(2,6,0)
1744   // no support for keyval files before Glib 2.6.0
1745   if (glib_minor_version<6) return; 
1746   ui.config_data = g_key_file_new();
1747   if (!g_key_file_load_from_file(ui.config_data, ui.configfile, 
1748          G_KEY_FILE_KEEP_COMMENTS, NULL)) {
1749     g_key_file_free(ui.config_data);
1750     ui.config_data = g_key_file_new();
1751     g_key_file_set_comment(ui.config_data, NULL, NULL, 
1752            " Xournal configuration file.\n"
1753            " This file is generated automatically upon saving preferences.\n"
1754            " Use caution when editing this file manually.\n", NULL);
1755     return;
1756   }
1757
1758   // parse keys from the keyfile to set defaults
1759   if (parse_keyval_float("general", "display_dpi", &f, 10., 500.))
1760     DEFAULT_ZOOM = f/72.0;
1761   if (parse_keyval_float("general", "initial_zoom", &f, 
1762               MIN_ZOOM*100/DEFAULT_ZOOM, MAX_ZOOM*100/DEFAULT_ZOOM))
1763     ui.zoom = ui.startup_zoom = DEFAULT_ZOOM*f/100.0;
1764   parse_keyval_boolean("general", "window_maximize", &ui.maximize_at_start);
1765   parse_keyval_boolean("general", "window_fullscreen", &ui.fullscreen);
1766   parse_keyval_int("general", "window_width", &ui.window_default_width, 10, 5000);
1767   parse_keyval_int("general", "window_height", &ui.window_default_height, 10, 5000);
1768   parse_keyval_int("general", "scrollbar_speed", &ui.scrollbar_step_increment, 1, 5000);
1769   parse_keyval_int("general", "zoom_dialog_increment", &ui.zoom_step_increment, 1, 500);
1770   parse_keyval_float("general", "zoom_step_factor", &ui.zoom_step_factor, 1., 5.);
1771   parse_keyval_boolean("general", "view_continuous", &ui.view_continuous);
1772   parse_keyval_boolean("general", "use_xinput", &ui.allow_xinput);
1773   parse_keyval_boolean("general", "discard_corepointer", &ui.discard_corepointer);
1774   parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip);
1775   parse_keyval_string("general", "default_path", &ui.default_path);
1776   parse_keyval_vorderlist("general", "interface_order", ui.vertical_order[0]);
1777   parse_keyval_vorderlist("general", "interface_fullscreen", ui.vertical_order[1]);
1778   parse_keyval_float("general", "highlighter_opacity", &ui.hiliter_opacity, 0., 1.);
1779   
1780   parse_keyval_float("paper", "width", &ui.default_page.width, 1., 5000.);
1781   parse_keyval_float("paper", "height", &ui.default_page.height, 1., 5000.);
1782   parse_keyval_enum("paper", "color", &(ui.default_page.bg->color_no), bgcolor_names, COLOR_MAX);
1783   ui.default_page.bg->color_rgba = predef_bgcolors_rgba[ui.default_page.bg->color_no];
1784   parse_keyval_enum("paper", "style", &(ui.default_page.bg->ruling), bgstyle_names, 4);
1785   parse_keyval_boolean("paper", "apply_all", &ui.bg_apply_all_pages);
1786   parse_keyval_enum("paper", "default_unit", &ui.default_unit, unit_names, 4);
1787   parse_keyval_boolean("paper", "antialias_bg", &ui.antialias_bg);
1788   parse_keyval_boolean("paper", "progressive_bg", &ui.progressive_bg);
1789   parse_keyval_boolean("paper", "print_ruling", &ui.print_ruling);
1790   parse_keyval_int("paper", "gs_bitmap_dpi", &GS_BITMAP_DPI, 1, 1200);
1791   parse_keyval_int("paper", "pdftoppm_printing_dpi", &PDFTOPPM_PRINTING_DPI, 1, 1200);
1792
1793   parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS);
1794   ui.toolno[0] = ui.startuptool;
1795   if (ui.startuptool == TOOL_PEN || ui.startuptool == TOOL_HIGHLIGHTER) {
1796     parse_keyval_boolean("tools", "startup_ruler", &ui.startupruler);
1797     ui.ruler[0] = ui.startupruler;
1798   }
1799   parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX);
1800   parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4);
1801   parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3);
1802   parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2);
1803   parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX);
1804   parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4);
1805   for (i=0; i< NUM_STROKE_TOOLS; i++)
1806     for (j=1; j<=NUM_BUTTONS; j++)
1807       g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1808
1809   parse_keyval_enum("tools", "btn2_tool", &(ui.toolno[1]), tool_names, NUM_TOOLS);
1810   if (parse_keyval_boolean("tools", "btn2_linked", &b))
1811     ui.linked_brush[1] = b?BRUSH_LINKED:BRUSH_STATIC;
1812   parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS);
1813   if (parse_keyval_boolean("tools", "btn3_linked", &b))
1814     ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC;
1815   for (i=1; i<=NUM_BUTTONS; i++)
1816     if (ui.toolno[i]==TOOL_PEN || ui.toolno[i]==TOOL_HIGHLIGHTER)
1817       ui.ruler[i] = ui.ruler[0];
1818   if (ui.linked_brush[1]!=BRUSH_LINKED) {
1819     if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) {
1820       parse_keyval_boolean("tools", "btn2_ruler", &(ui.ruler[1]));
1821       parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX);
1822     }
1823     if (ui.toolno[1]<NUM_STROKE_TOOLS)
1824       parse_keyval_int("tools", "btn2_thickness", &(ui.brushes[1][ui.toolno[1]].thickness_no), 0, 4);
1825     if (ui.toolno[1]==TOOL_ERASER)
1826       parse_keyval_int("tools", "btn2_erasermode", &(ui.brushes[1][TOOL_ERASER].tool_options), 0, 2);
1827   }
1828   if (ui.linked_brush[2]!=BRUSH_LINKED) {
1829     if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) {
1830       parse_keyval_boolean("tools", "btn3_ruler", &(ui.ruler[2]));
1831       parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX);
1832     }
1833     if (ui.toolno[2]<NUM_STROKE_TOOLS)
1834       parse_keyval_int("tools", "btn3_thickness", &(ui.brushes[2][ui.toolno[2]].thickness_no), 0, 4);
1835     if (ui.toolno[2]==TOOL_ERASER)
1836       parse_keyval_int("tools", "btn3_erasermode", &(ui.brushes[2][TOOL_ERASER].tool_options), 0, 2);
1837   }
1838   parse_keyval_floatlist("tools", "pen_thicknesses", predef_thickness[TOOL_PEN], 5, 0.01, 1000.0);
1839   parse_keyval_floatlist("tools", "eraser_thicknesses", predef_thickness[TOOL_ERASER]+1, 3, 0.01, 1000.0);
1840   parse_keyval_floatlist("tools", "highlighter_thicknesses", predef_thickness[TOOL_HIGHLIGHTER]+1, 3, 0.01, 1000.0);
1841   if (parse_keyval_string("tools", "default_font", &str))
1842     if (str!=NULL) { g_free(ui.default_font_name); ui.default_font_name = str; }
1843   parse_keyval_float("tools", "default_font_size", &ui.default_font_size, 1., 200.);
1844 #endif
1845 }