11 #include <libgnomecanvas/libgnomecanvas.h>
19 #include "xo-interface.h"
20 #include "xo-support.h"
21 #include "xo-callbacks.h"
25 const char *tool_names[NUM_TOOLS] = {"pen", "eraser", "highlighter", "", "", "selectrect", "vertspace", "hand"};
26 const char *color_names[COLOR_MAX] = {"black", "blue", "red", "green",
27 "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white"};
28 const char *bgtype_names[3] = {"solid", "pixmap", "pdf"};
29 const char *bgcolor_names[COLOR_MAX] = {"", "blue", "pink", "green",
30 "", "", "", "", "orange", "yellow", "white"};
31 const char *bgstyle_names[4] = {"plain", "lined", "ruled", "graph"};
32 const char *file_domain_names[3] = {"absolute", "attach", "clone"};
33 const char *unit_names[4] = {"cm", "in", "px", "pt"};
34 int PDFTOPPM_PRINTING_DPI, GS_BITMAP_DPI;
36 // creates a new empty journal
38 void new_journal(void)
41 journal.pages = g_list_append(NULL, new_page(&ui.default_page));
42 journal.last_attach_no = 0;
45 ui.cur_page = (struct Page *) journal.pages->data;
46 ui.cur_layer = (struct Layer *) ui.cur_page->layers->data;
49 update_file_name(NULL);
52 // check attachment names
54 void chk_attach_names(void)
57 struct Background *bg;
59 for (list = journal.pages; list!=NULL; list = list->next) {
60 bg = ((struct Page *)list->data)->bg;
61 if (bg->type == BG_SOLID || bg->file_domain != DOMAIN_ATTACH ||
62 bg->filename->s != NULL) continue;
63 bg->filename->s = g_strdup_printf("bg_%d.png", ++journal.last_attach_no);
67 // saves the journal to a file: returns true on success, false on error
69 gboolean save_journal(const char *filename)
72 struct Page *pg, *tmppg;
81 GList *pagelist, *layerlist, *itemlist, *list;
84 f = gzopen(filename, "w");
85 if (f==NULL) return FALSE;
88 setlocale(LC_NUMERIC, "C");
90 gzprintf(f, "<?xml version=\"1.0\" standalone=\"no\"?>\n"
91 "<title>Xournal document - see http://math.mit.edu/~auroux/software/xournal/</title>\n"
92 "<xournal version=\"" VERSION "\"/>\n");
93 for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next) {
94 pg = (struct Page *)pagelist->data;
95 gzprintf(f, "<page width=\"%.2f\" height=\"%.2f\">\n", pg->width, pg->height);
96 gzprintf(f, "<background type=\"%s\" ", bgtype_names[pg->bg->type]);
97 if (pg->bg->type == BG_SOLID) {
98 gzputs(f, "color=\"");
99 if (pg->bg->color_no >= 0) gzputs(f, bgcolor_names[pg->bg->color_no]);
100 else gzprintf(f, "#%08x", pg->bg->color_rgba);
101 gzprintf(f, "\" style=\"%s\" ", bgstyle_names[pg->bg->ruling]);
103 else if (pg->bg->type == BG_PIXMAP) {
105 for (list = journal.pages, i = 0; list!=pagelist; list = list->next, i++) {
106 tmppg = (struct Page *)list->data;
107 if (tmppg->bg->type == BG_PIXMAP &&
108 tmppg->bg->pixbuf == pg->bg->pixbuf &&
109 tmppg->bg->filename == pg->bg->filename)
110 { is_clone = i; break; }
113 gzprintf(f, "domain=\"clone\" filename=\"%d\" ", is_clone);
115 if (pg->bg->file_domain == DOMAIN_ATTACH) {
116 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
117 if (!gdk_pixbuf_save(pg->bg->pixbuf, tmpfn, "png", NULL, NULL)) {
118 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
119 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
120 "Could not write background '%s'. Continuing anyway.", tmpfn);
121 gtk_dialog_run(GTK_DIALOG(dialog));
122 gtk_widget_destroy(dialog);
126 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
127 file_domain_names[pg->bg->file_domain], pg->bg->filename->s);
130 else if (pg->bg->type == BG_PDF) {
132 for (list = journal.pages; list!=pagelist; list = list->next) {
133 tmppg = (struct Page *)list->data;
134 if (tmppg->bg->type == BG_PDF) { is_clone = 1; break; }
137 if (pg->bg->file_domain == DOMAIN_ATTACH) {
138 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
140 if (bgpdf.status != STATUS_NOT_INIT &&
141 g_file_get_contents(bgpdf.tmpfile_copy, &pdfbuf, &pdflen, NULL))
143 tmpf = fopen(tmpfn, "w");
144 if (tmpf != NULL && fwrite(pdfbuf, 1, pdflen, tmpf) == pdflen)
150 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
151 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
152 "Could not write background '%s'. Continuing anyway.", tmpfn);
153 gtk_dialog_run(GTK_DIALOG(dialog));
154 gtk_widget_destroy(dialog);
158 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
159 file_domain_names[pg->bg->file_domain], pg->bg->filename->s);
161 gzprintf(f, "pageno=\"%d\" ", pg->bg->file_page_seq);
164 for (layerlist = pg->layers; layerlist!=NULL; layerlist = layerlist->next) {
165 layer = (struct Layer *)layerlist->data;
166 gzprintf(f, "<layer>\n");
167 for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
168 item = (struct Item *)itemlist->data;
169 if (item->type == ITEM_STROKE) {
170 gzprintf(f, "<stroke tool=\"%s\" color=\"",
171 tool_names[item->brush.tool_type]);
172 if (item->brush.color_no >= 0)
173 gzputs(f, color_names[item->brush.color_no]);
175 gzprintf(f, "#%08x", item->brush.color_rgba);
176 gzprintf(f, "\" width=\"%.2f\">\n", item->brush.thickness);
177 for (i=0;i<2*item->path->num_points;i++)
178 gzprintf(f, "%.2f ", item->path->coords[i]);
179 gzprintf(f, "\n</stroke>\n");
182 gzprintf(f, "</layer>\n");
184 gzprintf(f, "</page>\n");
187 setlocale(LC_NUMERIC, "");
192 // closes a journal: returns true on success, false on abort
194 gboolean close_journal(void)
196 if (!ok_to_close()) return FALSE;
198 // free everything...
204 delete_journal(&journal);
207 /* note: various members of ui and journal are now in invalid states,
208 use new_journal() to reinitialize them */
211 // sanitize a string containing floats, in case it may have , instead of .
213 void cleanup_numeric(char *s)
215 while (*s!=0) { if (*s==',') *s='.'; s++; }
218 // the XML parser functions for open_journal()
220 struct Journal tmpJournal;
221 struct Page *tmpPage;
222 struct Layer *tmpLayer;
223 struct Item *tmpItem;
225 struct Background *tmpBg_pdf;
227 GError *xoj_invalid(void)
229 return g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Invalid file contents");
232 void xoj_parser_start_element(GMarkupParseContext *context,
233 const gchar *element_name, const gchar **attribute_names,
234 const gchar **attribute_values, gpointer user_data, GError **error)
238 struct Background *tmpbg;
239 char *tmpbg_filename;
242 if (!strcmp(element_name, "title") || !strcmp(element_name, "xournal")) {
243 if (tmpPage != NULL) {
244 *error = xoj_invalid();
247 // nothing special to do
249 else if (!strcmp(element_name, "page")) { // start of a page
250 if (tmpPage != NULL) {
251 *error = xoj_invalid();
254 tmpPage = (struct Page *)g_malloc(sizeof(struct Page));
255 tmpPage->layers = NULL;
256 tmpPage->nlayers = 0;
257 tmpPage->group = NULL;
258 tmpPage->bg = g_new(struct Background, 1);
259 tmpPage->bg->type = -1;
260 tmpPage->bg->canvas_item = NULL;
261 tmpPage->bg->pixbuf = NULL;
262 tmpPage->bg->filename = NULL;
263 tmpJournal.pages = g_list_append(tmpJournal.pages, tmpPage);
265 // scan for height and width attributes
267 while (*attribute_names!=NULL) {
268 if (!strcmp(*attribute_names, "width")) {
269 if (has_attr & 1) *error = xoj_invalid();
270 cleanup_numeric((gchar *)*attribute_values);
271 tmpPage->width = g_ascii_strtod(*attribute_values, &ptr);
272 if (ptr == *attribute_values) *error = xoj_invalid();
275 else if (!strcmp(*attribute_names, "height")) {
276 if (has_attr & 2) *error = xoj_invalid();
277 cleanup_numeric((gchar *)*attribute_values);
278 tmpPage->height = g_ascii_strtod(*attribute_values, &ptr);
279 if (ptr == *attribute_values) *error = xoj_invalid();
282 else *error = xoj_invalid();
286 if (has_attr!=3) *error = xoj_invalid();
288 else if (!strcmp(element_name, "background")) {
289 if (tmpPage == NULL || tmpLayer !=NULL || tmpPage->bg->type >= 0) {
290 *error = xoj_invalid();
294 while (*attribute_names!=NULL) {
295 if (!strcmp(*attribute_names, "type")) {
296 if (has_attr) *error = xoj_invalid();
298 if (!strcmp(*attribute_values, bgtype_names[i]))
299 tmpPage->bg->type = i;
300 if (tmpPage->bg->type < 0) *error = xoj_invalid();
302 if (tmpPage->bg->type == BG_PDF) {
303 if (tmpBg_pdf == NULL) tmpBg_pdf = tmpPage->bg;
306 tmpPage->bg->filename = refstring_ref(tmpBg_pdf->filename);
307 tmpPage->bg->file_domain = tmpBg_pdf->file_domain;
311 else if (!strcmp(*attribute_names, "color")) {
312 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
313 if (has_attr & 2) *error = xoj_invalid();
314 tmpPage->bg->color_no = COLOR_OTHER;
315 for (i=0; i<COLOR_MAX; i++)
316 if (!strcmp(*attribute_values, bgcolor_names[i])) {
317 tmpPage->bg->color_no = i;
318 tmpPage->bg->color_rgba = predef_bgcolors_rgba[i];
320 // there's also the case of hex (#rrggbbaa) colors
321 if (tmpPage->bg->color_no == COLOR_OTHER && **attribute_values == '#') {
322 tmpPage->bg->color_rgba = strtol(*attribute_values + 1, &ptr, 16);
323 if (*ptr!=0) *error = xoj_invalid();
327 else if (!strcmp(*attribute_names, "style")) {
328 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
329 if (has_attr & 4) *error = xoj_invalid();
330 tmpPage->bg->ruling = -1;
332 if (!strcmp(*attribute_values, bgstyle_names[i]))
333 tmpPage->bg->ruling = i;
334 if (tmpPage->bg->ruling < 0) *error = xoj_invalid();
337 else if (!strcmp(*attribute_names, "domain")) {
338 if (tmpPage->bg->type <= BG_SOLID || (has_attr & 8))
339 { *error = xoj_invalid(); return; }
340 tmpPage->bg->file_domain = -1;
342 if (!strcmp(*attribute_values, file_domain_names[i]))
343 tmpPage->bg->file_domain = i;
344 if (tmpPage->bg->file_domain < 0)
345 { *error = xoj_invalid(); return; }
348 else if (!strcmp(*attribute_names, "filename")) {
349 if (tmpPage->bg->type <= BG_SOLID || (has_attr != 9))
350 { *error = xoj_invalid(); return; }
351 if (tmpPage->bg->file_domain == DOMAIN_CLONE) {
352 // filename is a page number
353 i = strtol(*attribute_values, &ptr, 10);
354 if (ptr == *attribute_values || i < 0 || i > tmpJournal.npages-2)
355 { *error = xoj_invalid(); return; }
356 tmpbg = ((struct Page *)g_list_nth_data(tmpJournal.pages, i))->bg;
357 if (tmpbg->type != tmpPage->bg->type)
358 { *error = xoj_invalid(); return; }
359 tmpPage->bg->filename = refstring_ref(tmpbg->filename);
360 tmpPage->bg->pixbuf = tmpbg->pixbuf;
361 if (tmpbg->pixbuf!=NULL) gdk_pixbuf_ref(tmpbg->pixbuf);
362 tmpPage->bg->file_domain = tmpbg->file_domain;
365 tmpPage->bg->filename = new_refstring(*attribute_values);
366 if (tmpPage->bg->type == BG_PIXMAP) {
367 if (tmpPage->bg->file_domain == DOMAIN_ATTACH) {
368 tmpbg_filename = g_strdup_printf("%s.%s", tmpFilename, *attribute_values);
369 if (sscanf(*attribute_values, "bg_%d.png", &i) == 1)
370 if (i > tmpJournal.last_attach_no)
371 tmpJournal.last_attach_no = i;
373 else tmpbg_filename = g_strdup(*attribute_values);
374 tmpPage->bg->pixbuf = gdk_pixbuf_new_from_file(tmpbg_filename, NULL);
375 if (tmpPage->bg->pixbuf == NULL) {
376 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
377 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
378 "Could not open background '%s'. Setting background to white.",
380 gtk_dialog_run(GTK_DIALOG(dialog));
381 gtk_widget_destroy(dialog);
382 tmpPage->bg->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
383 gdk_pixbuf_fill(tmpPage->bg->pixbuf, 0xffffffff); // solid white
385 g_free(tmpbg_filename);
390 else if (!strcmp(*attribute_names, "pageno")) {
391 if (tmpPage->bg->type != BG_PDF || (has_attr & 32))
392 { *error = xoj_invalid(); return; }
393 tmpPage->bg->file_page_seq = strtol(*attribute_values, &ptr, 10);
394 if (ptr == *attribute_values) *error = xoj_invalid();
397 else *error = xoj_invalid();
401 if (tmpPage->bg->type < 0) *error = xoj_invalid();
402 if (tmpPage->bg->type == BG_SOLID && has_attr != 7) *error = xoj_invalid();
403 if (tmpPage->bg->type == BG_PIXMAP && has_attr != 25) *error = xoj_invalid();
404 if (tmpPage->bg->type == BG_PDF && has_attr != 57) *error = xoj_invalid();
406 else if (!strcmp(element_name, "layer")) { // start of a layer
407 if (tmpPage == NULL || tmpLayer != NULL) {
408 *error = xoj_invalid();
411 tmpLayer = (struct Layer *)g_malloc(sizeof(struct Layer));
412 tmpLayer->items = NULL;
413 tmpLayer->nitems = 0;
414 tmpLayer->group = NULL;
415 tmpPage->layers = g_list_append(tmpPage->layers, tmpLayer);
418 else if (!strcmp(element_name, "stroke")) { // start of a stroke
419 if (tmpLayer == NULL || tmpItem != NULL) {
420 *error = xoj_invalid();
423 tmpItem = (struct Item *)g_malloc(sizeof(struct Item));
424 tmpItem->type = ITEM_STROKE;
425 tmpItem->path = NULL;
426 tmpItem->canvas_item = NULL;
427 tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
429 // scan for tool, color, and width attributes
431 while (*attribute_names!=NULL) {
432 if (!strcmp(*attribute_names, "width")) {
433 if (has_attr & 1) *error = xoj_invalid();
434 cleanup_numeric((gchar *)*attribute_values);
435 tmpItem->brush.thickness = g_ascii_strtod(*attribute_values, &ptr);
436 if (ptr == *attribute_values) *error = xoj_invalid();
439 else if (!strcmp(*attribute_names, "color")) {
440 if (has_attr & 2) *error = xoj_invalid();
441 tmpItem->brush.color_no = COLOR_OTHER;
442 for (i=0; i<COLOR_MAX; i++)
443 if (!strcmp(*attribute_values, color_names[i])) {
444 tmpItem->brush.color_no = i;
445 tmpItem->brush.color_rgba = predef_colors_rgba[i];
447 // there's also the case of hex (#rrggbbaa) colors
448 if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
449 tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
450 if (*ptr!=0) *error = xoj_invalid();
454 else if (!strcmp(*attribute_names, "tool")) {
455 if (has_attr & 4) *error = xoj_invalid();
456 tmpItem->brush.tool_type = -1;
457 for (i=0; i<NUM_STROKE_TOOLS; i++)
458 if (!strcmp(*attribute_values, tool_names[i])) {
459 tmpItem->brush.tool_type = i;
461 if (tmpItem->brush.tool_type == -1) *error = xoj_invalid();
464 else *error = xoj_invalid();
468 if (has_attr!=7) *error = xoj_invalid();
469 // finish filling the brush info
470 tmpItem->brush.thickness_no = 0; // who cares ?
471 tmpItem->brush.tool_options = 0; // who cares ?
472 if (tmpItem->brush.tool_type == TOOL_HIGHLIGHTER) {
473 if (tmpItem->brush.color_no >= 0)
474 tmpItem->brush.color_rgba &= HILITER_ALPHA_MASK;
479 void xoj_parser_end_element(GMarkupParseContext *context,
480 const gchar *element_name, gpointer user_data, GError **error)
482 if (!strcmp(element_name, "page")) {
483 if (tmpPage == NULL || tmpLayer != NULL) {
484 *error = xoj_invalid();
487 if (tmpPage->nlayers == 0 || tmpPage->bg->type < 0) *error = xoj_invalid();
490 if (!strcmp(element_name, "layer")) {
491 if (tmpLayer == NULL || tmpItem != NULL) {
492 *error = xoj_invalid();
497 if (!strcmp(element_name, "stroke")) {
498 if (tmpItem == NULL) {
499 *error = xoj_invalid();
502 update_item_bbox(tmpItem);
507 void xoj_parser_text(GMarkupParseContext *context,
508 const gchar *text, gsize text_len, gpointer user_data, GError **error)
510 const gchar *element_name, *ptr;
513 element_name = g_markup_parse_context_get_element(context);
514 if (element_name == NULL) return;
515 if (!strcmp(element_name, "stroke")) {
516 cleanup_numeric((gchar *)text);
519 while (text_len > 0) {
520 realloc_cur_path(n/2 + 1);
521 ui.cur_path.coords[n] = g_ascii_strtod(text, (char **)(&ptr));
522 if (ptr == text) break;
523 text_len -= (ptr - text);
527 if (n<4 || n&1) { *error = xoj_invalid(); return; }
528 tmpItem->path = gnome_canvas_points_new(n/2);
529 g_memmove(tmpItem->path->coords, ui.cur_path.coords, n*sizeof(double));
533 gboolean user_wants_second_chance(char **filename)
536 GtkFileFilter *filt_all, *filt_pdf;
537 GtkResponseType response;
539 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
540 GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO,
541 "Could not open background '%s'.\nSelect another file?",
543 response = gtk_dialog_run(GTK_DIALOG(dialog));
544 gtk_widget_destroy(dialog);
545 if (response != GTK_RESPONSE_YES) return FALSE;
546 dialog = gtk_file_chooser_dialog_new("Open PDF", GTK_WINDOW (winMain),
547 GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
548 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
550 filt_all = gtk_file_filter_new();
551 gtk_file_filter_set_name(filt_all, "All files");
552 gtk_file_filter_add_pattern(filt_all, "*");
553 filt_pdf = gtk_file_filter_new();
554 gtk_file_filter_set_name(filt_pdf, "PDF files");
555 gtk_file_filter_add_pattern(filt_pdf, "*.pdf");
556 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_pdf);
557 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_all);
559 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) {
560 gtk_widget_destroy(dialog);
564 *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
565 gtk_widget_destroy(dialog);
569 gboolean open_journal(char *filename)
571 const GMarkupParser parser = { xoj_parser_start_element,
572 xoj_parser_end_element,
573 xoj_parser_text, NULL, NULL};
574 GMarkupParseContext *context;
584 f = gzopen(filename, "r");
585 if (f==NULL) return FALSE;
587 context = g_markup_parse_context_new(&parser, 0, NULL, NULL);
589 tmpJournal.npages = 0;
590 tmpJournal.pages = NULL;
591 tmpJournal.last_attach_no = 0;
595 tmpFilename = filename;
600 while (valid && !gzeof(f)) {
601 len = gzread(f, buffer, 1000);
602 if (len<0) valid = FALSE;
603 if (maybe_pdf && len>=4 && !strncmp(buffer, "%PDF", 4))
604 { valid = FALSE; break; } // most likely pdf
605 else maybe_pdf = FALSE;
607 valid = g_markup_parse_context_parse(context, buffer, len, &error);
610 if (valid) valid = g_markup_parse_context_end_parse(context, &error);
611 if (tmpJournal.npages == 0) valid = FALSE;
612 g_markup_parse_context_free(context);
615 delete_journal(&tmpJournal);
616 if (!maybe_pdf) return FALSE;
617 // essentially same as on_fileNewBackground from here on
620 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
622 ui.zoom = ui.startup_zoom;
623 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
625 return init_bgpdf(filename, TRUE, DOMAIN_ABSOLUTE);
628 ui.saved = TRUE; // force close_journal() to do its job
630 g_memmove(&journal, &tmpJournal, sizeof(struct Journal));
632 // if we need to initialize a fresh pdf loader
633 if (tmpBg_pdf!=NULL) {
634 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
635 if (tmpBg_pdf->file_domain == DOMAIN_ATTACH)
636 tmpfn = g_strdup_printf("%s.%s", filename, tmpBg_pdf->filename->s);
638 tmpfn = g_strdup(tmpBg_pdf->filename->s);
639 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
640 // in case the file name became invalid
641 if (!valid && tmpBg_pdf->file_domain != DOMAIN_ATTACH)
642 if (user_wants_second_chance(&tmpfn)) {
643 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
644 if (valid) { // change the file name...
645 g_free(tmpBg_pdf->filename->s);
646 tmpBg_pdf->filename->s = g_strdup(tmpfn);
650 refstring_unref(bgpdf.filename);
651 bgpdf.filename = refstring_ref(tmpBg_pdf->filename);
653 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
654 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open background '%s'.",
656 gtk_dialog_run(GTK_DIALOG(dialog));
657 gtk_widget_destroy(dialog);
663 ui.cur_page = (struct Page *)journal.pages->data;
664 ui.layerno = ui.cur_page->nlayers-1;
665 ui.cur_layer = (struct Layer *)(g_list_last(ui.cur_page->layers)->data);
667 ui.zoom = ui.startup_zoom;
668 update_file_name(g_strdup(filename));
669 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
672 gtk_adjustment_set_value(gtk_layout_get_vadjustment(GTK_LAYOUT(canvas)), 0);
676 /************ file backgrounds *************/
678 struct Background *attempt_load_pix_bg(char *filename, gboolean attach)
680 struct Background *bg;
683 pix = gdk_pixbuf_new_from_file(filename, NULL);
684 if (pix == NULL) return NULL;
686 bg = g_new(struct Background, 1);
687 bg->type = BG_PIXMAP;
688 bg->canvas_item = NULL;
690 bg->pixbuf_scale = DEFAULT_ZOOM;
692 bg->filename = new_refstring(NULL);
693 bg->file_domain = DOMAIN_ATTACH;
695 bg->filename = new_refstring(filename);
696 bg->file_domain = DOMAIN_ABSOLUTE;
701 #define BUFSIZE 65536 // a reasonable buffer size for reads from gs pipe
703 GList *attempt_load_gv_bg(char *filename)
705 struct Background *bg;
708 GdkPixbufLoader *loader;
712 int buflen, remnlen, file_pageno;
714 buf = g_malloc(BUFSIZE); // a reasonable buffer size
715 f = fopen(filename, "r");
716 if (fread(buf, 1, 4, f) !=4 ||
717 (strncmp((char *)buf, "%!PS", 4) && strncmp((char *)buf, "%PDF", 4))) {
724 pipename = g_strdup_printf(GS_CMDLINE, (double)GS_BITMAP_DPI, filename);
725 gs_pipe = popen(pipename, "r");
733 while (!feof(gs_pipe)) {
734 if (!remnlen) { // new page: get a BMP header ?
735 buflen = fread(buf, 1, 54, gs_pipe);
736 if (buflen < 6) buflen += fread(buf, 1, 54-buflen, gs_pipe);
737 if (buflen < 6 || buf[0]!='B' || buf[1]!='M') break; // fatal: abort
738 remnlen = (int)(buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + (buf[2]);
739 loader = gdk_pixbuf_loader_new();
741 else buflen = fread(buf, 1, (remnlen < BUFSIZE)?remnlen:BUFSIZE, gs_pipe);
743 if (buflen == 0) break;
744 if (!gdk_pixbuf_loader_write(loader, buf, buflen, NULL)) break;
745 if (remnlen == 0) { // make a new bg
746 pix = gdk_pixbuf_loader_get_pixbuf(loader);
747 if (pix == NULL) break;
749 gdk_pixbuf_loader_close(loader, NULL);
750 g_object_unref(loader);
752 bg = g_new(struct Background, 1);
753 bg->canvas_item = NULL;
755 bg->pixbuf_scale = (GS_BITMAP_DPI/72.0);
756 bg->type = BG_PIXMAP;
757 bg->filename = new_refstring(NULL);
758 bg->file_domain = DOMAIN_ATTACH;
760 bg_list = g_list_append(bg_list, bg);
763 if (loader != NULL) gdk_pixbuf_loader_close(loader, NULL);
769 struct Background *attempt_screenshot_bg(void)
771 struct Background *bg;
774 GError *error = NULL;
778 Window x_root, x_win;
780 x_root = gdk_x11_get_default_root_xwindow();
782 if (!XGrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root,
783 False, ButtonReleaseMask, GrabModeAsync, GrabModeSync, None, None))
786 XWindowEvent (GDK_DISPLAY(), x_root, ButtonReleaseMask, &x_event);
787 XUngrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root);
789 x_win = x_event.xbutton.subwindow;
790 if (x_win == None) x_win = x_root;
792 window = gdk_window_foreign_new_for_display(gdk_display_get_default(), x_win);
794 gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
796 pix = gdk_pixbuf_get_from_drawable(NULL, window,
797 gdk_colormap_get_system(), 0, 0, 0, 0, w, h);
799 if (pix == NULL) return NULL;
801 bg = g_new(struct Background, 1);
802 bg->type = BG_PIXMAP;
803 bg->canvas_item = NULL;
805 bg->pixbuf_scale = DEFAULT_ZOOM;
806 bg->filename = new_refstring(NULL);
807 bg->file_domain = DOMAIN_ATTACH;
811 /************** pdf annotation ***************/
813 /* free tmp directory */
815 void end_bgpdf_shutdown(void)
817 if (bgpdf.tmpdir!=NULL) {
818 if (bgpdf.tmpfile_copy!=NULL) {
819 g_unlink(bgpdf.tmpfile_copy);
820 g_free(bgpdf.tmpfile_copy);
821 bgpdf.tmpfile_copy = NULL;
823 g_rmdir(bgpdf.tmpdir);
824 g_free(bgpdf.tmpdir);
827 bgpdf.status = STATUS_NOT_INIT;
830 /* cancel a request */
832 void cancel_bgpdf_request(struct BgPdfRequest *req)
836 list_link = g_list_find(bgpdf.requests, req);
837 if (list_link == NULL) return;
838 if (list_link->prev == NULL && bgpdf.pid > 0) {
839 // this is being processed: kill the child but don't remove the request yet
840 if (bgpdf.status == STATUS_RUNNING) bgpdf.status = STATUS_ABORTED;
841 kill(bgpdf.pid, SIGHUP);
842 // printf("Cancelling a request - killing %d\n", bgpdf.pid);
845 // remove the request
846 bgpdf.requests = g_list_delete_link(bgpdf.requests, list_link);
848 // printf("Cancelling a request - no kill needed\n");
852 /* sigchld callback */
854 void bgpdf_child_handler(GPid pid, gint status, gpointer data)
856 struct BgPdfRequest *req;
857 struct BgPdfPage *bgpg;
861 if (bgpdf.requests == NULL) return;
862 req = (struct BgPdfRequest *)bgpdf.requests->data;
864 ppm_name = g_strdup_printf("%s/p-%06d.ppm", bgpdf.tmpdir, req->pageno);
865 // printf("Child %d finished, should look for %s... \n", pid, ppm_name);
867 if (bgpdf.status == STATUS_ABORTED || bgpdf.status == STATUS_SHUTDOWN)
870 pixbuf = gdk_pixbuf_new_from_file(ppm_name, NULL);
875 if (pixbuf != NULL) { // success
876 // printf("success\n");
877 while (req->pageno > bgpdf.npages) {
878 bgpg = g_new(struct BgPdfPage, 1);
880 bgpdf.pages = g_list_append(bgpdf.pages, bgpg);
883 bgpg = g_list_nth_data(bgpdf.pages, req->pageno-1);
884 if (bgpg->pixbuf!=NULL) gdk_pixbuf_unref(bgpg->pixbuf);
885 bgpg->pixbuf = pixbuf;
886 bgpg->dpi = req->dpi;
887 if (req->initial_request && bgpdf.create_pages) {
888 bgpdf_create_page_with_bg(req->pageno, bgpg);
889 // create page n, resize it, set its bg - all without any undo effect
891 if (!req->is_printing) bgpdf_update_bg(req->pageno, bgpg);
892 // look for all pages with this bg, and update their bg pixmaps
896 // printf("failed or aborted\n");
897 bgpdf.create_pages = FALSE;
898 req->initial_request = FALSE;
902 g_spawn_close_pid(pid);
904 if (req->initial_request)
905 req->pageno++; // try for next page
907 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
909 if (bgpdf.status == STATUS_SHUTDOWN) {
910 end_bgpdf_shutdown();
914 bgpdf.status = STATUS_IDLE;
915 if (bgpdf.requests != NULL) bgpdf_spawn_child();
918 /* spawn a child to process the head request */
920 void bgpdf_spawn_child(void)
922 struct BgPdfRequest *req;
924 gchar pageno_str[10], dpi_str[10];
925 gchar *pdf_filename = bgpdf.tmpfile_copy;
926 gchar *ppm_root = g_strdup_printf("%s/p", bgpdf.tmpdir);
927 gchar *argv[]= PDFTOPPM_ARGV;
930 if (bgpdf.requests == NULL) return;
931 req = (struct BgPdfRequest *)bgpdf.requests->data;
932 if (req->pageno > bgpdf.npages+1 ||
933 (!req->initial_request && req->pageno <= bgpdf.npages &&
934 req->dpi == ((struct BgPdfPage *)g_list_nth_data(bgpdf.pages, req->pageno-1))->dpi))
935 { // ignore this request - it's redundant, or in outer space
937 bgpdf.status = STATUS_IDLE;
938 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
940 if (bgpdf.requests != NULL) bgpdf_spawn_child();
943 g_snprintf(pageno_str, 10, "%d", req->pageno);
944 g_snprintf(dpi_str, 10, "%d", req->dpi);
945 /* printf("Processing request for page %d at %d dpi -- in %s\n",
946 req->pageno, req->dpi, ppm_root); */
947 if (!g_spawn_async(NULL, argv, NULL,
948 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
949 NULL, NULL, &pid, NULL))
951 // couldn't spawn... abort this request, try next one maybe ?
952 // printf("Couldn't spawn\n");
954 bgpdf.status = STATUS_IDLE;
955 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
957 if (!bgpdf.has_failed) {
958 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
959 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Unable to start PDF loader %s.", argv[0]);
960 gtk_dialog_run(GTK_DIALOG(dialog));
961 gtk_widget_destroy(dialog);
963 bgpdf.has_failed = TRUE;
964 if (bgpdf.requests != NULL) bgpdf_spawn_child();
968 // printf("Spawned process %d\n", pid);
970 bgpdf.status = STATUS_RUNNING;
971 g_child_watch_add(pid, bgpdf_child_handler, NULL);
977 void add_bgpdf_request(int pageno, double zoom, gboolean printing)
979 struct BgPdfRequest *req, *cmp_req;
982 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN)
983 return; // don't accept requests in those modes...
984 req = g_new(struct BgPdfRequest, 1);
985 req->is_printing = printing;
986 if (printing) req->dpi = PDFTOPPM_PRINTING_DPI;
987 else req->dpi = (int)floor(72*zoom+0.5);
988 // printf("Enqueuing request for page %d at %d dpi\n", pageno, req->dpi);
990 // cancel any request this may supersede
991 for (list = bgpdf.requests; list != NULL; ) {
992 cmp_req = (struct BgPdfRequest *)list->data;
994 if (!cmp_req->initial_request && cmp_req->pageno == pageno &&
995 cmp_req->is_printing == printing)
996 cancel_bgpdf_request(cmp_req);
998 req->pageno = pageno;
999 req->initial_request = FALSE;
1002 req->initial_request = TRUE;
1004 bgpdf.requests = g_list_append(bgpdf.requests, req);
1005 if (!bgpdf.pid) bgpdf_spawn_child();
1008 /* shutdown the PDF reader */
1010 void shutdown_bgpdf(void)
1013 struct BgPdfPage *pdfpg;
1014 struct BgPdfRequest *req;
1016 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN) return;
1017 refstring_unref(bgpdf.filename);
1018 for (list = bgpdf.pages; list != NULL; list = list->next) {
1019 pdfpg = (struct BgPdfPage *)list->data;
1020 if (pdfpg->pixbuf!=NULL) gdk_pixbuf_unref(pdfpg->pixbuf);
1022 g_list_free(bgpdf.pages);
1023 bgpdf.status = STATUS_SHUTDOWN;
1024 for (list = g_list_last(bgpdf.requests); list != NULL; ) {
1025 req = (struct BgPdfRequest *)list->data;
1027 cancel_bgpdf_request(req);
1029 if (!bgpdf.pid) end_bgpdf_shutdown();
1030 /* The above will ultimately remove all requests and kill the child if needed.
1031 The child will set status to STATUS_NOT_INIT, clear the requests list,
1032 empty tmpdir, ... except if there's no child! */
1033 /* note: it could look like there's a race condition here - if a child
1034 terminates and a new request is enqueued while we are destroying the
1035 queue - but actually the child handler callback is NOT a signal
1036 callback, so execution of this function is atomic */
1039 gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain)
1045 if (bgpdf.status != STATUS_NOT_INIT) return FALSE;
1046 bgpdf.tmpfile_copy = NULL;
1047 bgpdf.tmpdir = mkdtemp(g_strdup(TMPDIR_TEMPLATE));
1048 if (!bgpdf.tmpdir) return FALSE;
1049 // make a local copy and check if it's a PDF
1050 if (!g_file_get_contents(pdfname, &filebuf, &filelen, NULL))
1051 { end_bgpdf_shutdown(); return FALSE; }
1052 if (filelen < 4 || strncmp(filebuf, "%PDF", 4))
1053 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1054 bgpdf.tmpfile_copy = g_strdup_printf("%s/bg.pdf", bgpdf.tmpdir);
1055 f = fopen(bgpdf.tmpfile_copy, "w");
1056 if (f == NULL || fwrite(filebuf, 1, filelen, f) != filelen)
1057 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1060 bgpdf.status = STATUS_IDLE;
1062 bgpdf.filename = new_refstring((file_domain == DOMAIN_ATTACH) ? "bg.pdf" : pdfname);
1063 bgpdf.file_domain = file_domain;
1066 bgpdf.requests = NULL;
1067 bgpdf.create_pages = create_pages;
1068 bgpdf.has_failed = FALSE;
1069 add_bgpdf_request(-1, ui.startup_zoom, FALSE); // request all pages
1073 // create page n, resize it, set its bg
1074 void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg)
1077 struct Background *bg;
1079 if (journal.npages < pageno) {
1080 bg = g_new(struct Background, 1);
1081 bg->canvas_item = NULL;
1083 pg = (struct Page *)g_list_nth_data(journal.pages, pageno-1);
1085 if (bg->type != BG_SOLID) return;
1086 // don't mess with a page the user has modified significantly...
1090 bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1091 bg->filename = refstring_ref(bgpdf.filename);
1092 bg->file_domain = bgpdf.file_domain;
1093 bg->file_page_seq = pageno;
1094 bg->pixbuf_scale = ui.startup_zoom;
1095 bg->pixbuf_dpi = bgpg->dpi;
1097 if (journal.npages < pageno) {
1098 pg = new_page_with_bg(bg,
1099 gdk_pixbuf_get_width(bg->pixbuf)*72.0/bg->pixbuf_dpi,
1100 gdk_pixbuf_get_height(bg->pixbuf)*72.0/bg->pixbuf_dpi);
1101 journal.pages = g_list_append(journal.pages, pg);
1104 pg->width = gdk_pixbuf_get_width(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1105 pg->height = gdk_pixbuf_get_height(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1106 make_page_clipbox(pg);
1107 update_canvas_bg(pg);
1109 update_page_stuff();
1112 // look for all journal pages with given pdf bg, and update their bg pixmaps
1113 void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg)
1118 for (list = journal.pages; list!= NULL; list = list->next) {
1119 pg = (struct Page *)list->data;
1120 if (pg->bg->type == BG_PDF && pg->bg->file_page_seq == pageno) {
1121 if (pg->bg->pixbuf!=NULL) gdk_pixbuf_unref(pg->bg->pixbuf);
1122 pg->bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1123 pg->bg->pixbuf_dpi = bgpg->dpi;
1124 update_canvas_bg(pg);
1129 // initialize the recent files list
1139 g_strlcpy(s, "mru0", 5);
1140 for (s[3]='0', i=0; i<MRU_SIZE; s[3]++, i++) {
1141 ui.mrumenu[i] = GET_COMPONENT(s);
1144 f = g_io_channel_new_file(ui.mrufile, "r", NULL);
1145 if (f) status = G_IO_STATUS_NORMAL;
1146 else status = G_IO_STATUS_ERROR;
1148 while (status == G_IO_STATUS_NORMAL && i<MRU_SIZE) {
1150 status = g_io_channel_read_line(f, &str, NULL, &lfptr, NULL);
1151 if (status == G_IO_STATUS_NORMAL && lfptr>0) {
1158 g_io_channel_shutdown(f, FALSE, NULL);
1159 g_io_channel_unref(f);
1164 void update_mru_menu(void)
1167 gboolean anyone = FALSE;
1169 for (i=0; i<MRU_SIZE; i++) {
1170 if (ui.mru[i]!=NULL) {
1171 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(ui.mrumenu[i]))),
1172 g_basename(ui.mru[i]));
1173 gtk_widget_show(ui.mrumenu[i]);
1176 else gtk_widget_hide(ui.mrumenu[i]);
1178 gtk_widget_set_sensitive(GET_COMPONENT("fileRecentFiles"), anyone);
1181 void new_mru_entry(char *name)
1185 for (i=0;i<MRU_SIZE;i++)
1186 if (ui.mru[i]!=NULL && !strcmp(ui.mru[i], name)) {
1188 for (j=i+1; j<MRU_SIZE; j++) ui.mru[j-1] = ui.mru[j];
1189 ui.mru[MRU_SIZE-1]=NULL;
1191 if (ui.mru[MRU_SIZE-1]!=NULL) g_free(ui.mru[MRU_SIZE-1]);
1192 for (j=MRU_SIZE-1; j>=1; j--) ui.mru[j] = ui.mru[j-1];
1193 ui.mru[0] = g_strdup(name);
1197 void delete_mru_entry(int which)
1201 if (ui.mru[which]!=NULL) g_free(ui.mru[which]);
1202 for (i=which+1;i<MRU_SIZE;i++)
1203 ui.mru[i-1] = ui.mru[i];
1204 ui.mru[MRU_SIZE-1] = NULL;
1208 void save_mru_list(void)
1213 f = fopen(ui.mrufile, "w");
1214 if (f==NULL) return;
1215 for (i=0; i<MRU_SIZE; i++)
1216 if (ui.mru[i]!=NULL) fprintf(f, "%s\n", ui.mru[i]);
1220 void init_config_default(void)
1224 DEFAULT_ZOOM = DISPLAY_DPI_DEFAULT/72.0;
1225 ui.zoom = ui.startup_zoom = 1.0*DEFAULT_ZOOM;
1226 ui.default_page.height = 792.0;
1227 ui.default_page.width = 612.0;
1228 ui.default_page.bg->type = BG_SOLID;
1229 ui.default_page.bg->color_no = COLOR_WHITE;
1230 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[COLOR_WHITE];
1231 ui.default_page.bg->ruling = RULING_LINED;
1232 ui.view_continuous = TRUE;
1233 ui.allow_xinput = TRUE;
1234 ui.discard_corepointer = TRUE;
1235 ui.bg_apply_all_pages = FALSE;
1236 ui.use_erasertip = FALSE;
1237 ui.window_default_width = 720;
1238 ui.window_default_height = 480;
1239 ui.maximize_at_start = FALSE;
1240 ui.fullscreen = FALSE;
1241 ui.scrollbar_step_increment = 30;
1242 ui.zoom_step_increment = 1;
1243 ui.zoom_step_factor = 1.5;
1244 ui.antialias_bg = TRUE;
1245 ui.progressive_bg = TRUE;
1246 ui.print_ruling = TRUE;
1247 ui.default_unit = UNIT_CM;
1248 ui.default_path = NULL;
1250 // the default UI vertical order
1251 ui.vertical_order[0][0] = 1;
1252 ui.vertical_order[0][1] = 2;
1253 ui.vertical_order[0][2] = 3;
1254 ui.vertical_order[0][3] = 0;
1255 ui.vertical_order[0][4] = 4;
1256 ui.vertical_order[1][0] = 2;
1257 ui.vertical_order[1][1] = 3;
1258 ui.vertical_order[1][2] = 0;
1259 ui.vertical_order[1][3] = ui.vertical_order[1][4] = -1;
1261 ui.toolno[0] = ui.startuptool = TOOL_PEN;
1262 ui.ruler[0] = ui.startupruler = FALSE;
1263 for (i=1; i<=NUM_BUTTONS; i++) {
1264 ui.toolno[i] = TOOL_ERASER;
1265 ui.ruler[i] = FALSE;
1267 for (i=0; i<=NUM_BUTTONS; i++)
1268 ui.linked_brush[i] = BRUSH_LINKED;
1269 ui.brushes[0][TOOL_PEN].color_no = COLOR_BLACK;
1270 ui.brushes[0][TOOL_ERASER].color_no = COLOR_WHITE;
1271 ui.brushes[0][TOOL_HIGHLIGHTER].color_no = COLOR_YELLOW;
1272 for (i=0; i < NUM_STROKE_TOOLS; i++) {
1273 ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM;
1274 ui.brushes[0][i].tool_options = 0;
1276 for (i=0; i< NUM_STROKE_TOOLS; i++)
1277 for (j=1; j<=NUM_BUTTONS; j++)
1278 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1280 // predef_thickness is already initialized as a global variable
1281 GS_BITMAP_DPI = 144;
1282 PDFTOPPM_PRINTING_DPI = 150;
1285 #if GLIB_CHECK_VERSION(2,6,0)
1287 void update_keyval(const gchar *group_name, const gchar *key,
1288 const gchar *comment, gchar *value)
1290 gboolean has_it = g_key_file_has_key(ui.config_data, group_name, key, NULL);
1291 cleanup_numeric(value);
1292 g_key_file_set_value(ui.config_data, group_name, key, value);
1294 if (!has_it) g_key_file_set_comment(ui.config_data, group_name, key, comment, NULL);
1299 const char *vorder_usernames[VBOX_MAIN_NITEMS+1] =
1300 {"drawarea", "menu", "main_toolbar", "pen_toolbar", "statusbar", NULL};
1302 gchar *verbose_vertical_order(int *order)
1304 gchar buf[80], *p; // longer than needed
1308 for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1309 if (order[i]<0 || order[i]>=VBOX_MAIN_NITEMS) continue;
1310 if (p!=buf) *(p++) = ' ';
1311 p = g_stpcpy(p, vorder_usernames[order[i]]);
1313 return g_strdup(buf);
1316 void save_config_to_file(void)
1321 #if GLIB_CHECK_VERSION(2,6,0)
1322 // no support for keyval files before Glib 2.6.0
1323 if (glib_minor_version<6) return;
1325 // save some data...
1326 ui.maximize_at_start = (gdk_window_get_state(winMain->window) & GDK_WINDOW_STATE_MAXIMIZED);
1327 if (!ui.maximize_at_start && !ui.fullscreen)
1328 gdk_drawable_get_size(winMain->window,
1329 &ui.window_default_width, &ui.window_default_height);
1331 update_keyval("general", "display_dpi",
1332 " the display resolution, in pixels per inch",
1333 g_strdup_printf("%.2f", DEFAULT_ZOOM*72));
1334 update_keyval("general", "initial_zoom",
1335 " the initial zoom level, in percent",
1336 g_strdup_printf("%.2f", 100*ui.zoom/DEFAULT_ZOOM));
1337 update_keyval("general", "window_maximize",
1338 " maximize the window at startup (true/false)",
1339 g_strdup(ui.maximize_at_start?"true":"false"));
1340 update_keyval("general", "window_fullscreen",
1341 " start in full screen mode (true/false)",
1342 g_strdup(ui.fullscreen?"true":"false"));
1343 update_keyval("general", "window_width",
1344 " the window width in pixels (when not maximized)",
1345 g_strdup_printf("%d", ui.window_default_width));
1346 update_keyval("general", "window_height",
1347 " the window height in pixels",
1348 g_strdup_printf("%d", ui.window_default_height));
1349 update_keyval("general", "scrollbar_speed",
1350 " scrollbar step increment (in pixels)",
1351 g_strdup_printf("%d", ui.scrollbar_step_increment));
1352 update_keyval("general", "zoom_dialog_increment",
1353 " the step increment in the zoom dialog box",
1354 g_strdup_printf("%d", ui.zoom_step_increment));
1355 update_keyval("general", "zoom_step_factor",
1356 " the multiplicative factor for zoom in/out",
1357 g_strdup_printf("%.3f", ui.zoom_step_factor));
1358 update_keyval("general", "view_continuous",
1359 " document view (true = continuous, false = single page)",
1360 g_strdup(ui.view_continuous?"true":"false"));
1361 update_keyval("general", "use_xinput",
1362 " use XInput extensions (true/false)",
1363 g_strdup(ui.allow_xinput?"true":"false"));
1364 update_keyval("general", "discard_corepointer",
1365 " discard Core Pointer events in XInput mode (true/false)",
1366 g_strdup(ui.discard_corepointer?"true":"false"));
1367 update_keyval("general", "use_erasertip",
1368 " always map eraser tip to eraser (true/false)",
1369 g_strdup(ui.use_erasertip?"true":"false"));
1370 update_keyval("general", "default_path",
1371 " default path for open/save (leave blank for current directory)",
1372 g_strdup((ui.default_path!=NULL)?ui.default_path:""));
1373 update_keyval("general", "interface_order",
1374 " interface components from top to bottom\n valid values: drawarea menu main_toolbar pen_toolbar statusbar",
1375 verbose_vertical_order(ui.vertical_order[0]));
1376 update_keyval("general", "interface_fullscreen",
1377 " interface components in fullscreen mode, from top to bottom",
1378 verbose_vertical_order(ui.vertical_order[1]));
1380 update_keyval("paper", "width",
1381 " the default page width, in points (1/72 in)",
1382 g_strdup_printf("%.2f", ui.default_page.width));
1383 update_keyval("paper", "height",
1384 " the default page height, in points (1/72 in)",
1385 g_strdup_printf("%.2f", ui.default_page.height));
1386 update_keyval("paper", "color",
1387 " the default paper color",
1388 g_strdup(bgcolor_names[ui.default_page.bg->color_no]));
1389 update_keyval("paper", "style",
1390 " the default paper style (plain, lined, ruled, or graph)",
1391 g_strdup(bgstyle_names[ui.default_page.bg->ruling]));
1392 update_keyval("paper", "apply_all",
1393 " apply paper style changes to all pages (true/false)",
1394 g_strdup(ui.bg_apply_all_pages?"true":"false"));
1395 update_keyval("paper", "default_unit",
1396 " preferred unit (cm, in, px, pt)",
1397 g_strdup(unit_names[ui.default_unit]));
1398 update_keyval("paper", "print_ruling",
1399 " include paper ruling when printing or exporting to PDF (true/false)",
1400 g_strdup(ui.print_ruling?"true":"false"));
1401 update_keyval("paper", "antialias_bg",
1402 " antialiased bitmap backgrounds (true/false)",
1403 g_strdup(ui.antialias_bg?"true":"false"));
1404 update_keyval("paper", "progressive_bg",
1405 " progressive scaling of bitmap backgrounds (true/false)",
1406 g_strdup(ui.progressive_bg?"true":"false"));
1407 update_keyval("paper", "gs_bitmap_dpi",
1408 " bitmap resolution of PS/PDF backgrounds rendered using ghostscript (dpi)",
1409 g_strdup_printf("%d", GS_BITMAP_DPI));
1410 update_keyval("paper", "pdftoppm_printing_dpi",
1411 " bitmap resolution of PDF backgrounds when printing with libgnomeprint (dpi)",
1412 g_strdup_printf("%d", PDFTOPPM_PRINTING_DPI));
1414 update_keyval("tools", "startup_tool",
1415 " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)",
1416 g_strdup(tool_names[ui.startuptool]));
1417 update_keyval("tools", "startup_ruler",
1418 " ruler mode at startup (true/false) (for pen or highlighter only)",
1419 g_strdup(ui.startupruler?"true":"false"));
1420 update_keyval("tools", "pen_color",
1421 " default pen color",
1422 g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no]));
1423 update_keyval("tools", "pen_thickness",
1424 " default pen thickness (fine = 1, medium = 2, thick = 3)",
1425 g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no));
1426 update_keyval("tools", "eraser_thickness",
1427 " default eraser thickness (fine = 1, medium = 2, thick = 3)",
1428 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no));
1429 update_keyval("tools", "eraser_mode",
1430 " default eraser mode (standard = 0, whiteout = 1, strokes = 2)",
1431 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].tool_options));
1432 update_keyval("tools", "highlighter_color",
1433 " default highlighter color",
1434 g_strdup(color_names[ui.default_brushes[TOOL_HIGHLIGHTER].color_no]));
1435 update_keyval("tools", "highlighter_thickness",
1436 " default highlighter thickness (fine = 1, medium = 2, thick = 3)",
1437 g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no));
1438 update_keyval("tools", "btn2_tool",
1439 " button 2 tool (pen, eraser, highlighter, selectrect, vertspace, hand)",
1440 g_strdup(tool_names[ui.toolno[1]]));
1441 update_keyval("tools", "btn2_linked",
1442 " button 2 brush linked to primary brush (true/false) (overrides all other settings)",
1443 g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false"));
1444 update_keyval("tools", "btn2_ruler",
1445 " button 2 ruler mode (true/false) (for pen or highlighter only)",
1446 g_strdup(ui.ruler[1]?"true":"false"));
1447 update_keyval("tools", "btn2_color",
1448 " button 2 brush color (for pen or highlighter only)",
1449 g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)?
1450 color_names[ui.brushes[1][ui.toolno[1]].color_no]:"white"));
1451 update_keyval("tools", "btn2_thickness",
1452 " button 2 brush thickness (pen, eraser, or highlighter only)",
1453 g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)?
1454 ui.brushes[1][ui.toolno[1]].thickness_no:0));
1455 update_keyval("tools", "btn2_erasermode",
1456 " button 2 eraser mode (eraser only)",
1457 g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options));
1458 update_keyval("tools", "btn3_tool",
1459 " button 3 tool (pen, eraser, highlighter, selectrect, vertspace, hand)",
1460 g_strdup(tool_names[ui.toolno[2]]));
1461 update_keyval("tools", "btn3_linked",
1462 " button 3 brush linked to primary brush (true/false) (overrides all other settings)",
1463 g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false"));
1464 update_keyval("tools", "btn3_ruler",
1465 " button 3 ruler mode (true/false) (for pen or highlighter only)",
1466 g_strdup(ui.ruler[2]?"true":"false"));
1467 update_keyval("tools", "btn3_color",
1468 " button 3 brush color (for pen or highlighter only)",
1469 g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)?
1470 color_names[ui.brushes[2][ui.toolno[2]].color_no]:"white"));
1471 update_keyval("tools", "btn3_thickness",
1472 " button 3 brush thickness (pen, eraser, or highlighter only)",
1473 g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)?
1474 ui.brushes[2][ui.toolno[2]].thickness_no:0));
1475 update_keyval("tools", "btn3_erasermode",
1476 " button 3 eraser mode (eraser only)",
1477 g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options));
1479 update_keyval("tools", "pen_thicknesses",
1480 " thickness of the various pens (in points, 1 pt = 1/72 in)",
1481 g_strdup_printf("%.2f;%.2f;%.2f;%.2f;%.2f",
1482 predef_thickness[TOOL_PEN][0], predef_thickness[TOOL_PEN][1],
1483 predef_thickness[TOOL_PEN][2], predef_thickness[TOOL_PEN][3],
1484 predef_thickness[TOOL_PEN][4]));
1485 update_keyval("tools", "eraser_thicknesses",
1486 " thickness of the various erasers (in points, 1 pt = 1/72 in)",
1487 g_strdup_printf("%.2f;%.2f;%.2f",
1488 predef_thickness[TOOL_ERASER][1], predef_thickness[TOOL_ERASER][2],
1489 predef_thickness[TOOL_ERASER][3]));
1490 update_keyval("tools", "highlighter_thicknesses",
1491 " thickness of the various highlighters (in points, 1 pt = 1/72 in)",
1492 g_strdup_printf("%.2f;%.2f;%.2f",
1493 predef_thickness[TOOL_HIGHLIGHTER][1], predef_thickness[TOOL_HIGHLIGHTER][2],
1494 predef_thickness[TOOL_HIGHLIGHTER][3]));
1495 // set comments for keys / groups that don't exist
1496 // set keyvals to current options
1498 buf = g_key_file_to_data(ui.config_data, NULL, NULL);
1499 if (buf == NULL) return;
1500 f = fopen(ui.configfile, "w");
1501 if (f==NULL) { g_free(buf); return; }
1508 #if GLIB_CHECK_VERSION(2,6,0)
1509 gboolean parse_keyval_float(const gchar *group, const gchar *key, double *val, double inf, double sup)
1514 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1515 if (ret==NULL) return FALSE;
1516 conv = g_ascii_strtod(ret, &end);
1517 if (*end!=0) { g_free(ret); return FALSE; }
1519 if (conv < inf || conv > sup) return FALSE;
1524 gboolean parse_keyval_floatlist(const gchar *group, const gchar *key, double *val, int n, double inf, double sup)
1530 if (n>5) return FALSE;
1531 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1532 if (ret==NULL) return FALSE;
1534 for (i=0; i<n; i++) {
1535 conv[i] = g_ascii_strtod(end, &end);
1536 if ((i==n-1 && *end!=0) || (i<n-1 && *end!=';') ||
1537 (conv[i] < inf) || (conv[i] > sup)) { g_free(ret); return FALSE; }
1541 for (i=0; i<n; i++) val[i] = conv[i];
1545 gboolean parse_keyval_int(const gchar *group, const gchar *key, int *val, int inf, int sup)
1550 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1551 if (ret==NULL) return FALSE;
1552 conv = strtol(ret, &end, 10);
1553 if (*end!=0) { g_free(ret); return FALSE; }
1555 if (conv < inf || conv > sup) return FALSE;
1560 gboolean parse_keyval_enum(const gchar *group, const gchar *key, int *val, const char **names, int n)
1565 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1566 if (ret==NULL) return FALSE;
1567 for (i=0; i<n; i++) {
1568 if (!names[i][0]) continue; // "" is for invalid values
1569 if (!g_ascii_strcasecmp(ret, names[i]))
1570 { *val = i; g_free(ret); return TRUE; }
1575 gboolean parse_keyval_boolean(const gchar *group, const gchar *key, gboolean *val)
1579 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1580 if (ret==NULL) return FALSE;
1581 if (!g_ascii_strcasecmp(ret, "true"))
1582 { *val = TRUE; g_free(ret); return TRUE; }
1583 if (!g_ascii_strcasecmp(ret, "false"))
1584 { *val = FALSE; g_free(ret); return TRUE; }
1589 gboolean parse_keyval_string(const gchar *group, const gchar *key, gchar **val)
1593 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1594 if (ret==NULL) return FALSE;
1595 if (strlen(ret) == 0) {
1603 gboolean parse_keyval_vorderlist(const gchar *group, const gchar *key, int *order)
1606 int tmp[VBOX_MAIN_NITEMS];
1609 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1610 if (ret==NULL) return FALSE;
1612 for (i=0; i<VBOX_MAIN_NITEMS; i++) tmp[i] = -1;
1614 while (*p==' ') p++;
1616 if (n>VBOX_MAIN_NITEMS) return FALSE; // too many items
1617 for (i=0; i<VBOX_MAIN_NITEMS; i++) {
1618 if (!g_str_has_prefix(p, vorder_usernames[i])) continue;
1619 l = strlen(vorder_usernames[i]);
1620 if (p[l]==' '||p[l]==0) { p+=l; break; }
1622 if (i>=VBOX_MAIN_NITEMS) { g_free(ret); return FALSE; } // parse error
1625 while (*p==' ') p++;
1628 for (n=0; n<VBOX_MAIN_NITEMS; n++) order[n] = tmp[n];
1635 void load_config_from_file(void)
1641 #if GLIB_CHECK_VERSION(2,6,0)
1642 // no support for keyval files before Glib 2.6.0
1643 if (glib_minor_version<6) return;
1644 ui.config_data = g_key_file_new();
1645 if (!g_key_file_load_from_file(ui.config_data, ui.configfile,
1646 G_KEY_FILE_KEEP_COMMENTS, NULL)) {
1647 g_key_file_free(ui.config_data);
1648 ui.config_data = g_key_file_new();
1649 g_key_file_set_comment(ui.config_data, NULL, NULL,
1650 " Xournal configuration file.\n"
1651 " This file is generated automatically upon saving preferences.\n"
1652 " Use caution when editing this file manually.\n", NULL);
1656 // parse keys from the keyfile to set defaults
1657 if (parse_keyval_float("general", "display_dpi", &f, 10., 500.))
1658 DEFAULT_ZOOM = f/72.0;
1659 if (parse_keyval_float("general", "initial_zoom", &f,
1660 MIN_ZOOM*100/DEFAULT_ZOOM, MAX_ZOOM*100/DEFAULT_ZOOM))
1661 ui.zoom = ui.startup_zoom = DEFAULT_ZOOM*f/100.0;
1662 parse_keyval_boolean("general", "window_maximize", &ui.maximize_at_start);
1663 parse_keyval_boolean("general", "window_fullscreen", &ui.fullscreen);
1664 parse_keyval_int("general", "window_width", &ui.window_default_width, 10, 5000);
1665 parse_keyval_int("general", "window_height", &ui.window_default_height, 10, 5000);
1666 parse_keyval_int("general", "scrollbar_speed", &ui.scrollbar_step_increment, 1, 5000);
1667 parse_keyval_int("general", "zoom_dialog_increment", &ui.zoom_step_increment, 1, 500);
1668 parse_keyval_float("general", "zoom_step_factor", &ui.zoom_step_factor, 1., 5.);
1669 parse_keyval_boolean("general", "view_continuous", &ui.view_continuous);
1670 parse_keyval_boolean("general", "use_xinput", &ui.allow_xinput);
1671 parse_keyval_boolean("general", "discard_corepointer", &ui.discard_corepointer);
1672 parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip);
1673 parse_keyval_string("general", "default_path", &ui.default_path);
1674 parse_keyval_vorderlist("general", "interface_order", ui.vertical_order[0]);
1675 parse_keyval_vorderlist("general", "interface_fullscreen", ui.vertical_order[1]);
1677 parse_keyval_float("paper", "width", &ui.default_page.width, 1., 5000.);
1678 parse_keyval_float("paper", "height", &ui.default_page.height, 1., 5000.);
1679 parse_keyval_enum("paper", "color", &(ui.default_page.bg->color_no), bgcolor_names, COLOR_MAX);
1680 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[ui.default_page.bg->color_no];
1681 parse_keyval_enum("paper", "style", &(ui.default_page.bg->ruling), bgstyle_names, 4);
1682 parse_keyval_boolean("paper", "apply_all", &ui.bg_apply_all_pages);
1683 parse_keyval_enum("paper", "default_unit", &ui.default_unit, unit_names, 4);
1684 parse_keyval_boolean("paper", "antialias_bg", &ui.antialias_bg);
1685 parse_keyval_boolean("paper", "progressive_bg", &ui.progressive_bg);
1686 parse_keyval_boolean("paper", "print_ruling", &ui.print_ruling);
1687 parse_keyval_int("paper", "gs_bitmap_dpi", &GS_BITMAP_DPI, 1, 1200);
1688 parse_keyval_int("paper", "pdftoppm_printing_dpi", &PDFTOPPM_PRINTING_DPI, 1, 1200);
1690 parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS);
1691 ui.toolno[0] = ui.startuptool;
1692 if (ui.startuptool == TOOL_PEN || ui.startuptool == TOOL_HIGHLIGHTER) {
1693 parse_keyval_boolean("tools", "startup_ruler", &ui.startupruler);
1694 ui.ruler[0] = ui.startupruler;
1696 parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX);
1697 parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4);
1698 parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3);
1699 parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2);
1700 parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX);
1701 parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4);
1702 for (i=0; i< NUM_STROKE_TOOLS; i++)
1703 for (j=1; j<=NUM_BUTTONS; j++)
1704 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1706 parse_keyval_enum("tools", "btn2_tool", &(ui.toolno[1]), tool_names, NUM_TOOLS);
1707 if (parse_keyval_boolean("tools", "btn2_linked", &b))
1708 ui.linked_brush[1] = b?BRUSH_LINKED:BRUSH_STATIC;
1709 parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS);
1710 if (parse_keyval_boolean("tools", "btn3_linked", &b))
1711 ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC;
1712 for (i=1; i<=NUM_BUTTONS; i++)
1713 if (ui.toolno[i]==TOOL_PEN || ui.toolno[i]==TOOL_HIGHLIGHTER)
1714 ui.ruler[i] = ui.ruler[0];
1715 if (ui.linked_brush[1]!=BRUSH_LINKED) {
1716 if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) {
1717 parse_keyval_boolean("tools", "btn2_ruler", &(ui.ruler[1]));
1718 parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX);
1720 if (ui.toolno[1]<NUM_STROKE_TOOLS)
1721 parse_keyval_int("tools", "btn2_thickness", &(ui.brushes[1][ui.toolno[1]].thickness_no), 0, 4);
1722 if (ui.toolno[1]==TOOL_ERASER)
1723 parse_keyval_int("tools", "btn2_erasermode", &(ui.brushes[1][TOOL_ERASER].tool_options), 0, 2);
1725 if (ui.linked_brush[2]!=BRUSH_LINKED) {
1726 if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) {
1727 parse_keyval_boolean("tools", "btn3_ruler", &(ui.ruler[2]));
1728 parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX);
1730 if (ui.toolno[2]<NUM_STROKE_TOOLS)
1731 parse_keyval_int("tools", "btn3_thickness", &(ui.brushes[2][ui.toolno[2]].thickness_no), 0, 4);
1732 if (ui.toolno[2]==TOOL_ERASER)
1733 parse_keyval_int("tools", "btn3_erasermode", &(ui.brushes[2][TOOL_ERASER].tool_options), 0, 2);
1735 parse_keyval_floatlist("tools", "pen_thicknesses", predef_thickness[TOOL_PEN], 5, 0.01, 1000.0);
1736 parse_keyval_floatlist("tools", "eraser_thicknesses", predef_thickness[TOOL_ERASER]+1, 3, 0.01, 1000.0);
1737 parse_keyval_floatlist("tools", "highlighter_thicknesses", predef_thickness[TOOL_HIGHLIGHTER]+1, 3, 0.01, 1000.0);