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 = DEFAULT_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 = DEFAULT_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, DEFAULT_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 = DEFAULT_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 = 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.bg_apply_all_pages = FALSE;
1235 ui.use_erasertip = FALSE;
1236 ui.window_default_width = 720;
1237 ui.window_default_height = 480;
1238 ui.maximize_at_start = FALSE;
1239 ui.fullscreen = FALSE;
1240 ui.scrollbar_step_increment = 30;
1241 ui.zoom_step_increment = 1;
1242 ui.zoom_step_factor = 1.5;
1243 ui.antialias_bg = TRUE;
1244 ui.progressive_bg = TRUE;
1245 ui.print_ruling = TRUE;
1246 ui.default_unit = UNIT_CM;
1248 ui.toolno[0] = ui.startuptool = TOOL_PEN;
1249 ui.ruler[0] = ui.startupruler = FALSE;
1250 for (i=1; i<=NUM_BUTTONS; i++) {
1251 ui.toolno[i] = TOOL_ERASER;
1252 ui.ruler[i] = FALSE;
1254 for (i=0; i<=NUM_BUTTONS; i++)
1255 ui.linked_brush[i] = BRUSH_LINKED;
1256 ui.brushes[0][TOOL_PEN].color_no = COLOR_BLACK;
1257 ui.brushes[0][TOOL_ERASER].color_no = COLOR_WHITE;
1258 ui.brushes[0][TOOL_HIGHLIGHTER].color_no = COLOR_YELLOW;
1259 for (i=0; i < NUM_STROKE_TOOLS; i++) {
1260 ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM;
1261 ui.brushes[0][i].tool_options = 0;
1263 for (i=0; i< NUM_STROKE_TOOLS; i++)
1264 for (j=1; j<=NUM_BUTTONS; j++)
1265 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1267 // predef_thickness is already initialized as a global variable
1269 GS_BITMAP_DPI = 144;
1270 PDFTOPPM_PRINTING_DPI = 150;
1273 #if GLIB_CHECK_VERSION(2,6,0)
1275 void update_keyval(const gchar *group_name, const gchar *key,
1276 const gchar *comment, gchar *value)
1278 gboolean has_it = g_key_file_has_key(ui.config_data, group_name, key, NULL);
1279 cleanup_numeric(value);
1280 g_key_file_set_value(ui.config_data, group_name, key, value);
1282 if (!has_it) g_key_file_set_comment(ui.config_data, group_name, key, comment, NULL);
1287 void save_config_to_file(void)
1292 #if GLIB_CHECK_VERSION(2,6,0)
1293 // no support for keyval files before Glib 2.6.0
1294 if (glib_minor_version<6) return;
1296 // save some data...
1297 ui.maximize_at_start = (gdk_window_get_state(winMain->window) & GDK_WINDOW_STATE_MAXIMIZED);
1298 if (!ui.maximize_at_start && !ui.fullscreen)
1299 gdk_drawable_get_size(winMain->window,
1300 &ui.window_default_width, &ui.window_default_height);
1302 update_keyval("general", "display_dpi",
1303 " the display resolution, in pixels per inch",
1304 g_strdup_printf("%.2f", DEFAULT_ZOOM*72));
1305 update_keyval("general", "initial_zoom",
1306 " the initial zoom level, in percent",
1307 g_strdup_printf("%.2f", 100*ui.zoom/DEFAULT_ZOOM));
1308 update_keyval("general", "window_maximize",
1309 " maximize the window at startup (true/false)",
1310 g_strdup(ui.maximize_at_start?"true":"false"));
1311 update_keyval("general", "window_fullscreen",
1312 " start in full screen mode (true/false)",
1313 g_strdup(ui.fullscreen?"true":"false"));
1314 update_keyval("general", "window_width",
1315 " the window width in pixels (when not maximized)",
1316 g_strdup_printf("%d", ui.window_default_width));
1317 update_keyval("general", "window_height",
1318 " the window height in pixels",
1319 g_strdup_printf("%d", ui.window_default_height));
1320 update_keyval("general", "scrollbar_speed",
1321 " scrollbar step increment (in pixels)",
1322 g_strdup_printf("%d", ui.scrollbar_step_increment));
1323 update_keyval("general", "zoom_dialog_increment",
1324 " the step increment in the zoom dialog box",
1325 g_strdup_printf("%d", ui.zoom_step_increment));
1326 update_keyval("general", "zoom_step_factor",
1327 " the multiplicative factor for zoom in/out",
1328 g_strdup_printf("%.3f", ui.zoom_step_factor));
1329 update_keyval("general", "view_continuous",
1330 " document view (true = continuous, false = single page)",
1331 g_strdup(ui.view_continuous?"true":"false"));
1332 update_keyval("general", "use_xinput",
1333 " use XInput extensions (true/false)",
1334 g_strdup(ui.allow_xinput?"true":"false"));
1335 update_keyval("general", "use_erasertip",
1336 " always map eraser tip to eraser (true/false)",
1337 g_strdup(ui.use_erasertip?"true":"false"));
1339 update_keyval("paper", "width",
1340 " the default page width, in points (1/72 in)",
1341 g_strdup_printf("%.2f", ui.default_page.width));
1342 update_keyval("paper", "height",
1343 " the default page height, in points (1/72 in)",
1344 g_strdup_printf("%.2f", ui.default_page.height));
1345 update_keyval("paper", "color",
1346 " the default paper color",
1347 g_strdup(bgcolor_names[ui.default_page.bg->color_no]));
1348 update_keyval("paper", "style",
1349 " the default paper style (plain, lined, ruled, or graph)",
1350 g_strdup(bgstyle_names[ui.default_page.bg->ruling]));
1351 update_keyval("paper", "apply_all",
1352 " apply paper style changes to all pages (true/false)",
1353 g_strdup(ui.bg_apply_all_pages?"true":"false"));
1354 update_keyval("paper", "default_unit",
1355 " preferred unit (cm, in, px, pt)",
1356 g_strdup(unit_names[ui.default_unit]));
1357 update_keyval("paper", "print_ruling",
1358 " include paper ruling when printing or exporting to PDF (true/false)",
1359 g_strdup(ui.print_ruling?"true":"false"));
1360 update_keyval("paper", "antialias_bg",
1361 " antialiased bitmap backgrounds (true/false)",
1362 g_strdup(ui.antialias_bg?"true":"false"));
1363 update_keyval("paper", "progressive_bg",
1364 " progressive scaling of bitmap backgrounds (true/false)",
1365 g_strdup(ui.progressive_bg?"true":"false"));
1366 update_keyval("paper", "gs_bitmap_dpi",
1367 " bitmap resolution of PS/PDF backgrounds rendered using ghostscript (dpi)",
1368 g_strdup_printf("%d", GS_BITMAP_DPI));
1369 update_keyval("paper", "pdftoppm_printing_dpi",
1370 " bitmap resolution of PDF backgrounds when printing with libgnomeprint (dpi)",
1371 g_strdup_printf("%d", PDFTOPPM_PRINTING_DPI));
1373 update_keyval("tools", "startup_tool",
1374 " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)",
1375 g_strdup(tool_names[ui.startuptool]));
1376 update_keyval("tools", "startup_ruler",
1377 " ruler mode at startup (true/false) (for pen or highlighter only)",
1378 g_strdup(ui.startupruler?"true":"false"));
1379 update_keyval("tools", "pen_color",
1380 " default pen color",
1381 g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no]));
1382 update_keyval("tools", "pen_thickness",
1383 " default pen thickness (fine = 1, medium = 2, thick = 3)",
1384 g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no));
1385 update_keyval("tools", "eraser_thickness",
1386 " default eraser thickness (fine = 1, medium = 2, thick = 3)",
1387 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no));
1388 update_keyval("tools", "eraser_mode",
1389 " default eraser mode (standard = 0, whiteout = 1, strokes = 2)",
1390 g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].tool_options));
1391 update_keyval("tools", "highlighter_color",
1392 " default highlighter color",
1393 g_strdup(color_names[ui.default_brushes[TOOL_HIGHLIGHTER].color_no]));
1394 update_keyval("tools", "highlighter_thickness",
1395 " default highlighter thickness (fine = 1, medium = 2, thick = 3)",
1396 g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no));
1397 update_keyval("tools", "btn2_tool",
1398 " button 2 tool (pen, eraser, highlighter, selectrect, vertspace, hand)",
1399 g_strdup(tool_names[ui.toolno[1]]));
1400 update_keyval("tools", "btn2_linked",
1401 " button 2 brush linked to primary brush (true/false) (overrides all other settings)",
1402 g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false"));
1403 update_keyval("tools", "btn2_ruler",
1404 " button 2 ruler mode (true/false) (for pen or highlighter only)",
1405 g_strdup(ui.ruler[1]?"true":"false"));
1406 update_keyval("tools", "btn2_color",
1407 " button 2 brush color (for pen or highlighter only)",
1408 g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)?
1409 color_names[ui.brushes[1][ui.toolno[1]].color_no]:"white"));
1410 update_keyval("tools", "btn2_thickness",
1411 " button 2 brush thickness (pen, eraser, or highlighter only)",
1412 g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)?
1413 ui.brushes[1][ui.toolno[1]].thickness_no:0));
1414 update_keyval("tools", "btn2_erasermode",
1415 " button 2 eraser mode (eraser only)",
1416 g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options));
1417 update_keyval("tools", "btn3_tool",
1418 " button 3 tool (pen, eraser, highlighter, selectrect, vertspace, hand)",
1419 g_strdup(tool_names[ui.toolno[2]]));
1420 update_keyval("tools", "btn3_linked",
1421 " button 3 brush linked to primary brush (true/false) (overrides all other settings)",
1422 g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false"));
1423 update_keyval("tools", "btn3_ruler",
1424 " button 3 ruler mode (true/false) (for pen or highlighter only)",
1425 g_strdup(ui.ruler[2]?"true":"false"));
1426 update_keyval("tools", "btn3_color",
1427 " button 3 brush color (for pen or highlighter only)",
1428 g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)?
1429 color_names[ui.brushes[2][ui.toolno[2]].color_no]:"white"));
1430 update_keyval("tools", "btn3_thickness",
1431 " button 3 brush thickness (pen, eraser, or highlighter only)",
1432 g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)?
1433 ui.brushes[2][ui.toolno[2]].thickness_no:0));
1434 update_keyval("tools", "btn3_erasermode",
1435 " button 3 eraser mode (eraser only)",
1436 g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options));
1438 update_keyval("tools", "pen_thicknesses",
1439 " thickness of the various pens (in points, 1 pt = 1/72 in)",
1440 g_strdup_printf("%.2f;%.2f;%.2f;%.2f;%.2f",
1441 predef_thickness[TOOL_PEN][0], predef_thickness[TOOL_PEN][1],
1442 predef_thickness[TOOL_PEN][2], predef_thickness[TOOL_PEN][3],
1443 predef_thickness[TOOL_PEN][4]));
1444 update_keyval("tools", "eraser_thicknesses",
1445 " thickness of the various erasers (in points, 1 pt = 1/72 in)",
1446 g_strdup_printf("%.2f;%.2f;%.2f",
1447 predef_thickness[TOOL_ERASER][1], predef_thickness[TOOL_ERASER][2],
1448 predef_thickness[TOOL_ERASER][3]));
1449 update_keyval("tools", "highlighter_thicknesses",
1450 " thickness of the various highlighters (in points, 1 pt = 1/72 in)",
1451 g_strdup_printf("%.2f;%.2f;%.2f",
1452 predef_thickness[TOOL_HIGHLIGHTER][1], predef_thickness[TOOL_HIGHLIGHTER][2],
1453 predef_thickness[TOOL_HIGHLIGHTER][3]));
1454 // set comments for keys / groups that don't exist
1455 // set keyvals to current options
1457 buf = g_key_file_to_data(ui.config_data, NULL, NULL);
1458 if (buf == NULL) return;
1459 f = fopen(ui.configfile, "w");
1460 if (f==NULL) { g_free(buf); return; }
1467 #if GLIB_CHECK_VERSION(2,6,0)
1468 gboolean parse_keyval_float(const gchar *group, const gchar *key, double *val, double inf, double sup)
1473 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1474 if (ret==NULL) return FALSE;
1475 conv = g_ascii_strtod(ret, &end);
1476 if (*end!=0) { g_free(ret); return FALSE; }
1478 if (conv < inf || conv > sup) return FALSE;
1483 gboolean parse_keyval_floatlist(const gchar *group, const gchar *key, double *val, int n, double inf, double sup)
1489 if (n>5) return FALSE;
1490 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1491 if (ret==NULL) return FALSE;
1493 for (i=0; i<n; i++) {
1494 conv[i] = g_ascii_strtod(end, &end);
1495 if ((i==n-1 && *end!=0) || (i<n-1 && *end!=';') ||
1496 (conv[i] < inf) || (conv[i] > sup)) { g_free(ret); return FALSE; }
1500 for (i=0; i<n; i++) val[i] = conv[i];
1504 gboolean parse_keyval_int(const gchar *group, const gchar *key, int *val, int inf, int sup)
1509 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1510 if (ret==NULL) return FALSE;
1511 conv = strtol(ret, &end, 10);
1512 if (*end!=0) { g_free(ret); return FALSE; }
1514 if (conv < inf || conv > sup) return FALSE;
1519 gboolean parse_keyval_enum(const gchar *group, const gchar *key, int *val, const char **names, int n)
1524 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1525 if (ret==NULL) return FALSE;
1526 for (i=0; i<n; i++) {
1527 if (!names[i][0]) continue; // "" is for invalid values
1528 if (!g_ascii_strcasecmp(ret, names[i]))
1529 { *val = i; g_free(ret); return TRUE; }
1534 gboolean parse_keyval_boolean(const gchar *group, const gchar *key, gboolean *val)
1538 ret = g_key_file_get_value(ui.config_data, group, key, NULL);
1539 if (ret==NULL) return FALSE;
1540 if (!g_ascii_strcasecmp(ret, "true"))
1541 { *val = TRUE; g_free(ret); return TRUE; }
1542 if (!g_ascii_strcasecmp(ret, "false"))
1543 { *val = FALSE; g_free(ret); return TRUE; }
1550 void load_config_from_file(void)
1556 #if GLIB_CHECK_VERSION(2,6,0)
1557 // no support for keyval files before Glib 2.6.0
1558 if (glib_minor_version<6) return;
1559 ui.config_data = g_key_file_new();
1560 if (!g_key_file_load_from_file(ui.config_data, ui.configfile,
1561 G_KEY_FILE_KEEP_COMMENTS, NULL)) {
1562 g_key_file_free(ui.config_data);
1563 ui.config_data = g_key_file_new();
1564 g_key_file_set_comment(ui.config_data, NULL, NULL,
1565 " Xournal configuration file.\n"
1566 " This file is generated automatically upon saving preferences.\n"
1567 " Use caution when editing this file manually.\n", NULL);
1571 // parse keys from the keyfile to set defaults
1572 if (parse_keyval_float("general", "display_dpi", &f, 10., 500.))
1573 DEFAULT_ZOOM = f/72.0;
1574 if (parse_keyval_float("general", "initial_zoom", &f,
1575 MIN_ZOOM*100/DEFAULT_ZOOM, MAX_ZOOM*100/DEFAULT_ZOOM))
1576 ui.zoom = DEFAULT_ZOOM*f/100.0;
1577 parse_keyval_boolean("general", "window_maximize", &ui.maximize_at_start);
1578 parse_keyval_boolean("general", "window_fullscreen", &ui.fullscreen);
1579 parse_keyval_int("general", "window_width", &ui.window_default_width, 10, 5000);
1580 parse_keyval_int("general", "window_height", &ui.window_default_height, 10, 5000);
1581 parse_keyval_int("general", "scrollbar_speed", &ui.scrollbar_step_increment, 1, 5000);
1582 parse_keyval_int("general", "zoom_dialog_increment", &ui.zoom_step_increment, 1, 500);
1583 parse_keyval_float("general", "zoom_step_factor", &ui.zoom_step_factor, 1., 5.);
1584 parse_keyval_boolean("general", "view_continuous", &ui.view_continuous);
1585 parse_keyval_boolean("general", "use_xinput", &ui.allow_xinput);
1586 parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip);
1588 parse_keyval_float("paper", "width", &ui.default_page.width, 1., 5000.);
1589 parse_keyval_float("paper", "height", &ui.default_page.height, 1., 5000.);
1590 parse_keyval_enum("paper", "color", &(ui.default_page.bg->color_no), bgcolor_names, COLOR_MAX);
1591 ui.default_page.bg->color_rgba = predef_bgcolors_rgba[ui.default_page.bg->color_no];
1592 parse_keyval_enum("paper", "style", &(ui.default_page.bg->ruling), bgstyle_names, 4);
1593 parse_keyval_boolean("paper", "apply_all", &ui.bg_apply_all_pages);
1594 parse_keyval_enum("paper", "default_unit", &ui.default_unit, unit_names, 4);
1595 parse_keyval_boolean("paper", "antialias_bg", &ui.antialias_bg);
1596 parse_keyval_boolean("paper", "progressive_bg", &ui.progressive_bg);
1597 parse_keyval_boolean("paper", "print_ruling", &ui.print_ruling);
1598 parse_keyval_int("paper", "gs_bitmap_dpi", &GS_BITMAP_DPI, 1, 1200);
1599 parse_keyval_int("paper", "pdftoppm_printing_dpi", &PDFTOPPM_PRINTING_DPI, 1, 1200);
1601 parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS);
1602 ui.toolno[0] = ui.startuptool;
1603 if (ui.startuptool == TOOL_PEN || ui.startuptool == TOOL_HIGHLIGHTER) {
1604 parse_keyval_boolean("tools", "startup_ruler", &ui.startupruler);
1605 ui.ruler[0] = ui.startupruler;
1607 parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX);
1608 parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4);
1609 parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3);
1610 parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2);
1611 parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX);
1612 parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4);
1613 for (i=0; i< NUM_STROKE_TOOLS; i++)
1614 for (j=1; j<=NUM_BUTTONS; j++)
1615 g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush));
1617 parse_keyval_enum("tools", "btn2_tool", &(ui.toolno[1]), tool_names, NUM_TOOLS);
1618 if (parse_keyval_boolean("tools", "btn2_linked", &b))
1619 ui.linked_brush[1] = b?BRUSH_LINKED:BRUSH_STATIC;
1620 parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS);
1621 if (parse_keyval_boolean("tools", "btn3_linked", &b))
1622 ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC;
1623 for (i=1; i<=NUM_BUTTONS; i++)
1624 if (ui.toolno[i]==TOOL_PEN || ui.toolno[i]==TOOL_HIGHLIGHTER)
1625 ui.ruler[i] = ui.ruler[0];
1626 if (ui.linked_brush[1]!=BRUSH_LINKED) {
1627 if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) {
1628 parse_keyval_boolean("tools", "btn2_ruler", &(ui.ruler[1]));
1629 parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX);
1631 if (ui.toolno[1]<NUM_STROKE_TOOLS)
1632 parse_keyval_int("tools", "btn2_thickness", &(ui.brushes[1][ui.toolno[1]].thickness_no), 0, 4);
1633 if (ui.toolno[1]==TOOL_ERASER)
1634 parse_keyval_int("tools", "btn2_erasermode", &(ui.brushes[1][TOOL_ERASER].tool_options), 0, 2);
1636 if (ui.linked_brush[2]!=BRUSH_LINKED) {
1637 if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) {
1638 parse_keyval_boolean("tools", "btn3_ruler", &(ui.ruler[2]));
1639 parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX);
1641 if (ui.toolno[2]<NUM_STROKE_TOOLS)
1642 parse_keyval_int("tools", "btn3_thickness", &(ui.brushes[2][ui.toolno[2]].thickness_no), 0, 4);
1643 if (ui.toolno[2]==TOOL_ERASER)
1644 parse_keyval_int("tools", "btn3_erasermode", &(ui.brushes[2][TOOL_ERASER].tool_options), 0, 2);
1646 parse_keyval_floatlist("tools", "pen_thicknesses", predef_thickness[TOOL_PEN], 5, 0.01, 1000.0);
1647 parse_keyval_floatlist("tools", "eraser_thicknesses", predef_thickness[TOOL_ERASER]+1, 3, 0.01, 1000.0);
1648 parse_keyval_floatlist("tools", "highlighter_thicknesses", predef_thickness[TOOL_HIGHLIGHTER]+1, 3, 0.01, 1000.0);