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