From bc1db27c7eef7de6d5097a5e708d2de540d35b88 Mon Sep 17 00:00:00 2001 From: auroux Date: Thu, 28 Jun 2012 22:20:32 +0000 Subject: [PATCH] Image patch --- AUTHORS | 5 +- ChangeLog | 4 + configure | 2 +- configure.in | 2 +- src/Makefile.am | 6 +- src/Makefile.in | 15 ++- src/TODO | 18 ++- src/xo-callbacks.c | 86 +++++++++---- src/xo-callbacks.h | 12 ++ src/xo-clipboard.c | 304 +++++++++++++++++++++++++++++++++++++++++++++ src/xo-clipboard.h | 17 +++ src/xo-file.c | 111 ++++++++++++++++- src/xo-image.c | 167 +++++++++++++++++++++++++ src/xo-image.h | 19 +++ src/xo-interface.c | 54 +++++++- src/xo-misc.c | 95 +++++++++++++- src/xo-misc.h | 2 + src/xo-paint.c | 205 ------------------------------ src/xo-paint.h | 2 - src/xo-print.c | 148 +++++++++++++++++++++- src/xo-print.h | 9 ++ src/xournal.h | 13 +- xournal.glade | 56 ++++++++- 23 files changed, 1094 insertions(+), 258 deletions(-) create mode 100644 src/xo-clipboard.c create mode 100644 src/xo-clipboard.h create mode 100644 src/xo-image.c create mode 100644 src/xo-image.h diff --git a/AUTHORS b/AUTHORS index fadb86c..7950cc0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,10 +4,11 @@ The source code includes contributions by the following people: Alvaro, Kit Barnes, Eduardo de Barros Lima, Mathieu Bouchard, Ole Joergen Broenner, Robert Buchholz, Vincenzo Ciancia, Luca de Cicco, -Michele Codutti, Robert Gerlach, Daniel German, Dirk Gerrits, +Michele Codutti, Robert Gerlach, Daniel German, Dirk Gerrits, Simon Guest, Lukasz Kaiser, Timo Kluck, David Kolibac, Danny Kukawka, Stefan Lembach, Bob McElrath, Andy Neitzke, David Planella, Marco Poletti, Alex Ray, -Jean-Baptiste Rouquier, Marco Souza, Mike Ter Louw, Uwe Winter, Lu Zhihe. +Jean-Baptiste Rouquier, Victor Saase, Marco Souza, Mike Ter Louw, +Uwe Winter, Lu Zhihe. (Let me know if you are missing from this list or if your name is mis-spelled) diff --git a/ChangeLog b/ChangeLog index dd5f70a..c4f2fd0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +This version: + - insert image tool (based on patches by Victor Saase and Simon Guest) + - renamed "Journal" menu to "Page" + Version 0.4.6 (May 22, 2012): - win32 portability code (contributed by Dirk Gerrits) - fix bug in PDF export code on 64-bit systems (patch by Robert Buchholz) diff --git a/configure b/configure index f3c87c8..1f0cd5d 100755 --- a/configure +++ b/configure @@ -2627,7 +2627,7 @@ fi # Define the identity of the package. PACKAGE=xournal - VERSION=0.4.6 + VERSION=0.4.6+image cat >>confdefs.h <<_ACEOF diff --git a/configure.in b/configure.in index 5d4aa09..99d500b 100644 --- a/configure.in +++ b/configure.in @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. AC_INIT(configure.in) -AM_INIT_AUTOMAKE(xournal, 0.4.6) +AM_INIT_AUTOMAKE(xournal, 0.4.6+image) AM_CONFIG_HEADER(config.h) AM_MAINTAINER_MODE diff --git a/src/Makefile.am b/src/Makefile.am index 03ecd35..55aa52c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,8 @@ xournal_SOURCES = \ xo-misc.c xo-misc.h \ xo-file.c xo-file.h \ xo-paint.c xo-paint.h \ + xo-clipboard.c xo-clipboard.h \ + xo-image.c xo-image.h \ xo-print.c xo-print.h \ xo-support.c xo-support.h \ xo-interface.c xo-interface.h \ @@ -26,8 +28,8 @@ xournal_SOURCES = \ if WIN32 xournal_LDFLAGS = -mwindows - xournal_LDADD = win32/xournal.res ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) + xournal_LDADD = win32/xournal.res ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) -lz else - xournal_LDADD = ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) -lX11 + xournal_LDADD = ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) -lX11 -lz endif diff --git a/src/Makefile.in b/src/Makefile.in index 45ebffa..a0bbbe9 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -48,9 +48,10 @@ CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) am_xournal_OBJECTS = main.$(OBJEXT) xo-misc.$(OBJEXT) \ - xo-file.$(OBJEXT) xo-paint.$(OBJEXT) xo-print.$(OBJEXT) \ - xo-support.$(OBJEXT) xo-interface.$(OBJEXT) \ - xo-callbacks.$(OBJEXT) xo-shapes.$(OBJEXT) + xo-file.$(OBJEXT) xo-paint.$(OBJEXT) xo-clipboard.$(OBJEXT) \ + xo-image.$(OBJEXT) xo-print.$(OBJEXT) xo-support.$(OBJEXT) \ + xo-interface.$(OBJEXT) xo-callbacks.$(OBJEXT) \ + xo-shapes.$(OBJEXT) xournal_OBJECTS = $(am_xournal_OBJECTS) am__DEPENDENCIES_1 = @WIN32_FALSE@xournal_DEPENDENCIES = ttsubset/libttsubset.a \ @@ -240,6 +241,8 @@ xournal_SOURCES = \ xo-misc.c xo-misc.h \ xo-file.c xo-file.h \ xo-paint.c xo-paint.h \ + xo-clipboard.c xo-clipboard.h \ + xo-image.c xo-image.h \ xo-print.c xo-print.h \ xo-support.c xo-support.h \ xo-interface.c xo-interface.h \ @@ -247,8 +250,8 @@ xournal_SOURCES = \ xo-shapes.c xo-shapes.h @WIN32_TRUE@xournal_LDFLAGS = -mwindows -@WIN32_FALSE@xournal_LDADD = ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) -lX11 -@WIN32_TRUE@xournal_LDADD = win32/xournal.res ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) +@WIN32_FALSE@xournal_LDADD = ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) -lX11 -lz +@WIN32_TRUE@xournal_LDADD = win32/xournal.res ttsubset/libttsubset.a @PACKAGE_LIBS@ $(INTLLIBS) -lz all: all-recursive .SUFFIXES: @@ -332,7 +335,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-callbacks.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-clipboard.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-image.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-interface.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-misc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xo-paint.Po@am__quote@ diff --git a/src/TODO b/src/TODO index e6d19ed..01bdcd9 100644 --- a/src/TODO +++ b/src/TODO @@ -20,6 +20,12 @@ List of features to be implemented (not in any particular order) xournal). (cf #2944459). Recalibration probably requires talking directly to X server to query input device geometry and compensate for GTK. +TODO WITH IMAGE PATCH: +TODO: paste and selresize don't put rectangle at top +TODO: select click in image selects it +TODO: option for image mode to revert to previous tool +TODO: direct paste text + BUGS: - lingering issues with synaptics touchpads? (#2872667) (todo) - set device to Absolute mode at startup? (GDK doesn't expose API, @@ -29,6 +35,11 @@ BUGS: - export to PDF should see cropbox size if smaller, and map the cropbox -- not the entire page -- to the page area! (2009-11-18 forum) +BEHAVIOR TODO: +- option to have "Save Prefs" save current brushes as settings, not defaults +- method to map more general devices to specific tools or to Ignore + (e.g.: X220T ignore trackpoint; map touchscreen to Hand, eraser to Eraser) + WIN32: - test further - write an installer @@ -150,10 +161,9 @@ https://sourceforge.net/tracker/index.php?func=detail&aid=3447356&group_id=16343 - proximity detection: eraser proximity switches mapping? proximity out removes cursor until next motionnotify? - - render page to bitmap: for export, preview, and copy-paste (render using libart, see how gnomecanvas does it?) - NO: render using Cairo !!! then can switch to GtkPrint as well. + NO: render using Cairo !!! (copy-paste: config option to render only current layer or all below?) - cut-and-paste of selection into other apps (as bitmap; as SVG?) @@ -184,10 +194,6 @@ https://sourceforge.net/tracker/index.php?func=detail&aid=3447356&group_id=16343 - add config option to limit total memory usage for PDF bitmaps - ability to select entire page for copy-paste (as bitmap / reorder xournal) - copy/paste of an entire page (beware if PDF bg is not compatible!) -- rewrite printing using GtkPrint + Cairo as GnomePrint replacement - (keep GnomePrint option for compatibility with GTK+ <2.10) -- insert images (screen capture or from file or from clipboard), - not as full-page backgrounds (new ITEM type) - convert to/from Jarnal format; to/from MS Journal format??? - export as SVG, as bitmap (use Cairo for this) diff --git a/src/xo-callbacks.c b/src/xo-callbacks.c index 3b2b319..8c6f098 100644 --- a/src/xo-callbacks.c +++ b/src/xo-callbacks.c @@ -468,7 +468,7 @@ on_editUndo_activate (GtkMenuItem *menuitem, if (undo == NULL) return; // nothing to undo! reset_selection(); // safer reset_recognizer(); // safer - if (undo->type == ITEM_STROKE || undo->type == ITEM_TEXT) { + if (undo->type == ITEM_STROKE || undo->type == ITEM_TEXT || undo->type == ITEM_IMAGE) { // we're keeping the stroke info, but deleting the canvas item gtk_object_destroy(GTK_OBJECT(undo->item->canvas_item)); undo->item->canvas_item = NULL; @@ -682,7 +682,7 @@ on_editRedo_activate (GtkMenuItem *menuitem, if (redo == NULL) return; // nothing to redo! reset_selection(); // safer reset_recognizer(); // safer - if (redo->type == ITEM_STROKE || redo->type == ITEM_TEXT) { + if (redo->type == ITEM_STROKE || redo->type == ITEM_TEXT || redo->type == ITEM_IMAGE) { // re-create the canvas_item make_canvas_item_one(redo->layer->group, redo->item); // reinsert the item on its layer @@ -840,6 +840,12 @@ on_editRedo_activate (GtkMenuItem *menuitem, if (it->type == ITEM_TEXT && it->canvas_item != NULL) gnome_canvas_item_set(it->canvas_item, "fill-color-rgba", it->brush.color_rgba, NULL); + if (it->type == ITEM_IMAGE && it->canvas_item != NULL) { + // remark: a variable-width item might have lost its variable-width + group = (GnomeCanvasGroup *) it->canvas_item->parent; + gtk_object_destroy(GTK_OBJECT(it->canvas_item)); + make_canvas_item_one(group, it); + } } } else if (redo->type == ITEM_TEXT_EDIT) { @@ -962,6 +968,7 @@ on_viewZoomIn_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } @@ -974,6 +981,7 @@ on_viewZoomOut_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } @@ -985,6 +993,7 @@ on_viewNormalSize_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } @@ -996,6 +1005,7 @@ on_viewPageWidth_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } @@ -1555,6 +1565,7 @@ on_journalLoadBackground_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } do_switch_page(ui.pageno, TRUE, TRUE); } @@ -1601,6 +1612,7 @@ on_journalScreenshot_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } do_switch_page(ui.pageno, TRUE, TRUE); } @@ -1764,6 +1776,33 @@ on_toolsText_activate (GtkMenuItem *menuitem, } +void +on_toolsImage_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + if (GTK_OBJECT_TYPE(menuitem) == GTK_TYPE_RADIO_MENU_ITEM) { + if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (menuitem))) + return; + } else { + if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON (menuitem))) + return; + } + + if (ui.cur_mapping != 0 && !ui.button_switch_mapping) return; // not user-generated + if (ui.toolno[ui.cur_mapping] == TOOL_IMAGE) return; + + ui.cur_mapping = 0; // don't use switch_mapping() (refreshes buttons too soon) + end_text(); + reset_selection(); + ui.toolno[ui.cur_mapping] = TOOL_IMAGE; + update_mapping_linkings(-1); + update_tool_buttons(); + update_tool_menu(); + update_color_menu(); + update_cursor(); +} + + void on_toolsSelectRegion_activate (GtkMenuItem *menuitem, gpointer user_data) @@ -2337,8 +2376,6 @@ on_canvas_button_press_event (GtkWidget *widget, gpointer user_data) { double pt[2]; - gboolean page_change; - struct Page *tmppage; GtkWidget *dialog; int mapping; gboolean is_core; @@ -2425,24 +2462,8 @@ on_canvas_button_press_event (GtkWidget *widget, else mapping = event->button-1; // check whether we're in a page - page_change = FALSE; - tmppage = ui.cur_page; get_pointer_coords((GdkEvent *)event, pt); - while (ui.view_continuous && (pt[1] < - VIEW_CONTINUOUS_SKIP)) { - if (ui.pageno == 0) break; - page_change = TRUE; - ui.pageno--; - tmppage = g_list_nth_data(journal.pages, ui.pageno); - pt[1] += tmppage->height + VIEW_CONTINUOUS_SKIP; - } - while (ui.view_continuous && (pt[1] > tmppage->height + VIEW_CONTINUOUS_SKIP)) { - if (ui.pageno == journal.npages-1) break; - pt[1] -= tmppage->height + VIEW_CONTINUOUS_SKIP; - page_change = TRUE; - ui.pageno++; - tmppage = g_list_nth_data(journal.pages, ui.pageno); - } - if (page_change) do_switch_page(ui.pageno, FALSE, FALSE); + set_current_page(pt); // can't paint on the background... @@ -2508,6 +2529,9 @@ on_canvas_button_press_event (GtkWidget *widget, else if (ui.toolno[mapping] == TOOL_TEXT) { start_text((GdkEvent *)event, NULL); } + else if (ui.toolno[mapping] == TOOL_IMAGE) { + insert_image((GdkEvent *)event); + } return FALSE; } @@ -2697,7 +2721,7 @@ on_canvas_motion_notify_event (GtkWidget *widget, or if there's a selection (then we might want to change the mouse cursor to indicate the possibility of resizing) */ if (ui.cur_item_type == ITEM_NONE && ui.selection==NULL) return FALSE; - if (ui.cur_item_type == ITEM_TEXT) return FALSE; + if (ui.cur_item_type == ITEM_TEXT || ui.cur_item_type == ITEM_IMAGE) return FALSE; is_core = (event->device == gdk_device_get_core_pointer()); if (!ui.use_xinput && !is_core) return FALSE; @@ -3203,6 +3227,14 @@ on_button2Text_activate (GtkMenuItem *menuitem, } +void +on_button2Image_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + process_mapping_activate(menuitem, 1, TOOL_IMAGE); +} + + void on_button2SelectRegion_activate (GtkMenuItem *menuitem, gpointer user_data) @@ -3288,6 +3320,14 @@ on_button3Text_activate (GtkMenuItem *menuitem, } +void +on_button3Image_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + process_mapping_activate(menuitem, 2, TOOL_IMAGE); +} + + void on_button3SelectRegion_activate (GtkMenuItem *menuitem, gpointer user_data) @@ -3381,6 +3421,7 @@ on_viewSetZoom_activate (GtkMenuItem *menuitem, gnome_canvas_set_pixels_per_unit(canvas, ui.zoom); rescale_text_items(); rescale_bg_pixmaps(); + rescale_images(); } } while (response == GTK_RESPONSE_APPLY); @@ -3604,3 +3645,4 @@ on_optionsButtonsSwitchMappings_activate(GtkMenuItem *menuitem, ui.button_switch_mapping = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (menuitem)); } + diff --git a/src/xo-callbacks.h b/src/xo-callbacks.h index d3d173e..bce8b48 100644 --- a/src/xo-callbacks.h +++ b/src/xo-callbacks.h @@ -648,3 +648,15 @@ on_buttonColorChooser_set (GtkColorButton *colorbutton, void on_optionsButtonsSwitchMappings_activate(GtkMenuItem *menuitem, gpointer user_data); + +void +on_toolsImage_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_button2Image_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_button3Image_activate (GtkMenuItem *menuitem, + gpointer user_data); diff --git a/src/xo-clipboard.c b/src/xo-clipboard.c new file mode 100644 index 0000000..3aa3653 --- /dev/null +++ b/src/xo-clipboard.c @@ -0,0 +1,304 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "xournal.h" +#include "xo-callbacks.h" +#include "xo-interface.h" +#include "xo-support.h" +#include "xo-misc.h" +#include "xo-paint.h" +#include "xo-image.h" + + +void callback_clipboard_get(GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, gpointer user_data) +{ + int length; + + g_memmove(&length, user_data, sizeof(int)); + gtk_selection_data_set(selection_data, + gdk_atom_intern("_XOURNAL", FALSE), 8, user_data, length); +} + +void callback_clipboard_clear(GtkClipboard *clipboard, gpointer user_data) +{ + g_free(user_data); +} + +void selection_to_clip(void) +{ + int bufsz, nitems, val; + char *buf, *p; + GList *list; + struct Item *item; + GtkTargetEntry target; + + if (ui.selection == NULL) return; + bufsz = 2*sizeof(int) // bufsz, nitems + + sizeof(struct BBox); // bbox + nitems = 0; + for (list = ui.selection->items; list != NULL; list = list->next) { + item = (struct Item *)list->data; + nitems++; + if (item->type == ITEM_STROKE) { + bufsz+= sizeof(int) // type + + sizeof(struct Brush) // brush + + sizeof(int) // num_points + + 2*item->path->num_points*sizeof(double); // the points + if (item->brush.variable_width) + bufsz += (item->path->num_points-1)*sizeof(double); // the widths + } + else if (item->type == ITEM_TEXT) { + bufsz+= sizeof(int) // type + + sizeof(struct Brush) // brush + + 2*sizeof(double) // bbox upper-left + + sizeof(int) // text len + + strlen(item->text)+1 // text + + sizeof(int) // font_name len + + strlen(item->font_name)+1 // font_name + + sizeof(double); // font_size + } + else if (item->type == ITEM_IMAGE) { + if (item->image_png == NULL) { + set_cursor_busy(TRUE); + if (!gdk_pixbuf_save_to_buffer(item->image, &item->image_png, &item->image_png_len, "png", NULL, NULL)) + item->image_png_len = 0; // failed for some reason, so forget it + set_cursor_busy(FALSE); + } + bufsz+= sizeof(int) // type + + sizeof(struct BBox) + + sizeof(gsize) // png_buflen + + item->image_png_len; + } + else bufsz+= sizeof(int); // type + } + p = buf = g_malloc(bufsz); + g_memmove(p, &bufsz, sizeof(int)); p+= sizeof(int); + g_memmove(p, &nitems, sizeof(int)); p+= sizeof(int); + g_memmove(p, &ui.selection->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox); + for (list = ui.selection->items; list != NULL; list = list->next) { + item = (struct Item *)list->data; + g_memmove(p, &item->type, sizeof(int)); p+= sizeof(int); + if (item->type == ITEM_STROKE) { + g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush); + g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int); + g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double)); + p+= 2*item->path->num_points*sizeof(double); + if (item->brush.variable_width) { + g_memmove(p, item->widths, (item->path->num_points-1)*sizeof(double)); + p+= (item->path->num_points-1)*sizeof(double); + } + } + if (item->type == ITEM_TEXT) { + g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush); + g_memmove(p, &item->bbox.left, sizeof(double)); p+= sizeof(double); + g_memmove(p, &item->bbox.top, sizeof(double)); p+= sizeof(double); + val = strlen(item->text); + g_memmove(p, &val, sizeof(int)); p+= sizeof(int); + g_memmove(p, item->text, val+1); p+= val+1; + val = strlen(item->font_name); + g_memmove(p, &val, sizeof(int)); p+= sizeof(int); + g_memmove(p, item->font_name, val+1); p+= val+1; + g_memmove(p, &item->font_size, sizeof(double)); p+= sizeof(double); + } + if (item->type == ITEM_IMAGE) { + g_memmove(p, &item->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox); + g_memmove(p, &item->image_png_len, sizeof(gsize)); p+= sizeof(gsize); + if (item->image_png_len > 0) { + g_memmove(p, item->image_png, item->image_png_len); p+= item->image_png_len; + } + } + } + + target.target = "_XOURNAL"; + target.flags = 0; + target.info = 0; + + gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), + &target, 1, + callback_clipboard_get, callback_clipboard_clear, buf); +} + +// local paste within xournal +void clipboard_paste_from_xournal(GtkSelectionData *sel_data) +{ + unsigned char *p; + int nitems, npts, i, len; + struct Item *item; + double hoffset, voffset, cx, cy; + double *pf; + int sx, sy, wx, wy; + + reset_selection(); + + ui.selection = g_new(struct Selection, 1); + p = sel_data->data + sizeof(int); + g_memmove(&nitems, p, sizeof(int)); p+= sizeof(int); + ui.selection->type = ITEM_SELECTRECT; + ui.selection->layer = ui.cur_layer; + g_memmove(&ui.selection->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox); + ui.selection->items = NULL; + + // find by how much we translate the pasted selection + gnome_canvas_get_scroll_offsets(canvas, &sx, &sy); + gdk_window_get_geometry(GTK_WIDGET(canvas)->window, NULL, NULL, &wx, &wy, NULL); + gnome_canvas_window_to_world(canvas, sx + wx/2, sy + wy/2, &cx, &cy); + cx -= ui.cur_page->hoffset; + cy -= ui.cur_page->voffset; + if (cx + (ui.selection->bbox.right-ui.selection->bbox.left)/2 > ui.cur_page->width) + cx = ui.cur_page->width - (ui.selection->bbox.right-ui.selection->bbox.left)/2; + if (cx - (ui.selection->bbox.right-ui.selection->bbox.left)/2 < 0) + cx = (ui.selection->bbox.right-ui.selection->bbox.left)/2; + if (cy + (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 > ui.cur_page->height) + cy = ui.cur_page->height - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2; + if (cy - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 < 0) + cy = (ui.selection->bbox.bottom-ui.selection->bbox.top)/2; + hoffset = cx - (ui.selection->bbox.right+ui.selection->bbox.left)/2; + voffset = cy - (ui.selection->bbox.top+ui.selection->bbox.bottom)/2; + ui.selection->bbox.left += hoffset; + ui.selection->bbox.right += hoffset; + ui.selection->bbox.top += voffset; + ui.selection->bbox.bottom += voffset; + + ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group, + gnome_canvas_rect_get_type(), "width-pixels", 1, + "outline-color-rgba", 0x000000ff, + "fill-color-rgba", 0x80808040, + "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, + "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL); + make_dashed(ui.selection->canvas_item); + + while (nitems-- > 0) { + item = g_new(struct Item, 1); + ui.selection->items = g_list_append(ui.selection->items, item); + ui.cur_layer->items = g_list_append(ui.cur_layer->items, item); + ui.cur_layer->nitems++; + g_memmove(&item->type, p, sizeof(int)); p+= sizeof(int); + if (item->type == ITEM_STROKE) { + g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush); + g_memmove(&npts, p, sizeof(int)); p+= sizeof(int); + item->path = gnome_canvas_points_new(npts); + pf = (double *)p; + for (i=0; ipath->coords[2*i] = pf[2*i] + hoffset; + item->path->coords[2*i+1] = pf[2*i+1] + voffset; + } + p+= 2*item->path->num_points*sizeof(double); + if (item->brush.variable_width) { + item->widths = g_memdup(p, (item->path->num_points-1)*sizeof(double)); + p+= (item->path->num_points-1)*sizeof(double); + } + else item->widths = NULL; + update_item_bbox(item); + make_canvas_item_one(ui.cur_layer->group, item); + } + if (item->type == ITEM_TEXT) { + g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush); + g_memmove(&item->bbox.left, p, sizeof(double)); p+= sizeof(double); + g_memmove(&item->bbox.top, p, sizeof(double)); p+= sizeof(double); + item->bbox.left += hoffset; + item->bbox.top += voffset; + g_memmove(&len, p, sizeof(int)); p+= sizeof(int); + item->text = g_malloc(len+1); + g_memmove(item->text, p, len+1); p+= len+1; + g_memmove(&len, p, sizeof(int)); p+= sizeof(int); + item->font_name = g_malloc(len+1); + g_memmove(item->font_name, p, len+1); p+= len+1; + g_memmove(&item->font_size, p, sizeof(double)); p+= sizeof(double); + make_canvas_item_one(ui.cur_layer->group, item); + } + if (item->type == ITEM_IMAGE) { + item->canvas_item = NULL; + item->image_png = NULL; + item->image_png_len = 0; + g_memmove(&item->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox); + item->bbox.left += hoffset; + item->bbox.right += hoffset; + item->bbox.top += voffset; + item->bbox.bottom += voffset; + g_memmove(&item->image_png_len, p, sizeof(gsize)); p+= sizeof(gsize); + if (item->image_png_len > 0) { + item->image_png = g_memdup(p, item->image_png_len); + item->image = pixbuf_from_buffer(item->image_png, item->image_png_len); + p+= item->image_png_len; + } else { + item->image = NULL; + } + make_canvas_item_one(ui.cur_layer->group, item); + } + } + + prepare_new_undo(); + undo->type = ITEM_PASTE; + undo->layer = ui.cur_layer; + undo->itemlist = g_list_copy(ui.selection->items); + + gtk_selection_data_free(sel_data); + update_copy_paste_enabled(); + update_color_menu(); + update_thickness_buttons(); + update_color_buttons(); + update_font_button(); + update_cursor(); // FIXME: can't know if pointer is within selection! +} + +// paste an external image +void clipboard_paste_image(GdkPixbuf *pixbuf) +{ + double pt[2]; + + reset_selection(); + + get_current_pointer_coords(pt); + set_current_page(pt); + + create_image_from_pixbuf(pixbuf, pt); +} + +// work out what format the clipboard data is in, and paste accordingly +void clipboard_paste(void) +{ + GtkSelectionData *sel_data; + GtkClipboard *clipboard; + GdkPixbuf *pixbuf; + + if (ui.cur_layer == NULL) return; + + ui.cur_item_type = ITEM_PASTE; + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + // try xournal data + sel_data = gtk_clipboard_wait_for_contents( + clipboard, + gdk_atom_intern("_XOURNAL", FALSE)); + ui.cur_item_type = ITEM_NONE; + if (sel_data != NULL) { + clipboard_paste_from_xournal(sel_data); + return; + } + // try image data + pixbuf = gtk_clipboard_wait_for_image(clipboard); + if (pixbuf != NULL) { + clipboard_paste_image(pixbuf); + return; + } +} diff --git a/src/xo-clipboard.h b/src/xo-clipboard.h new file mode 100644 index 0000000..4007fa5 --- /dev/null +++ b/src/xo-clipboard.h @@ -0,0 +1,17 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +void selection_to_clip(void); +void clipboard_paste(void); diff --git a/src/xo-file.c b/src/xo-file.c index 81ce156..129ba87 100644 --- a/src/xo-file.c +++ b/src/xo-file.c @@ -43,8 +43,9 @@ #include "xo-misc.h" #include "xo-file.h" #include "xo-paint.h" +#include "xo-image.h" -const char *tool_names[NUM_TOOLS] = {"pen", "eraser", "highlighter", "text", "", "selectrect", "vertspace", "hand"}; +const char *tool_names[NUM_TOOLS] = {"pen", "eraser", "highlighter", "text", "", "selectrect", "vertspace", "hand", "image"}; const char *color_names[COLOR_MAX] = {"black", "blue", "red", "green", "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white"}; const char *bgtype_names[3] = {"solid", "pixmap", "pdf"}; @@ -86,6 +87,47 @@ void chk_attach_names(void) } } +/* Write image to file: returns true on success, false on error. + The image is written as a base64 encoded PNG. */ + +gboolean write_image(gzFile f, Item *item) +{ + gchar *base64_str; + + if (item->image_png == NULL) { + if (!gdk_pixbuf_save_to_buffer(item->image, &item->image_png, &item->image_png_len, "png", NULL, NULL)) { + item->image_png_len = 0; // failed for some reason, so forget it + return FALSE; + } + } + + base64_str = g_base64_encode(item->image_png, item->image_png_len); + gzputs(f, base64_str); + g_free(base64_str); + return TRUE; +} + +// create pixbuf from base64 encoded PNG, or return NULL on failure + +GdkPixbuf *read_pixbuf(const gchar *base64_str, gsize base64_strlen) +{ + gchar *base64_str2; + gchar *png_buf; + gsize png_buflen; + GdkPixbuf *pixbuf; + + // We have to copy the string in order to null terminate it, sigh. + base64_str2 = g_memdup(base64_str, base64_strlen+1); + base64_str2[base64_strlen] = 0; + png_buf = g_base64_decode(base64_str2, &png_buflen); + + pixbuf = pixbuf_from_buffer(png_buf, png_buflen); + + g_free(png_buf); + g_free(base64_str2); + return pixbuf; +} + // saves the journal to a file: returns true on success, false on error gboolean save_journal(const char *filename) @@ -219,6 +261,12 @@ gboolean save_journal(const char *filename) gzputs(f, "\n"); g_free(tmpstr); } + if (item->type == ITEM_IMAGE) { + gzprintf(f, "", + item->bbox.left, item->bbox.top, item->bbox.right, item->bbox.bottom); + if (!write_image(f, item)) success = FALSE; + gzprintf(f, "\n"); + } } gzprintf(f, "\n"); } @@ -601,6 +649,56 @@ void xoj_parser_start_element(GMarkupParseContext *context, } if (has_attr!=31) *error = xoj_invalid(); } + else if (!strcmp(element_name, "image")) { // start of a image item + if (tmpLayer == NULL || tmpItem != NULL) { + *error = xoj_invalid(); + return; + } + tmpItem = (struct Item *)g_malloc0(sizeof(struct Item)); + tmpItem->type = ITEM_IMAGE; + tmpItem->canvas_item = NULL; + tmpItem->image=NULL; + tmpItem->image_png = NULL; + tmpItem->image_png_len = 0; + tmpLayer->items = g_list_append(tmpLayer->items, tmpItem); + tmpLayer->nitems++; + // scan for x, y + has_attr = 0; + while (*attribute_names!=NULL) { + if (!strcmp(*attribute_names, "left")) { + if (has_attr & 1) *error = xoj_invalid(); + cleanup_numeric((gchar *)*attribute_values); + tmpItem->bbox.left = g_ascii_strtod(*attribute_values, &ptr); + if (ptr == *attribute_values) *error = xoj_invalid(); + has_attr |= 1; + } + else if (!strcmp(*attribute_names, "top")) { + if (has_attr & 2) *error = xoj_invalid(); + cleanup_numeric((gchar *)*attribute_values); + tmpItem->bbox.top = g_ascii_strtod(*attribute_values, &ptr); + if (ptr == *attribute_values) *error = xoj_invalid(); + has_attr |= 2; + } + else if (!strcmp(*attribute_names, "right")) { + if (has_attr & 4) *error = xoj_invalid(); + cleanup_numeric((gchar *)*attribute_values); + tmpItem->bbox.right = g_ascii_strtod(*attribute_values, &ptr); + if (ptr == *attribute_values) *error = xoj_invalid(); + has_attr |= 4; + } + else if (!strcmp(*attribute_names, "bottom")) { + if (has_attr & 8) *error = xoj_invalid(); + cleanup_numeric((gchar *)*attribute_values); + tmpItem->bbox.bottom = g_ascii_strtod(*attribute_values, &ptr); + if (ptr == *attribute_values) *error = xoj_invalid(); + has_attr |= 8; + } + else *error = xoj_invalid(); + attribute_names++; + attribute_values++; + } + if (has_attr!=15) *error = xoj_invalid(); + } } void xoj_parser_end_element(GMarkupParseContext *context, @@ -636,6 +734,13 @@ void xoj_parser_end_element(GMarkupParseContext *context, } tmpItem = NULL; } + if (!strcmp(element_name, "image")) { + if (tmpItem == NULL) { + *error = xoj_invalid(); + return; + } + tmpItem = NULL; + } } void xoj_parser_text(GMarkupParseContext *context, @@ -673,6 +778,9 @@ void xoj_parser_text(GMarkupParseContext *context, g_memmove(tmpItem->text, text, text_len); tmpItem->text[text_len]=0; } + if (!strcmp(element_name, "image")) { + tmpItem->image = read_pixbuf(text, text_len); + } } gboolean user_wants_second_chance(char **filename) @@ -1386,6 +1494,7 @@ void init_config_default(void) ui.print_ruling = TRUE; ui.default_unit = UNIT_CM; ui.default_path = NULL; + ui.default_image = NULL; ui.default_font_name = g_strdup(DEFAULT_FONT); ui.default_font_size = DEFAULT_FONT_SIZE; ui.pressure_sensitivity = FALSE; diff --git a/src/xo-image.c b/src/xo-image.c new file mode 100644 index 0000000..884bf23 --- /dev/null +++ b/src/xo-image.c @@ -0,0 +1,167 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "xournal.h" +#include "xo-support.h" +#include "xo-image.h" + +// create pixbuf from buffer, or return NULL on failure +GdkPixbuf *pixbuf_from_buffer(const gchar *buf, gsize buflen) +{ + GInputStream *istream; + GdkPixbuf *pixbuf; + GError *error; + + error = NULL; + + istream = g_memory_input_stream_new_from_data (buf, buflen, NULL); + pixbuf = gdk_pixbuf_new_from_stream(istream, NULL, &error); + g_input_stream_close(istream, NULL, &error); + return pixbuf; +} + +void create_image_from_pixbuf(GdkPixbuf *pixbuf, double *pt) +{ + double scale; + struct Item *item; + + item = g_new(struct Item, 1); + item->type = ITEM_IMAGE; + item->canvas_item = NULL; + item->bbox.left = pt[0]; + item->bbox.top = pt[1]; + item->image = pixbuf; + item->image_png = NULL; + item->image_png_len = 0; + + // Scale at native size, unless that won't fit, in which case we shrink it down. + scale = 1 / ui.zoom; + if ((scale * gdk_pixbuf_get_width(item->image)) > ui.cur_page->width - item->bbox.left) + scale = (ui.cur_page->width - item->bbox.left) / gdk_pixbuf_get_width(item->image); + if ((scale * gdk_pixbuf_get_height(item->image)) > ui.cur_page->height - item->bbox.top) + scale = (ui.cur_page->height - item->bbox.top) / gdk_pixbuf_get_height(item->image); + + item->bbox.right = item->bbox.left + scale * gdk_pixbuf_get_width(item->image); + item->bbox.bottom = item->bbox.top + scale * gdk_pixbuf_get_height(item->image); + ui.cur_layer->items = g_list_append(ui.cur_layer->items, item); + ui.cur_layer->nitems++; + + make_canvas_item_one(ui.cur_layer->group, item); + + // add undo information + prepare_new_undo(); + undo->type = ITEM_IMAGE; + undo->item = item; + undo->layer = ui.cur_layer; + ui.cur_item = NULL; + ui.cur_item_type = ITEM_NONE; + + // select image + reset_selection(); + ui.selection = g_new0(struct Selection, 1); + ui.selection->type = ITEM_SELECTRECT; + ui.selection->layer = ui.cur_layer; + ui.selection->bbox = item->bbox; + ui.selection->items = g_list_append(ui.selection->items, item); + ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group, + gnome_canvas_rect_get_type(), "width-pixels", 1, + "outline-color-rgba", 0x000000ff, + "fill-color-rgba", 0x80808040, + "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, + "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL); + make_dashed(ui.selection->canvas_item); + update_copy_paste_enabled(); +} + +void insert_image(GdkEvent *event) +{ + GtkTextBuffer *buffer; + GnomeCanvasItem *canvas_item; + GdkColor color; + GtkWidget *dialog; + GtkFileFilter *filt_all; + GtkFileFilter *filt_gdkimage; + char *filename; + GdkPixbuf *pixbuf; + double scale=1; + double pt[2]; + + dialog = gtk_file_chooser_dialog_new(_("Insert Image"), GTK_WINDOW (winMain), + GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL); +#ifdef FILE_DIALOG_SIZE_BUGFIX + gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 400); +#endif + + filt_all = gtk_file_filter_new(); + gtk_file_filter_set_name(filt_all, _("All files")); + gtk_file_filter_add_pattern(filt_all, "*"); + filt_gdkimage = gtk_file_filter_new(); + gtk_file_filter_set_name(filt_gdkimage, _("Image files")); + gtk_file_filter_add_pixbuf_formats(filt_gdkimage); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_gdkimage); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER (dialog), filt_all); + + if (ui.default_image != NULL) gtk_file_chooser_set_filename(GTK_FILE_CHOOSER (dialog), ui.default_image); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) { + gtk_widget_destroy(dialog); + return; + } + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog)); + gtk_widget_destroy(dialog); + + if (filename == NULL) return; /* nothing selected */ + + if (ui.default_image != NULL) g_free(ui.default_image); + ui.default_image = g_strdup(filename); + + set_cursor_busy(TRUE); + pixbuf=gdk_pixbuf_new_from_file(filename, NULL); + set_cursor_busy(FALSE); + + if(pixbuf==NULL) { /* open failed */ + dialog = gtk_message_dialog_new(GTK_WINDOW (winMain), GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Error opening image '%s'"), filename); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_free(filename); + ui.cur_item = NULL; + ui.cur_item_type = ITEM_NONE; + return; + } + + ui.cur_item_type = ITEM_IMAGE; + + get_pointer_coords(event, pt); + set_current_page(pt); + + create_image_from_pixbuf(pixbuf, pt); +} + +void rescale_images(void) +{ + // nothing needed in this implementation +} + diff --git a/src/xo-image.h b/src/xo-image.h new file mode 100644 index 0000000..e0cb8fa --- /dev/null +++ b/src/xo-image.h @@ -0,0 +1,19 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +GdkPixbuf *pixbuf_from_buffer(const gchar *buf, gsize buflen); +void create_image_from_pixbuf(GdkPixbuf *pixbuf, double *pt); +void insert_image(GdkEvent *event); +void rescale_images(void); diff --git a/src/xo-interface.c b/src/xo-interface.c index f6022ea..00a72d1 100644 --- a/src/xo-interface.c +++ b/src/xo-interface.c @@ -143,6 +143,7 @@ create_winMain (void) GtkWidget *toolsEraser; GtkWidget *toolsHighlighter; GtkWidget *toolsText; + GtkWidget *toolsImage; GtkWidget *separator15; GtkWidget *toolsReco; GtkWidget *toolsRuler; @@ -215,6 +216,7 @@ create_winMain (void) GtkWidget *button2Eraser; GtkWidget *button2Highlighter; GtkWidget *button2Text; + GtkWidget *button2Image; GtkWidget *button2SelectRegion; GtkWidget *button2SelectRectangle; GtkWidget *button2VerticalSpace; @@ -231,6 +233,7 @@ create_winMain (void) GtkWidget *button3Eraser; GtkWidget *button3Highlighter; GtkWidget *button3Text; + GtkWidget *button3Image; GtkWidget *button3SelectRegion; GtkWidget *button3SelectRectangle; GtkWidget *button3VerticalSpace; @@ -289,6 +292,7 @@ create_winMain (void) GtkWidget *buttonEraser; GtkWidget *buttonHighlighter; GtkWidget *buttonText; + GtkWidget *buttonImage; GtkWidget *buttonReco; GtkWidget *buttonRuler; GtkWidget *toolitem15; @@ -662,7 +666,7 @@ create_winMain (void) gtk_widget_show (image631); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (viewHideLayer), image631); - menuJournal = gtk_menu_item_new_with_mnemonic (_("_Journal")); + menuJournal = gtk_menu_item_new_with_mnemonic (_("_Page")); gtk_widget_show (menuJournal); gtk_container_add (GTK_CONTAINER (menubar), menuJournal); @@ -876,6 +880,15 @@ create_winMain (void) GTK_ACCEL_VISIBLE); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (toolsText), TRUE); + toolsImage = gtk_radio_menu_item_new_with_mnemonic (toolsPen_group, _("_Image")); + toolsPen_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (toolsImage)); + gtk_widget_show (toolsImage); + gtk_container_add (GTK_CONTAINER (menuTools_menu), toolsImage); + gtk_widget_add_accelerator (toolsImage, "activate", accel_group, + GDK_I, (GdkModifierType) GDK_CONTROL_MASK | GDK_SHIFT_MASK, + GTK_ACCEL_VISIBLE); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (toolsImage), TRUE); + separator15 = gtk_separator_menu_item_new (); gtk_widget_show (separator15); gtk_container_add (GTK_CONTAINER (menuTools_menu), separator15); @@ -1227,6 +1240,12 @@ create_winMain (void) gtk_container_add (GTK_CONTAINER (button2_mapping_menu), button2Text); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (button2Text), TRUE); + button2Image = gtk_radio_menu_item_new_with_mnemonic (button2Pen_group, _("_Image")); + button2Pen_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (button2Image)); + gtk_widget_show (button2Image); + gtk_container_add (GTK_CONTAINER (button2_mapping_menu), button2Image); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (button2Image), TRUE); + button2SelectRegion = gtk_radio_menu_item_new_with_mnemonic (button2Pen_group, _("Select Re_gion")); button2Pen_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (button2SelectRegion)); gtk_widget_show (button2SelectRegion); @@ -1303,6 +1322,12 @@ create_winMain (void) gtk_container_add (GTK_CONTAINER (button3_mapping_menu), button3Text); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (button3Text), TRUE); + button3Image = gtk_radio_menu_item_new_with_mnemonic (button3Pen_group, _("_Image")); + button3Pen_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (button3Image)); + gtk_widget_show (button3Image); + gtk_container_add (GTK_CONTAINER (button3_mapping_menu), button3Image); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (button3Image), TRUE); + button3SelectRegion = gtk_radio_menu_item_new_with_mnemonic (button3Pen_group, _("Select Re_gion")); button3Pen_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (button3SelectRegion)); gtk_widget_show (button3SelectRegion); @@ -1588,6 +1613,17 @@ create_winMain (void) gtk_radio_tool_button_set_group (GTK_RADIO_TOOL_BUTTON (buttonText), buttonPen_group); buttonPen_group = gtk_radio_tool_button_get_group (GTK_RADIO_TOOL_BUTTON (buttonText)); + buttonImage = (GtkWidget*) gtk_radio_tool_button_new (NULL); + gtk_tool_button_set_label (GTK_TOOL_BUTTON (buttonImage), _("Text")); + tmp_image = gtk_image_new_from_stock ("gtk-orientation-portrait", tmp_toolbar_icon_size); + gtk_widget_show (tmp_image); + gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (buttonImage), tmp_image); + gtk_widget_show (buttonImage); + gtk_container_add (GTK_CONTAINER (toolbarPen), buttonImage); + gtk_tool_item_set_tooltip (GTK_TOOL_ITEM (buttonImage), tooltips, _("Text"), NULL); + gtk_radio_tool_button_set_group (GTK_RADIO_TOOL_BUTTON (buttonImage), buttonPen_group); + buttonPen_group = gtk_radio_tool_button_get_group (GTK_RADIO_TOOL_BUTTON (buttonImage)); + buttonReco = (GtkWidget*) gtk_toggle_tool_button_new (); gtk_tool_button_set_label (GTK_TOOL_BUTTON (buttonReco), _("Shape Recognizer")); tmp_image = create_pixmap (winMain, "shapes.png"); @@ -2147,6 +2183,9 @@ create_winMain (void) g_signal_connect ((gpointer) toolsText, "toggled", G_CALLBACK (on_toolsText_activate), NULL); + g_signal_connect ((gpointer) toolsImage, "toggled", + G_CALLBACK (on_toolsImage_activate), + NULL); g_signal_connect ((gpointer) toolsReco, "toggled", G_CALLBACK (on_toolsReco_activate), NULL); @@ -2282,6 +2321,9 @@ create_winMain (void) g_signal_connect ((gpointer) button2Text, "activate", G_CALLBACK (on_button2Text_activate), NULL); + g_signal_connect ((gpointer) button2Image, "activate", + G_CALLBACK (on_button2Image_activate), + NULL); g_signal_connect ((gpointer) button2SelectRegion, "activate", G_CALLBACK (on_button2SelectRegion_activate), NULL); @@ -2312,6 +2354,9 @@ create_winMain (void) g_signal_connect ((gpointer) button3Text, "activate", G_CALLBACK (on_button3Text_activate), NULL); + g_signal_connect ((gpointer) button3Image, "activate", + G_CALLBACK (on_button3Image_activate), + NULL); g_signal_connect ((gpointer) button3SelectRegion, "activate", G_CALLBACK (on_button3SelectRegion_activate), NULL); @@ -2426,6 +2471,9 @@ create_winMain (void) g_signal_connect ((gpointer) buttonText, "toggled", G_CALLBACK (on_toolsText_activate), NULL); + g_signal_connect ((gpointer) buttonImage, "toggled", + G_CALLBACK (on_toolsImage_activate), + NULL); g_signal_connect ((gpointer) buttonReco, "toggled", G_CALLBACK (on_toolsReco_activate), NULL); @@ -2616,6 +2664,7 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, toolsEraser, "toolsEraser"); GLADE_HOOKUP_OBJECT (winMain, toolsHighlighter, "toolsHighlighter"); GLADE_HOOKUP_OBJECT (winMain, toolsText, "toolsText"); + GLADE_HOOKUP_OBJECT (winMain, toolsImage, "toolsImage"); GLADE_HOOKUP_OBJECT (winMain, separator15, "separator15"); GLADE_HOOKUP_OBJECT (winMain, toolsReco, "toolsReco"); GLADE_HOOKUP_OBJECT (winMain, toolsRuler, "toolsRuler"); @@ -2682,6 +2731,7 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, button2Eraser, "button2Eraser"); GLADE_HOOKUP_OBJECT (winMain, button2Highlighter, "button2Highlighter"); GLADE_HOOKUP_OBJECT (winMain, button2Text, "button2Text"); + GLADE_HOOKUP_OBJECT (winMain, button2Image, "button2Image"); GLADE_HOOKUP_OBJECT (winMain, button2SelectRegion, "button2SelectRegion"); GLADE_HOOKUP_OBJECT (winMain, button2SelectRectangle, "button2SelectRectangle"); GLADE_HOOKUP_OBJECT (winMain, button2VerticalSpace, "button2VerticalSpace"); @@ -2696,6 +2746,7 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, button3Eraser, "button3Eraser"); GLADE_HOOKUP_OBJECT (winMain, button3Highlighter, "button3Highlighter"); GLADE_HOOKUP_OBJECT (winMain, button3Text, "button3Text"); + GLADE_HOOKUP_OBJECT (winMain, button3Image, "button3Image"); GLADE_HOOKUP_OBJECT (winMain, button3SelectRegion, "button3SelectRegion"); GLADE_HOOKUP_OBJECT (winMain, button3SelectRectangle, "button3SelectRectangle"); GLADE_HOOKUP_OBJECT (winMain, button3VerticalSpace, "button3VerticalSpace"); @@ -2750,6 +2801,7 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, buttonEraser, "buttonEraser"); GLADE_HOOKUP_OBJECT (winMain, buttonHighlighter, "buttonHighlighter"); GLADE_HOOKUP_OBJECT (winMain, buttonText, "buttonText"); + GLADE_HOOKUP_OBJECT (winMain, buttonImage, "buttonImage"); GLADE_HOOKUP_OBJECT (winMain, buttonReco, "buttonReco"); GLADE_HOOKUP_OBJECT (winMain, buttonRuler, "buttonRuler"); GLADE_HOOKUP_OBJECT (winMain, toolitem15, "toolitem15"); diff --git a/src/xo-misc.c b/src/xo-misc.c index bc4747c..bd65f1a 100644 --- a/src/xo-misc.c +++ b/src/xo-misc.c @@ -31,6 +31,7 @@ #include "xo-file.h" #include "xo-paint.h" #include "xo-shapes.h" +#include "xo-image.h" // some global constants @@ -104,6 +105,31 @@ struct Page *new_page_with_bg(struct Background *bg, double width, double height return pg; } +// change the current page if necessary for pointer at pt +void set_current_page(gdouble *pt) +{ + gboolean page_change; + struct Page *tmppage; + + page_change = FALSE; + tmppage = ui.cur_page; + while (ui.view_continuous && (pt[1] < - VIEW_CONTINUOUS_SKIP)) { + if (ui.pageno == 0) break; + page_change = TRUE; + ui.pageno--; + tmppage = g_list_nth_data(journal.pages, ui.pageno); + pt[1] += tmppage->height + VIEW_CONTINUOUS_SKIP; + } + while (ui.view_continuous && (pt[1] > tmppage->height + VIEW_CONTINUOUS_SKIP)) { + if (ui.pageno == journal.npages-1) break; + pt[1] -= tmppage->height + VIEW_CONTINUOUS_SKIP; + page_change = TRUE; + ui.pageno++; + tmppage = g_list_nth_data(journal.pages, ui.pageno); + } + if (page_change) do_switch_page(ui.pageno, FALSE, FALSE); +} + void realloc_cur_path(int n) { if (n <= ui.cur_path_storage_alloc) return; @@ -156,6 +182,11 @@ void clear_redo_stack(void) g_free(redo->item->font_name); g_free(redo->item); } + else if (redo->type == ITEM_IMAGE) { + g_object_unref(redo->item->image); + g_free(redo->item->image_png); + g_free(redo->item); + } else if (redo->type == ITEM_ERASURE || redo->type == ITEM_RECOGNIZER) { for (list = redo->erasurelist; list!=NULL; list=list->next) { erasure = (struct UndoErasureData *)list->data; @@ -232,6 +263,10 @@ void clear_undo_stack(void) } if (erasure->item->type == ITEM_TEXT) { g_free(erasure->item->text); g_free(erasure->item->font_name); } + if (erasure->item->type == ITEM_IMAGE) { + g_object_unref(erasure->item->image); + g_free(erasure->item->image_png); + } g_free(erasure->item); g_list_free(erasure->replacement_items); g_free(erasure); @@ -316,6 +351,10 @@ void delete_layer(struct Layer *l) if (item->type == ITEM_TEXT) { g_free(item->font_name); g_free(item->text); } + if (item->type == ITEM_IMAGE) { + g_object_unref(item->image); + g_free(item->image_png); + } // don't need to delete the canvas_item, as it's part of the group destroyed below g_free(item); l->items = g_list_delete_link(l->items, l->items); @@ -370,6 +409,17 @@ void get_pointer_coords(GdkEvent *event, gdouble *ret) ret[1] -= ui.cur_page->voffset; } +void get_current_pointer_coords(gdouble *ret) +{ + gint wx, wy, sx, sy; + + gtk_widget_get_pointer((GtkWidget *)canvas, &wx, &wy); + gnome_canvas_get_scroll_offsets(canvas, &sx, &sy); + gnome_canvas_window_to_world(canvas, (double)(wx + sx), (double)(wy + sy), ret, ret+1); + ret[0] -= ui.cur_page->hoffset; + ret[1] -= ui.cur_page->voffset; +} + void fix_xinput_coords(GdkEvent *event) { double *axes, *px, *py, axis_width; @@ -533,6 +583,16 @@ void make_canvas_item_one(GnomeCanvasGroup *group, struct Item *item) "text", item->text, NULL); update_item_bbox(item); } + if (item->type == ITEM_IMAGE) { + item->canvas_item = gnome_canvas_item_new(group, + gnome_canvas_pixbuf_get_type(), + "pixbuf", item->image, + "x", item->bbox.left, "y", item->bbox.top, + "width", item->bbox.right - item->bbox.left, + "height", item->bbox.bottom - item->bbox.top, + "width-set", TRUE, "height-set", TRUE, + NULL); + } } void make_canvas_items(void) @@ -881,6 +941,10 @@ void update_tool_buttons(void) gtk_toggle_tool_button_set_active( GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonText")), TRUE); break; + case TOOL_IMAGE: + gtk_toggle_tool_button_set_active( + GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonImage")), TRUE); + break; case TOOL_SELECTREGION: gtk_toggle_tool_button_set_active( GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonSelectRegion")), TRUE); @@ -929,6 +993,10 @@ void update_tool_menu(void) gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsText")), TRUE); break; + case TOOL_IMAGE: + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsImage")), TRUE); + break; case TOOL_SELECTREGION: gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsSelectRegion")), TRUE); @@ -1159,6 +1227,10 @@ void update_mappings_menu(void) gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("button2Text")), TRUE); break; + case TOOL_IMAGE: + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(GET_COMPONENT("button2Image")), TRUE); + break; case TOOL_SELECTREGION: gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("button2SelectRegion")), TRUE); @@ -1189,6 +1261,10 @@ void update_mappings_menu(void) gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("button3Text")), TRUE); break; + case TOOL_IMAGE: + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(GET_COMPONENT("button3Image")), TRUE); + break; case TOOL_SELECTREGION: gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("button3SelectRegion")), TRUE); @@ -1743,7 +1819,8 @@ void move_journal_items_by(GList *itemlist, double dx, double dy, if (item->type == ITEM_STROKE) for (pt=item->path->coords, i=0; ipath->num_points; i++, pt+=2) { pt[0] += dx; pt[1] += dy; } - if (item->type == ITEM_STROKE || item->type == ITEM_TEXT || item->type == ITEM_TEMP_TEXT) { + if (item->type == ITEM_STROKE || item->type == ITEM_TEXT || + item->type == ITEM_TEMP_TEXT || item->type == ITEM_IMAGE) { item->bbox.left += dx; item->bbox.right += dx; item->bbox.top += dy; @@ -1826,6 +1903,22 @@ void resize_journal_items_by(GList *itemlist, double scaling_x, double scaling_y item->bbox.left = item->bbox.left*scaling_x + offset_x; item->bbox.top = item->bbox.top*scaling_y + offset_y; } + if (item->type == ITEM_IMAGE) { + item->bbox.left = item->bbox.left*scaling_x + offset_x; + item->bbox.right = item->bbox.right*scaling_x + offset_x; + item->bbox.top = item->bbox.top*scaling_y + offset_y; + item->bbox.bottom = item->bbox.bottom*scaling_y + offset_y; + if (item->bbox.left > item->bbox.right) { + temp = item->bbox.left; + item->bbox.left = item->bbox.right; + item->bbox.right = temp; + } + if (item->bbox.top > item->bbox.bottom) { + temp = item->bbox.top; + item->bbox.top = item->bbox.bottom; + item->bbox.bottom = temp; + } + } // redraw the item if (item->canvas_item!=NULL) { group = (GnomeCanvasGroup *) item->canvas_item->parent; diff --git a/src/xo-misc.h b/src/xo-misc.h index da1433d..617bbda 100644 --- a/src/xo-misc.h +++ b/src/xo-misc.h @@ -17,6 +17,7 @@ struct Page *new_page(struct Page *template); struct Page *new_page_with_bg(struct Background *bg, double width, double height); +void set_current_page(gdouble *pt); void realloc_cur_path(int n); void realloc_cur_widths(int n); void clear_redo_stack(void); @@ -36,6 +37,7 @@ void refstring_unref(struct Refstring *rs); int finite_sized(double x); void get_pointer_coords(GdkEvent *event, double *ret); +void get_current_pointer_coords(double *ret); double get_pressure_multiplier(GdkEvent *event); void fix_xinput_coords(GdkEvent *event); void update_item_bbox(struct Item *item); diff --git a/src/xo-paint.c b/src/xo-paint.c index c777482..33576f8 100644 --- a/src/xo-paint.c +++ b/src/xo-paint.c @@ -964,211 +964,6 @@ void selection_delete(void) the forward direction */ } -void callback_clipboard_get(GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, gpointer user_data) -{ - int length; - - g_memmove(&length, user_data, sizeof(int)); - gtk_selection_data_set(selection_data, - gdk_atom_intern("_XOURNAL", FALSE), 8, user_data, length); -} - -void callback_clipboard_clear(GtkClipboard *clipboard, gpointer user_data) -{ - g_free(user_data); -} - -void selection_to_clip(void) -{ - int bufsz, nitems, val; - char *buf, *p; - GList *list; - struct Item *item; - GtkTargetEntry target; - - if (ui.selection == NULL) return; - bufsz = 2*sizeof(int) // bufsz, nitems - + sizeof(struct BBox); // bbox - nitems = 0; - for (list = ui.selection->items; list != NULL; list = list->next) { - item = (struct Item *)list->data; - nitems++; - if (item->type == ITEM_STROKE) { - bufsz+= sizeof(int) // type - + sizeof(struct Brush) // brush - + sizeof(int) // num_points - + 2*item->path->num_points*sizeof(double); // the points - if (item->brush.variable_width) - bufsz += (item->path->num_points-1)*sizeof(double); // the widths - } - else if (item->type == ITEM_TEXT) { - bufsz+= sizeof(int) // type - + sizeof(struct Brush) // brush - + 2*sizeof(double) // bbox upper-left - + sizeof(int) // text len - + strlen(item->text)+1 // text - + sizeof(int) // font_name len - + strlen(item->font_name)+1 // font_name - + sizeof(double); // font_size - } - else bufsz+= sizeof(int); // type - } - p = buf = g_malloc(bufsz); - g_memmove(p, &bufsz, sizeof(int)); p+= sizeof(int); - g_memmove(p, &nitems, sizeof(int)); p+= sizeof(int); - g_memmove(p, &ui.selection->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox); - for (list = ui.selection->items; list != NULL; list = list->next) { - item = (struct Item *)list->data; - g_memmove(p, &item->type, sizeof(int)); p+= sizeof(int); - if (item->type == ITEM_STROKE) { - g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush); - g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int); - g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double)); - p+= 2*item->path->num_points*sizeof(double); - if (item->brush.variable_width) { - g_memmove(p, item->widths, (item->path->num_points-1)*sizeof(double)); - p+= (item->path->num_points-1)*sizeof(double); - } - } - if (item->type == ITEM_TEXT) { - g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush); - g_memmove(p, &item->bbox.left, sizeof(double)); p+= sizeof(double); - g_memmove(p, &item->bbox.top, sizeof(double)); p+= sizeof(double); - val = strlen(item->text); - g_memmove(p, &val, sizeof(int)); p+= sizeof(int); - g_memmove(p, item->text, val+1); p+= val+1; - val = strlen(item->font_name); - g_memmove(p, &val, sizeof(int)); p+= sizeof(int); - g_memmove(p, item->font_name, val+1); p+= val+1; - g_memmove(p, &item->font_size, sizeof(double)); p+= sizeof(double); - } - } - - target.target = "_XOURNAL"; - target.flags = 0; - target.info = 0; - - gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), - &target, 1, - callback_clipboard_get, callback_clipboard_clear, buf); -} - - -void clipboard_paste(void) -{ - GtkSelectionData *sel_data; - unsigned char *p; - int nitems, npts, i, len; - struct Item *item; - double hoffset, voffset, cx, cy; - double *pf; - int sx, sy, wx, wy; - - if (ui.cur_layer == NULL) return; - - ui.cur_item_type = ITEM_PASTE; - sel_data = gtk_clipboard_wait_for_contents( - gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), - gdk_atom_intern("_XOURNAL", FALSE)); - ui.cur_item_type = ITEM_NONE; - if (sel_data == NULL) return; // paste failed - - reset_selection(); - - ui.selection = g_new(struct Selection, 1); - p = sel_data->data + sizeof(int); - g_memmove(&nitems, p, sizeof(int)); p+= sizeof(int); - ui.selection->type = ITEM_SELECTRECT; - ui.selection->layer = ui.cur_layer; - g_memmove(&ui.selection->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox); - ui.selection->items = NULL; - - // find by how much we translate the pasted selection - gnome_canvas_get_scroll_offsets(canvas, &sx, &sy); - gdk_window_get_geometry(GTK_WIDGET(canvas)->window, NULL, NULL, &wx, &wy, NULL); - gnome_canvas_window_to_world(canvas, sx + wx/2, sy + wy/2, &cx, &cy); - cx -= ui.cur_page->hoffset; - cy -= ui.cur_page->voffset; - if (cx + (ui.selection->bbox.right-ui.selection->bbox.left)/2 > ui.cur_page->width) - cx = ui.cur_page->width - (ui.selection->bbox.right-ui.selection->bbox.left)/2; - if (cx - (ui.selection->bbox.right-ui.selection->bbox.left)/2 < 0) - cx = (ui.selection->bbox.right-ui.selection->bbox.left)/2; - if (cy + (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 > ui.cur_page->height) - cy = ui.cur_page->height - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2; - if (cy - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 < 0) - cy = (ui.selection->bbox.bottom-ui.selection->bbox.top)/2; - hoffset = cx - (ui.selection->bbox.right+ui.selection->bbox.left)/2; - voffset = cy - (ui.selection->bbox.top+ui.selection->bbox.bottom)/2; - ui.selection->bbox.left += hoffset; - ui.selection->bbox.right += hoffset; - ui.selection->bbox.top += voffset; - ui.selection->bbox.bottom += voffset; - - ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group, - gnome_canvas_rect_get_type(), "width-pixels", 1, - "outline-color-rgba", 0x000000ff, - "fill-color-rgba", 0x80808040, - "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, - "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL); - make_dashed(ui.selection->canvas_item); - - while (nitems-- > 0) { - item = g_new(struct Item, 1); - ui.selection->items = g_list_append(ui.selection->items, item); - ui.cur_layer->items = g_list_append(ui.cur_layer->items, item); - ui.cur_layer->nitems++; - g_memmove(&item->type, p, sizeof(int)); p+= sizeof(int); - if (item->type == ITEM_STROKE) { - g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush); - g_memmove(&npts, p, sizeof(int)); p+= sizeof(int); - item->path = gnome_canvas_points_new(npts); - pf = (double *)p; - for (i=0; ipath->coords[2*i] = pf[2*i] + hoffset; - item->path->coords[2*i+1] = pf[2*i+1] + voffset; - } - p+= 2*item->path->num_points*sizeof(double); - if (item->brush.variable_width) { - item->widths = g_memdup(p, (item->path->num_points-1)*sizeof(double)); - p+= (item->path->num_points-1)*sizeof(double); - } - else item->widths = NULL; - update_item_bbox(item); - make_canvas_item_one(ui.cur_layer->group, item); - } - if (item->type == ITEM_TEXT) { - g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush); - g_memmove(&item->bbox.left, p, sizeof(double)); p+= sizeof(double); - g_memmove(&item->bbox.top, p, sizeof(double)); p+= sizeof(double); - item->bbox.left += hoffset; - item->bbox.top += voffset; - g_memmove(&len, p, sizeof(int)); p+= sizeof(int); - item->text = g_malloc(len+1); - g_memmove(item->text, p, len+1); p+= len+1; - g_memmove(&len, p, sizeof(int)); p+= sizeof(int); - item->font_name = g_malloc(len+1); - g_memmove(item->font_name, p, len+1); p+= len+1; - g_memmove(&item->font_size, p, sizeof(double)); p+= sizeof(double); - make_canvas_item_one(ui.cur_layer->group, item); - } - } - - prepare_new_undo(); - undo->type = ITEM_PASTE; - undo->layer = ui.cur_layer; - undo->itemlist = g_list_copy(ui.selection->items); - - gtk_selection_data_free(sel_data); - update_copy_paste_enabled(); - update_color_menu(); - update_thickness_buttons(); - update_color_buttons(); - update_font_button(); - update_cursor(); // FIXME: can't know if pointer is within selection! -} - // modify the color or thickness of pen strokes in a selection void recolor_selection(int color_no, guint color_rgba) diff --git a/src/xo-paint.h b/src/xo-paint.h index 4ee4e40..8d69bd6 100644 --- a/src/xo-paint.h +++ b/src/xo-paint.h @@ -38,8 +38,6 @@ void continue_resizesel(GdkEvent *event); void finalize_resizesel(void); void selection_delete(void); -void selection_to_clip(void); -void clipboard_paste(void); void recolor_selection(int color_no, guint color_rgba); void rethicken_selection(int val); diff --git a/src/xo-print.c b/src/xo-print.c index 2cfc18e..029500e 100644 --- a/src/xo-print.c +++ b/src/xo-print.c @@ -787,6 +787,81 @@ int pdf_draw_bitmap_background(struct Page *pg, GString *str, return xref->last; } +gboolean pdf_draw_image(PdfImage *image, struct XrefTable *xref, GString *pdfbuf) +{ + char *buf, *p1, *p2; + int height, width, stride, x, y, chan; + GString *zpix; + + if (gdk_pixbuf_get_bits_per_sample(image->pixbuf) != 8 || + gdk_pixbuf_get_colorspace(image->pixbuf) != GDK_COLORSPACE_RGB) { + return FALSE; + } + + width = gdk_pixbuf_get_width(image->pixbuf); + height = gdk_pixbuf_get_height(image->pixbuf); + stride = gdk_pixbuf_get_rowstride(image->pixbuf); + chan = gdk_pixbuf_get_n_channels(image->pixbuf); + if (!((chan==3 && !image->has_alpha) || (chan==4 && image->has_alpha))) { + return FALSE; + } + + p2 = buf = (char *)g_malloc(3*width*height); + for (y=0; ypixbuf)+stride*y; + for (x=0; xdata[image->n_obj] = pdfbuf->len; + g_string_append_printf(pdfbuf, + "%d 0 obj\n<< /Length %d /Filter /FlateDecode /Type /Xobject " + "/Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB " + "/BitsPerComponent 8 ", + image->n_obj, zpix->len, width, height); + if (image->has_alpha) { + g_string_append_printf(pdfbuf, + "/SMask %d 0 R ", + image->n_obj_smask); + } + g_string_append_printf(pdfbuf, " >> stream\n"); + + g_string_append_len(pdfbuf, zpix->str, zpix->len); + g_string_free(zpix, TRUE); + g_string_append(pdfbuf, "endstream\nendobj\n"); + + if (image->has_alpha) { + p2 = buf = (char *)g_malloc(width*height); + for (y=0; ypixbuf)+stride*y; + for (x=0; xdata[image->n_obj_smask] = pdfbuf->len; + g_string_append_printf(pdfbuf, + "%d 0 obj\n<< /Length %d /Filter /FlateDecode /Type /Xobject " + "/Subtype /Image /Width %d /Height %d /ColorSpace /DeviceGray " + "/BitsPerComponent 8 >> stream\n", + image->n_obj_smask, zpix->len, width, height); + + g_string_append_len(pdfbuf, zpix->str, zpix->len); + g_string_free(zpix, TRUE); + g_string_append(pdfbuf, "endstream\nendobj\n"); + } + + return TRUE; +} + + // manipulate Pdf fonts struct PdfFont *new_pdffont(struct XrefTable *xref, GList **fonts, @@ -1023,10 +1098,31 @@ void embed_pdffont(GString *pdfbuf, struct XrefTable *xref, struct PdfFont *font g_string_append(pdfbuf, ">> endobj\n"); } +// Pdf images + +struct PdfImage *new_pdfimage(struct XrefTable *xref, GList **images, GdkPixbuf *pixbuf) +{ + GList *list; + struct PdfImage *image; + + image = g_malloc(sizeof(struct PdfImage)); + *images = g_list_append(*images, image); + image->n_obj = xref->last+1; + make_xref(xref, xref->last+1, 0); // will give it a value later + image->has_alpha = gdk_pixbuf_get_has_alpha(pixbuf); + if (image->has_alpha) { + image->n_obj_smask = xref->last+1; + make_xref(xref, xref->last+1, 0); // will give it a value later + } + image->pixbuf = pixbuf; + + return image; +} + // draw a page's graphics void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, - struct XrefTable *xref, GList **pdffonts) + struct XrefTable *xref, GList **pdffonts, GList **pdfimages) { GList *layerlist, *itemlist, *tmplist; struct Layer *l; @@ -1051,6 +1147,7 @@ void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, int font_id; FT_Face ftface; struct PdfFont *cur_font; + struct PdfImage *cur_image; gboolean in_string; old_rgba = old_text_rgba = 0x12345678; // not any values we use, so we'll reset them @@ -1059,6 +1156,10 @@ void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, cur_font = (struct PdfFont *)tmplist->data; cur_font->used_in_this_page = FALSE; } + for (tmplist = *pdfimages; tmplist!=NULL; tmplist = tmplist->next) { + cur_image = (struct PdfImage *)tmplist->data; + cur_image->used_in_this_page = FALSE; + } for (layerlist = pg->layers; layerlist!=NULL; layerlist = layerlist->next) { l = (struct Layer *)layerlist->data; @@ -1178,6 +1279,14 @@ void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, pango_layout_iter_free(iter); g_object_unref(layout); } + else if (item->type == ITEM_IMAGE) { + cur_image = new_pdfimage(xref, pdfimages, item->image); + cur_image->used_in_this_page = TRUE; + g_string_append_printf(str, "\nq 1 0 0 1 %.2f %.2f cm %.2f 0 0 %.2f 0 %.2f cm /Im%d Do Q ", + item->bbox.left, item->bbox.top, // translation + item->bbox.right-item->bbox.left, item->bbox.top-item->bbox.bottom, item->bbox.bottom-item->bbox.top, // scaling + cur_image->n_obj); + } } } } @@ -1204,8 +1313,9 @@ gboolean print_to_pdf(char *filename) gboolean use_hiliter; struct PdfInfo pdfinfo; struct PdfObj *obj; - GList *pdffonts, *list; + GList *pdffonts, *pdfimages, *list; struct PdfFont *font; + struct PdfImage *image; char *tmpbuf; f = fopen(filename, "wb"); @@ -1215,6 +1325,7 @@ gboolean print_to_pdf(char *filename) xref.data = NULL; uses_pdf = FALSE; pdffonts = NULL; + pdfimages = NULL; for (pglist = journal.pages; pglist!=NULL; pglist = pglist->next) { pg = (struct Page *)pglist->data; if (pg->bg->type == BG_PDF) uses_pdf = TRUE; @@ -1284,7 +1395,7 @@ gboolean print_to_pdf(char *filename) n_obj_bgpix = pdf_draw_bitmap_background(pg, pgstrm, &xref, pdfbuf); // draw the page contents use_hiliter = FALSE; - pdf_draw_page(pg, pgstrm, &use_hiliter, &xref, &pdffonts); + pdf_draw_page(pg, pgstrm, &use_hiliter, &xref, &pdffonts, &pdfimages); g_string_append_printf(pgstrm, "Q\n"); // deflate pgstrm and write it @@ -1340,7 +1451,7 @@ gboolean print_to_pdf(char *filename) } add_dict_subentry(pdfbuf, &xref, obj, "/ProcSet", PDFTYPE_ARRAY, NULL, mk_pdfname("/PDF")); - if (n_obj_bgpix>0) + if (n_obj_bgpix>0 || pdfimages!=NULL) add_dict_subentry(pdfbuf, &xref, obj, "/ProcSet", PDFTYPE_ARRAY, NULL, mk_pdfname("/ImageC")); if (use_hiliter) @@ -1360,12 +1471,21 @@ gboolean print_to_pdf(char *filename) g_free(tmpbuf); } } + for (list=pdfimages; list!=NULL; list = list->next) { + image = (struct PdfImage *)list->data; + if (image->used_in_this_page) { + tmpbuf = g_strdup_printf("/Im%d", image->n_obj); + add_dict_subentry(pdfbuf, &xref, + obj, "/XObject", PDFTYPE_DICT, tmpbuf, mk_pdfref(image->n_obj)); + g_free(tmpbuf); + } + } show_pdfobj(obj, pdfbuf); free_pdfobj(obj); g_string_append(pdfbuf, " >> endobj\n"); } - // after the pages, we insert fonts + // after the pages, we insert fonts and images for (list = pdffonts; list!=NULL; list = list->next) { font = (struct PdfFont *)list->data; embed_pdffont(pdfbuf, &xref, font); @@ -1374,6 +1494,14 @@ gboolean print_to_pdf(char *filename) g_free(font); } g_list_free(pdffonts); + for (list = pdfimages; list!=NULL; list = list->next) { + image = (struct PdfImage *)list->data; + if (!pdf_draw_image(image, &xref, pdfbuf)) { + return FALSE; + } + g_free(image); + } + g_list_free(pdfimages); // PDF trailer startxref = pdfbuf->len; @@ -1565,6 +1693,16 @@ void print_job_render_page(GtkPrintOperation *print, GtkPrintContext *context, g pango_cairo_show_layout(cr, layout); g_object_unref(layout); } + if (item->type == ITEM_IMAGE) { + double scalex = (item->bbox.right-item->bbox.left)/gdk_pixbuf_get_width(item->image); + double scaley = (item->bbox.bottom-item->bbox.top)/gdk_pixbuf_get_height(item->image); + cairo_scale(cr, scalex, scaley); + gdk_cairo_set_source_pixbuf(cr,item->image, item->bbox.left/scalex, item->bbox.top/scaley); + cairo_scale(cr, 1/scalex, 1/scaley); + cairo_paint(cr); + old_rgba = predef_colors_rgba[COLOR_BLACK]; + cairo_set_source_rgb(cr, 0, 0, 0); + } } } } diff --git a/src/xo-print.h b/src/xo-print.h index 95a96f7..498be84 100644 --- a/src/xo-print.h +++ b/src/xo-print.h @@ -60,6 +60,15 @@ typedef struct PdfFont { int flags; } PdfFont; +typedef struct PdfImage { + int n_obj; + gboolean has_alpha; + int n_obj_smask; /* only if has_alpha */ + GdkPixbuf *pixbuf; + gboolean used_in_this_page; +} PdfImage; + + #define PDFTYPE_CST 0 // intval: true=1, false=0, null=-1 #define PDFTYPE_INT 1 // intval #define PDFTYPE_REAL 2 // realval diff --git a/src/xournal.h b/src/xournal.h index 605c823..9f801cc 100644 --- a/src/xournal.h +++ b/src/xournal.h @@ -143,8 +143,9 @@ extern guint predef_bgcolors_rgba[COLOR_MAX]; #define TOOL_SELECTRECT 5 #define TOOL_VERTSPACE 6 #define TOOL_HAND 7 +#define TOOL_IMAGE 8 #define NUM_STROKE_TOOLS 3 -#define NUM_TOOLS 8 +#define NUM_TOOLS 9 #define NUM_BUTTONS 3 #define TOOLOPT_ERASER_STANDARD 0 @@ -173,6 +174,10 @@ typedef struct Item { gchar *font_name; gdouble font_size; GtkWidget *widget; // the widget while text is being edited (ITEM_TEMP_TEXT) + // the following fields for ITEM_IMAGE: + GdkPixbuf *image; // the image + gchar *image_png; // PNG of original image, for save and clipboard + gsize image_png_len; } Item; // item type values for Item.type, UndoItem.type, ui.cur_item_type ... @@ -201,6 +206,7 @@ typedef struct Item { #define ITEM_TEXT_ATTRIB 21 #define ITEM_RESIZESEL 22 #define ITEM_RECOGNIZER 23 +#define ITEM_IMAGE 24 typedef struct Layer { GList *items; // the items on the layer, from bottom to top @@ -275,6 +281,7 @@ typedef struct UIData { gboolean hand_scrollto_pending; char *filename; gchar *default_path; // default path for new notes + gchar *default_image; // path for previous image gboolean view_continuous, fullscreen, maximize_at_start; gboolean in_update_page_stuff; // semaphore to avoid scrollbar retroaction struct Selection *selection; @@ -326,8 +333,8 @@ typedef struct UndoErasureData { typedef struct UndoItem { int type; - struct Item *item; // for ITEM_STROKE, ITEM_TEXT, ITEM_TEXT_EDIT, ITEM_TEXT_ATTRIB - struct Layer *layer; // for ITEM_STROKE, ITEM_ERASURE, ITEM_PASTE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_MOVESEL, ITEM_TEXT, ITEM_TEXT_EDIT, ITEM_RECOGNIZER + struct Item *item; // for ITEM_STROKE, ITEM_TEXT, ITEM_TEXT_EDIT, ITEM_TEXT_ATTRIB, ITEM_IMAGE + struct Layer *layer; // for ITEM_STROKE, ITEM_ERASURE, ITEM_PASTE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_MOVESEL, ITEM_TEXT, ITEM_TEXT_EDIT, ITEM_RECOGNIZER, ITEM_IMAGE struct Layer *layer2; // for ITEM_DELETE_LAYER with val=-1, ITEM_MOVESEL struct Page *page; // for ITEM_NEW_BG_ONE/RESIZE, ITEM_NEW_PAGE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_DELETE_PAGE GList *erasurelist; // for ITEM_ERASURE, ITEM_RECOGNIZER diff --git a/xournal.glade b/xournal.glade index cebf20f..1dacfe5 100644 --- a/xournal.glade +++ b/xournal.glade @@ -607,7 +607,7 @@ True - _Journal + _Page True @@ -988,6 +988,18 @@ + + + True + _Image + True + True + toolsPen + + + + + True @@ -1608,6 +1620,17 @@ + + + True + _Image + True + True + button2Pen + + + + True @@ -1744,6 +1767,17 @@ + + + True + _Image + True + True + button3Pen + + + + True @@ -2430,6 +2464,26 @@ + + + True + Image + Image + True + gtk-orientation-portrait + True + True + False + False + buttonPen + + + + False + True + + + True -- 2.39.2