]> git.donarmstrong.com Git - xournal.git/commitdiff
Image patch
authorauroux <auroux>
Thu, 28 Jun 2012 22:20:32 +0000 (22:20 +0000)
committerauroux <auroux>
Thu, 28 Jun 2012 22:20:32 +0000 (22:20 +0000)
23 files changed:
AUTHORS
ChangeLog
configure
configure.in
src/Makefile.am
src/Makefile.in
src/TODO
src/xo-callbacks.c
src/xo-callbacks.h
src/xo-clipboard.c [new file with mode: 0644]
src/xo-clipboard.h [new file with mode: 0644]
src/xo-file.c
src/xo-image.c [new file with mode: 0644]
src/xo-image.h [new file with mode: 0644]
src/xo-interface.c
src/xo-misc.c
src/xo-misc.h
src/xo-paint.c
src/xo-paint.h
src/xo-print.c
src/xo-print.h
src/xournal.h
xournal.glade

diff --git a/AUTHORS b/AUTHORS
index fadb86c03fe94909024af9eb199f03de8883cd11..7950cc0a06fdef3e495db9c55fdfdfcc18e4b654 100644 (file)
--- 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)
index dd5f70a047b239043342c3048e82174bee5eaeb9..c4f2fd045154ff862f5cbbb4524e31856e676164 100644 (file)
--- 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)
index f3c87c82ec8467853434d09235f96c6df4cb7219..1f0cd5d7d8e8fe4421a9571b575400b1909d88c7 100755 (executable)
--- 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
index 5d4aa09d3c0b5e9863ecda0aa727eecbfcd0407a..99d500bb64b838b1453fb8ec8e6686a1e82260af 100644 (file)
@@ -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
 
index 03ecd359df28532db8639bab416b07a7919bad95..55aa52c6a4533b66073f3bf44e239f58feaaa2bb 100644 (file)
@@ -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
 
index 45ebffa3456dde36eba7bfdb0c2d60d4725dfe27..a0bbbe9757ba7e4a8dc0199a0ca32451a27c2d8f 100644 (file)
@@ -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@
index e6d19ed52d5fcffca02818d1e56acf954918e0e6..01bdcd932fd14a11a55fa50930e78de0ed45b7ae 100644 (file)
--- 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)
index 3b2b3199421c8e5c5c034d0b1721ef69081735cc..8c6f098a1cdc88566305da6b6181386ad3960a0d 100644 (file)
@@ -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));
 }
 
+
index d3d173ed5c5ea0fa69a6c3d1b6facd3af405f01e..bce8b486e40daf0c8b537f66713d44d95303ffe0 100644 (file)
@@ -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 (file)
index 0000000..3aa3653
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+
+#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; i<npts; i++) {
+        item->path->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 (file)
index 0000000..4007fa5
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+void selection_to_clip(void);
+void clipboard_paste(void);
index 81ce1569453295f2658c307c21bfd0344b7e87dc..129ba87873f8cf920491278c554178bfb9cccbf9 100644 (file)
@@ -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, "</text>\n");
           g_free(tmpstr);
         }
+        if (item->type == ITEM_IMAGE) {
+          gzprintf(f, "<image left=\"%.2f\" top=\"%.2f\" right=\"%.2f\" bottom=\"%.2f\">", 
+            item->bbox.left, item->bbox.top, item->bbox.right, item->bbox.bottom);
+          if (!write_image(f, item)) success = FALSE;
+          gzprintf(f, "</image>\n");
+        }
       }
       gzprintf(f, "</layer>\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 (file)
index 0000000..884bf23
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#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 (file)
index 0000000..e0cb8fa
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+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);
index f6022eaccc314bd395d5963f5251500597dac383..00a72d1780f0ebb06a7c6e95bdd895aa60d99ff4 100644 (file)
@@ -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");
index bc4747cd35e28d75c26f1738610c599e13fada41..bd65f1afbede9a8a3a9cde569a3c0a197a1cb581 100644 (file)
@@ -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; i<item->path->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;
index da1433dcfdae007ac51bcf5722cfcb1410c53f4d..617bbda92da29dcae0cffdf02b9f8f15b281c68d 100644 (file)
@@ -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);
index c777482546691566c085be2dba87571d7be913c3..33576f8a21cca3fc5ccbef072e631fb5d9f4e361 100644 (file)
@@ -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; i<npts; i++) {
-        item->path->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)
index 4ee4e40d9fa99984c69351c22b39a7230f69abf4..8d69bd68efed31358f82d5a03894aefcec003b77 100644 (file)
@@ -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);
index 2cfc18ecd46dac68ac5b1581e504732e82a6d16c..029500ecba345d808c4ce4901227b185294dc32e 100644 (file)
@@ -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; y<height; y++) {
+    p1 = (char *)gdk_pixbuf_get_pixels(image->pixbuf)+stride*y;
+    for (x=0; x<width; x++) {
+      *(p2++)=*(p1++); *(p2++)=*(p1++); *(p2++)=*(p1++);
+      if (chan==4) p1++;
+    }
+  }
+  zpix = do_deflate(buf, 3*width*height);
+  g_free(buf);
+
+  xref->data[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; y<height; y++) {
+      p1 = (char *)gdk_pixbuf_get_pixels(image->pixbuf)+stride*y;
+      for (x=0; x<width; x++) {
+        p1+=3;                  /* skip the RGB */
+        *(p2++)=*(p1++);        /* just copy the alpha */
+      }
+    }
+    zpix = do_deflate(buf, width*height);
+    g_free(buf);
+    
+    xref->data[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);
+      }
     }
   }
 }
