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