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