11 #include <libgnomecanvas/libgnomecanvas.h>
18 #include "xo-interface.h"
19 #include "xo-support.h"
20 #include "xo-callbacks.h"
24 const char *tool_names[NUM_STROKE_TOOLS] = {"pen", "eraser", "highlighter"};
25 const char *color_names[COLOR_MAX] = {"black", "blue", "red", "green",
26 "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white"};
27 const char *bgtype_names[3] = {"solid", "pixmap", "pdf"};
28 const char *bgcolor_names[COLOR_MAX] = {"", "blue", "pink", "green",
29 "", "", "", "", "orange", "yellow", "white"};
30 const char *bgstyle_names[4] = {"plain", "lined", "ruled", "graph"};
31 const char *file_domain_names[3] = {"absolute", "attach", "clone"};
33 // creates a new empty journal
35 void new_journal(void)
38 journal.pages = g_list_append(NULL, new_page(&ui.default_page));
39 journal.last_attach_no = 0;
42 ui.cur_page = (struct Page *) journal.pages->data;
43 ui.cur_layer = (struct Layer *) ui.cur_page->layers->data;
46 update_file_name(NULL);
49 // check attachment names
51 void chk_attach_names(void)
54 struct Background *bg;
56 for (list = journal.pages; list!=NULL; list = list->next) {
57 bg = ((struct Page *)list->data)->bg;
58 if (bg->type == BG_SOLID || bg->file_domain != DOMAIN_ATTACH ||
59 bg->filename->s != NULL) continue;
60 bg->filename->s = g_strdup_printf("bg_%d.png", ++journal.last_attach_no);
64 // saves the journal to a file: returns true on success, false on error
66 gboolean save_journal(const char *filename)
69 struct Page *pg, *tmppg;
78 GList *pagelist, *layerlist, *itemlist, *list;
81 f = gzopen(filename, "w");
82 if (f==NULL) return FALSE;
85 gzprintf(f, "<?xml version=\"1.0\" standalone=\"no\"?>\n"
86 "<title>Xournal document - see http://math.mit.edu/~auroux/software/xournal/</title>\n"
87 "<xournal version=\"" VERSION "\"/>\n");
88 for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next) {
89 pg = (struct Page *)pagelist->data;
90 gzprintf(f, "<page width=\"%.2f\" height=\"%.2f\">\n", pg->width, pg->height);
91 gzprintf(f, "<background type=\"%s\" ", bgtype_names[pg->bg->type]);
92 if (pg->bg->type == BG_SOLID) {
93 gzputs(f, "color=\"");
94 if (pg->bg->color_no >= 0) gzputs(f, bgcolor_names[pg->bg->color_no]);
95 else gzprintf(f, "#%08x", pg->bg->color_rgba);
96 gzprintf(f, "\" style=\"%s\" ", bgstyle_names[pg->bg->ruling]);
98 else if (pg->bg->type == BG_PIXMAP) {
100 for (list = journal.pages, i = 0; list!=pagelist; list = list->next, i++) {
101 tmppg = (struct Page *)list->data;
102 if (tmppg->bg->type == BG_PIXMAP &&
103 tmppg->bg->pixbuf == pg->bg->pixbuf &&
104 tmppg->bg->filename == pg->bg->filename)
105 { is_clone = i; break; }
108 gzprintf(f, "domain=\"clone\" filename=\"%d\" ", is_clone);
110 if (pg->bg->file_domain == DOMAIN_ATTACH) {
111 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
112 if (!gdk_pixbuf_save(pg->bg->pixbuf, tmpfn, "png", NULL, NULL)) {
113 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
114 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
115 "Could not write background '%s'. Continuing anyway.", tmpfn);
116 gtk_dialog_run(GTK_DIALOG(dialog));
117 gtk_widget_destroy(dialog);
121 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
122 file_domain_names[pg->bg->file_domain], pg->bg->filename->s);
125 else if (pg->bg->type == BG_PDF) {
127 for (list = journal.pages; list!=pagelist; list = list->next) {
128 tmppg = (struct Page *)list->data;
129 if (tmppg->bg->type == BG_PDF) { is_clone = 1; break; }
132 if (pg->bg->file_domain == DOMAIN_ATTACH) {
133 tmpfn = g_strdup_printf("%s.%s", filename, pg->bg->filename->s);
135 if (bgpdf.status != STATUS_NOT_INIT &&
136 g_file_get_contents(bgpdf.tmpfile_copy, &pdfbuf, &pdflen, NULL))
138 tmpf = fopen(tmpfn, "w");
139 if (tmpf != NULL && fwrite(pdfbuf, 1, pdflen, tmpf) == pdflen)
145 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
146 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
147 "Could not write background '%s'. Continuing anyway.", tmpfn);
148 gtk_dialog_run(GTK_DIALOG(dialog));
149 gtk_widget_destroy(dialog);
153 gzprintf(f, "domain=\"%s\" filename=\"%s\" ",
154 file_domain_names[pg->bg->file_domain], pg->bg->filename->s);
156 gzprintf(f, "pageno=\"%d\" ", pg->bg->file_page_seq);
159 for (layerlist = pg->layers; layerlist!=NULL; layerlist = layerlist->next) {
160 layer = (struct Layer *)layerlist->data;
161 gzprintf(f, "<layer>\n");
162 for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
163 item = (struct Item *)itemlist->data;
164 if (item->type == ITEM_STROKE) {
165 gzprintf(f, "<stroke tool=\"%s\" color=\"",
166 tool_names[item->brush.tool_type]);
167 if (item->brush.color_no >= 0)
168 gzputs(f, color_names[item->brush.color_no]);
170 gzprintf(f, "#%08x", item->brush.color_rgba);
171 gzprintf(f, "\" width=\"%.2f\">\n", item->brush.thickness);
172 for (i=0;i<2*item->path->num_points;i++)
173 gzprintf(f, "%.2f ", item->path->coords[i]);
174 gzprintf(f, "\n</stroke>\n");
177 gzprintf(f, "</layer>\n");
179 gzprintf(f, "</page>\n");
185 // closes a journal: returns true on success, false on abort
187 gboolean close_journal(void)
189 if (!ok_to_close()) return FALSE;
191 // free everything...
197 delete_journal(&journal);
200 /* note: various members of ui and journal are now in invalid states,
201 use new_journal() to reinitialize them */
204 // the XML parser functions for open_journal()
206 struct Journal tmpJournal;
207 struct Page *tmpPage;
208 struct Layer *tmpLayer;
209 struct Item *tmpItem;
211 struct Background *tmpBg_pdf;
213 GError *xoj_invalid(void)
215 return g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Invalid file contents");
218 void xoj_parser_start_element(GMarkupParseContext *context,
219 const gchar *element_name, const gchar **attribute_names,
220 const gchar **attribute_values, gpointer user_data, GError **error)
224 struct Background *tmpbg;
225 char *tmpbg_filename;
228 if (!strcmp(element_name, "title") || !strcmp(element_name, "xournal")) {
229 if (tmpPage != NULL) {
230 *error = xoj_invalid();
233 // nothing special to do
235 else if (!strcmp(element_name, "page")) { // start of a page
236 if (tmpPage != NULL) {
237 *error = xoj_invalid();
240 tmpPage = (struct Page *)g_malloc(sizeof(struct Page));
241 tmpPage->layers = NULL;
242 tmpPage->nlayers = 0;
243 tmpPage->group = NULL;
244 tmpPage->bg = g_new(struct Background, 1);
245 tmpPage->bg->type = -1;
246 tmpPage->bg->canvas_item = NULL;
247 tmpPage->bg->pixbuf = NULL;
248 tmpPage->bg->filename = NULL;
249 tmpJournal.pages = g_list_append(tmpJournal.pages, tmpPage);
251 // scan for height and width attributes
253 while (*attribute_names!=NULL) {
254 if (!strcmp(*attribute_names, "width")) {
255 if (has_attr & 1) *error = xoj_invalid();
256 tmpPage->width = strtod(*attribute_values, &ptr);
257 if (ptr == *attribute_values) *error = xoj_invalid();
260 else if (!strcmp(*attribute_names, "height")) {
261 if (has_attr & 2) *error = xoj_invalid();
262 tmpPage->height = strtod(*attribute_values, &ptr);
263 if (ptr == *attribute_values) *error = xoj_invalid();
266 else *error = xoj_invalid();
270 if (has_attr!=3) *error = xoj_invalid();
272 else if (!strcmp(element_name, "background")) {
273 if (tmpPage == NULL || tmpLayer !=NULL || tmpPage->bg->type >= 0) {
274 *error = xoj_invalid();
278 while (*attribute_names!=NULL) {
279 if (!strcmp(*attribute_names, "type")) {
280 if (has_attr) *error = xoj_invalid();
282 if (!strcmp(*attribute_values, bgtype_names[i]))
283 tmpPage->bg->type = i;
284 if (tmpPage->bg->type < 0) *error = xoj_invalid();
286 if (tmpPage->bg->type == BG_PDF) {
287 if (tmpBg_pdf == NULL) tmpBg_pdf = tmpPage->bg;
290 tmpPage->bg->filename = refstring_ref(tmpBg_pdf->filename);
291 tmpPage->bg->file_domain = tmpBg_pdf->file_domain;
295 else if (!strcmp(*attribute_names, "color")) {
296 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
297 if (has_attr & 2) *error = xoj_invalid();
298 tmpPage->bg->color_no = COLOR_OTHER;
299 for (i=0; i<COLOR_MAX; i++)
300 if (!strcmp(*attribute_values, bgcolor_names[i])) {
301 tmpPage->bg->color_no = i;
302 tmpPage->bg->color_rgba = predef_bgcolors_rgba[i];
304 // there's also the case of hex (#rrggbbaa) colors
305 if (tmpPage->bg->color_no == COLOR_OTHER && **attribute_values == '#') {
306 tmpPage->bg->color_rgba = strtol(*attribute_values + 1, &ptr, 16);
307 if (*ptr!=0) *error = xoj_invalid();
311 else if (!strcmp(*attribute_names, "style")) {
312 if (tmpPage->bg->type != BG_SOLID) *error = xoj_invalid();
313 if (has_attr & 4) *error = xoj_invalid();
314 tmpPage->bg->ruling = -1;
316 if (!strcmp(*attribute_values, bgstyle_names[i]))
317 tmpPage->bg->ruling = i;
318 if (tmpPage->bg->ruling < 0) *error = xoj_invalid();
321 else if (!strcmp(*attribute_names, "domain")) {
322 if (tmpPage->bg->type <= BG_SOLID || (has_attr & 8))
323 { *error = xoj_invalid(); return; }
324 tmpPage->bg->file_domain = -1;
326 if (!strcmp(*attribute_values, file_domain_names[i]))
327 tmpPage->bg->file_domain = i;
328 if (tmpPage->bg->file_domain < 0)
329 { *error = xoj_invalid(); return; }
332 else if (!strcmp(*attribute_names, "filename")) {
333 if (tmpPage->bg->type <= BG_SOLID || (has_attr != 9))
334 { *error = xoj_invalid(); return; }
335 if (tmpPage->bg->file_domain == DOMAIN_CLONE) {
336 // filename is a page number
337 i = strtol(*attribute_values, &ptr, 10);
338 if (ptr == *attribute_values || i < 0 || i > tmpJournal.npages-2)
339 { *error = xoj_invalid(); return; }
340 tmpbg = ((struct Page *)g_list_nth_data(tmpJournal.pages, i))->bg;
341 if (tmpbg->type != tmpPage->bg->type)
342 { *error = xoj_invalid(); return; }
343 tmpPage->bg->filename = refstring_ref(tmpbg->filename);
344 tmpPage->bg->pixbuf = tmpbg->pixbuf;
345 if (tmpbg->pixbuf!=NULL) gdk_pixbuf_ref(tmpbg->pixbuf);
346 tmpPage->bg->file_domain = tmpbg->file_domain;
349 tmpPage->bg->filename = new_refstring(*attribute_values);
350 if (tmpPage->bg->type == BG_PIXMAP) {
351 if (tmpPage->bg->file_domain == DOMAIN_ATTACH) {
352 tmpbg_filename = g_strdup_printf("%s.%s", tmpFilename, *attribute_values);
353 if (sscanf(*attribute_values, "bg_%d.png", &i) == 1)
354 if (i > tmpJournal.last_attach_no)
355 tmpJournal.last_attach_no = i;
357 else tmpbg_filename = g_strdup(*attribute_values);
358 tmpPage->bg->pixbuf = gdk_pixbuf_new_from_file(tmpbg_filename, NULL);
359 if (tmpPage->bg->pixbuf == NULL) {
360 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
361 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
362 "Could not open background '%s'. Setting background to white.",
364 gtk_dialog_run(GTK_DIALOG(dialog));
365 gtk_widget_destroy(dialog);
366 tmpPage->bg->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
367 gdk_pixbuf_fill(tmpPage->bg->pixbuf, 0xffffffff); // solid white
369 g_free(tmpbg_filename);
374 else if (!strcmp(*attribute_names, "pageno")) {
375 if (tmpPage->bg->type != BG_PDF || (has_attr & 32))
376 { *error = xoj_invalid(); return; }
377 tmpPage->bg->file_page_seq = strtod(*attribute_values, &ptr);
378 if (ptr == *attribute_values) *error = xoj_invalid();
381 else *error = xoj_invalid();
385 if (tmpPage->bg->type < 0) *error = xoj_invalid();
386 if (tmpPage->bg->type == BG_SOLID && has_attr != 7) *error = xoj_invalid();
387 if (tmpPage->bg->type == BG_PIXMAP && has_attr != 25) *error = xoj_invalid();
388 if (tmpPage->bg->type == BG_PDF && has_attr != 57) *error = xoj_invalid();
390 else if (!strcmp(element_name, "layer")) { // start of a layer
391 if (tmpPage == NULL || tmpLayer != NULL) {
392 *error = xoj_invalid();
395 tmpLayer = (struct Layer *)g_malloc(sizeof(struct Layer));
396 tmpLayer->items = NULL;
397 tmpLayer->nitems = 0;
398 tmpLayer->group = NULL;
399 tmpPage->layers = g_list_append(tmpPage->layers, tmpLayer);
402 else if (!strcmp(element_name, "stroke")) { // start of a stroke
403 if (tmpLayer == NULL || tmpItem != NULL) {
404 *error = xoj_invalid();
407 tmpItem = (struct Item *)g_malloc(sizeof(struct Item));
408 tmpItem->type = ITEM_STROKE;
409 tmpItem->path = NULL;
410 tmpItem->canvas_item = NULL;
411 tmpLayer->items = g_list_append(tmpLayer->items, tmpItem);
413 // scan for tool, color, and width attributes
415 while (*attribute_names!=NULL) {
416 if (!strcmp(*attribute_names, "width")) {
417 if (has_attr & 1) *error = xoj_invalid();
418 tmpItem->brush.thickness = strtod(*attribute_values, &ptr);
419 if (ptr == *attribute_values) *error = xoj_invalid();
422 else if (!strcmp(*attribute_names, "color")) {
423 if (has_attr & 2) *error = xoj_invalid();
424 tmpItem->brush.color_no = COLOR_OTHER;
425 for (i=0; i<COLOR_MAX; i++)
426 if (!strcmp(*attribute_values, color_names[i])) {
427 tmpItem->brush.color_no = i;
428 tmpItem->brush.color_rgba = predef_colors_rgba[i];
430 // there's also the case of hex (#rrggbbaa) colors
431 if (tmpItem->brush.color_no == COLOR_OTHER && **attribute_values == '#') {
432 tmpItem->brush.color_rgba = strtol(*attribute_values + 1, &ptr, 16);
433 if (*ptr!=0) *error = xoj_invalid();
437 else if (!strcmp(*attribute_names, "tool")) {
438 if (has_attr & 4) *error = xoj_invalid();
439 tmpItem->brush.tool_type = -1;
440 for (i=0; i<NUM_STROKE_TOOLS; i++)
441 if (!strcmp(*attribute_values, tool_names[i])) {
442 tmpItem->brush.tool_type = i;
444 if (tmpItem->brush.tool_type == -1) *error = xoj_invalid();
447 else *error = xoj_invalid();
451 if (has_attr!=7) *error = xoj_invalid();
452 // finish filling the brush info
453 tmpItem->brush.thickness_no = 0; // who cares ?
454 tmpItem->brush.tool_options = 0; // who cares ?
455 if (tmpItem->brush.tool_type == TOOL_HIGHLIGHTER) {
456 if (tmpItem->brush.color_no >= 0)
457 tmpItem->brush.color_rgba &= HILITER_ALPHA_MASK;
462 void xoj_parser_end_element(GMarkupParseContext *context,
463 const gchar *element_name, gpointer user_data, GError **error)
465 if (!strcmp(element_name, "page")) {
466 if (tmpPage == NULL || tmpLayer != NULL) {
467 *error = xoj_invalid();
470 if (tmpPage->nlayers == 0 || tmpPage->bg->type < 0) *error = xoj_invalid();
473 if (!strcmp(element_name, "layer")) {
474 if (tmpLayer == NULL || tmpItem != NULL) {
475 *error = xoj_invalid();
480 if (!strcmp(element_name, "stroke")) {
481 if (tmpItem == NULL) {
482 *error = xoj_invalid();
485 update_item_bbox(tmpItem);
490 void xoj_parser_text(GMarkupParseContext *context,
491 const gchar *text, gsize text_len, gpointer user_data, GError **error)
493 const gchar *element_name, *ptr;
496 element_name = g_markup_parse_context_get_element(context);
497 if (element_name == NULL) return;
498 if (!strcmp(element_name, "stroke")) {
501 while (text_len > 0) {
502 realloc_cur_path(n/2 + 1);
503 ui.cur_path.coords[n] = strtod(text, (char **)(&ptr));
504 if (ptr == text) break;
505 text_len -= (ptr - text);
509 if (n<4 || n&1) { *error = xoj_invalid(); return; }
510 tmpItem->path = gnome_canvas_points_new(n/2);
511 g_memmove(tmpItem->path->coords, ui.cur_path.coords, n*sizeof(double));
515 gboolean user_wants_second_chance(char **filename)
518 GtkFileFilter *filt_all, *filt_pdf;
519 GtkResponseType response;
521 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
522 GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO,
523 "Could not open background '%s'.\nSelect another file?",
525 response = gtk_dialog_run(GTK_DIALOG(dialog));
526 gtk_widget_destroy(dialog);
527 if (response != GTK_RESPONSE_YES) return FALSE;
528 dialog = gtk_file_chooser_dialog_new("Open PDF", GTK_WINDOW (winMain),
529 GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
530 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
532 filt_all = gtk_file_filter_new();
533 gtk_file_filter_set_name(filt_all, "All files");
534 gtk_file_filter_add_pattern(filt_all, "*");
535 filt_pdf = gtk_file_filter_new();
536 gtk_file_filter_set_name(filt_pdf, "PDF files");
537 gtk_file_filter_add_pattern(filt_pdf, "*.pdf");
538 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_pdf);
539 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_all);
541 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
542 gtk_widget_destroy(dialog);
546 *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
547 gtk_widget_destroy(dialog);
551 gboolean open_journal(char *filename)
553 const GMarkupParser parser = { xoj_parser_start_element,
554 xoj_parser_end_element,
555 xoj_parser_text, NULL, NULL};
556 GMarkupParseContext *context;
566 f = gzopen(filename, "r");
567 if (f==NULL) return FALSE;
569 context = g_markup_parse_context_new(&parser, 0, NULL, NULL);
571 tmpJournal.npages = 0;
572 tmpJournal.pages = NULL;
573 tmpJournal.last_attach_no = 0;
577 tmpFilename = filename;
582 while (valid && !gzeof(f)) {
583 len = gzread(f, buffer, 1000);
584 if (len<0) valid = FALSE;
585 if (maybe_pdf && len>=4 && !strncmp(buffer, "%PDF", 4))
586 { valid = FALSE; break; } // most likely pdf
587 else maybe_pdf = FALSE;
589 valid = g_markup_parse_context_parse(context, buffer, len, &error);
592 if (valid) valid = g_markup_parse_context_end_parse(context, &error);
593 if (tmpJournal.npages == 0) valid = FALSE;
594 g_markup_parse_context_free(context);
597 delete_journal(&tmpJournal);
598 if (!maybe_pdf) return FALSE;
599 // essentially same as on_fileNewBackground from here on
602 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
604 ui.zoom = DEFAULT_ZOOM;
605 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
607 return init_bgpdf(filename, TRUE, DOMAIN_ABSOLUTE);
610 ui.saved = TRUE; // force close_journal() to do its job
612 g_memmove(&journal, &tmpJournal, sizeof(struct Journal));
614 // if we need to initialize a fresh pdf loader
615 if (tmpBg_pdf!=NULL) {
616 while (bgpdf.status != STATUS_NOT_INIT) gtk_main_iteration();
617 if (tmpBg_pdf->file_domain == DOMAIN_ATTACH)
618 tmpfn = g_strdup_printf("%s.%s", filename, tmpBg_pdf->filename->s);
620 tmpfn = g_strdup(tmpBg_pdf->filename->s);
621 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
622 // in case the file name became invalid
623 if (!valid && tmpBg_pdf->file_domain != DOMAIN_ATTACH)
624 if (user_wants_second_chance(&tmpfn)) {
625 valid = init_bgpdf(tmpfn, FALSE, tmpBg_pdf->file_domain);
626 if (valid) { // change the file name...
627 g_free(tmpBg_pdf->filename->s);
628 tmpBg_pdf->filename->s = g_strdup(tmpfn);
632 refstring_unref(bgpdf.filename);
633 bgpdf.filename = refstring_ref(tmpBg_pdf->filename);
635 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
636 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open background '%s'.",
638 gtk_dialog_run(GTK_DIALOG(dialog));
639 gtk_widget_destroy(dialog);
645 ui.cur_page = (struct Page *)journal.pages->data;
646 ui.layerno = ui.cur_page->nlayers-1;
647 ui.cur_layer = (struct Layer *)(g_list_last(ui.cur_page->layers)->data);
649 ui.zoom = DEFAULT_ZOOM;
650 update_file_name(g_strdup(filename));
651 gnome_canvas_set_pixels_per_unit(canvas, ui.zoom);
654 gtk_adjustment_set_value(gtk_layout_get_vadjustment(GTK_LAYOUT(canvas)), 0);
658 /************ file backgrounds *************/
660 struct Background *attempt_load_pix_bg(char *filename, gboolean attach)
662 struct Background *bg;
665 pix = gdk_pixbuf_new_from_file(filename, NULL);
666 if (pix == NULL) return NULL;
668 bg = g_new(struct Background, 1);
669 bg->type = BG_PIXMAP;
670 bg->canvas_item = NULL;
672 bg->pixbuf_scale = DEFAULT_ZOOM;
674 bg->filename = new_refstring(NULL);
675 bg->file_domain = DOMAIN_ATTACH;
677 bg->filename = new_refstring(filename);
678 bg->file_domain = DOMAIN_ABSOLUTE;
683 #define BUFSIZE 65536 // a reasonable buffer size for reads from gs pipe
685 GList *attempt_load_gv_bg(char *filename)
687 struct Background *bg;
690 GdkPixbufLoader *loader;
694 int buflen, remnlen, file_pageno;
696 buf = g_malloc(BUFSIZE); // a reasonable buffer size
697 f = fopen(filename, "r");
698 if (fread(buf, 1, 4, f) !=4 ||
699 (strncmp((char *)buf, "%!PS", 4) && strncmp((char *)buf, "%PDF", 4))) {
706 pipename = g_strdup_printf(GS_CMDLINE, (double)GS_BITMAP_DPI, filename);
707 gs_pipe = popen(pipename, "r");
715 while (!feof(gs_pipe)) {
716 if (!remnlen) { // new page: get a BMP header ?
717 buflen = fread(buf, 1, 54, gs_pipe);
718 if (buflen < 6) buflen += fread(buf, 1, 54-buflen, gs_pipe);
719 if (buflen < 6 || buf[0]!='B' || buf[1]!='M') break; // fatal: abort
720 remnlen = (int)(buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + (buf[2]);
721 loader = gdk_pixbuf_loader_new();
723 else buflen = fread(buf, 1, (remnlen < BUFSIZE)?remnlen:BUFSIZE, gs_pipe);
725 if (buflen == 0) break;
726 if (!gdk_pixbuf_loader_write(loader, buf, buflen, NULL)) break;
727 if (remnlen == 0) { // make a new bg
728 pix = gdk_pixbuf_loader_get_pixbuf(loader);
729 if (pix == NULL) break;
731 gdk_pixbuf_loader_close(loader, NULL);
732 g_object_unref(loader);
734 bg = g_new(struct Background, 1);
735 bg->canvas_item = NULL;
737 bg->pixbuf_scale = (GS_BITMAP_DPI/72.0);
738 bg->type = BG_PIXMAP;
739 bg->filename = new_refstring(NULL);
740 bg->file_domain = DOMAIN_ATTACH;
742 bg_list = g_list_append(bg_list, bg);
745 if (loader != NULL) gdk_pixbuf_loader_close(loader, NULL);
751 struct Background *attempt_screenshot_bg(void)
753 struct Background *bg;
756 GError *error = NULL;
760 Window x_root, x_win;
762 x_root = gdk_x11_get_default_root_xwindow();
764 if (!XGrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root,
765 False, ButtonReleaseMask, GrabModeAsync, GrabModeSync, None, None))
768 XWindowEvent (GDK_DISPLAY(), x_root, ButtonReleaseMask, &x_event);
769 XUngrabButton(GDK_DISPLAY(), AnyButton, AnyModifier, x_root);
771 x_win = x_event.xbutton.subwindow;
772 if (x_win == None) x_win = x_root;
774 window = gdk_window_foreign_new_for_display(gdk_display_get_default(), x_win);
776 gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
778 pix = gdk_pixbuf_get_from_drawable(NULL, window,
779 gdk_colormap_get_system(), 0, 0, 0, 0, w, h);
781 if (pix == NULL) return NULL;
783 bg = g_new(struct Background, 1);
784 bg->type = BG_PIXMAP;
785 bg->canvas_item = NULL;
787 bg->pixbuf_scale = DEFAULT_ZOOM;
788 bg->filename = new_refstring(NULL);
789 bg->file_domain = DOMAIN_ATTACH;
793 /************** pdf annotation ***************/
795 /* free tmp directory */
797 void end_bgpdf_shutdown(void)
799 if (bgpdf.tmpdir!=NULL) {
800 if (bgpdf.tmpfile_copy!=NULL) {
801 g_unlink(bgpdf.tmpfile_copy);
802 g_free(bgpdf.tmpfile_copy);
803 bgpdf.tmpfile_copy = NULL;
805 g_rmdir(bgpdf.tmpdir);
806 g_free(bgpdf.tmpdir);
809 bgpdf.status = STATUS_NOT_INIT;
812 /* cancel a request */
814 void cancel_bgpdf_request(struct BgPdfRequest *req)
818 list_link = g_list_find(bgpdf.requests, req);
819 if (list_link == NULL) return;
820 if (list_link->prev == NULL && bgpdf.pid > 0) {
821 // this is being processed: kill the child but don't remove the request yet
822 if (bgpdf.status == STATUS_RUNNING) bgpdf.status = STATUS_ABORTED;
823 kill(bgpdf.pid, SIGHUP);
824 // printf("Cancelling a request - killing %d\n", bgpdf.pid);
827 // remove the request
828 bgpdf.requests = g_list_delete_link(bgpdf.requests, list_link);
830 // printf("Cancelling a request - no kill needed\n");
834 /* sigchld callback */
836 void bgpdf_child_handler(GPid pid, gint status, gpointer data)
838 struct BgPdfRequest *req;
839 struct BgPdfPage *bgpg;
843 if (bgpdf.requests == NULL) return;
844 req = (struct BgPdfRequest *)bgpdf.requests->data;
846 ppm_name = g_strdup_printf("%s/p-%06d.ppm", bgpdf.tmpdir, req->pageno);
847 // printf("Child %d finished, should look for %s... \n", pid, ppm_name);
849 if (bgpdf.status == STATUS_ABORTED || bgpdf.status == STATUS_SHUTDOWN)
852 pixbuf = gdk_pixbuf_new_from_file(ppm_name, NULL);
857 if (pixbuf != NULL) { // success
858 // printf("success\n");
859 while (req->pageno > bgpdf.npages) {
860 bgpg = g_new(struct BgPdfPage, 1);
862 bgpdf.pages = g_list_append(bgpdf.pages, bgpg);
865 bgpg = g_list_nth_data(bgpdf.pages, req->pageno-1);
866 if (bgpg->pixbuf!=NULL) gdk_pixbuf_unref(bgpg->pixbuf);
867 bgpg->pixbuf = pixbuf;
868 bgpg->dpi = req->dpi;
869 if (req->initial_request && bgpdf.create_pages) {
870 bgpdf_create_page_with_bg(req->pageno, bgpg);
871 // create page n, resize it, set its bg - all without any undo effect
873 if (!req->is_printing) bgpdf_update_bg(req->pageno, bgpg);
874 // look for all pages with this bg, and update their bg pixmaps
878 // printf("failed or aborted\n");
879 bgpdf.create_pages = FALSE;
880 req->initial_request = FALSE;
884 g_spawn_close_pid(pid);
886 if (req->initial_request)
887 req->pageno++; // try for next page
889 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
891 if (bgpdf.status == STATUS_SHUTDOWN) {
892 end_bgpdf_shutdown();
896 bgpdf.status = STATUS_IDLE;
897 if (bgpdf.requests != NULL) bgpdf_spawn_child();
900 /* spawn a child to process the head request */
902 void bgpdf_spawn_child(void)
904 struct BgPdfRequest *req;
906 gchar pageno_str[10], dpi_str[10];
907 gchar *pdf_filename = bgpdf.tmpfile_copy;
908 gchar *ppm_root = g_strdup_printf("%s/p", bgpdf.tmpdir);
909 gchar *argv[]= PDFTOPPM_ARGV;
912 if (bgpdf.requests == NULL) return;
913 req = (struct BgPdfRequest *)bgpdf.requests->data;
914 if (req->pageno > bgpdf.npages+1 ||
915 (!req->initial_request && req->pageno <= bgpdf.npages &&
916 req->dpi == ((struct BgPdfPage *)g_list_nth_data(bgpdf.pages, req->pageno-1))->dpi))
917 { // ignore this request - it's redundant, or in outer space
919 bgpdf.status = STATUS_IDLE;
920 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
922 if (bgpdf.requests != NULL) bgpdf_spawn_child();
925 g_snprintf(pageno_str, 10, "%d", req->pageno);
926 g_snprintf(dpi_str, 10, "%d", req->dpi);
927 /* printf("Processing request for page %d at %d dpi -- in %s\n",
928 req->pageno, req->dpi, ppm_root); */
929 if (!g_spawn_async(NULL, argv, NULL,
930 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
931 NULL, NULL, &pid, NULL))
933 // couldn't spawn... abort this request, try next one maybe ?
934 // printf("Couldn't spawn\n");
936 bgpdf.status = STATUS_IDLE;
937 bgpdf.requests = g_list_delete_link(bgpdf.requests, bgpdf.requests);
939 if (!bgpdf.has_failed) {
940 dialog = gtk_message_dialog_new(GTK_WINDOW(winMain), GTK_DIALOG_MODAL,
941 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Unable to start PDF loader %s.", argv[0]);
942 gtk_dialog_run(GTK_DIALOG(dialog));
943 gtk_widget_destroy(dialog);
945 bgpdf.has_failed = TRUE;
946 if (bgpdf.requests != NULL) bgpdf_spawn_child();
950 // printf("Spawned process %d\n", pid);
952 bgpdf.status = STATUS_RUNNING;
953 g_child_watch_add(pid, bgpdf_child_handler, NULL);
959 void add_bgpdf_request(int pageno, double zoom, gboolean printing)
961 struct BgPdfRequest *req, *cmp_req;
964 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN)
965 return; // don't accept requests in those modes...
966 req = g_new(struct BgPdfRequest, 1);
967 req->is_printing = printing;
968 if (printing) req->dpi = PDFTOPPM_PRINTING_DPI;
969 else req->dpi = (int)floor(72*zoom+0.5);
970 // printf("Enqueuing request for page %d at %d dpi\n", pageno, req->dpi);
972 // cancel any request this may supersede
973 for (list = bgpdf.requests; list != NULL; ) {
974 cmp_req = (struct BgPdfRequest *)list->data;
976 if (!cmp_req->initial_request && cmp_req->pageno == pageno &&
977 cmp_req->is_printing == printing)
978 cancel_bgpdf_request(cmp_req);
980 req->pageno = pageno;
981 req->initial_request = FALSE;
984 req->initial_request = TRUE;
986 bgpdf.requests = g_list_append(bgpdf.requests, req);
987 if (!bgpdf.pid) bgpdf_spawn_child();
990 /* shutdown the PDF reader */
992 void shutdown_bgpdf(void)
995 struct BgPdfPage *pdfpg;
996 struct BgPdfRequest *req;
998 if (bgpdf.status == STATUS_NOT_INIT || bgpdf.status == STATUS_SHUTDOWN) return;
999 refstring_unref(bgpdf.filename);
1000 for (list = bgpdf.pages; list != NULL; list = list->next) {
1001 pdfpg = (struct BgPdfPage *)list->data;
1002 if (pdfpg->pixbuf!=NULL) gdk_pixbuf_unref(pdfpg->pixbuf);
1004 g_list_free(bgpdf.pages);
1005 bgpdf.status = STATUS_SHUTDOWN;
1006 for (list = g_list_last(bgpdf.requests); list != NULL; ) {
1007 req = (struct BgPdfRequest *)list->data;
1009 cancel_bgpdf_request(req);
1011 if (!bgpdf.pid) end_bgpdf_shutdown();
1012 /* The above will ultimately remove all requests and kill the child if needed.
1013 The child will set status to STATUS_NOT_INIT, clear the requests list,
1014 empty tmpdir, ... except if there's no child! */
1015 /* note: it could look like there's a race condition here - if a child
1016 terminates and a new request is enqueued while we are destroying the
1017 queue - but actually the child handler callback is NOT a signal
1018 callback, so execution of this function is atomic */
1021 gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain)
1027 if (bgpdf.status != STATUS_NOT_INIT) return FALSE;
1028 bgpdf.tmpfile_copy = NULL;
1029 bgpdf.tmpdir = mkdtemp(g_strdup(TMPDIR_TEMPLATE));
1030 if (!bgpdf.tmpdir) return FALSE;
1031 // make a local copy and check if it's a PDF
1032 if (!g_file_get_contents(pdfname, &filebuf, &filelen, NULL))
1033 { end_bgpdf_shutdown(); return FALSE; }
1034 if (filelen < 4 || strncmp(filebuf, "%PDF", 4))
1035 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1036 bgpdf.tmpfile_copy = g_strdup_printf("%s/bg.pdf", bgpdf.tmpdir);
1037 f = fopen(bgpdf.tmpfile_copy, "w");
1038 if (f == NULL || fwrite(filebuf, 1, filelen, f) != filelen)
1039 { g_free(filebuf); end_bgpdf_shutdown(); return FALSE; }
1042 bgpdf.status = STATUS_IDLE;
1044 bgpdf.filename = new_refstring((file_domain == DOMAIN_ATTACH) ? "bg.pdf" : pdfname);
1045 bgpdf.file_domain = file_domain;
1048 bgpdf.requests = NULL;
1049 bgpdf.create_pages = create_pages;
1050 bgpdf.has_failed = FALSE;
1051 add_bgpdf_request(-1, DEFAULT_ZOOM, FALSE); // request all pages
1055 // create page n, resize it, set its bg
1056 void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg)
1059 struct Background *bg;
1061 if (journal.npages < pageno) {
1062 bg = g_new(struct Background, 1);
1063 bg->canvas_item = NULL;
1065 pg = (struct Page *)g_list_nth_data(journal.pages, pageno-1);
1067 if (bg->type != BG_SOLID) return;
1068 // don't mess with a page the user has modified significantly...
1072 bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1073 bg->filename = refstring_ref(bgpdf.filename);
1074 bg->file_domain = bgpdf.file_domain;
1075 bg->file_page_seq = pageno;
1076 bg->pixbuf_scale = DEFAULT_ZOOM;
1077 bg->pixbuf_dpi = bgpg->dpi;
1079 if (journal.npages < pageno) {
1080 pg = new_page_with_bg(bg,
1081 gdk_pixbuf_get_width(bg->pixbuf)*72.0/bg->pixbuf_dpi,
1082 gdk_pixbuf_get_height(bg->pixbuf)*72.0/bg->pixbuf_dpi);
1083 journal.pages = g_list_append(journal.pages, pg);
1086 pg->width = gdk_pixbuf_get_width(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1087 pg->height = gdk_pixbuf_get_height(bgpg->pixbuf)*72.0/bg->pixbuf_dpi;
1088 make_page_clipbox(pg);
1089 update_canvas_bg(pg);
1091 update_page_stuff();
1094 // look for all journal pages with given pdf bg, and update their bg pixmaps
1095 void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg)
1100 for (list = journal.pages; list!= NULL; list = list->next) {
1101 pg = (struct Page *)list->data;
1102 if (pg->bg->type == BG_PDF && pg->bg->file_page_seq == pageno) {
1103 if (pg->bg->pixbuf!=NULL) gdk_pixbuf_unref(pg->bg->pixbuf);
1104 pg->bg->pixbuf = gdk_pixbuf_ref(bgpg->pixbuf);
1105 pg->bg->pixbuf_dpi = bgpg->dpi;
1106 update_canvas_bg(pg);
1111 // initialize the recent files list
1121 g_strlcpy(s, "mru0", 5);
1122 for (s[3]='0', i=0; i<MRU_SIZE; s[3]++, i++) {
1123 ui.mrumenu[i] = GET_COMPONENT(s);
1126 f = g_io_channel_new_file(ui.mrufile, "r", NULL);
1127 if (f) status = G_IO_STATUS_NORMAL;
1128 else status = G_IO_STATUS_ERROR;
1130 while (status == G_IO_STATUS_NORMAL && i<MRU_SIZE) {
1132 status = g_io_channel_read_line(f, &str, NULL, &lfptr, NULL);
1133 if (status == G_IO_STATUS_NORMAL && lfptr>0) {
1140 g_io_channel_shutdown(f, FALSE, NULL);
1141 g_io_channel_unref(f);
1146 void update_mru_menu(void)
1149 gboolean anyone = FALSE;
1151 for (i=0; i<MRU_SIZE; i++) {
1152 if (ui.mru[i]!=NULL) {
1153 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(ui.mrumenu[i]))),
1154 g_basename(ui.mru[i]));
1155 gtk_widget_show(ui.mrumenu[i]);
1158 else gtk_widget_hide(ui.mrumenu[i]);
1160 gtk_widget_set_sensitive(GET_COMPONENT("fileRecentFiles"), anyone);
1163 void new_mru_entry(char *name)
1167 for (i=0;i<MRU_SIZE;i++)
1168 if (ui.mru[i]!=NULL && !strcmp(ui.mru[i], name)) {
1170 for (j=i+1; j<MRU_SIZE; j++) ui.mru[j-1] = ui.mru[j];
1171 ui.mru[MRU_SIZE-1]=NULL;
1173 if (ui.mru[MRU_SIZE-1]!=NULL) g_free(ui.mru[MRU_SIZE-1]);
1174 for (j=MRU_SIZE-1; j>=1; j--) ui.mru[j] = ui.mru[j-1];
1175 ui.mru[0] = g_strdup(name);
1179 void delete_mru_entry(int which)
1183 if (ui.mru[which]!=NULL) g_free(ui.mru[which]);
1184 for (i=which+1;i<MRU_SIZE;i++)
1185 ui.mru[i-1] = ui.mru[i];
1186 ui.mru[MRU_SIZE-1] = NULL;
1190 void save_mru_list(void)
1195 f = fopen(ui.mrufile, "w");
1196 if (f==NULL) return;
1197 for (i=0; i<MRU_SIZE; i++)
1198 if (ui.mru[i]!=NULL) fprintf(f, "%s\n", ui.mru[i]);