11 #include <libgnomecanvas/libgnomecanvas.h>
18 #include <glib/gstdio.h>
21 #include "xo-interface.h"
22 #include "xo-support.h"
23 #include "xo-callbacks.h"
28 const char *tool_names[NUM_TOOLS] = {"pen", "eraser", "highlighter", "text", "", "selectrect", "vertspace", "hand"};
29 const char *color_names[COLOR_MAX] = {"black", "blue", "red", "green",
30 "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white"};
31 const char *bgtype_names[3] = {"solid", "pixmap", "pdf"};
32 const char *bgcolor_names[COLOR_MAX] = {"", "blue", "pink", "green",
33 "", "", "", "", "orange", "yellow", "white"};
34 const char *bgstyle_names[4] = {"plain", "lined", "ruled", "graph"};
35 const char *file_domain_names[3] = {"absolute", "attach", "clone"};
36 const char *unit_names[4] = {"cm", "in", "px", "pt"};
37 int PDFTOPPM_PRINTING_DPI, GS_BITMAP_DPI;
39 // creates a new empty journal
41 void new_journal(void)
44 journal.pages = g_list_append(NULL, new_page(&ui.default_page));
45 journal.last_attach_no = 0;
48 ui.cur_page = (struct Page *) journal.pages->data;
49 ui.cur_layer = (struct Layer *) ui.cur_page->layers->data;
52 update_file_name(NULL);
55 // check attachment names
57 void chk_attach_names(void)
60 struct Background *bg;
62 for (list = journal.pages; list!=NULL; list = list->next) {
63 bg = ((struct Page *)list->data)->bg;
64 if (bg->type == BG_SOLID || bg->file_domain != DOMAIN_ATTACH ||
65 bg->filename->s != NULL) continue;
66 bg->filename->s = g_strdup_printf("bg_%d.png", ++journal.last_attach_no);
70 // saves the journal to a file: returns true on success, false on error
72 gboolean save_journal(const char *filename)
75 struct Page *pg, *tmppg;
84 GList *pagelist, *layerlist, *itemlist, *list;
87 f = gzopen(filename, "w");
88 if (f==NULL) return FALSE;
91 setlocale(LC_NUMERIC, "C");
93 gzprintf(f, "<?xml version=\"1.0\" standalone=\"no\"?>\n"
94 "<xournal version=\"" VERSION "\">\n"
95 "<title>Xournal document - see http://math.mit.edu/~auroux/software/xournal/</title>\n");
96 for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next) {
97 pg = (struct Page *)pagelist->data;
98 gzprintf(f, "<page width=\"%.2f\" height=\"%.2f\">\n", pg->width, pg->height);
99 gzprintf(f, "<background type=\"%s\" ", bgtype_names[pg->bg->type]);
100 if (pg->bg->type == BG_SOLID) {
101 gzputs(f, "color=\"");
102 if (pg->bg->color_no >= 0) gzputs(f, bgcolor_names[pg->bg->color_no]);
103 else gzprintf(f, "#%08x", pg->bg->color_rgba);
104 gzprintf(f, "\" style=\"%s\" ", bgstyle_names[pg->bg->ruling]);
106 else if (pg->bg->type == BG_PIXMAP) {
108 for (list = journal.pages, i = 0; list!=pagelist; list = list->next, i++) {
109 tmppg = (struct Page *)list->data;
110 if (tmppg->bg->type == BG_PIXMAP &&
111 tmppg->bg->pixbuf == pg->bg->pixbuf &&
112 tmppg->bg->filename == pg->bg->filename)
113 { is_clone = i; break; }
116 gzprintf(f, "domain=\"clone\" filename=\"%d\" ", is_clone);
118 if (pg->bg->file_domain == DOMAIN_ATTACH) {
119 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
120 if (!gdk_pixbuf_save(pg->bg->pixbuf, tmpfn, "png", NULL, NULL)) {
121 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
122 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
123 "Could not write background '%s'. Continuing anyway.", tmpfn);
124 gtk_dialog_run(GTK_DIALOG(dialog));
125 gtk_widget_destroy(dialog);
129 tmpstr = g_markup_escape_text(pg->bg->filename->s, -1);
130 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
131 file_domain_names[pg->bg->file_domain], tmpstr);
135 else if (pg->bg->type == BG_PDF) {
137 for (list = journal.pages; list!=pagelist; list = list->next) {
138 tmppg = (struct Page *)list->data;
139 if (tmppg->bg->type == BG_PDF) { is_clone = 1; break; }
142 if (pg->bg->file_domain == DOMAIN_ATTACH) {
143 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
145 if (bgpdf.status != STATUS_NOT_INIT &&
146 g_file_get_contents(bgpdf.tmpfile_copy, &pdfbuf, &pdflen, NULL))
148 tmpf = fopen(tmpfn, "w");
149 if (tmpf != NULL && fwrite(pdfbuf, 1, pdflen, tmpf) == pdflen)
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);
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);
168 gzprintf(f, "pageno=\"%d\" ", pg->bg->file_page_seq);
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]);
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");
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);
197 if (item->brush.color_no >= 0)
198 gzputs(f, color_names[item->brush.color_no]);
200 gzprintf(f, "#%08x", item->brush.color_rgba);
201 tmpstr = g_markup_escape_text(item->text, -1);
202 gzprintf(f, "\">%s</text>\n", tmpstr);
206 gzprintf(f, "</layer>\n");
208 gzprintf(f, "</page>\n");
210 gzprintf(f, "</xournal>\n");
212 setlocale(LC_NUMERIC, "");
217 // closes a journal: returns true on success, false on abort
219 gboolean close_journal(void)
221 if (!ok_to_close()) return FALSE;
223 // free everything...
229 delete_journal(&journal);
232 /* note: various members of ui and journal are now in invalid states,
233 use new_journal() to reinitialize them */
236 // sanitize a string containing floats, in case it may have , instead of .
238 void cleanup_numeric(char *s)
240 while (*s!=0) { if (*s==',') *s='.'; s++; }
243 // the XML parser functions for open_journal()
245 struct Journal tmpJournal;
246 struct Page *tmpPage;
247 struct Layer *tmpLayer;
248 struct Item *tmpItem;
250 struct Background *tmpBg_pdf;
252 GError *xoj_invalid(void)
254 return g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Invalid file contents");
257 void xoj_parser_start_element(GMarkupParseContext *context,
258 const gchar *element_name, const gchar **attribute_names,
259 const gchar **attribute_values, gpointer user_data, GError **error)
263 struct Background *tmpbg;
264 char *tmpbg_filename;
268 if (!strcmp(element_name, "title") || !strcmp(element_name, "xournal")) {
269 if (tmpPage != NULL) {
270 *error = xoj_invalid();
273 // nothing special to do
275 else if (!strcmp(element_name, "page")) { // start of a page
276 if (tmpPage != NULL) {
277 *error = xoj_invalid();
280 tmpPage = (struct Page *)g_malloc(sizeof(struct Page));
281 tmpPage->layers = NULL;
282 tmpPage->nlayers = 0;
283 tmpPage->group = NULL;
284 tmpPage->bg = g_new(struct Background, 1);
285 tmpPage->bg->type = -1;
286 tmpPage->bg->canvas_item = NULL;
287 tmpPage->bg->pixbuf = NULL;
288 tmpPage->bg->filename = NULL;
289 tmpJournal.pages = g_list_append(tmpJournal.pages, tmpPage);
291 // scan for height and width attributes
293 while (*attribute_names!=NULL) {
294 if (!strcmp(*attribute_names, "width")) {
295 if (has_attr & 1) *error = xoj_invalid();
296 cleanup_numeric((gchar *)*attribute_values);
297 tmpPage->width = g_ascii_strtod(*attribute_values, &ptr);
298 if (ptr == *attribute_values) *error = xoj_invalid();
301 else if (!strcmp(*attribute_names, "height")) {
302 if (has_attr & 2) *error = xoj_invalid();
303 cleanup_numeric((gchar *)*attribute_values);
304 tmpPage->height = g_ascii_strtod(*attribute_values, &ptr);
305 if (ptr == *attribute_values) *error = xoj_invalid();
308 else *error = xoj_invalid();
312 if (has_attr!=3) *error = xoj_invalid();
314 else if (!strcmp(element_name, "background")) {
315 if (tmpPage == NULL || tmpLayer !=NULL || tmpPage->bg->type >= 0) {
316 *error = xoj_invalid();
320 while (*attribute_names!=NULL) {
321 if (!strcmp(*attribute_names, "type")) {
322 if (has_attr) *error = xoj_invalid();
324 if (!strcmp(*attribute_values, bgtype_names[i]))
325 tmpPage->bg->type = i;
326 if (tmpPage->bg->type < 0) *error = xoj_invalid();
328 if (tmpPage->bg->type == BG_PDF) {
329 if (tmpBg_pdf == NULL) tmpBg_pdf = tmpPage->bg;
332 tmpPage->bg->filename = refstring_ref(tmpBg_pdf->filename);
333 tmpPage->bg->file_domain = tmpBg_pdf->file_domain;
337 else if (!strcmp(*attribute_names, "color")) {
338 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
339 if (has_attr & 2) *error = xoj_invalid();
340 tmpPage->bg->color_no = COLOR_OTHER;
341 for (i=0; i<COLOR_MAX; i++)
342 if (!strcmp(*attribute_values, bgcolor_names[i])) {
343 tmpPage->bg->color_no = i;
344 tmpPage->bg->color_rgba = predef_bgcolors_rgba[i];
346 // there's also the case of hex (#rrggbbaa) colors
347 if (tmpPage->bg->color_no == COLOR_OTHER && **attribute_values == '#') {
348 tmpPage->bg->color_rgba = strtol(*attribute_values + 1, &ptr, 16);
349 if (*ptr!=0) *error = xoj_invalid();
353 else if (!strcmp(*attribute_names, "style")) {
354 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
355 if (has_attr & 4) *error = xoj_invalid();
356 tmpPage->bg->ruling = -1;
358 if (!strcmp(*attribute_values, bgstyle_names[i]))
359 tmpPage->bg->ruling = i;
360 if (tmpPage->bg->ruling < 0) *error = xoj_invalid();
363 else if (!strcmp(*attribute_names, "domain")) {
364 if (tmpPage->bg->type <= BG_SOLID || (has_attr & 8))
365 { *error = xoj_invalid(); return; }
366 tmpPage->bg->file_domain = -1;
368 if (!strcmp(*attribute_values, file_domain_names[i]))
369 tmpPage->bg->file_domain = i;
370 if (tmpPage->bg->file_domain < 0)
371 { *error = xoj_invalid(); return; }
374 else if (!strcmp(*attribute_names, "filename")) {
375 if (tmpPage->bg->type <= BG_SOLID || (has_attr != 9))
376 { *error = xoj_invalid(); return; }
377 if (tmpPage->bg->file_domain == DOMAIN_CLONE) {
378 // filename is a page number
379 i = strtol(*attribute_values, &ptr, 10);
380 if (ptr == *attribute_values || i < 0 || i > tmpJournal.npages-2)
381 { *error = xoj_invalid(); return; }
382 tmpbg = ((struct Page *)g_list_nth_data(tmpJournal.pages, i))->bg;
383 if (tmpbg->type != tmpPage->bg->type)
384 { *error = xoj_invalid(); return; }
385 tmpPage->bg->filename = refstring_ref(tmpbg->filename);
386 tmpPage->bg->pixbuf = tmpbg->pixbuf;
387 if (tmpbg->pixbuf!=NULL) gdk_pixbuf_ref(tmpbg->pixbuf);
388 tmpPage->bg->file_domain = tmpbg->file_domain;
391 tmpPage->bg->filename = new_refstring(*attribute_values);
392 if (tmpPage->bg->type == BG_PIXMAP) {
393 if (tmpPage->bg->file_domain == DOMAIN_ATTACH) {
394 tmpbg_filename = g_strdup_printf("%s.%s", tmpFilename, *attribute_values);
395 if (sscanf(*attribute_values, "bg_%d.png", &i) == 1)
396 if (i > tmpJournal.last_attach_no)
397 tmpJournal.last_attach_no = i;
399 else tmpbg_filename = g_strdup(*attribute_values);
400 tmpPage->bg->pixbuf = gdk_pixbuf_new_from_file(tmpbg_filename, NULL);
401 if (tmpPage->bg->pixbuf == NULL) {
402 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
403 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
404 "Could not open background '%s'. Setting background to white.",
406 gtk_dialog_run(GTK_DIALOG(dialog));
407 gtk_widget_destroy(dialog);
408 tmpPage->bg->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
409 gdk_pixbuf_fill(tmpPage->bg->pixbuf, 0xffffffff); // solid white
411 g_free(tmpbg_filename);
416 else if (!strcmp(*attribute_names, "pageno")) {
417 if (tmpPage->bg->type != BG_PDF || (has_attr & 32))
418 { *error = xoj_invalid(); return; }
419 tmpPage->bg->file_page_seq = strtol(*attribute_values, &ptr, 10);
420 if (ptr == *attribute_values) *error = xoj_invalid();
423 else *error = xoj_invalid();
427 if (tmpPage->bg->type < 0) *error = xoj_invalid();
428 if (tmpPage->bg->type == BG_SOLID && has_attr != 7) *error = xoj_invalid();
429 if (tmpPage->bg->type == BG_PIXMAP && has_attr != 25) *error = xoj_invalid();
430 if (tmpPage->bg->type == BG_PDF && has_attr != 57) *error = xoj_invalid();
432 else if (!strcmp(element_name, "layer")) { // start of a layer
433 if (tmpPage == NULL || tmpLayer != NULL) {
434 *error = xoj_invalid();
437 tmpLayer = (struct Layer *)g_malloc(sizeof(struct Layer));
438 tmpLayer->items = NULL;
439 tmpLayer->nitems = 0;
440 tmpLayer->group = NULL;
441 tmpPage->layers = g_list_append(tmpPage->layers, tmpLayer);
444 else if (!strcmp(element_name, "stroke")) { // start of a stroke
445 if (tmpLayer == NULL || tmpItem != NULL) {
446 *error = xoj_invalid();
449 tmpItem = (struct Item *)g_malloc(sizeof(struct Item));
450 tmpItem->type = ITEM_STROKE;
451 tmpItem->path = NULL;
452 tmpItem->canvas_item = NULL;
453 tmpItem->widths = NULL;
454 tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
456 // scan for tool, color, and width attributes
458 while (*attribute_names!=NULL) {
459 if (!strcmp(*attribute_names, "width")) {
460 if (has_attr & 1) *error = xoj_invalid();
461 cleanup_numeric((gchar *)*attribute_values);
462 tmpItem->brush.thickness = g_ascii_strtod(*attribute_values, &ptr);
463 if (ptr == *attribute_values) *error = xoj_invalid();
466 realloc_cur_widths(i+1);
467 ui.cur_widths[i] = g_ascii_strtod(ptr, &tmpptr);
468 if (tmpptr == ptr) break;
472 tmpItem->brush.variable_width = (i>0);
474 tmpItem->brush.variable_width = TRUE;
475 tmpItem->widths = (gdouble *) g_memdup(ui.cur_widths, i*sizeof(gdouble));
476 ui.cur_path.num_points = i+1;
480 else if (!strcmp(*attribute_names, "color")) {
481 if (has_attr & 2) *error = xoj_invalid();
482 tmpItem->brush.color_no = COLOR_OTHER;
483 for (i=0; i<COLOR_MAX; i++)
484 if (!strcmp(*attribute_values, color_names[i])) {
485 tmpItem->brush.color_no = i;
486 tmpItem->brush.color_rgba = predef_colors_rgba[i];
488 // there's also the case of hex (#rrggbbaa) colors
489 if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
490 tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
491 if (*ptr!=0) *error = xoj_invalid();
495 else if (!strcmp(*attribute_names, "tool")) {
496 if (has_attr & 4) *error = xoj_invalid();
497 tmpItem->brush.tool_type = -1;
498 for (i=0; i<NUM_STROKE_TOOLS; i++)
499 if (!strcmp(*attribute_values, tool_names[i])) {
500 tmpItem->brush.tool_type = i;
502 if (tmpItem->brush.tool_type == -1) *error = xoj_invalid();
505 else *error = xoj_invalid();
509 if (has_attr!=7) *error = xoj_invalid();
510 // finish filling the brush info
511 tmpItem->brush.thickness_no = 0; // who cares ?
512 tmpItem->brush.tool_options = 0; // who cares ?
513 tmpItem->brush.ruler = FALSE;
514 tmpItem->brush.recognizer = FALSE;
515 if (tmpItem->brush.tool_type == TOOL_HIGHLIGHTER) {
516 if (tmpItem->brush.color_no >= 0)
517 tmpItem->brush.color_rgba &= ui.hiliter_alpha_mask;
520 else if (!strcmp(element_name, "text")) { // start of a text item
521 if (tmpLayer == NULL || tmpItem != NULL) {
522 *error = xoj_invalid();
525 tmpItem = (struct Item *)g_malloc0(sizeof(struct Item));
526 tmpItem->type = ITEM_TEXT;
527 tmpItem->canvas_item = NULL;
528 tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
530 // scan for font, size, x, y, and color attributes
532 while (*attribute_names!=NULL) {
533 if (!strcmp(*attribute_names, "font")) {
534 if (has_attr & 1) *error = xoj_invalid();
535 tmpItem->font_name = g_strdup(*attribute_values);
538 else if (!strcmp(*attribute_names, "size")) {
539 if (has_attr & 2) *error = xoj_invalid();
540 cleanup_numeric((gchar *)*attribute_values);
541 tmpItem->font_size = g_ascii_strtod(*attribute_values, &ptr);
542 if (ptr == *attribute_values) *error = xoj_invalid();
545 else if (!strcmp(*attribute_names, "x")) {
546 if (has_attr & 4) *error = xoj_invalid();
547 cleanup_numeric((gchar *)*attribute_values);
548 tmpItem->bbox.left = g_ascii_strtod(*attribute_values, &ptr);
549 if (ptr == *attribute_values) *error = xoj_invalid();
552 else if (!strcmp(*attribute_names, "y")) {
553 if (has_attr & 8) *error = xoj_invalid();
554 cleanup_numeric((gchar *)*attribute_values);
555 tmpItem->bbox.top = g_ascii_strtod(*attribute_values, &ptr);
556 if (ptr == *attribute_values) *error = xoj_invalid();
559 else if (!strcmp(*attribute_names, "color")) {
560 if (has_attr & 16) *error = xoj_invalid();
561 tmpItem->brush.color_no = COLOR_OTHER;
562 for (i=0; i<COLOR_MAX; i++)
563 if (!strcmp(*attribute_values, color_names[i])) {
564 tmpItem->brush.color_no = i;
565 tmpItem->brush.color_rgba = predef_colors_rgba[i];
567 // there's also the case of hex (#rrggbbaa) colors
568 if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
569 tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
570 if (*ptr!=0) *error = xoj_invalid();
574 else *error = xoj_invalid();
578 if (has_attr!=31) *error = xoj_invalid();
582 void xoj_parser_end_element(GMarkupParseContext *context,
583 const gchar *element_name, gpointer user_data, GError **error)
585 if (!strcmp(element_name, "page")) {
586 if (tmpPage == NULL || tmpLayer != NULL) {
587 *error = xoj_invalid();
590 if (tmpPage->nlayers == 0 || tmpPage->bg->type < 0) *error = xoj_invalid();
593 if (!strcmp(element_name, "layer")) {
594 if (tmpLayer == NULL || tmpItem != NULL) {
595 *error = xoj_invalid();
600 if (!strcmp(element_name, "stroke")) {
601 if (tmpItem == NULL) {
602 *error = xoj_invalid();
605 update_item_bbox(tmpItem);
608 if (!strcmp(element_name, "text")) {
609 if (tmpItem == NULL) {
610 *error = xoj_invalid();
617 void xoj_parser_text(GMarkupParseContext *context,
618 const gchar *text, gsize text_len, gpointer user_data, GError **error)
620 const gchar *element_name, *ptr;
623 element_name = g_markup_parse_context_get_element(context);
624 if (element_name == NULL) return;
625 if (!strcmp(element_name, "stroke")) {
626 cleanup_numeric((gchar *)text);
629 while (text_len > 0) {
630 realloc_cur_path(n/2 + 1);
631 ui.cur_path.coords[n] = g_ascii_strtod(text, (char **)(&ptr));
632 if (ptr == text) break;
633 text_len -= (ptr - text);
635 if (!finite(ui.cur_path.coords[n])) {
636 if (n>=2) ui.cur_path.coords[n] = ui.cur_path.coords[n-2];
637 else ui.cur_path.coords[n] = 0;
642 (tmpItem->brush.variable_width && (n!=2*ui.cur_path.num_points)))
643 { *error = xoj_invalid(); return; } // wrong number of points
644 tmpItem->path = gnome_canvas_points_new(n/2);
645 g_memmove(tmpItem->path->coords, ui.cur_path.coords, n*sizeof(double));
647 if (!strcmp(element_name, "text")) {
648 tmpItem->text = g_malloc(text_len+1);
649 g_memmove(tmpItem->text, text, text_len);
650 tmpItem->text[text_len]=0;
654 gboolean user_wants_second_chance(char **filename)
657 GtkFileFilter *filt_all, *filt_pdf;
658 GtkResponseType response;
660 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
661 GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO,
662 "Could not open background '%s'.\nSelect another file?",
664 response = gtk_dialog_run(GTK_DIALOG(dialog));
665 gtk_widget_destroy(dialog);
666 if (response != GTK_RESPONSE_YES) return FALSE;
667 dialog = gtk_file_chooser_dialog_new("Open PDF", GTK_WINDOW (winMain),
668 GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
669 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
671 filt_all = gtk_file_filter_new();
672 gtk_file_filter_set_name(filt_all, "All files");
673 gtk_file_filter_add_pattern(filt_all, "*");
674 filt_pdf = gtk_file_filter_new();
675 gtk_file_filter_set_name(filt_pdf, "PDF files");
676 gtk_file_filter_add_pattern(filt_pdf, "*.pdf");
677 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_pdf);
678 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_all);
680 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) {
681 gtk_widget_destroy(dialog);
685 *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
686 gtk_widget_destroy(dialog);
690 gboolean open_journal(char *filename)
692 const GMarkupParser parser = { xoj_parser_start_element,
693 xoj_parser_end_element,
694 xoj_parser_text, NULL, NULL};
695 GMarkupParseContext *context;
705 f = gzopen(filename, "r");
706 if (f==NULL) return FALSE;
708 context = g_markup_parse_context_new(&parser, 0, NULL, NULL);
710 tmpJournal.npages = 0;
711 tmpJournal.pages = NULL;
712 tmpJournal.last_attach_no = 0;
716 tmpFilename = filename;
721 while (valid && !gzeof(f)) {
722 len = gzread(f, buffer, 1000);
723 if (len<0) valid = FALSE;
724 if (maybe_pdf && len>=4 && !strncmp(buffer, "%PDF", 4))
725 { valid = FALSE; break; } // most likely pdf
726 else maybe_pdf = FALSE;
728 valid = g_markup_parse_context_parse(context, buffer, len, &error);
731 if (valid) valid = g_markup_parse_context_end_parse(context, &error);
732 if (tmpJournal.npages == 0) valid = FALSE;
733 g_markup_parse_context_free(context);
736 delete_journal(&tmpJournal);
737 if (!maybe_pdf) return FALSE;
738 // essentially same as on_fileNewBackground from here on
741 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
743 ui.zoom = ui.startup_zoom;
744 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
746 return init_bgpdf(filename, TRUE, DOMAIN_ABSOLUTE);
749 ui.saved = TRUE; // force close_journal() to do its job
751 g_memmove(&journal, &tmpJournal, sizeof(struct Journal));
753 // if we need to initialize a fresh pdf loader
754 if (tmpBg_pdf!=NULL) {
755 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
756 if (tmpBg_pdf->file_domain == DOMAIN_ATTACH)
757 tmpfn = g_strdup_printf("%s.%s", filename, tmpBg_pdf->filename->s);
759 tmpfn = g_strdup(tmpBg_pdf->filename->s);
760 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
761 // in case the file name became invalid
762 if (!valid && tmpBg_pdf->file_domain != DOMAIN_ATTACH)
763 if (user_wants_second_chance(&tmpfn)) {
764 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
765 if (valid) { // change the file name...
766 g_free(tmpBg_pdf->filename->s);
767 tmpBg_pdf->filename->s = g_strdup(tmpfn);
771 refstring_unref(bgpdf.filename);
772 bgpdf.filename = refstring_ref(tmpBg_pdf->filename);
774 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
775 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open background '%s'.",
777 gtk_dialog_run(GTK_DIALOG(dialog));
778 gtk_widget_destroy(dialog);
784 ui.cur_page = (struct Page *)journal.pages->data;
785 ui.layerno = ui.cur_page->nlayers-1;
786 ui.cur_layer = (struct Layer *)(g_list_last(ui.cur_page->layers)->data);
788 ui.zoom = ui.startup_zoom;
789 update_file_name(g_strdup(filename));
790 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
793 gtk_adjustment_set_value(gtk_layout_get_vadjustment(GTK_LAYOUT(canvas)), 0);
797 /************ file backgrounds *************/
799 struct Background *attempt_load_pix_bg(char *filename, gboolean attach)
801 struct Background *bg;
804 pix = gdk_pixbuf_new_from_file(filename, NULL);
805 if (pix == NULL) return NULL;
807 bg = g_new(struct Background, 1);
808 bg->type = BG_PIXMAP;
809 bg->canvas_item = NULL;
811 bg->pixbuf_scale = DEFAULT_ZOOM;
813 bg->filename = new_refstring(NULL);
814 bg->file_domain = DOMAIN_ATTACH;
816 bg->filename = new_refstring(filename);
817 bg->file_domain = DOMAIN_ABSOLUTE;
822 #define BUFSIZE 65536 // a reasonable buffer size for reads from gs pipe
824 GList *attempt_load_gv_bg(char *filename)
826 struct Background *bg;
829 GdkPixbufLoader *loader;
833 int buflen, remnlen, file_pageno;
835 f = fopen(filename, "r");
836 if (f == NULL) return NULL;
837 buf = g_malloc(BUFSIZE); // a reasonable buffer size
838 if (fread(buf, 1, 4, f) !=4 ||
839 (strncmp((char *)buf, "%!PS", 4) && strncmp((char *)buf, "%PDF", 4))) {
846 pipename = g_strdup_printf(GS_CMDLINE, (double)GS_BITMAP_DPI, filename);
847 gs_pipe = popen(pipename, "r");
855 while (!feof(gs_pipe)) {
856 if (!remnlen) { // new page: get a BMP header ?
857 buflen = fread(buf, 1, 54, gs_pipe);
858 if (buflen < 6) buflen += fread(buf, 1, 54-buflen, gs_pipe);
859 if (buflen < 6 || buf[0]!='B' || buf[1]!='M') break; // fatal: abort
860 remnlen = (int)(buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + (buf[2]);
861 loader = gdk_pixbuf_loader_new();
863 else buflen = fread(buf, 1, (remnlen < BUFSIZE)?remnlen:BUFSIZE, gs_pipe);
865 if (buflen == 0) break;
866 if (!gdk_pixbuf_loader_write(loader, buf, buflen, NULL)) break;
867 if (remnlen == 0) { // make a new bg
868 pix = gdk_pixbuf_loader_get_pixbuf(loader);
869 if (pix == NULL) break;
871 gdk_pixbuf_loader_close(loader, NULL);
872 g_object_unref(loader);
874 bg = g_new(struct Background, 1);
875 bg->canvas_item = NULL;
877 bg->pixbuf_scale = (GS_BITMAP_DPI/72.0);
878 bg->type = BG_PIXMAP;
879 bg->filename = new_refstring(NULL);
880 bg->file_domain = DOMAIN_ATTACH;
882 bg_list = g_list_append(bg_list, bg);
885 if (loader != NULL) gdk_pixbuf_loader_close(loader, NULL);
891 struct Background *attempt_screenshot_bg(void)
893 struct Background *bg;
898 Window x_root, x_win;
900 x_root = gdk_x11_get_default_root_xwindow();
902 if (!XGrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root,
903 False, ButtonReleaseMask, GrabModeAsync, GrabModeSync, None, None))
906 XWindowEvent (GDK_DISPLAY(), x_root, ButtonReleaseMask, &x_event);
907 XUngrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root);
909 x_win = x_event.xbutton.subwindow;
910 if (x_win == None) x_win = x_root;
912 window = gdk_window_foreign_new_for_display(gdk_display_get_default(), x_win);
914 gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
916 pix = gdk_pixbuf_get_from_drawable(NULL, window,
917 gdk_colormap_get_system(), 0, 0, 0, 0, w, h);
919 if (pix == NULL) return NULL;
921 bg = g_new(struct Background, 1);
922 bg->type = BG_PIXMAP;
923 bg->canvas_item = NULL;
925 bg->pixbuf_scale = DEFAULT_ZOOM;
926 bg->filename = new_refstring(NULL);
927 bg->file_domain = DOMAIN_ATTACH;
931 /************** pdf annotation ***************/
933 /* free tmp directory */
935 void end_bgpdf_shutdown(void)
937 if (bgpdf.tmpdir!=NULL) {
938 if (bgpdf.tmpfile_copy!=NULL) {
939 g_unlink(bgpdf.tmpfile_copy);
940 g_free(bgpdf.tmpfile_copy);
941 bgpdf.tmpfile_copy = NULL;
943 g_rmdir(bgpdf.tmpdir);
944 g_free(bgpdf.tmpdir);
947 bgpdf.status = STATUS_NOT_INIT;
950 /* cancel a request */
952 void cancel_bgpdf_request(struct BgPdfRequest *req)
956 list_link = g_list_find(bgpdf.requests, req);
957 if (list_link == NULL) return;
958 if (list_link->prev == NULL && bgpdf.pid > 0) {
959 // this is being processed: kill the child but don't remove the request yet
960 if (bgpdf.status == STATUS_RUNNING) bgpdf.status = STATUS_ABORTED;
961 kill(bgpdf.pid, SIGHUP);
962 // printf("Cancelling a request - killing %d\n", bgpdf.pid);
965 // remove the request
966 bgpdf.requests = g_list_delete_link(bgpdf.requests, list_link);
968 // printf("Cancelling a request - no kill needed\n");
972 /* sigchld callback */
974 void bgpdf_child_handler(GPid pid, gint status, gpointer data)
976 struct BgPdfRequest *req;
977 struct BgPdfPage *bgpg;
982 if (bgpdf.requests == NULL) return;
983 req = (struct BgPdfRequest *)bgpdf.requests->data;
986 // pdftoppm used to generate p-nnnnnn.ppm (6 digits); new versions produce variable width
987 for (npad = 6; npad>0; npad--) {
988 ppm_name = g_strdup_printf("%s/p-%0*d.ppm", bgpdf.tmpdir, npad, req->pageno);
989 if (bgpdf.status != STATUS_ABORTED && bgpdf.status != STATUS_SHUTDOWN)
990 pixbuf = gdk_pixbuf_new_from_file(ppm_name, NULL);
991 ret = unlink(ppm_name);
993 if (pixbuf != NULL || ret == 0) break;
996 if (pixbuf != NULL) { // success
997 // printf("success\n");
998 while (req->pageno > bgpdf.npages) {
999 bgpg = g_new(struct BgPdfPage, 1);
1000 bgpg->pixbuf = NULL;
1001 bgpdf.pages = g_list_append(bgpdf.pages, bgpg);
1004 bgpg = g_list_nth_data(bgpdf.pages, req->pageno-1);
1005 if (bgpg->pixbuf!=NULL) gdk_pixbuf_unref(bgpg->pixbuf);
1006 bgpg->pixbuf = pixbuf;
1007 bgpg->dpi = req->dpi;
1008 if (req->initial_request && bgpdf.create_pages) {
1009 bgpdf_create_page_with_bg(req->pageno, bgpg);
1010 // create page n, resize it, set its bg - all without any undo effect
1012 if (!req->is_printing) bgpdf_update_bg(req->pageno, bgpg);
1013 // look for all pages with this bg, and update their bg pixmaps
1017 // printf("failed or aborted\n");
1018 bgpdf.create_pages = FALSE;
1019 req->initial_request = FALSE;
1023 g_spawn_close_pid(pid);
1025 if (req->initial_request)
1026 req->pageno++; // try for next page
1028 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1030 if (bgpdf.status == STATUS_SHUTDOWN) {
1031 end_bgpdf_shutdown();
1035 bgpdf.status = STATUS_IDLE;
1036 if (bgpdf.requests != NULL) bgpdf_spawn_child();
1039 /* spawn a child to process the head request */
1041 void bgpdf_spawn_child(void)
1043 struct BgPdfRequest *req;
1045 gchar pageno_str[10], dpi_str[10];
1046 gchar *pdf_filename = bgpdf.tmpfile_copy;
1047 gchar *ppm_root = g_strdup_printf("%s/p", bgpdf.tmpdir);
1048 gchar *argv[]= PDFTOPPM_ARGV;
1051 if (bgpdf.requests == NULL) return;
1052 req = (struct BgPdfRequest *)bgpdf.requests->data;
1053 if (req->pageno > bgpdf.npages+1 ||
1054 (!req->initial_request && req->pageno <= bgpdf.npages &&
1055 req->dpi == ((struct BgPdfPage *)g_list_nth_data(bgpdf.pages, req->pageno-1))->dpi))
1056 { // ignore this request - it's redundant, or in outer space
1058 bgpdf.status = STATUS_IDLE;
1059 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1061 if (bgpdf.requests != NULL) bgpdf_spawn_child();
1064 g_snprintf(pageno_str, 10, "%d", req->pageno);
1065 g_snprintf(dpi_str, 10, "%d", req->dpi);
1066 /* printf("Processing request for page %d at %d dpi -- in %s\n",
1067 req->pageno, req->dpi, ppm_root); */
1068 if (!g_spawn_async(NULL, argv, NULL,
1069 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
1070 NULL, NULL, &pid, NULL))
1072 // couldn't spawn... abort this request, try next one maybe ?
1073 // printf("Couldn't spawn\n");
1075 bgpdf.status = STATUS_IDLE;
1076 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1078 if (!bgpdf.has_failed) {
1079 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
1080 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Unable to start PDF loader %s.", argv[0]);
1081 gtk_dialog_run(GTK_DIALOG(dialog));
1082 gtk_widget_destroy(dialog);
1084 bgpdf.has_failed = TRUE;
1085 if (bgpdf.requests != NULL) bgpdf_spawn_child();
1089 // printf("Spawned process %d\n", pid);
1091 bgpdf.status = STATUS_RUNNING;
1092 g_child_watch_add(pid, bgpdf_child_handler, NULL);
1096 /* make a request */
1098 void add_bgpdf_request(int pageno, double zoom, gboolean printing)
1100 struct BgPdfRequest *req, *cmp_req;
1103 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN)
1104 return; // don't accept requests in those modes...
1105 req = g_new(struct BgPdfRequest, 1);
1106 req->is_printing = printing;
1107 if (printing) req->dpi = PDFTOPPM_PRINTING_DPI;
1108 else req->dpi = (int)floor(72*zoom+0.5);
1109 // printf("Enqueuing request for page %d at %d dpi\n", pageno, req->dpi);
1111 // cancel any request this may supersede
1112 for (list = bgpdf.requests; list != NULL; ) {
1113 cmp_req = (struct BgPdfRequest *)list->data;
1115 if (!cmp_req->initial_request && cmp_req->pageno == pageno &&
1116 cmp_req->is_printing == printing)
1117 cancel_bgpdf_request(cmp_req);
1119 req->pageno = pageno;
1120 req->initial_request = FALSE;
1123 req->initial_request = TRUE;
1125 bgpdf.requests = g_list_append(bgpdf.requests, req);
1126 if (!bgpdf.pid) bgpdf_spawn_child();
1129 /* shutdown the PDF reader */
1131 void shutdown_bgpdf(void)
1134 struct BgPdfPage *pdfpg;
1135 struct BgPdfRequest *req;
1137 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN) return;
1138 refstring_unref(bgpdf.filename);
1139 for (list = bgpdf.pages; list != NULL; list = list->next) {
1140 pdfpg = (struct BgPdfPage *)list->data;
1141 if (pdfpg->pixbuf!=NULL) gdk_pixbuf_unref(pdfpg->pixbuf);
1143 g_list_free(bgpdf.pages);
1144 bgpdf.status = STATUS_SHUTDOWN;
1145 for (list = g_list_last(bgpdf.requests); list != NULL; ) {
1146 req = (struct BgPdfRequest *)list->data;
1148 cancel_bgpdf_request(req);
1150 if (!bgpdf.pid) end_bgpdf_shutdown();
1151 /* The above will ultimately remove all requests and kill the child if needed.
1152 The child will set status to STATUS_NOT_INIT, clear the requests list,
1153 empty tmpdir, ... except if there's no child! */
1154 /* note: it could look like there's a race condition here - if a child
1155 terminates and a new request is enqueued while we are destroying the
1156 queue - but actually the child handler callback is NOT a signal
1157 callback, so execution of this function is atomic */
1160 gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain)
1166 if (bgpdf.status != STATUS_NOT_INIT) return FALSE;
1167 bgpdf.tmpfile_copy = NULL;
1168 bgpdf.tmpdir = mkdtemp(g_strdup(TMPDIR_TEMPLATE));
1169 if (!bgpdf.tmpdir) return FALSE;
1170 // make a local copy and check if it's a PDF
1171 if (!g_file_get_contents(pdfname, &filebuf, &filelen, NULL))
1172 { end_bgpdf_shutdown(); return FALSE; }
1173 if (filelen < 4 || strncmp(filebuf, "%PDF", 4))
1174 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1175 bgpdf.tmpfile_copy = g_strdup_printf("%s/bg.pdf", bgpdf.tmpdir);
1176 f = fopen(bgpdf.tmpfile_copy, "w");
1177 if (f == NULL || fwrite(filebuf, 1, filelen, f) != filelen)
1178 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1181 bgpdf.status = STATUS_IDLE;
1183 bgpdf.filename = new_refstring((file_domain == DOMAIN_ATTACH) ? "bg.pdf" : pdfname);
1184 bgpdf.file_domain = file_domain;
1187 bgpdf.requests = NULL;
1188 bgpdf.create_pages = create_pages;
1189 bgpdf.has_failed = FALSE;
1190 add_bgpdf_request(-1, ui.startup_zoom, FALSE); // request all pages
1194 // create page n, resize it, set its bg
1195 void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg)
1197 struct Page *pg = NULL;
1198 struct Background *bg;
1200 if (journal.npages < pageno) {
1201 bg = g_new(struct Background, 1);
1202 bg->canvas_item = NULL;
1204 pg = (struct Page *)g_list_nth_data(journal.pages, pageno-1);
1206 if (bg->type != BG_SOLID) return;
1207 // don't mess with a page the user has modified significantly...
1211 bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1212 bg->filename = refstring_ref(bgpdf.filename);
1213 bg->file_domain = bgpdf.file_domain;
1214 bg->file_page_seq = pageno;
1215 bg->pixbuf_scale = ui.startup_zoom;
1216 bg->pixbuf_dpi = bgpg->dpi;
1218 if (journal.npages < pageno) {
1219 pg = new_page_with_bg(bg,
1220 gdk_pixbuf_get_width(bg->pixbuf)*72.0/bg->pixbuf_dpi,
1221 gdk_pixbuf_get_height(bg->pixbuf)*72.0/bg->pixbuf_dpi);
1222 journal.pages = g_list_append(journal.pages, pg);
1225 pg->width = gdk_pixbuf_get_width(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1226 pg->height = gdk_pixbuf_get_height(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1227 make_page_clipbox(pg);
1228 update_canvas_bg(pg);
1230 update_page_stuff();
1233 // look for all journal pages with given pdf bg, and update their bg pixmaps
1234 void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg)
1239 for (list = journal.pages; list!= NULL; list = list->next) {
1240 pg = (struct Page *)list->data;
1241 if (pg->bg->type == BG_PDF && pg->bg->file_page_seq == pageno) {
1242 if (pg->bg->pixbuf!=NULL) gdk_pixbuf_unref(pg->bg->pixbuf);
1243 pg->bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1244 pg->bg->pixbuf_dpi = bgpg->dpi;
1245 update_canvas_bg(pg);
1250 // initialize the recent files list
1260 g_strlcpy(s, "mru0", 5);
1261 for (s[3]='0', i=0; i<MRU_SIZE; s[3]++, i++) {
1262 ui.mrumenu[i] = GET_COMPONENT(s);
1265 f = g_io_channel_new_file(ui.mrufile, "r", NULL);
1266 if (f) status = G_IO_STATUS_NORMAL;
1267 else status = G_IO_STATUS_ERROR;
1269 while (status == G_IO_STATUS_NORMAL && i<MRU_SIZE) {
1271 status = g_io_channel_read_line(f, &str, NULL, &lfptr, NULL);
1272 if (status == G_IO_STATUS_NORMAL && lfptr>0) {
1279 g_io_channel_shutdown(f, FALSE, NULL);
1280 g_io_channel_unref(f);
1285 void update_mru_menu(void)
1288 gboolean anyone = FALSE;
1291 for (i=0; i<MRU_SIZE; i++) {
1292 if (ui.mru[i]!=NULL) {
1293 tmp = g_strdup_printf("_%d %s", i+1, g_basename(ui.mru[i]));
1294 gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(ui.mrumenu[i]))),
1297 gtk_widget_show(ui.mrumenu[i]);
1300 else gtk_widget_hide(ui.mrumenu[i]);
1302 gtk_widget_set_sensitive(GET_COMPONENT("fileRecentFiles"), anyone);
1305 void new_mru_entry(char *name)
1309 for (i=0;i<MRU_SIZE;i++)
1310 if (ui.mru[i]!=NULL && !strcmp(ui.mru[i], name)) {
1312 for (j=i+1; j<MRU_SIZE; j++) ui.mru[j-1] = ui.mru[j];
1313 ui.mru[MRU_SIZE-1]=NULL;
1315 if (ui.mru[MRU_SIZE-1]!=NULL) g_free(ui.mru[MRU_SIZE-1]);
1316 for (j=MRU_SIZE-1; j>=1; j--) ui.mru[j] = ui.mru[j-1];
1317 ui.mru[0] = g_strdup(name);
1321 void delete_mru_entry(int which)
1325 if (ui.mru[which]!=NULL) g_free(ui.mru[which]);
1326 for (i=which+1;i<MRU_SIZE;i++)
1327 ui.mru[i-1] = ui.mru[i];
1328 ui.mru[MRU_SIZE-1] = NULL;
1332 void save_mru_list(void)
1337 f = fopen(ui.mrufile, "w");
1338 if (f==NULL) return;
1339 for (i=0; i<MRU_SIZE; i++)
1340 if (ui.mru[i]!=NULL) fprintf(f, "%s\n", ui.mru[i]);
1344 void init_config_default(void)
1348 DEFAULT_ZOOM = DISPLAY_DPI_DEFAULT/72.0;
1349 ui.zoom = ui.startup_zoom = 1.0*DEFAULT_ZOOM;
1350 ui.default_page.height = 792.0;
1351 ui.default_page.width = 612.0;
1352 ui.default_page.bg->type = BG_SOLID;
1353 ui.default_page.bg->color_no = COLOR_WHITE;
1354 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[COLOR_WHITE];
1355 ui.default_page.bg->ruling = RULING_LINED;
1356 ui.view_continuous = TRUE;
1357 ui.allow_xinput = TRUE;
1358 ui.discard_corepointer = FALSE;
1359 ui.left_handed = FALSE;
1360 ui.shorten_menus = FALSE;
1361 ui.shorten_menu_items = g_strdup(DEFAULT_SHORTEN_MENUS);
1362 ui.auto_save_prefs = FALSE;
1363 ui.bg_apply_all_pages = FALSE;
1364 ui.use_erasertip = FALSE;
1365 ui.window_default_width = 720;
1366 ui.window_default_height = 480;
1367 ui.maximize_at_start = FALSE;
1368 ui.fullscreen = FALSE;
1369 ui.scrollbar_step_increment = 30;
1370 ui.zoom_step_increment = 1;
1371 ui.zoom_step_factor = 1.5;
1372 ui.antialias_bg = TRUE;
1373 ui.progressive_bg = TRUE;
1374 ui.print_ruling = TRUE;
1375 ui.default_unit = UNIT_CM;
1376 ui.default_path = NULL;
1377 ui.default_font_name = g_strdup(DEFAULT_FONT);
1378 ui.default_font_size = DEFAULT_FONT_SIZE;
1379 ui.pressure_sensitivity = FALSE;
1380 ui.width_minimum_multiplier = 0.0;
1381 ui.width_maximum_multiplier = 1.25;
1383 // the default UI vertical order
1384 ui.vertical_order[0][0] = 1;
1385 ui.vertical_order[0][1] = 2;
1386 ui.vertical_order[0][2] = 3;
1387 ui.vertical_order[0][3] = 0;
1388 ui.vertical_order[0][4] = 4;
1389 ui.vertical_order[1][0] = 2;
1390 ui.vertical_order[1][1] = 3;
1391 ui.vertical_order[1][2] = 0;
1392 ui.vertical_order[1][3] = ui.vertical_order[1][4] = -1;
1394 ui.toolno[0] = ui.startuptool = TOOL_PEN;
1395 for (i=1; i<=NUM_BUTTONS; i++) {
1396 ui.toolno[i] = TOOL_ERASER;
1398 for (i=0; i<=NUM_BUTTONS; i++)
1399 ui.linked_brush[i] = BRUSH_LINKED;
1400 ui.brushes[0][TOOL_PEN].color_no = COLOR_BLACK;
1401 ui.brushes[0][TOOL_ERASER].color_no = COLOR_WHITE;
1402 ui.brushes[0][TOOL_HIGHLIGHTER].color_no = COLOR_YELLOW;
1403 for (i=0; i < NUM_STROKE_TOOLS; i++) {
1404 ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM;
1405 ui.brushes[0][i].tool_options = 0;
1406 ui.brushes[0][i].ruler = FALSE;
1407 ui.brushes[0][i].recognizer = FALSE;
1408 ui.brushes[0][i].variable_width = FALSE;
1410 for (i=0; i< NUM_STROKE_TOOLS; i++)
1411 for (j=1; j<=NUM_BUTTONS; j++)
1412 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1414 // predef_thickness is already initialized as a global variable
1415 GS_BITMAP_DPI = 144;
1416 PDFTOPPM_PRINTING_DPI = 150;
1418 ui.hiliter_opacity = 0.5;
1421 #if GLIB_CHECK_VERSION(2,6,0)
1423 void update_keyval(const gchar *group_name, const gchar *key,
1424 const gchar *comment, gchar *value)
1426 gboolean has_it = g_key_file_has_key(ui.config_data, group_name, key, NULL);
1427 cleanup_numeric(value);
1428 g_key_file_set_value(ui.config_data, group_name, key, value);
1430 if (!has_it) g_key_file_set_comment(ui.config_data, group_name, key, comment, NULL);
1435 const char *vorder_usernames[VBOX_MAIN_NITEMS+1] =
1436 {"drawarea", "menu", "main_toolbar", "pen_toolbar", "statusbar", NULL};
1438 gchar *verbose_vertical_order(int *order)
1440 gchar buf[80], *p; // longer than needed
1444 for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1445 if (order[i]<0 || order[i]>=VBOX_MAIN_NITEMS) continue;
1446 if (p!=buf) *(p++) = ' ';
1447 p = g_stpcpy(p, vorder_usernames[order[i]]);
1449 return g_strdup(buf);
1452 void save_config_to_file(void)
1457 #if GLIB_CHECK_VERSION(2,6,0)
1458 // no support for keyval files before Glib 2.6.0
1459 if (glib_minor_version<6) return;
1461 // save some data...
1462 ui.maximize_at_start = (gdk_window_get_state(winMain->window) & GDK_WINDOW_STATE_MAXIMIZED);
1463 if (!ui.maximize_at_start && !ui.fullscreen)
1464 gdk_drawable_get_size(winMain->window,
1465 &ui.window_default_width, &ui.window_default_height);
1467 update_keyval("general", "display_dpi",
1468 " the display resolution, in pixels per inch",
1469 g_strdup_printf("%.2f", DEFAULT_ZOOM*72));
1470 update_keyval("general", "initial_zoom",
1471 " the initial zoom level, in percent",
1472 g_strdup_printf("%.2f", 100*ui.zoom/DEFAULT_ZOOM));
1473 update_keyval("general", "window_maximize",
1474 " maximize the window at startup (true/false)",
1475 g_strdup(ui.maximize_at_start?"true":"false"));
1476 update_keyval("general", "window_fullscreen",
1477 " start in full screen mode (true/false)",
1478 g_strdup(ui.fullscreen?"true":"false"));
1479 update_keyval("general", "window_width",
1480 " the window width in pixels (when not maximized)",
1481 g_strdup_printf("%d", ui.window_default_width));
1482 update_keyval("general", "window_height",
1483 " the window height in pixels",
1484 g_strdup_printf("%d", ui.window_default_height));
1485 update_keyval("general", "scrollbar_speed",
1486 " scrollbar step increment (in pixels)",
1487 g_strdup_printf("%d", ui.scrollbar_step_increment));
1488 update_keyval("general", "zoom_dialog_increment",
1489 " the step increment in the zoom dialog box",
1490 g_strdup_printf("%d", ui.zoom_step_increment));
1491 update_keyval("general", "zoom_step_factor",
1492 " the multiplicative factor for zoom in/out",
1493 g_strdup_printf("%.3f", ui.zoom_step_factor));
1494 update_keyval("general", "view_continuous",
1495 " document view (true = continuous, false = single page)",
1496 g_strdup(ui.view_continuous?"true":"false"));
1497 update_keyval("general", "use_xinput",
1498 " use XInput extensions (true/false)",
1499 g_strdup(ui.allow_xinput?"true":"false"));
1500 update_keyval("general", "discard_corepointer",
1501 " discard Core Pointer events in XInput mode (true/false)",
1502 g_strdup(ui.discard_corepointer?"true":"false"));
1503 update_keyval("general", "use_erasertip",
1504 " always map eraser tip to eraser (true/false)",
1505 g_strdup(ui.use_erasertip?"true":"false"));
1506 update_keyval("general", "default_path",
1507 " default path for open/save (leave blank for current directory)",
1508 g_strdup((ui.default_path!=NULL)?ui.default_path:""));
1509 update_keyval("general", "pressure_sensitivity",
1510 " use pressure sensitivity to control pen stroke width (true/false)",
1511 g_strdup(ui.pressure_sensitivity?"true":"false"));
1512 update_keyval("general", "width_minimum_multiplier",
1513 " minimum width multiplier",
1514 g_strdup_printf("%.2f", ui.width_minimum_multiplier));
1515 update_keyval("general", "width_maximum_multiplier",
1516 " maximum width multiplier",
1517 g_strdup_printf("%.2f", ui.width_maximum_multiplier));
1518 update_keyval("general", "interface_order",
1519 " interface components from top to bottom\n valid values: drawarea menu main_toolbar pen_toolbar statusbar",
1520 verbose_vertical_order(ui.vertical_order[0]));
1521 update_keyval("general", "interface_fullscreen",
1522 " interface components in fullscreen mode, from top to bottom",
1523 verbose_vertical_order(ui.vertical_order[1]));
1524 update_keyval("general", "interface_lefthanded",
1525 " interface has left-handed scrollbar (true/false)",
1526 g_strdup(ui.left_handed?"true":"false"));
1527 update_keyval("general", "shorten_menus",
1528 " hide some unwanted menu or toolbar items (true/false)",
1529 g_strdup(ui.shorten_menus?"true":"false"));
1530 update_keyval("general", "shorten_menu_items",
1531 " interface items to hide (customize at your own risk!)\n see source file xo-interface.c for a list of item names",
1532 g_strdup(ui.shorten_menu_items));
1533 update_keyval("general", "highlighter_opacity",
1534 " highlighter opacity (0 to 1, default 0.5)\n warning: opacity level is not saved in xoj files!",
1535 g_strdup_printf("%.2f", ui.hiliter_opacity));
1536 update_keyval("general", "autosave_prefs",
1537 " auto-save preferences on exit (true/false)",
1538 g_strdup(ui.auto_save_prefs?"true":"false"));
1540 update_keyval("paper", "width",
1541 " the default page width, in points (1/72 in)",
1542 g_strdup_printf("%.2f", ui.default_page.width));
1543 update_keyval("paper", "height",
1544 " the default page height, in points (1/72 in)",
1545 g_strdup_printf("%.2f", ui.default_page.height));
1546 update_keyval("paper", "color",
1547 " the default paper color",
1548 g_strdup(bgcolor_names[ui.default_page.bg->color_no]));
1549 update_keyval("paper", "style",
1550 " the default paper style (plain, lined, ruled, or graph)",
1551 g_strdup(bgstyle_names[ui.default_page.bg->ruling]));
1552 update_keyval("paper", "apply_all",
1553 " apply paper style changes to all pages (true/false)",
1554 g_strdup(ui.bg_apply_all_pages?"true":"false"));
1555 update_keyval("paper", "default_unit",
1556 " preferred unit (cm, in, px, pt)",
1557 g_strdup(unit_names[ui.default_unit]));
1558 update_keyval("paper", "print_ruling",
1559 " include paper ruling when printing or exporting to PDF (true/false)",
1560 g_strdup(ui.print_ruling?"true":"false"));
1561 update_keyval("paper", "antialias_bg",
1562 " antialiased bitmap backgrounds (true/false)",
1563 g_strdup(ui.antialias_bg?"true":"false"));
1564 update_keyval("paper", "progressive_bg",
1565 " progressive scaling of bitmap backgrounds (true/false)",
1566 g_strdup(ui.progressive_bg?"true":"false"));
1567 update_keyval("paper", "gs_bitmap_dpi",
1568 " bitmap resolution of PS/PDF backgrounds rendered using ghostscript (dpi)",
1569 g_strdup_printf("%d", GS_BITMAP_DPI));
1570 update_keyval("paper", "pdftoppm_printing_dpi",
1571 " bitmap resolution of PDF backgrounds when printing with libgnomeprint (dpi)",
1572 g_strdup_printf("%d", PDFTOPPM_PRINTING_DPI));
1574 update_keyval("tools", "startup_tool",
1575 " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)",
1576 g_strdup(tool_names[ui.startuptool]));
1577 update_keyval("tools", "pen_color",
1578 " default pen color",
1579 g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no]));
1580 update_keyval("tools", "pen_thickness",
1581 " default pen thickness (fine = 1, medium = 2, thick = 3)",
1582 g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no));
1583 update_keyval("tools", "pen_ruler",
1584 " default pen is in ruler mode (true/false)",
1585 g_strdup(ui.default_brushes[TOOL_PEN].ruler?"true":"false"));
1586 update_keyval("tools", "pen_recognizer",
1587 " default pen is in shape recognizer mode (true/false)",
1588 g_strdup(ui.default_brushes[TOOL_PEN].recognizer?"true":"false"));
1589 update_keyval("tools", "eraser_thickness",
1590 " default eraser thickness (fine = 1, medium = 2, thick = 3)",
1591 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no));
1592 update_keyval("tools", "eraser_mode",
1593 " default eraser mode (standard = 0, whiteout = 1, strokes = 2)",
1594 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].tool_options));
1595 update_keyval("tools", "highlighter_color",
1596 " default highlighter color",
1597 g_strdup(color_names[ui.default_brushes[TOOL_HIGHLIGHTER].color_no]));
1598 update_keyval("tools", "highlighter_thickness",
1599 " default highlighter thickness (fine = 1, medium = 2, thick = 3)",
1600 g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no));
1601 update_keyval("tools", "highlighter_ruler",
1602 " default highlighter is in ruler mode (true/false)",
1603 g_strdup(ui.default_brushes[TOOL_HIGHLIGHTER].ruler?"true":"false"));
1604 update_keyval("tools", "highlighter_recognizer",
1605 " default highlighter is in shape recognizer mode (true/false)",
1606 g_strdup(ui.default_brushes[TOOL_HIGHLIGHTER].recognizer?"true":"false"));
1607 update_keyval("tools", "btn2_tool",
1608 " button 2 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)",
1609 g_strdup(tool_names[ui.toolno[1]]));
1610 update_keyval("tools", "btn2_linked",
1611 " button 2 brush linked to primary brush (true/false) (overrides all other settings)",
1612 g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false"));
1613 update_keyval("tools", "btn2_color",
1614 " button 2 brush color (for pen or highlighter only)",
1615 g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)?
1616 color_names[ui.brushes[1][ui.toolno[1]].color_no]:"white"));
1617 update_keyval("tools", "btn2_thickness",
1618 " button 2 brush thickness (pen, eraser, or highlighter only)",
1619 g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)?
1620 ui.brushes[1][ui.toolno[1]].thickness_no:0));
1621 update_keyval("tools", "btn2_ruler",
1622 " button 2 ruler mode (true/false) (for pen or highlighter only)",
1623 g_strdup(((ui.toolno[1]<NUM_STROKE_TOOLS)?
1624 ui.brushes[1][ui.toolno[1]].ruler:FALSE)?"true":"false"));
1625 update_keyval("tools", "btn2_recognizer",
1626 " button 2 shape recognizer mode (true/false) (pen or highlighter only)",
1627 g_strdup(((ui.toolno[1]<NUM_STROKE_TOOLS)?
1628 ui.brushes[1][ui.toolno[1]].recognizer:FALSE)?"true":"false"));
1629 update_keyval("tools", "btn2_erasermode",
1630 " button 2 eraser mode (eraser only)",
1631 g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options));
1632 update_keyval("tools", "btn3_tool",
1633 " button 3 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)",
1634 g_strdup(tool_names[ui.toolno[2]]));
1635 update_keyval("tools", "btn3_linked",
1636 " button 3 brush linked to primary brush (true/false) (overrides all other settings)",
1637 g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false"));
1638 update_keyval("tools", "btn3_color",
1639 " button 3 brush color (for pen or highlighter only)",
1640 g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)?
1641 color_names[ui.brushes[2][ui.toolno[2]].color_no]:"white"));
1642 update_keyval("tools", "btn3_thickness",
1643 " button 3 brush thickness (pen, eraser, or highlighter only)",
1644 g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)?
1645 ui.brushes[2][ui.toolno[2]].thickness_no:0));
1646 update_keyval("tools", "btn3_ruler",
1647 " button 3 ruler mode (true/false) (for pen or highlighter only)",
1648 g_strdup(((ui.toolno[2]<NUM_STROKE_TOOLS)?
1649 ui.brushes[2][ui.toolno[2]].ruler:FALSE)?"true":"false"));
1650 update_keyval("tools", "btn3_recognizer",
1651 " button 3 shape recognizer mode (true/false) (pen or highlighter only)",
1652 g_strdup(((ui.toolno[2]<NUM_STROKE_TOOLS)?
1653 ui.brushes[2][ui.toolno[2]].recognizer:FALSE)?"true":"false"));
1654 update_keyval("tools", "btn3_erasermode",
1655 " button 3 eraser mode (eraser only)",
1656 g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options));
1658 update_keyval("tools", "pen_thicknesses",
1659 " thickness of the various pens (in points, 1 pt = 1/72 in)",
1660 g_strdup_printf("%.2f;%.2f;%.2f;%.2f;%.2f",
1661 predef_thickness[TOOL_PEN][0], predef_thickness[TOOL_PEN][1],
1662 predef_thickness[TOOL_PEN][2], predef_thickness[TOOL_PEN][3],
1663 predef_thickness[TOOL_PEN][4]));
1664 update_keyval("tools", "eraser_thicknesses",
1665 " thickness of the various erasers (in points, 1 pt = 1/72 in)",
1666 g_strdup_printf("%.2f;%.2f;%.2f",
1667 predef_thickness[TOOL_ERASER][1], predef_thickness[TOOL_ERASER][2],
1668 predef_thickness[TOOL_ERASER][3]));
1669 update_keyval("tools", "highlighter_thicknesses",
1670 " thickness of the various highlighters (in points, 1 pt = 1/72 in)",
1671 g_strdup_printf("%.2f;%.2f;%.2f",
1672 predef_thickness[TOOL_HIGHLIGHTER][1], predef_thickness[TOOL_HIGHLIGHTER][2],
1673 predef_thickness[TOOL_HIGHLIGHTER][3]));
1674 update_keyval("tools", "default_font",
1675 " name of the default font",
1676 g_strdup(ui.default_font_name));
1677 update_keyval("tools", "default_font_size",
1678 " default font size",
1679 g_strdup_printf("%.1f", ui.default_font_size));
1681 buf = g_key_file_to_data(ui.config_data, NULL, NULL);
1682 if (buf == NULL) return;
1683 f = fopen(ui.configfile, "w");
1684 if (f==NULL) { g_free(buf); return; }
1691 #if GLIB_CHECK_VERSION(2,6,0)
1692 gboolean parse_keyval_float(const gchar *group, const gchar *key, double *val, double inf, double sup)
1697 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1698 if (ret==NULL) return FALSE;
1699 conv = g_ascii_strtod(ret, &end);
1700 if (*end!=0) { g_free(ret); return FALSE; }
1702 if (conv < inf || conv > sup) return FALSE;
1707 gboolean parse_keyval_floatlist(const gchar *group, const gchar *key, double *val, int n, double inf, double sup)
1713 if (n>5) return FALSE;
1714 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1715 if (ret==NULL) return FALSE;
1717 for (i=0; i<n; i++) {
1718 conv[i] = g_ascii_strtod(end, &end);
1719 if ((i==n-1 && *end!=0) || (i<n-1 && *end!=';') ||
1720 (conv[i] < inf) || (conv[i] > sup)) { g_free(ret); return FALSE; }
1724 for (i=0; i<n; i++) val[i] = conv[i];
1728 gboolean parse_keyval_int(const gchar *group, const gchar *key, int *val, int inf, int sup)
1733 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1734 if (ret==NULL) return FALSE;
1735 conv = strtol(ret, &end, 10);
1736 if (*end!=0) { g_free(ret); return FALSE; }
1738 if (conv < inf || conv > sup) return FALSE;
1743 gboolean parse_keyval_enum(const gchar *group, const gchar *key, int *val, const char **names, int n)
1748 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1749 if (ret==NULL) return FALSE;
1750 for (i=0; i<n; i++) {
1751 if (!names[i][0]) continue; // "" is for invalid values
1752 if (!g_ascii_strcasecmp(ret, names[i]))
1753 { *val = i; g_free(ret); return TRUE; }
1758 gboolean parse_keyval_boolean(const gchar *group, const gchar *key, gboolean *val)
1762 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1763 if (ret==NULL) return FALSE;
1764 if (!g_ascii_strcasecmp(ret, "true"))
1765 { *val = TRUE; g_free(ret); return TRUE; }
1766 if (!g_ascii_strcasecmp(ret, "false"))
1767 { *val = FALSE; g_free(ret); return TRUE; }
1772 gboolean parse_keyval_string(const gchar *group, const gchar *key, gchar **val)
1776 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1777 if (ret==NULL) return FALSE;
1778 if (strlen(ret) == 0) {
1786 gboolean parse_keyval_vorderlist(const gchar *group, const gchar *key, int *order)
1789 int tmp[VBOX_MAIN_NITEMS];
1792 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1793 if (ret==NULL) return FALSE;
1795 for (i=0; i<VBOX_MAIN_NITEMS; i++) tmp[i] = -1;
1797 while (*p==' ') p++;
1799 if (n>VBOX_MAIN_NITEMS) return FALSE; // too many items
1800 for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1801 if (!g_str_has_prefix(p, vorder_usernames[i])) continue;
1802 l = strlen(vorder_usernames[i]);
1803 if (p[l]==' '||p[l]==0) { p+=l; break; }
1805 if (i>=VBOX_MAIN_NITEMS) { g_free(ret); return FALSE; } // parse error
1808 while (*p==' ') p++;
1811 for (n=0; n<VBOX_MAIN_NITEMS; n++) order[n] = tmp[n];
1818 void load_config_from_file(void)
1825 #if GLIB_CHECK_VERSION(2,6,0)
1826 // no support for keyval files before Glib 2.6.0
1827 if (glib_minor_version<6) return;
1828 ui.config_data = g_key_file_new();
1829 if (!g_key_file_load_from_file(ui.config_data, ui.configfile,
1830 G_KEY_FILE_KEEP_COMMENTS, NULL)) {
1831 g_key_file_free(ui.config_data);
1832 ui.config_data = g_key_file_new();
1833 g_key_file_set_comment(ui.config_data, NULL, NULL,
1834 " Xournal configuration file.\n"
1835 " This file is generated automatically upon saving preferences.\n"
1836 " Use caution when editing this file manually.\n", NULL);
1840 // parse keys from the keyfile to set defaults
1841 if (parse_keyval_float("general", "display_dpi", &f, 10., 500.))
1842 DEFAULT_ZOOM = f/72.0;
1843 if (parse_keyval_float("general", "initial_zoom", &f,
1844 MIN_ZOOM*100/DEFAULT_ZOOM, MAX_ZOOM*100/DEFAULT_ZOOM))
1845 ui.zoom = ui.startup_zoom = DEFAULT_ZOOM*f/100.0;
1846 parse_keyval_boolean("general", "window_maximize", &ui.maximize_at_start);
1847 parse_keyval_boolean("general", "window_fullscreen", &ui.fullscreen);
1848 parse_keyval_int("general", "window_width", &ui.window_default_width, 10, 5000);
1849 parse_keyval_int("general", "window_height", &ui.window_default_height, 10, 5000);
1850 parse_keyval_int("general", "scrollbar_speed", &ui.scrollbar_step_increment, 1, 5000);
1851 parse_keyval_int("general", "zoom_dialog_increment", &ui.zoom_step_increment, 1, 500);
1852 parse_keyval_float("general", "zoom_step_factor", &ui.zoom_step_factor, 1., 5.);
1853 parse_keyval_boolean("general", "view_continuous", &ui.view_continuous);
1854 parse_keyval_boolean("general", "use_xinput", &ui.allow_xinput);
1855 parse_keyval_boolean("general", "discard_corepointer", &ui.discard_corepointer);
1856 parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip);
1857 parse_keyval_string("general", "default_path", &ui.default_path);
1858 parse_keyval_boolean("general", "pressure_sensitivity", &ui.pressure_sensitivity);
1859 parse_keyval_float("general", "width_minimum_multiplier", &ui.width_minimum_multiplier, 0., 10.);
1860 parse_keyval_float("general", "width_maximum_multiplier", &ui.width_maximum_multiplier, 0., 10.);
1862 parse_keyval_vorderlist("general", "interface_order", ui.vertical_order[0]);
1863 parse_keyval_vorderlist("general", "interface_fullscreen", ui.vertical_order[1]);
1864 parse_keyval_boolean("general", "interface_lefthanded", &ui.left_handed);
1865 parse_keyval_boolean("general", "shorten_menus", &ui.shorten_menus);
1866 if (parse_keyval_string("general", "shorten_menu_items", &str))
1867 if (str!=NULL) { g_free(ui.shorten_menu_items); ui.shorten_menu_items = str; }
1868 parse_keyval_float("general", "highlighter_opacity", &ui.hiliter_opacity, 0., 1.);
1869 parse_keyval_boolean("general", "autosave_prefs", &ui.auto_save_prefs);
1871 parse_keyval_float("paper", "width", &ui.default_page.width, 1., 5000.);
1872 parse_keyval_float("paper", "height", &ui.default_page.height, 1., 5000.);
1873 parse_keyval_enum("paper", "color", &(ui.default_page.bg->color_no), bgcolor_names, COLOR_MAX);
1874 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[ui.default_page.bg->color_no];
1875 parse_keyval_enum("paper", "style", &(ui.default_page.bg->ruling), bgstyle_names, 4);
1876 parse_keyval_boolean("paper", "apply_all", &ui.bg_apply_all_pages);
1877 parse_keyval_enum("paper", "default_unit", &ui.default_unit, unit_names, 4);
1878 parse_keyval_boolean("paper", "antialias_bg", &ui.antialias_bg);
1879 parse_keyval_boolean("paper", "progressive_bg", &ui.progressive_bg);
1880 parse_keyval_boolean("paper", "print_ruling", &ui.print_ruling);
1881 parse_keyval_int("paper", "gs_bitmap_dpi", &GS_BITMAP_DPI, 1, 1200);
1882 parse_keyval_int("paper", "pdftoppm_printing_dpi", &PDFTOPPM_PRINTING_DPI, 1, 1200);
1884 parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS);
1885 ui.toolno[0] = ui.startuptool;
1886 parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX);
1887 parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4);
1888 parse_keyval_boolean("tools", "pen_ruler", &(ui.brushes[0][TOOL_PEN].ruler));
1889 parse_keyval_boolean("tools", "pen_recognizer", &(ui.brushes[0][TOOL_PEN].recognizer));
1890 parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3);
1891 parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2);
1892 parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX);
1893 parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4);
1894 parse_keyval_boolean("tools", "highlighter_ruler", &(ui.brushes[0][TOOL_HIGHLIGHTER].ruler));
1895 parse_keyval_boolean("tools", "highlighter_recognizer", &(ui.brushes[0][TOOL_HIGHLIGHTER].recognizer));
1896 ui.brushes[0][TOOL_PEN].variable_width = ui.pressure_sensitivity;
1897 for (i=0; i< NUM_STROKE_TOOLS; i++)
1898 for (j=1; j<=NUM_BUTTONS; j++)
1899 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1901 parse_keyval_enum("tools", "btn2_tool", &(ui.toolno[1]), tool_names, NUM_TOOLS);
1902 if (parse_keyval_boolean("tools", "btn2_linked", &b))
1903 ui.linked_brush[1] = b?BRUSH_LINKED:BRUSH_STATIC;
1904 parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS);
1905 if (parse_keyval_boolean("tools", "btn3_linked", &b))
1906 ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC;
1907 if (ui.linked_brush[1]!=BRUSH_LINKED) {
1908 if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) {
1909 parse_keyval_boolean("tools", "btn2_ruler", &(ui.brushes[1][ui.toolno[1]].ruler));
1910 parse_keyval_boolean("tools", "btn2_recognizer", &(ui.brushes[1][ui.toolno[1]].recognizer));
1911 parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX);
1913 if (ui.toolno[1]<NUM_STROKE_TOOLS)
1914 parse_keyval_int("tools", "btn2_thickness", &(ui.brushes[1][ui.toolno[1]].thickness_no), 0, 4);
1915 if (ui.toolno[1]==TOOL_ERASER)
1916 parse_keyval_int("tools", "btn2_erasermode", &(ui.brushes[1][TOOL_ERASER].tool_options), 0, 2);
1918 if (ui.linked_brush[2]!=BRUSH_LINKED) {
1919 if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) {
1920 parse_keyval_boolean("tools", "btn3_ruler", &(ui.brushes[2][ui.toolno[2]].ruler));
1921 parse_keyval_boolean("tools", "btn3_recognizer", &(ui.brushes[2][ui.toolno[2]].recognizer));
1922 parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX);
1924 if (ui.toolno[2]<NUM_STROKE_TOOLS)
1925 parse_keyval_int("tools", "btn3_thickness", &(ui.brushes[2][ui.toolno[2]].thickness_no), 0, 4);
1926 if (ui.toolno[2]==TOOL_ERASER)
1927 parse_keyval_int("tools", "btn3_erasermode", &(ui.brushes[2][TOOL_ERASER].tool_options), 0, 2);
1929 parse_keyval_floatlist("tools", "pen_thicknesses", predef_thickness[TOOL_PEN], 5, 0.01, 1000.0);
1930 parse_keyval_floatlist("tools", "eraser_thicknesses", predef_thickness[TOOL_ERASER]+1, 3, 0.01, 1000.0);
1931 parse_keyval_floatlist("tools", "highlighter_thicknesses", predef_thickness[TOOL_HIGHLIGHTER]+1, 3, 0.01, 1000.0);
1932 if (parse_keyval_string("tools", "default_font", &str))
1933 if (str!=NULL) { g_free(ui.default_font_name); ui.default_font_name = str; }
1934 parse_keyval_float("tools", "default_font_size", &ui.default_font_size, 1., 200.);