11 #include <libgnomecanvas/libgnomecanvas.h>
19 #include "xo-interface.h"
20 #include "xo-support.h"
21 #include "xo-callbacks.h"
26 const char *tool_names[NUM_TOOLS] = {"pen", "eraser", "highlighter", "text", "", "selectrect", "vertspace", "hand"};
27 const char *color_names[COLOR_MAX] = {"black", "blue", "red", "green",
28 "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white"};
29 const char *bgtype_names[3] = {"solid", "pixmap", "pdf"};
30 const char *bgcolor_names[COLOR_MAX] = {"", "blue", "pink", "green",
31 "", "", "", "", "orange", "yellow", "white"};
32 const char *bgstyle_names[4] = {"plain", "lined", "ruled", "graph"};
33 const char *file_domain_names[3] = {"absolute", "attach", "clone"};
34 const char *unit_names[4] = {"cm", "in", "px", "pt"};
35 int PDFTOPPM_PRINTING_DPI, GS_BITMAP_DPI;
37 // creates a new empty journal
39 void new_journal(void)
42 journal.pages = g_list_append(NULL, new_page(&ui.default_page));
43 journal.last_attach_no = 0;
46 ui.cur_page = (struct Page *) journal.pages->data;
47 ui.cur_layer = (struct Layer *) ui.cur_page->layers->data;
50 update_file_name(NULL);
53 // check attachment names
55 void chk_attach_names(void)
58 struct Background *bg;
60 for (list = journal.pages; list!=NULL; list = list->next) {
61 bg = ((struct Page *)list->data)->bg;
62 if (bg->type == BG_SOLID || bg->file_domain != DOMAIN_ATTACH ||
63 bg->filename->s != NULL) continue;
64 bg->filename->s = g_strdup_printf("bg_%d.png", ++journal.last_attach_no);
68 // saves the journal to a file: returns true on success, false on error
70 gboolean save_journal(const char *filename)
73 struct Page *pg, *tmppg;
82 GList *pagelist, *layerlist, *itemlist, *list;
85 f = gzopen(filename, "w");
86 if (f==NULL) return FALSE;
89 setlocale(LC_NUMERIC, "C");
91 gzprintf(f, "<?xml version=\"1.0\" standalone=\"no\"?>\n"
92 "<xournal version=\"" VERSION "\">\n"
93 "<title>Xournal document - see http://math.mit.edu/~auroux/software/xournal/</title>\n");
94 for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next) {
95 pg = (struct Page *)pagelist->data;
96 gzprintf(f, "<page width=\"%.2f\" height=\"%.2f\">\n", pg->width, pg->height);
97 gzprintf(f, "<background type=\"%s\" ", bgtype_names[pg->bg->type]);
98 if (pg->bg->type == BG_SOLID) {
99 gzputs(f, "color=\"");
100 if (pg->bg->color_no >= 0) gzputs(f, bgcolor_names[pg->bg->color_no]);
101 else gzprintf(f, "#%08x", pg->bg->color_rgba);
102 gzprintf(f, "\" style=\"%s\" ", bgstyle_names[pg->bg->ruling]);
104 else if (pg->bg->type == BG_PIXMAP) {
106 for (list = journal.pages, i = 0; list!=pagelist; list = list->next, i++) {
107 tmppg = (struct Page *)list->data;
108 if (tmppg->bg->type == BG_PIXMAP &&
109 tmppg->bg->pixbuf == pg->bg->pixbuf &&
110 tmppg->bg->filename == pg->bg->filename)
111 { is_clone = i; break; }
114 gzprintf(f, "domain=\"clone\" filename=\"%d\" ", is_clone);
116 if (pg->bg->file_domain == DOMAIN_ATTACH) {
117 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
118 if (!gdk_pixbuf_save(pg->bg->pixbuf, tmpfn, "png", NULL, NULL)) {
119 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
120 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
121 "Could not write background '%s'. Continuing anyway.", tmpfn);
122 gtk_dialog_run(GTK_DIALOG(dialog));
123 gtk_widget_destroy(dialog);
127 tmpstr = g_markup_escape_text(pg->bg->filename->s, -1);
128 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
129 file_domain_names[pg->bg->file_domain], tmpstr);
133 else if (pg->bg->type == BG_PDF) {
135 for (list = journal.pages; list!=pagelist; list = list->next) {
136 tmppg = (struct Page *)list->data;
137 if (tmppg->bg->type == BG_PDF) { is_clone = 1; break; }
140 if (pg->bg->file_domain == DOMAIN_ATTACH) {
141 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
143 if (bgpdf.status != STATUS_NOT_INIT &&
144 g_file_get_contents(bgpdf.tmpfile_copy, &pdfbuf, &pdflen, NULL))
146 tmpf = fopen(tmpfn, "w");
147 if (tmpf != NULL && fwrite(pdfbuf, 1, pdflen, tmpf) == pdflen)
153 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
154 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
155 "Could not write background '%s'. Continuing anyway.", tmpfn);
156 gtk_dialog_run(GTK_DIALOG(dialog));
157 gtk_widget_destroy(dialog);
161 tmpstr = g_markup_escape_text(pg->bg->filename->s, -1);
162 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
163 file_domain_names[pg->bg->file_domain], tmpstr);
166 gzprintf(f, "pageno=\"%d\" ", pg->bg->file_page_seq);
169 for (layerlist = pg->layers; layerlist!=NULL; layerlist = layerlist->next) {
170 layer = (struct Layer *)layerlist->data;
171 gzprintf(f, "<layer>\n");
172 for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
173 item = (struct Item *)itemlist->data;
174 if (item->type == ITEM_STROKE) {
175 gzprintf(f, "<stroke tool=\"%s\" color=\"",
176 tool_names[item->brush.tool_type]);
177 if (item->brush.color_no >= 0)
178 gzputs(f, color_names[item->brush.color_no]);
180 gzprintf(f, "#%08x", item->brush.color_rgba);
181 gzprintf(f, "\" width=\"%.2f\">\n", item->brush.thickness);
182 for (i=0;i<2*item->path->num_points;i++)
183 gzprintf(f, "%.2f ", item->path->coords[i]);
184 gzprintf(f, "\n</stroke>\n");
186 if (item->type == ITEM_TEXT) {
187 tmpstr = g_markup_escape_text(item->font_name, -1);
188 gzprintf(f, "<text font=\"%s\" size=\"%.2f\" x=\"%.2f\" y=\"%.2f\" color=\"",
189 tmpstr, item->font_size, item->bbox.left, item->bbox.top);
191 if (item->brush.color_no >= 0)
192 gzputs(f, color_names[item->brush.color_no]);
194 gzprintf(f, "#%08x", item->brush.color_rgba);
195 tmpstr = g_markup_escape_text(item->text, -1);
196 gzprintf(f, "\">%s</text>\n", tmpstr);
200 gzprintf(f, "</layer>\n");
202 gzprintf(f, "</page>\n");
204 gzprintf(f, "</xournal>\n");
206 setlocale(LC_NUMERIC, "");
211 // closes a journal: returns true on success, false on abort
213 gboolean close_journal(void)
215 if (!ok_to_close()) return FALSE;
217 // free everything...
223 delete_journal(&journal);
226 /* note: various members of ui and journal are now in invalid states,
227 use new_journal() to reinitialize them */
230 // sanitize a string containing floats, in case it may have , instead of .
232 void cleanup_numeric(char *s)
234 while (*s!=0) { if (*s==',') *s='.'; s++; }
237 // the XML parser functions for open_journal()
239 struct Journal tmpJournal;
240 struct Page *tmpPage;
241 struct Layer *tmpLayer;
242 struct Item *tmpItem;
244 struct Background *tmpBg_pdf;
246 GError *xoj_invalid(void)
248 return g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Invalid file contents");
251 void xoj_parser_start_element(GMarkupParseContext *context,
252 const gchar *element_name, const gchar **attribute_names,
253 const gchar **attribute_values, gpointer user_data, GError **error)
257 struct Background *tmpbg;
258 char *tmpbg_filename;
261 if (!strcmp(element_name, "title") || !strcmp(element_name, "xournal")) {
262 if (tmpPage != NULL) {
263 *error = xoj_invalid();
266 // nothing special to do
268 else if (!strcmp(element_name, "page")) { // start of a page
269 if (tmpPage != NULL) {
270 *error = xoj_invalid();
273 tmpPage = (struct Page *)g_malloc(sizeof(struct Page));
274 tmpPage->layers = NULL;
275 tmpPage->nlayers = 0;
276 tmpPage->group = NULL;
277 tmpPage->bg = g_new(struct Background, 1);
278 tmpPage->bg->type = -1;
279 tmpPage->bg->canvas_item = NULL;
280 tmpPage->bg->pixbuf = NULL;
281 tmpPage->bg->filename = NULL;
282 tmpJournal.pages = g_list_append(tmpJournal.pages, tmpPage);
284 // scan for height and width attributes
286 while (*attribute_names!=NULL) {
287 if (!strcmp(*attribute_names, "width")) {
288 if (has_attr & 1) *error = xoj_invalid();
289 cleanup_numeric((gchar *)*attribute_values);
290 tmpPage->width = g_ascii_strtod(*attribute_values, &ptr);
291 if (ptr == *attribute_values) *error = xoj_invalid();
294 else if (!strcmp(*attribute_names, "height")) {
295 if (has_attr & 2) *error = xoj_invalid();
296 cleanup_numeric((gchar *)*attribute_values);
297 tmpPage->height = g_ascii_strtod(*attribute_values, &ptr);
298 if (ptr == *attribute_values) *error = xoj_invalid();
301 else *error = xoj_invalid();
305 if (has_attr!=3) *error = xoj_invalid();
307 else if (!strcmp(element_name, "background")) {
308 if (tmpPage == NULL || tmpLayer !=NULL || tmpPage->bg->type >= 0) {
309 *error = xoj_invalid();
313 while (*attribute_names!=NULL) {
314 if (!strcmp(*attribute_names, "type")) {
315 if (has_attr) *error = xoj_invalid();
317 if (!strcmp(*attribute_values, bgtype_names[i]))
318 tmpPage->bg->type = i;
319 if (tmpPage->bg->type < 0) *error = xoj_invalid();
321 if (tmpPage->bg->type == BG_PDF) {
322 if (tmpBg_pdf == NULL) tmpBg_pdf = tmpPage->bg;
325 tmpPage->bg->filename = refstring_ref(tmpBg_pdf->filename);
326 tmpPage->bg->file_domain = tmpBg_pdf->file_domain;
330 else if (!strcmp(*attribute_names, "color")) {
331 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
332 if (has_attr & 2) *error = xoj_invalid();
333 tmpPage->bg->color_no = COLOR_OTHER;
334 for (i=0; i<COLOR_MAX; i++)
335 if (!strcmp(*attribute_values, bgcolor_names[i])) {
336 tmpPage->bg->color_no = i;
337 tmpPage->bg->color_rgba = predef_bgcolors_rgba[i];
339 // there's also the case of hex (#rrggbbaa) colors
340 if (tmpPage->bg->color_no == COLOR_OTHER && **attribute_values == '#') {
341 tmpPage->bg->color_rgba = strtol(*attribute_values + 1, &ptr, 16);
342 if (*ptr!=0) *error = xoj_invalid();
346 else if (!strcmp(*attribute_names, "style")) {
347 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
348 if (has_attr & 4) *error = xoj_invalid();
349 tmpPage->bg->ruling = -1;
351 if (!strcmp(*attribute_values, bgstyle_names[i]))
352 tmpPage->bg->ruling = i;
353 if (tmpPage->bg->ruling < 0) *error = xoj_invalid();
356 else if (!strcmp(*attribute_names, "domain")) {
357 if (tmpPage->bg->type <= BG_SOLID || (has_attr & 8))
358 { *error = xoj_invalid(); return; }
359 tmpPage->bg->file_domain = -1;
361 if (!strcmp(*attribute_values, file_domain_names[i]))
362 tmpPage->bg->file_domain = i;
363 if (tmpPage->bg->file_domain < 0)
364 { *error = xoj_invalid(); return; }
367 else if (!strcmp(*attribute_names, "filename")) {
368 if (tmpPage->bg->type <= BG_SOLID || (has_attr != 9))
369 { *error = xoj_invalid(); return; }
370 if (tmpPage->bg->file_domain == DOMAIN_CLONE) {
371 // filename is a page number
372 i = strtol(*attribute_values, &ptr, 10);
373 if (ptr == *attribute_values || i < 0 || i > tmpJournal.npages-2)
374 { *error = xoj_invalid(); return; }
375 tmpbg = ((struct Page *)g_list_nth_data(tmpJournal.pages, i))->bg;
376 if (tmpbg->type != tmpPage->bg->type)
377 { *error = xoj_invalid(); return; }
378 tmpPage->bg->filename = refstring_ref(tmpbg->filename);
379 tmpPage->bg->pixbuf = tmpbg->pixbuf;
380 if (tmpbg->pixbuf!=NULL) gdk_pixbuf_ref(tmpbg->pixbuf);
381 tmpPage->bg->file_domain = tmpbg->file_domain;
384 tmpPage->bg->filename = new_refstring(*attribute_values);
385 if (tmpPage->bg->type == BG_PIXMAP) {
386 if (tmpPage->bg->file_domain == DOMAIN_ATTACH) {
387 tmpbg_filename = g_strdup_printf("%s.%s", tmpFilename, *attribute_values);
388 if (sscanf(*attribute_values, "bg_%d.png", &i) == 1)
389 if (i > tmpJournal.last_attach_no)
390 tmpJournal.last_attach_no = i;
392 else tmpbg_filename = g_strdup(*attribute_values);
393 tmpPage->bg->pixbuf = gdk_pixbuf_new_from_file(tmpbg_filename, NULL);
394 if (tmpPage->bg->pixbuf == NULL) {
395 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
396 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
397 "Could not open background '%s'. Setting background to white.",
399 gtk_dialog_run(GTK_DIALOG(dialog));
400 gtk_widget_destroy(dialog);
401 tmpPage->bg->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
402 gdk_pixbuf_fill(tmpPage->bg->pixbuf, 0xffffffff); // solid white
404 g_free(tmpbg_filename);
409 else if (!strcmp(*attribute_names, "pageno")) {
410 if (tmpPage->bg->type != BG_PDF || (has_attr & 32))
411 { *error = xoj_invalid(); return; }
412 tmpPage->bg->file_page_seq = strtol(*attribute_values, &ptr, 10);
413 if (ptr == *attribute_values) *error = xoj_invalid();
416 else *error = xoj_invalid();
420 if (tmpPage->bg->type < 0) *error = xoj_invalid();
421 if (tmpPage->bg->type == BG_SOLID && has_attr != 7) *error = xoj_invalid();
422 if (tmpPage->bg->type == BG_PIXMAP && has_attr != 25) *error = xoj_invalid();
423 if (tmpPage->bg->type == BG_PDF && has_attr != 57) *error = xoj_invalid();
425 else if (!strcmp(element_name, "layer")) { // start of a layer
426 if (tmpPage == NULL || tmpLayer != NULL) {
427 *error = xoj_invalid();
430 tmpLayer = (struct Layer *)g_malloc(sizeof(struct Layer));
431 tmpLayer->items = NULL;
432 tmpLayer->nitems = 0;
433 tmpLayer->group = NULL;
434 tmpPage->layers = g_list_append(tmpPage->layers, tmpLayer);
437 else if (!strcmp(element_name, "stroke")) { // start of a stroke
438 if (tmpLayer == NULL || tmpItem != NULL) {
439 *error = xoj_invalid();
442 tmpItem = (struct Item *)g_malloc(sizeof(struct Item));
443 tmpItem->type = ITEM_STROKE;
444 tmpItem->path = NULL;
445 tmpItem->canvas_item = NULL;
446 tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
448 // scan for tool, color, and width attributes
450 while (*attribute_names!=NULL) {
451 if (!strcmp(*attribute_names, "width")) {
452 if (has_attr & 1) *error = xoj_invalid();
453 cleanup_numeric((gchar *)*attribute_values);
454 tmpItem->brush.thickness = g_ascii_strtod(*attribute_values, &ptr);
455 if (ptr == *attribute_values) *error = xoj_invalid();
458 else if (!strcmp(*attribute_names, "color")) {
459 if (has_attr & 2) *error = xoj_invalid();
460 tmpItem->brush.color_no = COLOR_OTHER;
461 for (i=0; i<COLOR_MAX; i++)
462 if (!strcmp(*attribute_values, color_names[i])) {
463 tmpItem->brush.color_no = i;
464 tmpItem->brush.color_rgba = predef_colors_rgba[i];
466 // there's also the case of hex (#rrggbbaa) colors
467 if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
468 tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
469 if (*ptr!=0) *error = xoj_invalid();
473 else if (!strcmp(*attribute_names, "tool")) {
474 if (has_attr & 4) *error = xoj_invalid();
475 tmpItem->brush.tool_type = -1;
476 for (i=0; i<NUM_STROKE_TOOLS; i++)
477 if (!strcmp(*attribute_values, tool_names[i])) {
478 tmpItem->brush.tool_type = i;
480 if (tmpItem->brush.tool_type == -1) *error = xoj_invalid();
483 else *error = xoj_invalid();
487 if (has_attr!=7) *error = xoj_invalid();
488 // finish filling the brush info
489 tmpItem->brush.thickness_no = 0; // who cares ?
490 tmpItem->brush.tool_options = 0; // who cares ?
491 if (tmpItem->brush.tool_type == TOOL_HIGHLIGHTER) {
492 if (tmpItem->brush.color_no >= 0)
493 tmpItem->brush.color_rgba &= ui.hiliter_alpha_mask;
496 else if (!strcmp(element_name, "text")) { // start of a text item
497 if (tmpLayer == NULL || tmpItem != NULL) {
498 *error = xoj_invalid();
501 tmpItem = (struct Item *)g_malloc0(sizeof(struct Item));
502 tmpItem->type = ITEM_TEXT;
503 tmpItem->canvas_item = NULL;
504 tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
506 // scan for font, size, x, y, and color attributes
508 while (*attribute_names!=NULL) {
509 if (!strcmp(*attribute_names, "font")) {
510 if (has_attr & 1) *error = xoj_invalid();
511 tmpItem->font_name = g_strdup(*attribute_values);
514 else if (!strcmp(*attribute_names, "size")) {
515 if (has_attr & 2) *error = xoj_invalid();
516 cleanup_numeric((gchar *)*attribute_values);
517 tmpItem->font_size = g_ascii_strtod(*attribute_values, &ptr);
518 if (ptr == *attribute_values) *error = xoj_invalid();
521 else if (!strcmp(*attribute_names, "x")) {
522 if (has_attr & 4) *error = xoj_invalid();
523 cleanup_numeric((gchar *)*attribute_values);
524 tmpItem->bbox.left = g_ascii_strtod(*attribute_values, &ptr);
525 if (ptr == *attribute_values) *error = xoj_invalid();
528 else if (!strcmp(*attribute_names, "y")) {
529 if (has_attr & 8) *error = xoj_invalid();
530 cleanup_numeric((gchar *)*attribute_values);
531 tmpItem->bbox.top = g_ascii_strtod(*attribute_values, &ptr);
532 if (ptr == *attribute_values) *error = xoj_invalid();
535 else if (!strcmp(*attribute_names, "color")) {
536 if (has_attr & 16) *error = xoj_invalid();
537 tmpItem->brush.color_no = COLOR_OTHER;
538 for (i=0; i<COLOR_MAX; i++)
539 if (!strcmp(*attribute_values, color_names[i])) {
540 tmpItem->brush.color_no = i;
541 tmpItem->brush.color_rgba = predef_colors_rgba[i];
543 // there's also the case of hex (#rrggbbaa) colors
544 if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
545 tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
546 if (*ptr!=0) *error = xoj_invalid();
550 else *error = xoj_invalid();
554 if (has_attr!=31) *error = xoj_invalid();
558 void xoj_parser_end_element(GMarkupParseContext *context,
559 const gchar *element_name, gpointer user_data, GError **error)
561 if (!strcmp(element_name, "page")) {
562 if (tmpPage == NULL || tmpLayer != NULL) {
563 *error = xoj_invalid();
566 if (tmpPage->nlayers == 0 || tmpPage->bg->type < 0) *error = xoj_invalid();
569 if (!strcmp(element_name, "layer")) {
570 if (tmpLayer == NULL || tmpItem != NULL) {
571 *error = xoj_invalid();
576 if (!strcmp(element_name, "stroke")) {
577 if (tmpItem == NULL) {
578 *error = xoj_invalid();
581 update_item_bbox(tmpItem);
584 if (!strcmp(element_name, "text")) {
585 if (tmpItem == NULL) {
586 *error = xoj_invalid();
593 void xoj_parser_text(GMarkupParseContext *context,
594 const gchar *text, gsize text_len, gpointer user_data, GError **error)
596 const gchar *element_name, *ptr;
599 element_name = g_markup_parse_context_get_element(context);
600 if (element_name == NULL) return;
601 if (!strcmp(element_name, "stroke")) {
602 cleanup_numeric((gchar *)text);
605 while (text_len > 0) {
606 realloc_cur_path(n/2 + 1);
607 ui.cur_path.coords[n] = g_ascii_strtod(text, (char **)(&ptr));
608 if (ptr == text) break;
609 text_len -= (ptr - text);
613 if (n<4 || n&1) { *error = xoj_invalid(); return; }
614 tmpItem->path = gnome_canvas_points_new(n/2);
615 g_memmove(tmpItem->path->coords, ui.cur_path.coords, n*sizeof(double));
617 if (!strcmp(element_name, "text")) {
618 tmpItem->text = g_malloc(text_len+1);
619 g_memmove(tmpItem->text, text, text_len);
620 tmpItem->text[text_len]=0;
624 gboolean user_wants_second_chance(char **filename)
627 GtkFileFilter *filt_all, *filt_pdf;
628 GtkResponseType response;
630 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
631 GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO,
632 "Could not open background '%s'.\nSelect another file?",
634 response = gtk_dialog_run(GTK_DIALOG(dialog));
635 gtk_widget_destroy(dialog);
636 if (response != GTK_RESPONSE_YES) return FALSE;
637 dialog = gtk_file_chooser_dialog_new("Open PDF", GTK_WINDOW (winMain),
638 GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
639 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
641 filt_all = gtk_file_filter_new();
642 gtk_file_filter_set_name(filt_all, "All files");
643 gtk_file_filter_add_pattern(filt_all, "*");
644 filt_pdf = gtk_file_filter_new();
645 gtk_file_filter_set_name(filt_pdf, "PDF files");
646 gtk_file_filter_add_pattern(filt_pdf, "*.pdf");
647 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_pdf);
648 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_all);
650 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) {
651 gtk_widget_destroy(dialog);
655 *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
656 gtk_widget_destroy(dialog);
660 gboolean open_journal(char *filename)
662 const GMarkupParser parser = { xoj_parser_start_element,
663 xoj_parser_end_element,
664 xoj_parser_text, NULL, NULL};
665 GMarkupParseContext *context;
675 f = gzopen(filename, "r");
676 if (f==NULL) return FALSE;
678 context = g_markup_parse_context_new(&parser, 0, NULL, NULL);
680 tmpJournal.npages = 0;
681 tmpJournal.pages = NULL;
682 tmpJournal.last_attach_no = 0;
686 tmpFilename = filename;
691 while (valid && !gzeof(f)) {
692 len = gzread(f, buffer, 1000);
693 if (len<0) valid = FALSE;
694 if (maybe_pdf && len>=4 && !strncmp(buffer, "%PDF", 4))
695 { valid = FALSE; break; } // most likely pdf
696 else maybe_pdf = FALSE;
698 valid = g_markup_parse_context_parse(context, buffer, len, &error);
701 if (valid) valid = g_markup_parse_context_end_parse(context, &error);
702 if (tmpJournal.npages == 0) valid = FALSE;
703 g_markup_parse_context_free(context);
706 delete_journal(&tmpJournal);
707 if (!maybe_pdf) return FALSE;
708 // essentially same as on_fileNewBackground from here on
711 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
713 ui.zoom = ui.startup_zoom;
714 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
716 return init_bgpdf(filename, TRUE, DOMAIN_ABSOLUTE);
719 ui.saved = TRUE; // force close_journal() to do its job
721 g_memmove(&journal, &tmpJournal, sizeof(struct Journal));
723 // if we need to initialize a fresh pdf loader
724 if (tmpBg_pdf!=NULL) {
725 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
726 if (tmpBg_pdf->file_domain == DOMAIN_ATTACH)
727 tmpfn = g_strdup_printf("%s.%s", filename, tmpBg_pdf->filename->s);
729 tmpfn = g_strdup(tmpBg_pdf->filename->s);
730 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
731 // in case the file name became invalid
732 if (!valid && tmpBg_pdf->file_domain != DOMAIN_ATTACH)
733 if (user_wants_second_chance(&tmpfn)) {
734 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
735 if (valid) { // change the file name...
736 g_free(tmpBg_pdf->filename->s);
737 tmpBg_pdf->filename->s = g_strdup(tmpfn);
741 refstring_unref(bgpdf.filename);
742 bgpdf.filename = refstring_ref(tmpBg_pdf->filename);
744 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
745 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open background '%s'.",
747 gtk_dialog_run(GTK_DIALOG(dialog));
748 gtk_widget_destroy(dialog);
754 ui.cur_page = (struct Page *)journal.pages->data;
755 ui.layerno = ui.cur_page->nlayers-1;
756 ui.cur_layer = (struct Layer *)(g_list_last(ui.cur_page->layers)->data);
758 ui.zoom = ui.startup_zoom;
759 update_file_name(g_strdup(filename));
760 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
763 gtk_adjustment_set_value(gtk_layout_get_vadjustment(GTK_LAYOUT(canvas)), 0);
767 /************ file backgrounds *************/
769 struct Background *attempt_load_pix_bg(char *filename, gboolean attach)
771 struct Background *bg;
774 pix = gdk_pixbuf_new_from_file(filename, NULL);
775 if (pix == NULL) return NULL;
777 bg = g_new(struct Background, 1);
778 bg->type = BG_PIXMAP;
779 bg->canvas_item = NULL;
781 bg->pixbuf_scale = DEFAULT_ZOOM;
783 bg->filename = new_refstring(NULL);
784 bg->file_domain = DOMAIN_ATTACH;
786 bg->filename = new_refstring(filename);
787 bg->file_domain = DOMAIN_ABSOLUTE;
792 #define BUFSIZE 65536 // a reasonable buffer size for reads from gs pipe
794 GList *attempt_load_gv_bg(char *filename)
796 struct Background *bg;
799 GdkPixbufLoader *loader;
803 int buflen, remnlen, file_pageno;
805 f = fopen(filename, "r");
806 if (f == NULL) return NULL;
807 buf = g_malloc(BUFSIZE); // a reasonable buffer size
808 if (fread(buf, 1, 4, f) !=4 ||
809 (strncmp((char *)buf, "%!PS", 4) && strncmp((char *)buf, "%PDF", 4))) {
816 pipename = g_strdup_printf(GS_CMDLINE, (double)GS_BITMAP_DPI, filename);
817 gs_pipe = popen(pipename, "r");
825 while (!feof(gs_pipe)) {
826 if (!remnlen) { // new page: get a BMP header ?
827 buflen = fread(buf, 1, 54, gs_pipe);
828 if (buflen < 6) buflen += fread(buf, 1, 54-buflen, gs_pipe);
829 if (buflen < 6 || buf[0]!='B' || buf[1]!='M') break; // fatal: abort
830 remnlen = (int)(buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + (buf[2]);
831 loader = gdk_pixbuf_loader_new();
833 else buflen = fread(buf, 1, (remnlen < BUFSIZE)?remnlen:BUFSIZE, gs_pipe);
835 if (buflen == 0) break;
836 if (!gdk_pixbuf_loader_write(loader, buf, buflen, NULL)) break;
837 if (remnlen == 0) { // make a new bg
838 pix = gdk_pixbuf_loader_get_pixbuf(loader);
839 if (pix == NULL) break;
841 gdk_pixbuf_loader_close(loader, NULL);
842 g_object_unref(loader);
844 bg = g_new(struct Background, 1);
845 bg->canvas_item = NULL;
847 bg->pixbuf_scale = (GS_BITMAP_DPI/72.0);
848 bg->type = BG_PIXMAP;
849 bg->filename = new_refstring(NULL);
850 bg->file_domain = DOMAIN_ATTACH;
852 bg_list = g_list_append(bg_list, bg);
855 if (loader != NULL) gdk_pixbuf_loader_close(loader, NULL);
861 struct Background *attempt_screenshot_bg(void)
863 struct Background *bg;
866 GError *error = NULL;
870 Window x_root, x_win;
872 x_root = gdk_x11_get_default_root_xwindow();
874 if (!XGrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root,
875 False, ButtonReleaseMask, GrabModeAsync, GrabModeSync, None, None))
878 XWindowEvent (GDK_DISPLAY(), x_root, ButtonReleaseMask, &x_event);
879 XUngrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root);
881 x_win = x_event.xbutton.subwindow;
882 if (x_win == None) x_win = x_root;
884 window = gdk_window_foreign_new_for_display(gdk_display_get_default(), x_win);
886 gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
888 pix = gdk_pixbuf_get_from_drawable(NULL, window,
889 gdk_colormap_get_system(), 0, 0, 0, 0, w, h);
891 if (pix == NULL) return NULL;
893 bg = g_new(struct Background, 1);
894 bg->type = BG_PIXMAP;
895 bg->canvas_item = NULL;
897 bg->pixbuf_scale = DEFAULT_ZOOM;
898 bg->filename = new_refstring(NULL);
899 bg->file_domain = DOMAIN_ATTACH;
903 /************** pdf annotation ***************/
905 /* free tmp directory */
907 void end_bgpdf_shutdown(void)
909 if (bgpdf.tmpdir!=NULL) {
910 if (bgpdf.tmpfile_copy!=NULL) {
911 g_unlink(bgpdf.tmpfile_copy);
912 g_free(bgpdf.tmpfile_copy);
913 bgpdf.tmpfile_copy = NULL;
915 g_rmdir(bgpdf.tmpdir);
916 g_free(bgpdf.tmpdir);
919 bgpdf.status = STATUS_NOT_INIT;
922 /* cancel a request */
924 void cancel_bgpdf_request(struct BgPdfRequest *req)
928 list_link = g_list_find(bgpdf.requests, req);
929 if (list_link == NULL) return;
930 if (list_link->prev == NULL && bgpdf.pid > 0) {
931 // this is being processed: kill the child but don't remove the request yet
932 if (bgpdf.status == STATUS_RUNNING) bgpdf.status = STATUS_ABORTED;
933 kill(bgpdf.pid, SIGHUP);
934 // printf("Cancelling a request - killing %d\n", bgpdf.pid);
937 // remove the request
938 bgpdf.requests = g_list_delete_link(bgpdf.requests, list_link);
940 // printf("Cancelling a request - no kill needed\n");
944 /* sigchld callback */
946 void bgpdf_child_handler(GPid pid, gint status, gpointer data)
948 struct BgPdfRequest *req;
949 struct BgPdfPage *bgpg;
954 if (bgpdf.requests == NULL) return;
955 req = (struct BgPdfRequest *)bgpdf.requests->data;
958 // pdftoppm used to generate p-nnnnnn.ppm (6 digits); new versions produce variable width
959 for (npad = 6; npad>0; npad--) {
960 ppm_name = g_strdup_printf("%s/p-%0*d.ppm", bgpdf.tmpdir, npad, req->pageno);
961 if (bgpdf.status != STATUS_ABORTED && bgpdf.status != STATUS_SHUTDOWN)
962 pixbuf = gdk_pixbuf_new_from_file(ppm_name, NULL);
963 ret = unlink(ppm_name);
965 if (pixbuf != NULL || ret == 0) break;
968 if (pixbuf != NULL) { // success
969 // printf("success\n");
970 while (req->pageno > bgpdf.npages) {
971 bgpg = g_new(struct BgPdfPage, 1);
973 bgpdf.pages = g_list_append(bgpdf.pages, bgpg);
976 bgpg = g_list_nth_data(bgpdf.pages, req->pageno-1);
977 if (bgpg->pixbuf!=NULL) gdk_pixbuf_unref(bgpg->pixbuf);
978 bgpg->pixbuf = pixbuf;
979 bgpg->dpi = req->dpi;
980 if (req->initial_request && bgpdf.create_pages) {
981 bgpdf_create_page_with_bg(req->pageno, bgpg);
982 // create page n, resize it, set its bg - all without any undo effect
984 if (!req->is_printing) bgpdf_update_bg(req->pageno, bgpg);
985 // look for all pages with this bg, and update their bg pixmaps
989 // printf("failed or aborted\n");
990 bgpdf.create_pages = FALSE;
991 req->initial_request = FALSE;
995 g_spawn_close_pid(pid);
997 if (req->initial_request)
998 req->pageno++; // try for next page
1000 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1002 if (bgpdf.status == STATUS_SHUTDOWN) {
1003 end_bgpdf_shutdown();
1007 bgpdf.status = STATUS_IDLE;
1008 if (bgpdf.requests != NULL) bgpdf_spawn_child();
1011 /* spawn a child to process the head request */
1013 void bgpdf_spawn_child(void)
1015 struct BgPdfRequest *req;
1017 gchar pageno_str[10], dpi_str[10];
1018 gchar *pdf_filename = bgpdf.tmpfile_copy;
1019 gchar *ppm_root = g_strdup_printf("%s/p", bgpdf.tmpdir);
1020 gchar *argv[]= PDFTOPPM_ARGV;
1023 if (bgpdf.requests == NULL) return;
1024 req = (struct BgPdfRequest *)bgpdf.requests->data;
1025 if (req->pageno > bgpdf.npages+1 ||
1026 (!req->initial_request && req->pageno <= bgpdf.npages &&
1027 req->dpi == ((struct BgPdfPage *)g_list_nth_data(bgpdf.pages, req->pageno-1))->dpi))
1028 { // ignore this request - it's redundant, or in outer space
1030 bgpdf.status = STATUS_IDLE;
1031 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1033 if (bgpdf.requests != NULL) bgpdf_spawn_child();
1036 g_snprintf(pageno_str, 10, "%d", req->pageno);
1037 g_snprintf(dpi_str, 10, "%d", req->dpi);
1038 /* printf("Processing request for page %d at %d dpi -- in %s\n",
1039 req->pageno, req->dpi, ppm_root); */
1040 if (!g_spawn_async(NULL, argv, NULL,
1041 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
1042 NULL, NULL, &pid, NULL))
1044 // couldn't spawn... abort this request, try next one maybe ?
1045 // printf("Couldn't spawn\n");
1047 bgpdf.status = STATUS_IDLE;
1048 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
1050 if (!bgpdf.has_failed) {
1051 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
1052 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Unable to start PDF loader %s.", argv[0]);
1053 gtk_dialog_run(GTK_DIALOG(dialog));
1054 gtk_widget_destroy(dialog);
1056 bgpdf.has_failed = TRUE;
1057 if (bgpdf.requests != NULL) bgpdf_spawn_child();
1061 // printf("Spawned process %d\n", pid);
1063 bgpdf.status = STATUS_RUNNING;
1064 g_child_watch_add(pid, bgpdf_child_handler, NULL);
1068 /* make a request */
1070 void add_bgpdf_request(int pageno, double zoom, gboolean printing)
1072 struct BgPdfRequest *req, *cmp_req;
1075 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN)
1076 return; // don't accept requests in those modes...
1077 req = g_new(struct BgPdfRequest, 1);
1078 req->is_printing = printing;
1079 if (printing) req->dpi = PDFTOPPM_PRINTING_DPI;
1080 else req->dpi = (int)floor(72*zoom+0.5);
1081 // printf("Enqueuing request for page %d at %d dpi\n", pageno, req->dpi);
1083 // cancel any request this may supersede
1084 for (list = bgpdf.requests; list != NULL; ) {
1085 cmp_req = (struct BgPdfRequest *)list->data;
1087 if (!cmp_req->initial_request && cmp_req->pageno == pageno &&
1088 cmp_req->is_printing == printing)
1089 cancel_bgpdf_request(cmp_req);
1091 req->pageno = pageno;
1092 req->initial_request = FALSE;
1095 req->initial_request = TRUE;
1097 bgpdf.requests = g_list_append(bgpdf.requests, req);
1098 if (!bgpdf.pid) bgpdf_spawn_child();
1101 /* shutdown the PDF reader */
1103 void shutdown_bgpdf(void)
1106 struct BgPdfPage *pdfpg;
1107 struct BgPdfRequest *req;
1109 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN) return;
1110 refstring_unref(bgpdf.filename);
1111 for (list = bgpdf.pages; list != NULL; list = list->next) {
1112 pdfpg = (struct BgPdfPage *)list->data;
1113 if (pdfpg->pixbuf!=NULL) gdk_pixbuf_unref(pdfpg->pixbuf);
1115 g_list_free(bgpdf.pages);
1116 bgpdf.status = STATUS_SHUTDOWN;
1117 for (list = g_list_last(bgpdf.requests); list != NULL; ) {
1118 req = (struct BgPdfRequest *)list->data;
1120 cancel_bgpdf_request(req);
1122 if (!bgpdf.pid) end_bgpdf_shutdown();
1123 /* The above will ultimately remove all requests and kill the child if needed.
1124 The child will set status to STATUS_NOT_INIT, clear the requests list,
1125 empty tmpdir, ... except if there's no child! */
1126 /* note: it could look like there's a race condition here - if a child
1127 terminates and a new request is enqueued while we are destroying the
1128 queue - but actually the child handler callback is NOT a signal
1129 callback, so execution of this function is atomic */
1132 gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain)
1138 if (bgpdf.status != STATUS_NOT_INIT) return FALSE;
1139 bgpdf.tmpfile_copy = NULL;
1140 bgpdf.tmpdir = mkdtemp(g_strdup(TMPDIR_TEMPLATE));
1141 if (!bgpdf.tmpdir) return FALSE;
1142 // make a local copy and check if it's a PDF
1143 if (!g_file_get_contents(pdfname, &filebuf, &filelen, NULL))
1144 { end_bgpdf_shutdown(); return FALSE; }
1145 if (filelen < 4 || strncmp(filebuf, "%PDF", 4))
1146 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1147 bgpdf.tmpfile_copy = g_strdup_printf("%s/bg.pdf", bgpdf.tmpdir);
1148 f = fopen(bgpdf.tmpfile_copy, "w");
1149 if (f == NULL || fwrite(filebuf, 1, filelen, f) != filelen)
1150 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1153 bgpdf.status = STATUS_IDLE;
1155 bgpdf.filename = new_refstring((file_domain == DOMAIN_ATTACH) ? "bg.pdf" : pdfname);
1156 bgpdf.file_domain = file_domain;
1159 bgpdf.requests = NULL;
1160 bgpdf.create_pages = create_pages;
1161 bgpdf.has_failed = FALSE;
1162 add_bgpdf_request(-1, ui.startup_zoom, FALSE); // request all pages
1166 // create page n, resize it, set its bg
1167 void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg)
1170 struct Background *bg;
1172 if (journal.npages < pageno) {
1173 bg = g_new(struct Background, 1);
1174 bg->canvas_item = NULL;
1176 pg = (struct Page *)g_list_nth_data(journal.pages, pageno-1);
1178 if (bg->type != BG_SOLID) return;
1179 // don't mess with a page the user has modified significantly...
1183 bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1184 bg->filename = refstring_ref(bgpdf.filename);
1185 bg->file_domain = bgpdf.file_domain;
1186 bg->file_page_seq = pageno;
1187 bg->pixbuf_scale = ui.startup_zoom;
1188 bg->pixbuf_dpi = bgpg->dpi;
1190 if (journal.npages < pageno) {
1191 pg = new_page_with_bg(bg,
1192 gdk_pixbuf_get_width(bg->pixbuf)*72.0/bg->pixbuf_dpi,
1193 gdk_pixbuf_get_height(bg->pixbuf)*72.0/bg->pixbuf_dpi);
1194 journal.pages = g_list_append(journal.pages, pg);
1197 pg->width = gdk_pixbuf_get_width(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1198 pg->height = gdk_pixbuf_get_height(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1199 make_page_clipbox(pg);
1200 update_canvas_bg(pg);
1202 update_page_stuff();
1205 // look for all journal pages with given pdf bg, and update their bg pixmaps
1206 void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg)
1211 for (list = journal.pages; list!= NULL; list = list->next) {
1212 pg = (struct Page *)list->data;
1213 if (pg->bg->type == BG_PDF && pg->bg->file_page_seq == pageno) {
1214 if (pg->bg->pixbuf!=NULL) gdk_pixbuf_unref(pg->bg->pixbuf);
1215 pg->bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1216 pg->bg->pixbuf_dpi = bgpg->dpi;
1217 update_canvas_bg(pg);
1222 // initialize the recent files list
1232 g_strlcpy(s, "mru0", 5);
1233 for (s[3]='0', i=0; i<MRU_SIZE; s[3]++, i++) {
1234 ui.mrumenu[i] = GET_COMPONENT(s);
1237 f = g_io_channel_new_file(ui.mrufile, "r", NULL);
1238 if (f) status = G_IO_STATUS_NORMAL;
1239 else status = G_IO_STATUS_ERROR;
1241 while (status == G_IO_STATUS_NORMAL && i<MRU_SIZE) {
1243 status = g_io_channel_read_line(f, &str, NULL, &lfptr, NULL);
1244 if (status == G_IO_STATUS_NORMAL && lfptr>0) {
1251 g_io_channel_shutdown(f, FALSE, NULL);
1252 g_io_channel_unref(f);
1257 void update_mru_menu(void)
1260 gboolean anyone = FALSE;
1263 for (i=0; i<MRU_SIZE; i++) {
1264 if (ui.mru[i]!=NULL) {
1265 tmp = g_strdup_printf("_%d %s", i+1, g_basename(ui.mru[i]));
1266 gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(ui.mrumenu[i]))),
1269 gtk_widget_show(ui.mrumenu[i]);
1272 else gtk_widget_hide(ui.mrumenu[i]);
1274 gtk_widget_set_sensitive(GET_COMPONENT("fileRecentFiles"), anyone);
1277 void new_mru_entry(char *name)
1281 for (i=0;i<MRU_SIZE;i++)
1282 if (ui.mru[i]!=NULL && !strcmp(ui.mru[i], name)) {
1284 for (j=i+1; j<MRU_SIZE; j++) ui.mru[j-1] = ui.mru[j];
1285 ui.mru[MRU_SIZE-1]=NULL;
1287 if (ui.mru[MRU_SIZE-1]!=NULL) g_free(ui.mru[MRU_SIZE-1]);
1288 for (j=MRU_SIZE-1; j>=1; j--) ui.mru[j] = ui.mru[j-1];
1289 ui.mru[0] = g_strdup(name);
1293 void delete_mru_entry(int which)
1297 if (ui.mru[which]!=NULL) g_free(ui.mru[which]);
1298 for (i=which+1;i<MRU_SIZE;i++)
1299 ui.mru[i-1] = ui.mru[i];
1300 ui.mru[MRU_SIZE-1] = NULL;
1304 void save_mru_list(void)
1309 f = fopen(ui.mrufile, "w");
1310 if (f==NULL) return;
1311 for (i=0; i<MRU_SIZE; i++)
1312 if (ui.mru[i]!=NULL) fprintf(f, "%s\n", ui.mru[i]);
1316 void init_config_default(void)
1320 DEFAULT_ZOOM = DISPLAY_DPI_DEFAULT/72.0;
1321 ui.zoom = ui.startup_zoom = 1.0*DEFAULT_ZOOM;
1322 ui.default_page.height = 792.0;
1323 ui.default_page.width = 612.0;
1324 ui.default_page.bg->type = BG_SOLID;
1325 ui.default_page.bg->color_no = COLOR_WHITE;
1326 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[COLOR_WHITE];
1327 ui.default_page.bg->ruling = RULING_LINED;
1328 ui.view_continuous = TRUE;
1329 ui.allow_xinput = TRUE;
1330 ui.discard_corepointer = FALSE;
1331 ui.left_handed = FALSE;
1332 ui.shorten_menus = FALSE;
1333 ui.shorten_menu_items = g_strdup(DEFAULT_SHORTEN_MENUS);
1334 ui.auto_save_prefs = FALSE;
1335 ui.bg_apply_all_pages = FALSE;
1336 ui.use_erasertip = FALSE;
1337 ui.window_default_width = 720;
1338 ui.window_default_height = 480;
1339 ui.maximize_at_start = FALSE;
1340 ui.fullscreen = FALSE;
1341 ui.scrollbar_step_increment = 30;
1342 ui.zoom_step_increment = 1;
1343 ui.zoom_step_factor = 1.5;
1344 ui.antialias_bg = TRUE;
1345 ui.progressive_bg = TRUE;
1346 ui.print_ruling = TRUE;
1347 ui.default_unit = UNIT_CM;
1348 ui.default_path = NULL;
1349 ui.default_font_name = g_strdup(DEFAULT_FONT);
1350 ui.default_font_size = DEFAULT_FONT_SIZE;
1352 // the default UI vertical order
1353 ui.vertical_order[0][0] = 1;
1354 ui.vertical_order[0][1] = 2;
1355 ui.vertical_order[0][2] = 3;
1356 ui.vertical_order[0][3] = 0;
1357 ui.vertical_order[0][4] = 4;
1358 ui.vertical_order[1][0] = 2;
1359 ui.vertical_order[1][1] = 3;
1360 ui.vertical_order[1][2] = 0;
1361 ui.vertical_order[1][3] = ui.vertical_order[1][4] = -1;
1363 ui.toolno[0] = ui.startuptool = TOOL_PEN;
1364 ui.ruler[0] = ui.startupruler = FALSE;
1365 for (i=1; i<=NUM_BUTTONS; i++) {
1366 ui.toolno[i] = TOOL_ERASER;
1367 ui.ruler[i] = FALSE;
1369 for (i=0; i<=NUM_BUTTONS; i++)
1370 ui.linked_brush[i] = BRUSH_LINKED;
1371 ui.brushes[0][TOOL_PEN].color_no = COLOR_BLACK;
1372 ui.brushes[0][TOOL_ERASER].color_no = COLOR_WHITE;
1373 ui.brushes[0][TOOL_HIGHLIGHTER].color_no = COLOR_YELLOW;
1374 for (i=0; i < NUM_STROKE_TOOLS; i++) {
1375 ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM;
1376 ui.brushes[0][i].tool_options = 0;
1378 for (i=0; i< NUM_STROKE_TOOLS; i++)
1379 for (j=1; j<=NUM_BUTTONS; j++)
1380 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1382 // predef_thickness is already initialized as a global variable
1383 GS_BITMAP_DPI = 144;
1384 PDFTOPPM_PRINTING_DPI = 150;
1386 ui.hiliter_opacity = 0.5;
1389 #if GLIB_CHECK_VERSION(2,6,0)
1391 void update_keyval(const gchar *group_name, const gchar *key,
1392 const gchar *comment, gchar *value)
1394 gboolean has_it = g_key_file_has_key(ui.config_data, group_name, key, NULL);
1395 cleanup_numeric(value);
1396 g_key_file_set_value(ui.config_data, group_name, key, value);
1398 if (!has_it) g_key_file_set_comment(ui.config_data, group_name, key, comment, NULL);
1403 const char *vorder_usernames[VBOX_MAIN_NITEMS+1] =
1404 {"drawarea", "menu", "main_toolbar", "pen_toolbar", "statusbar", NULL};
1406 gchar *verbose_vertical_order(int *order)
1408 gchar buf[80], *p; // longer than needed
1412 for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1413 if (order[i]<0 || order[i]>=VBOX_MAIN_NITEMS) continue;
1414 if (p!=buf) *(p++) = ' ';
1415 p = g_stpcpy(p, vorder_usernames[order[i]]);
1417 return g_strdup(buf);
1420 void save_config_to_file(void)
1425 #if GLIB_CHECK_VERSION(2,6,0)
1426 // no support for keyval files before Glib 2.6.0
1427 if (glib_minor_version<6) return;
1429 // save some data...
1430 ui.maximize_at_start = (gdk_window_get_state(winMain->window) & GDK_WINDOW_STATE_MAXIMIZED);
1431 if (!ui.maximize_at_start && !ui.fullscreen)
1432 gdk_drawable_get_size(winMain->window,
1433 &ui.window_default_width, &ui.window_default_height);
1435 update_keyval("general", "display_dpi",
1436 " the display resolution, in pixels per inch",
1437 g_strdup_printf("%.2f", DEFAULT_ZOOM*72));
1438 update_keyval("general", "initial_zoom",
1439 " the initial zoom level, in percent",
1440 g_strdup_printf("%.2f", 100*ui.zoom/DEFAULT_ZOOM));
1441 update_keyval("general", "window_maximize",
1442 " maximize the window at startup (true/false)",
1443 g_strdup(ui.maximize_at_start?"true":"false"));
1444 update_keyval("general", "window_fullscreen",
1445 " start in full screen mode (true/false)",
1446 g_strdup(ui.fullscreen?"true":"false"));
1447 update_keyval("general", "window_width",
1448 " the window width in pixels (when not maximized)",
1449 g_strdup_printf("%d", ui.window_default_width));
1450 update_keyval("general", "window_height",
1451 " the window height in pixels",
1452 g_strdup_printf("%d", ui.window_default_height));
1453 update_keyval("general", "scrollbar_speed",
1454 " scrollbar step increment (in pixels)",
1455 g_strdup_printf("%d", ui.scrollbar_step_increment));
1456 update_keyval("general", "zoom_dialog_increment",
1457 " the step increment in the zoom dialog box",
1458 g_strdup_printf("%d", ui.zoom_step_increment));
1459 update_keyval("general", "zoom_step_factor",
1460 " the multiplicative factor for zoom in/out",
1461 g_strdup_printf("%.3f", ui.zoom_step_factor));
1462 update_keyval("general", "view_continuous",
1463 " document view (true = continuous, false = single page)",
1464 g_strdup(ui.view_continuous?"true":"false"));
1465 update_keyval("general", "use_xinput",
1466 " use XInput extensions (true/false)",
1467 g_strdup(ui.allow_xinput?"true":"false"));
1468 update_keyval("general", "discard_corepointer",
1469 " discard Core Pointer events in XInput mode (true/false)",
1470 g_strdup(ui.discard_corepointer?"true":"false"));
1471 update_keyval("general", "use_erasertip",
1472 " always map eraser tip to eraser (true/false)",
1473 g_strdup(ui.use_erasertip?"true":"false"));
1474 update_keyval("general", "default_path",
1475 " default path for open/save (leave blank for current directory)",
1476 g_strdup((ui.default_path!=NULL)?ui.default_path:""));
1477 update_keyval("general", "interface_order",
1478 " interface components from top to bottom\n valid values: drawarea menu main_toolbar pen_toolbar statusbar",
1479 verbose_vertical_order(ui.vertical_order[0]));
1480 update_keyval("general", "interface_fullscreen",
1481 " interface components in fullscreen mode, from top to bottom",
1482 verbose_vertical_order(ui.vertical_order[1]));
1483 update_keyval("general", "interface_lefthanded",
1484 " interface has left-handed scrollbar (true/false)",
1485 g_strdup(ui.left_handed?"true":"false"));
1486 update_keyval("general", "shorten_menus",
1487 " hide some unwanted menu or toolbar items (true/false)",
1488 g_strdup(ui.shorten_menus?"true":"false"));
1489 update_keyval("general", "shorten_menu_items",
1490 " interface items to hide (customize at your own risk!)\n see source file xo-interface.c for a list of item names",
1491 g_strdup(ui.shorten_menu_items));
1492 update_keyval("general", "highlighter_opacity",
1493 " highlighter opacity (0 to 1, default 0.5)\n warning: opacity level is not saved in xoj files!",
1494 g_strdup_printf("%.2f", ui.hiliter_opacity));
1495 update_keyval("general", "autosave_prefs",
1496 " auto-save preferences on exit (true/false)",
1497 g_strdup(ui.auto_save_prefs?"true":"false"));
1499 update_keyval("paper", "width",
1500 " the default page width, in points (1/72 in)",
1501 g_strdup_printf("%.2f", ui.default_page.width));
1502 update_keyval("paper", "height",
1503 " the default page height, in points (1/72 in)",
1504 g_strdup_printf("%.2f", ui.default_page.height));
1505 update_keyval("paper", "color",
1506 " the default paper color",
1507 g_strdup(bgcolor_names[ui.default_page.bg->color_no]));
1508 update_keyval("paper", "style",
1509 " the default paper style (plain, lined, ruled, or graph)",
1510 g_strdup(bgstyle_names[ui.default_page.bg->ruling]));
1511 update_keyval("paper", "apply_all",
1512 " apply paper style changes to all pages (true/false)",
1513 g_strdup(ui.bg_apply_all_pages?"true":"false"));
1514 update_keyval("paper", "default_unit",
1515 " preferred unit (cm, in, px, pt)",
1516 g_strdup(unit_names[ui.default_unit]));
1517 update_keyval("paper", "print_ruling",
1518 " include paper ruling when printing or exporting to PDF (true/false)",
1519 g_strdup(ui.print_ruling?"true":"false"));
1520 update_keyval("paper", "antialias_bg",
1521 " antialiased bitmap backgrounds (true/false)",
1522 g_strdup(ui.antialias_bg?"true":"false"));
1523 update_keyval("paper", "progressive_bg",
1524 " progressive scaling of bitmap backgrounds (true/false)",
1525 g_strdup(ui.progressive_bg?"true":"false"));
1526 update_keyval("paper", "gs_bitmap_dpi",
1527 " bitmap resolution of PS/PDF backgrounds rendered using ghostscript (dpi)",
1528 g_strdup_printf("%d", GS_BITMAP_DPI));
1529 update_keyval("paper", "pdftoppm_printing_dpi",
1530 " bitmap resolution of PDF backgrounds when printing with libgnomeprint (dpi)",
1531 g_strdup_printf("%d", PDFTOPPM_PRINTING_DPI));
1533 update_keyval("tools", "startup_tool",
1534 " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)",
1535 g_strdup(tool_names[ui.startuptool]));
1536 update_keyval("tools", "startup_ruler",
1537 " ruler mode at startup (true/false) (for pen or highlighter only)",
1538 g_strdup(ui.startupruler?"true":"false"));
1539 update_keyval("tools", "pen_color",
1540 " default pen color",
1541 g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no]));
1542 update_keyval("tools", "pen_thickness",
1543 " default pen thickness (fine = 1, medium = 2, thick = 3)",
1544 g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no));
1545 update_keyval("tools", "eraser_thickness",
1546 " default eraser thickness (fine = 1, medium = 2, thick = 3)",
1547 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no));
1548 update_keyval("tools", "eraser_mode",
1549 " default eraser mode (standard = 0, whiteout = 1, strokes = 2)",
1550 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].tool_options));
1551 update_keyval("tools", "highlighter_color",
1552 " default highlighter color",
1553 g_strdup(color_names[ui.default_brushes[TOOL_HIGHLIGHTER].color_no]));
1554 update_keyval("tools", "highlighter_thickness",
1555 " default highlighter thickness (fine = 1, medium = 2, thick = 3)",
1556 g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no));
1557 update_keyval("tools", "btn2_tool",
1558 " button 2 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)",
1559 g_strdup(tool_names[ui.toolno[1]]));
1560 update_keyval("tools", "btn2_linked",
1561 " button 2 brush linked to primary brush (true/false) (overrides all other settings)",
1562 g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false"));
1563 update_keyval("tools", "btn2_ruler",
1564 " button 2 ruler mode (true/false) (for pen or highlighter only)",
1565 g_strdup(ui.ruler[1]?"true":"false"));
1566 update_keyval("tools", "btn2_color",
1567 " button 2 brush color (for pen or highlighter only)",
1568 g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)?
1569 color_names[ui.brushes[1][ui.toolno[1]].color_no]:"white"));
1570 update_keyval("tools", "btn2_thickness",
1571 " button 2 brush thickness (pen, eraser, or highlighter only)",
1572 g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)?
1573 ui.brushes[1][ui.toolno[1]].thickness_no:0));
1574 update_keyval("tools", "btn2_erasermode",
1575 " button 2 eraser mode (eraser only)",
1576 g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options));
1577 update_keyval("tools", "btn3_tool",
1578 " button 3 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)",
1579 g_strdup(tool_names[ui.toolno[2]]));
1580 update_keyval("tools", "btn3_linked",
1581 " button 3 brush linked to primary brush (true/false) (overrides all other settings)",
1582 g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false"));
1583 update_keyval("tools", "btn3_ruler",
1584 " button 3 ruler mode (true/false) (for pen or highlighter only)",
1585 g_strdup(ui.ruler[2]?"true":"false"));
1586 update_keyval("tools", "btn3_color",
1587 " button 3 brush color (for pen or highlighter only)",
1588 g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)?
1589 color_names[ui.brushes[2][ui.toolno[2]].color_no]:"white"));
1590 update_keyval("tools", "btn3_thickness",
1591 " button 3 brush thickness (pen, eraser, or highlighter only)",
1592 g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)?
1593 ui.brushes[2][ui.toolno[2]].thickness_no:0));
1594 update_keyval("tools", "btn3_erasermode",
1595 " button 3 eraser mode (eraser only)",
1596 g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options));
1598 update_keyval("tools", "pen_thicknesses",
1599 " thickness of the various pens (in points, 1 pt = 1/72 in)",
1600 g_strdup_printf("%.2f;%.2f;%.2f;%.2f;%.2f",
1601 predef_thickness[TOOL_PEN][0], predef_thickness[TOOL_PEN][1],
1602 predef_thickness[TOOL_PEN][2], predef_thickness[TOOL_PEN][3],
1603 predef_thickness[TOOL_PEN][4]));
1604 update_keyval("tools", "eraser_thicknesses",
1605 " thickness of the various erasers (in points, 1 pt = 1/72 in)",
1606 g_strdup_printf("%.2f;%.2f;%.2f",
1607 predef_thickness[TOOL_ERASER][1], predef_thickness[TOOL_ERASER][2],
1608 predef_thickness[TOOL_ERASER][3]));
1609 update_keyval("tools", "highlighter_thicknesses",
1610 " thickness of the various highlighters (in points, 1 pt = 1/72 in)",
1611 g_strdup_printf("%.2f;%.2f;%.2f",
1612 predef_thickness[TOOL_HIGHLIGHTER][1], predef_thickness[TOOL_HIGHLIGHTER][2],
1613 predef_thickness[TOOL_HIGHLIGHTER][3]));
1614 update_keyval("tools", "default_font",
1615 " name of the default font",
1616 g_strdup(ui.default_font_name));
1617 update_keyval("tools", "default_font_size",
1618 " default font size",
1619 g_strdup_printf("%.1f", ui.default_font_size));
1621 buf = g_key_file_to_data(ui.config_data, NULL, NULL);
1622 if (buf == NULL) return;
1623 f = fopen(ui.configfile, "w");
1624 if (f==NULL) { g_free(buf); return; }
1631 #if GLIB_CHECK_VERSION(2,6,0)
1632 gboolean parse_keyval_float(const gchar *group, const gchar *key, double *val, double inf, double sup)
1637 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1638 if (ret==NULL) return FALSE;
1639 conv = g_ascii_strtod(ret, &end);
1640 if (*end!=0) { g_free(ret); return FALSE; }
1642 if (conv < inf || conv > sup) return FALSE;
1647 gboolean parse_keyval_floatlist(const gchar *group, const gchar *key, double *val, int n, double inf, double sup)
1653 if (n>5) return FALSE;
1654 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1655 if (ret==NULL) return FALSE;
1657 for (i=0; i<n; i++) {
1658 conv[i] = g_ascii_strtod(end, &end);
1659 if ((i==n-1 && *end!=0) || (i<n-1 && *end!=';') ||
1660 (conv[i] < inf) || (conv[i] > sup)) { g_free(ret); return FALSE; }
1664 for (i=0; i<n; i++) val[i] = conv[i];
1668 gboolean parse_keyval_int(const gchar *group, const gchar *key, int *val, int inf, int sup)
1673 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1674 if (ret==NULL) return FALSE;
1675 conv = strtol(ret, &end, 10);
1676 if (*end!=0) { g_free(ret); return FALSE; }
1678 if (conv < inf || conv > sup) return FALSE;
1683 gboolean parse_keyval_enum(const gchar *group, const gchar *key, int *val, const char **names, int n)
1688 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1689 if (ret==NULL) return FALSE;
1690 for (i=0; i<n; i++) {
1691 if (!names[i][0]) continue; // "" is for invalid values
1692 if (!g_ascii_strcasecmp(ret, names[i]))
1693 { *val = i; g_free(ret); return TRUE; }
1698 gboolean parse_keyval_boolean(const gchar *group, const gchar *key, gboolean *val)
1702 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1703 if (ret==NULL) return FALSE;
1704 if (!g_ascii_strcasecmp(ret, "true"))
1705 { *val = TRUE; g_free(ret); return TRUE; }
1706 if (!g_ascii_strcasecmp(ret, "false"))
1707 { *val = FALSE; g_free(ret); return TRUE; }
1712 gboolean parse_keyval_string(const gchar *group, const gchar *key, gchar **val)
1716 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1717 if (ret==NULL) return FALSE;
1718 if (strlen(ret) == 0) {
1726 gboolean parse_keyval_vorderlist(const gchar *group, const gchar *key, int *order)
1729 int tmp[VBOX_MAIN_NITEMS];
1732 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1733 if (ret==NULL) return FALSE;
1735 for (i=0; i<VBOX_MAIN_NITEMS; i++) tmp[i] = -1;
1737 while (*p==' ') p++;
1739 if (n>VBOX_MAIN_NITEMS) return FALSE; // too many items
1740 for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1741 if (!g_str_has_prefix(p, vorder_usernames[i])) continue;
1742 l = strlen(vorder_usernames[i]);
1743 if (p[l]==' '||p[l]==0) { p+=l; break; }
1745 if (i>=VBOX_MAIN_NITEMS) { g_free(ret); return FALSE; } // parse error
1748 while (*p==' ') p++;
1751 for (n=0; n<VBOX_MAIN_NITEMS; n++) order[n] = tmp[n];
1758 void load_config_from_file(void)
1765 #if GLIB_CHECK_VERSION(2,6,0)
1766 // no support for keyval files before Glib 2.6.0
1767 if (glib_minor_version<6) return;
1768 ui.config_data = g_key_file_new();
1769 if (!g_key_file_load_from_file(ui.config_data, ui.configfile,
1770 G_KEY_FILE_KEEP_COMMENTS, NULL)) {
1771 g_key_file_free(ui.config_data);
1772 ui.config_data = g_key_file_new();
1773 g_key_file_set_comment(ui.config_data, NULL, NULL,
1774 " Xournal configuration file.\n"
1775 " This file is generated automatically upon saving preferences.\n"
1776 " Use caution when editing this file manually.\n", NULL);
1780 // parse keys from the keyfile to set defaults
1781 if (parse_keyval_float("general", "display_dpi", &f, 10., 500.))
1782 DEFAULT_ZOOM = f/72.0;
1783 if (parse_keyval_float("general", "initial_zoom", &f,
1784 MIN_ZOOM*100/DEFAULT_ZOOM, MAX_ZOOM*100/DEFAULT_ZOOM))
1785 ui.zoom = ui.startup_zoom = DEFAULT_ZOOM*f/100.0;
1786 parse_keyval_boolean("general", "window_maximize", &ui.maximize_at_start);
1787 parse_keyval_boolean("general", "window_fullscreen", &ui.fullscreen);
1788 parse_keyval_int("general", "window_width", &ui.window_default_width, 10, 5000);
1789 parse_keyval_int("general", "window_height", &ui.window_default_height, 10, 5000);
1790 parse_keyval_int("general", "scrollbar_speed", &ui.scrollbar_step_increment, 1, 5000);
1791 parse_keyval_int("general", "zoom_dialog_increment", &ui.zoom_step_increment, 1, 500);
1792 parse_keyval_float("general", "zoom_step_factor", &ui.zoom_step_factor, 1., 5.);
1793 parse_keyval_boolean("general", "view_continuous", &ui.view_continuous);
1794 parse_keyval_boolean("general", "use_xinput", &ui.allow_xinput);
1795 parse_keyval_boolean("general", "discard_corepointer", &ui.discard_corepointer);
1796 parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip);
1797 parse_keyval_string("general", "default_path", &ui.default_path);
1798 parse_keyval_vorderlist("general", "interface_order", ui.vertical_order[0]);
1799 parse_keyval_vorderlist("general", "interface_fullscreen", ui.vertical_order[1]);
1800 parse_keyval_boolean("general", "interface_lefthanded", &ui.left_handed);
1801 parse_keyval_boolean("general", "shorten_menus", &ui.shorten_menus);
1802 if (parse_keyval_string("general", "shorten_menu_items", &str))
1803 if (str!=NULL) { g_free(ui.shorten_menu_items); ui.shorten_menu_items = str; }
1804 parse_keyval_float("general", "highlighter_opacity", &ui.hiliter_opacity, 0., 1.);
1805 parse_keyval_boolean("general", "autosave_prefs", &ui.auto_save_prefs);
1807 parse_keyval_float("paper", "width", &ui.default_page.width, 1., 5000.);
1808 parse_keyval_float("paper", "height", &ui.default_page.height, 1., 5000.);
1809 parse_keyval_enum("paper", "color", &(ui.default_page.bg->color_no), bgcolor_names, COLOR_MAX);
1810 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[ui.default_page.bg->color_no];
1811 parse_keyval_enum("paper", "style", &(ui.default_page.bg->ruling), bgstyle_names, 4);
1812 parse_keyval_boolean("paper", "apply_all", &ui.bg_apply_all_pages);
1813 parse_keyval_enum("paper", "default_unit", &ui.default_unit, unit_names, 4);
1814 parse_keyval_boolean("paper", "antialias_bg", &ui.antialias_bg);
1815 parse_keyval_boolean("paper", "progressive_bg", &ui.progressive_bg);
1816 parse_keyval_boolean("paper", "print_ruling", &ui.print_ruling);
1817 parse_keyval_int("paper", "gs_bitmap_dpi", &GS_BITMAP_DPI, 1, 1200);
1818 parse_keyval_int("paper", "pdftoppm_printing_dpi", &PDFTOPPM_PRINTING_DPI, 1, 1200);
1820 parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS);
1821 ui.toolno[0] = ui.startuptool;
1822 if (ui.startuptool == TOOL_PEN || ui.startuptool == TOOL_HIGHLIGHTER) {
1823 parse_keyval_boolean("tools", "startup_ruler", &ui.startupruler);
1824 ui.ruler[0] = ui.startupruler;
1826 parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX);
1827 parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4);
1828 parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3);
1829 parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2);
1830 parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX);
1831 parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4);
1832 for (i=0; i< NUM_STROKE_TOOLS; i++)
1833 for (j=1; j<=NUM_BUTTONS; j++)
1834 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1836 parse_keyval_enum("tools", "btn2_tool", &(ui.toolno[1]), tool_names, NUM_TOOLS);
1837 if (parse_keyval_boolean("tools", "btn2_linked", &b))
1838 ui.linked_brush[1] = b?BRUSH_LINKED:BRUSH_STATIC;
1839 parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS);
1840 if (parse_keyval_boolean("tools", "btn3_linked", &b))
1841 ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC;
1842 for (i=1; i<=NUM_BUTTONS; i++)
1843 if (ui.toolno[i]==TOOL_PEN || ui.toolno[i]==TOOL_HIGHLIGHTER)
1844 ui.ruler[i] = ui.ruler[0];
1845 if (ui.linked_brush[1]!=BRUSH_LINKED) {
1846 if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) {
1847 parse_keyval_boolean("tools", "btn2_ruler", &(ui.ruler[1]));
1848 parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX);
1850 if (ui.toolno[1]<NUM_STROKE_TOOLS)
1851 parse_keyval_int("tools", "btn2_thickness", &(ui.brushes[1][ui.toolno[1]].thickness_no), 0, 4);
1852 if (ui.toolno[1]==TOOL_ERASER)
1853 parse_keyval_int("tools", "btn2_erasermode", &(ui.brushes[1][TOOL_ERASER].tool_options), 0, 2);
1855 if (ui.linked_brush[2]!=BRUSH_LINKED) {
1856 if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) {
1857 parse_keyval_boolean("tools", "btn3_ruler", &(ui.ruler[2]));
1858 parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX);
1860 if (ui.toolno[2]<NUM_STROKE_TOOLS)
1861 parse_keyval_int("tools", "btn3_thickness", &(ui.brushes[2][ui.toolno[2]].thickness_no), 0, 4);
1862 if (ui.toolno[2]==TOOL_ERASER)
1863 parse_keyval_int("tools", "btn3_erasermode", &(ui.brushes[2][TOOL_ERASER].tool_options), 0, 2);
1865 parse_keyval_floatlist("tools", "pen_thicknesses", predef_thickness[TOOL_PEN], 5, 0.01, 1000.0);
1866 parse_keyval_floatlist("tools", "eraser_thicknesses", predef_thickness[TOOL_ERASER]+1, 3, 0.01, 1000.0);
1867 parse_keyval_floatlist("tools", "highlighter_thicknesses", predef_thickness[TOOL_HIGHLIGHTER]+1, 3, 0.01, 1000.0);
1868 if (parse_keyval_string("tools", "default_font", &str))
1869 if (str!=NULL) { g_free(ui.default_font_name); ui.default_font_name = str; }
1870 parse_keyval_float("tools", "default_font_size", &ui.default_font_size, 1., 200.);