index 95a96f7e826b36d62e72ceb83a4cfad5d9dc9feb..498be84f0053b5d0a45404ec2effcf75af8fc5d2 100644 (file)
@@ -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
index 605c8232e133af4c992857e0303b7a3751faae74..9f801cc7154fe2a0ce7ebf49d8ebe4c320a6d606 100644 (file)
@@ -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
index cebf20f927625e630d090a01e70f653ec9de732d..1dacfe5df01d4ee27499eb6c0a23f7b5e4aa8423 100644 (file)
          <child>
            <widget class="GtkMenuItem" id="menuJournal">
              <property name="visible">True</property>
-             <property name="label" translatable="yes">_Journal</property>
+             <property name="label" translatable="yes">_Page</property>
              <property name="use_underline">True</property>
 
              <child>
                    </widget>
                  </child>
 
+                 <child>
+                   <widget class="GtkRadioMenuItem" id="toolsImage">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_Image</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">True</property>
+                     <property name="group">toolsPen</property>
+                     <signal name="toggled" handler="on_toolsImage_activate" last_modification_time="Wed, 27 Jun 2012 20:54:08 GMT"/>
+                     <accelerator key="I" modifiers="GDK_CONTROL_MASK | GDK_SHIFT_MASK" signal="activate"/>
+                   </widget>
+                 </child>
+
                  <child>
                    <widget class="GtkSeparatorMenuItem" id="separator15">
                      <property name="visible">True</property>
                            </widget>
                          </child>
 
+                         <child>
+                           <widget class="GtkRadioMenuItem" id="button2Image">
+                             <property name="visible">True</property>
+                             <property name="label" translatable="yes">_Image</property>
+                             <property name="use_underline">True</property>
+                             <property name="active">True</property>
+                             <property name="group">button2Pen</property>
+                             <signal name="activate" handler="on_button2Image_activate" last_modification_time="Wed, 27 Jun 2012 14:33:10 GMT"/>
+                           </widget>
+                         </child>
+
                          <child>
                            <widget class="GtkRadioMenuItem" id="button2SelectRegion">
                              <property name="visible">True</property>
                            </widget>
                          </child>
 
+                         <child>
+                           <widget class="GtkRadioMenuItem" id="button3Image">
+                             <property name="visible">True</property>
+                             <property name="label" translatable="yes">_Image</property>
+                             <property name="use_underline">True</property>
+                             <property name="active">True</property>
+                             <property name="group">button3Pen</property>
+                             <signal name="activate" handler="on_button3Image_activate" last_modification_time="Wed, 27 Jun 2012 14:33:10 GMT"/>
+                           </widget>
+                         </child>
+
                          <child>
                            <widget class="GtkRadioMenuItem" id="button3SelectRegion">
                              <property name="visible">True</property>
            </packing>
          </child>
 
+         <child>
+           <widget class="GtkRadioToolButton" id="buttonImage">
+             <property name="visible">True</property>
+             <property name="tooltip" translatable="yes">Image</property>
+             <property name="label" translatable="yes">Image</property>
+             <property name="use_underline">True</property>
+             <property name="stock_id">gtk-orientation-portrait</property>
+             <property name="visible_horizontal">True</property>
+             <property name="visible_vertical">True</property>
+             <property name="is_important">False</property>
+             <property name="active">False</property>
+             <property name="group">buttonPen</property>
+             <signal name="toggled" handler="on_toolsImage_activate" last_modification_time="Wed, 27 Jun 2012 20:49:10 GMT"/>
+           </widget>
+           <packing>
+             <property name="expand">False</property>
+             <property name="homogeneous">True</property>
+           </packing>
+         </child>
+
          <child>
            <widget class="GtkToggleToolButton" id="buttonReco">
              <property name="visible">True</property>