[dia] svg: allow to paste from clipboard via 'Edit/Paste Image'



commit 10a3da6272bc91d32d77e4ff7df6ff49d0395002
Author: Hans Breuer <hans breuer org>
Date:   Sun Oct 7 00:09:34 2012 +0200

    svg: allow to paste from clipboard via 'Edit/Paste Image'
    
     - API extension to support in memory transfer of data
       to a plug-in; implemented in svg-import.c
     - use "image/svg+xml" atom to identify SVG clipboard data
       and transfer it to ImportFilter::import_mem_func
     - implement special undo handling for import into diagram
       case - listening to additions and recording them regarding
       added layers and objects
    
    Tested with Inkscape and Internet Explorer 10 - seems to work
    flawlessly. For Firefox see https://bugzilla.mozilla.org/show_bug.cgi?id=334801

 app/commands.c            |   82 ++++++++++++++++++++++++++++--
 app/undo.c                |  119 +++++++++++++++++++++++++++++++++++++++++++++
 app/undo.h                |    6 ++
 lib/filter.h              |    5 ++
 plug-ins/svg/svg-import.c |   50 +++++++++++++++----
 5 files changed, 245 insertions(+), 17 deletions(-)
---
diff --git a/app/commands.c b/app/commands.c
index 0bdb7aa..63f39ac 100644
--- a/app/commands.c
+++ b/app/commands.c
@@ -307,16 +307,85 @@ received_clipboard_image_handler(GtkClipboard *clipboard,
   }
 }
 
+static void
+received_clipboard_content_handler (GtkClipboard     *clipboard,
+				    GtkSelectionData *selection_data,
+				    gpointer          data)
+{
+  DDisplay *ddisp = (DDisplay *)data;
+  GdkAtom type_atom;
+  gchar *type_name;
+  gint len;
+
+  if ((len = gtk_selection_data_get_length (selection_data)) > 0) {
+    const guchar *data = gtk_selection_data_get_data (selection_data);
+    type_atom = gtk_selection_data_get_data_type (selection_data);
+    type_name = gdk_atom_name (type_atom);
+    if (type_name && strcmp (type_name, "image/svg+xml") == 0) {
+      DiaImportFilter *ifilter = filter_import_get_by_name ("dia-svg");
+      DiaContext *ctx = dia_context_new (_("Clipboard Paste"));
+
+      if (ifilter->import_mem_func) {
+        Change *change = undo_import_change_setup (ddisp->diagram);
+
+	if (!ifilter->import_mem_func (data, len, DIA_DIAGRAM_DATA (ddisp->diagram),
+				      ctx, ifilter->user_data)) {
+	  /* might become more right some day ;) */
+	  message_error (_("No clipboard handler for '%s'"), type_name);
+	}
+	if (undo_import_change_done (ddisp->diagram, change)) {
+          undo_set_transactionpoint(ddisp->diagram->undo);
+          diagram_modified(ddisp->diagram);
+          diagram_flush(ddisp->diagram);
+	}
+      }
+    }
+    g_print ("Content is %s\n", type_name);
+    g_free (type_name);
+
+  }
+}
+
+static void
+_targets_receive (GtkClipboard *clipboard,
+		  GdkAtom      *atom,
+		  gint          n_atoms,
+		  gpointer      data)
+{
+  GdkAtom try_xml = 0;
+  gint i;
+  gchar *aname;
+
+  for (i = 0; i < n_atoms; ++i) {
+    aname = gdk_atom_name (atom[i]);
+    /* the name of the atom is arbitrary, probably depends on application and OS */
+    if (strcmp (aname, "image/svg+xml") == 0)
+      try_xml = atom[i];
+    g_print ("clipboard-targets %d: %s\n", i, aname);
+    g_free (aname);
+  }
+}
+
 void
 edit_paste_image_callback (GtkAction *action)
 {
+  GtkClipboard *clipboard = gtk_clipboard_get(GDK_NONE);
   DDisplay *ddisp;
+  GdkAtom  svg_atom = gdk_atom_intern_static_string ("image/svg+xml");
 
   ddisp = ddisplay_active();
   if (!ddisp) return;
 
-  gtk_clipboard_request_image (gtk_clipboard_get(GDK_NONE), 
-			       received_clipboard_image_handler, ddisp);
+  gtk_clipboard_request_targets (clipboard, _targets_receive, ddisp);
+  /* a second request call is asynchronuously to the above. I've found no reliable
+   * way to make the latter use information generted by the former
+   */
+  if (gtk_clipboard_wait_for_contents (clipboard, svg_atom))
+    gtk_clipboard_request_contents (clipboard, svg_atom,
+				    received_clipboard_content_handler, ddisp);
+  else
+    gtk_clipboard_request_image (clipboard,
+			         received_clipboard_image_handler, ddisp);
 }
 
 static PropDescription text_prop_singleton_desc[] = {
@@ -336,14 +405,15 @@ make_text_prop_singleton(GPtrArray **props, TextProperty **prop)
 
 static GtkTargetEntry target_entries[] = {
   { "image/svg", GTK_TARGET_OTHER_APP, 1 },
-  { "image/png", GTK_TARGET_OTHER_APP, 2 },
-  { "image/bmp", GTK_TARGET_OTHER_APP, 3 },
+  { "image/svg+xml", GTK_TARGET_OTHER_APP, 1 }, /* intentionally pointing to the first element */
+  { "image/png", GTK_TARGET_OTHER_APP, 3 },
+  { "image/bmp", GTK_TARGET_OTHER_APP, 4 },
 #ifdef G_OS_WIN32
   /* this is not working on win32 either, maybe we need to register it with
    * CF_ENHMETAFILE in Gtk+? Change order? Direct use of SetClipboardData()?
    */
-  { "image/emf", GTK_TARGET_OTHER_APP, 4 },
-  { "image/wmf", GTK_TARGET_OTHER_APP, 5 },
+  { "image/emf", GTK_TARGET_OTHER_APP, 5 },
+  { "image/wmf", GTK_TARGET_OTHER_APP, 6 },
 #endif
 };
 
diff --git a/app/undo.c b/app/undo.c
index bf060be..733a016 100644
--- a/app/undo.c
+++ b/app/undo.c
@@ -1304,6 +1304,124 @@ undo_move_object_other_layer(Diagram *dia, GList *selected_list,
   return change;
 }
 
+typedef struct _ImportChange {
+  Change change;
+
+  Diagram *dia;     /*!< the diagram under inspection */
+  GList   *layers;  /*!< layers added */
+  GList   *objects; /*!< objects added */
+} ImportChange;
+static void
+_import_change_apply (ImportChange *change,
+		      Diagram      *dia)
+{
+  GList *list;
+  Layer *layer = dia->data->active_layer;
+
+  /* add all objects and layers added from the diagram */
+  for (list = change->layers; list != NULL; list = list->next) {
+    layer = (Layer *)list->data;
+    data_add_layer (DIA_DIAGRAM_DATA(change->dia), layer);
+  }
+  for (list = change->objects; list != NULL; list = list->next) {
+    DiaObject *obj = (DiaObject *)list->data;
+     /* ToDo: layer assignment wont be triggered for removed objects.
+      *   Maybe we need to store all the layers with the objects ourself?
+      */
+    if (dia_object_get_parent_layer (obj))
+      layer = dia_object_get_parent_layer (obj);
+    layer_add_object (layer, obj);
+  }
+  diagram_update_extents (change->dia);
+}
+static void
+_import_change_revert (ImportChange *change,
+		       Diagram      *dia)
+{
+  GList *list;
+
+  /* otherwise we might end up with an empty selection */
+  diagram_unselect_objects (change->dia, change->objects);
+  /* remove all objects and layers added from the diagram */
+  for (list = change->objects; list != NULL; list = list->next) {
+    DiaObject *obj = (DiaObject *)list->data;
+    Layer *layer = dia_object_get_parent_layer (obj);
+    layer_remove_object (layer, obj);
+  }
+  for (list = change->layers; list != NULL; list = list->next) {
+    Layer *layer = (Layer *)list->data;
+    data_remove_layer (DIA_DIAGRAM_DATA(change->dia), layer);
+  }
+  diagram_update_extents (change->dia);
+}
+static void
+_import_change_free (ImportChange *change)
+{
+  g_return_if_fail (change->dia != NULL);
+
+  /* FIXME: do we need to delete more? */
+  g_list_free (change->objects);
+  g_list_free (change->layers);
+}
+
+/* listen on the diagram for object add */
+static void
+_import_object_add (DiagramData  *dia,
+		    Layer        *layer,
+		    DiaObject    *obj,
+		    ImportChange *change)
+{
+  g_return_if_fail (change->dia == DIA_DIAGRAM(dia));
+
+  if (!obj)
+    change->layers = g_list_prepend (change->layers, layer);
+  else
+    change->objects = g_list_prepend (change->objects, obj);
+}
+/*!
+ * \brief Create an import change object listening on diagram additions
+ * @param dia the diagram to watch
+ */
+Change *
+undo_import_change_setup (Diagram *dia)
+{
+  ImportChange *change = g_new0 (ImportChange, 1);
+
+  change->dia = dia;
+  change->change.apply  = (UndoApplyFunc)  _import_change_apply;
+  change->change.revert = (UndoRevertFunc) _import_change_revert;
+  change->change.free   = (UndoFreeFunc)   _import_change_free;
+
+  g_signal_connect (G_OBJECT(dia), "object_add", G_CALLBACK(_import_object_add), change);
+
+  return &change->change;  
+}
+/*!
+ * \brief Finish listening on the diagram changes
+ * @param chg the change object created by undo_import_change_setup()
+ * @return TRUE if the change was added to the undo stack, FALSE otherwise
+ */
+gboolean
+undo_import_change_done (Diagram *dia, Change *chg)
+{
+  ImportChange *change = (ImportChange *)chg;
+  
+  /* Some type checking first */
+  g_return_val_if_fail (change != NULL, FALSE);
+  g_return_val_if_fail (change->change.apply == (UndoApplyFunc)_import_change_apply, FALSE);
+  g_return_val_if_fail (change->change.revert == (UndoRevertFunc)_import_change_revert, FALSE);
+  g_return_val_if_fail (change->change.free == (UndoFreeFunc)_import_change_free, FALSE);
+
+  /* stop listening on this diagram */
+  g_signal_handlers_disconnect_by_func (change->dia, _import_object_add, change);
+
+  if (change->layers != NULL || change->objects != NULL) {
+    undo_push_change(dia->undo, (Change *) change);
+    return TRUE;
+  }
+  return FALSE;
+}
+
 typedef struct _MemSwapChange {
   Change change;
   
@@ -1324,6 +1442,7 @@ _swap_mem(MemSwapChange *change, Diagram *dia)
   diagram_add_update_all(dia);
   diagram_flush(dia);
 }
+
 /*!
  * \brief Record a memory region for undo (before actually changing it)
  *
diff --git a/app/undo.h b/app/undo.h
index 267dd36..4b2e4fc 100644
--- a/app/undo.h
+++ b/app/undo.h
@@ -83,6 +83,12 @@ Change *undo_parenting(Diagram *dia, DiaObject *parentobj, DiaObject *childobj,
 		       gboolean parent);
 Change *undo_move_object_other_layer(Diagram *diagram, GList *selected_list,
 				     gboolean moving_up);
+
+/* Create an import change object listening on diagram additions/removals */
+Change *undo_import_change_setup (Diagram *diagram);
+/* Finish listening on the diagram changes */
+gboolean undo_import_change_done (Diagram *dia, Change *chg);
+
 /* handle with care, just plain memory copy */
 Change *undo_change_memswap (Diagram *dia, gpointer dest, gsize size);
 
diff --git a/lib/filter.h b/lib/filter.h
index 6c24411..107c657 100644
--- a/lib/filter.h
+++ b/lib/filter.h
@@ -58,6 +58,9 @@ struct _DiaExportFilter {
 /* returns FALSE on error loading diagram */
 typedef gboolean (* DiaImportFunc) (const gchar* filename, DiagramData *dia, 
                                     DiaContext *ctx, void* user_data);
+/* load from given memory block instead of from file */
+typedef gboolean (* DiaImportMemFunc) (const guchar *p, guint size, DiagramData *dia,
+				       DiaContext *ctx, void *user_data);
 
 struct _DiaImportFilter {
   const gchar *description;
@@ -74,6 +77,8 @@ struct _DiaImportFilter {
   const gchar *unique_name;
   /* additional hints for export */
   guint hints;
+  /* Recent addition comin last for compatibility - check for NULL before calling */
+  DiaImportMemFunc import_mem_func;
 };
 
 /* gets called as menu callback */
diff --git a/plug-ins/svg/svg-import.c b/plug-ins/svg/svg-import.c
index 74e2f6c..d1a9074 100644
--- a/plug-ins/svg/svg-import.c
+++ b/plug-ins/svg/svg-import.c
@@ -49,7 +49,7 @@
 #include "font.h"
 #include "attributes.h"
 
-gboolean import_svg(const gchar *filename, DiagramData *dia, DiaContext *ctx, void* user_data);
+static gboolean import_svg (xmlDocPtr doc, DiagramData *dia, DiaContext *ctx, void* user_data);
 static GList *read_ellipse_svg(xmlNodePtr node, DiaSvgStyle *parent_style, GList *list);
 static GList *read_rect_svg(xmlNodePtr node, DiaSvgStyle *parent_style, GList *list);
 static GList *read_line_svg(xmlNodePtr node, DiaSvgStyle *parent_style, GList *list);
@@ -1268,21 +1268,45 @@ import_shape_info (xmlNodePtr start_node, DiagramData *dia, DiaContext *ctx)
   return TRUE;
 }
 
+gboolean
+import_memory_svg (const guchar *p, guint size, DiagramData *dia,
+		   DiaContext *ctx, void *user_data)
+{
+  xmlDocPtr doc = xmlParseMemory (p, size);
+
+  if (!doc) {
+    xmlErrorPtr err = xmlGetLastError ();
+
+    dia_context_add_message(ctx, _("Parse error for memory block.\n%s"), err->message);
+    return FALSE;
+  }
+  return import_svg (doc, dia, ctx, user_data);
+}
+
 /* imports the given SVG file, returns TRUE if successful */
 gboolean
-import_svg(const gchar *filename, DiagramData *dia, DiaContext *ctx, void* user_data) 
+import_file_svg(const gchar *filename, DiagramData *dia, DiaContext *ctx, void* user_data) 
 {
   xmlDocPtr doc = xmlDoParseFile(filename);
-  xmlNsPtr svg_ns;
-  xmlNodePtr root;
-  xmlNodePtr shape_root = NULL;
-  GList *items, *item;
 
   if (!doc) {
     dia_context_add_message(ctx, _("Parse error for %s"), 
 		            dia_context_get_filename (ctx));
     return FALSE;
   }
+  return import_svg (doc, dia, ctx, user_data);
+}
+
+gboolean
+import_svg (xmlDocPtr doc, DiagramData *dia,
+	    DiaContext *ctx, void *user_data)
+{
+  xmlNsPtr svg_ns;
+  xmlNodePtr root;
+  xmlNodePtr shape_root = NULL;
+  GList *items, *item;
+  guint num_items = 0;
+
   /* skip (emacs) comments */
   root = doc->xmlRootNode;
   while (root && (root->type != XML_ELEMENT_NODE)) root = root->next;
@@ -1369,18 +1393,20 @@ import_svg(const gchar *filename, DiagramData *dia, DiaContext *ctx, void* user_
 
   {
     GHashTable *defs_ht = g_hash_table_new (g_str_hash, g_str_equal);
-    items = read_items (root->xmlChildrenNode, NULL, defs_ht, filename, ctx);
+    items = read_items (root->xmlChildrenNode, NULL, defs_ht, dia_context_get_filename(ctx), ctx);
     g_hash_table_destroy (defs_ht);
   }
   /* Every top level item which is a group with a name/id we are converting
    * back to a layer. This is consistent with our SVG export and does
    * also work with layers from Sodipodi/Inkscape/iDraw/...
+   * But if there is just one item - even if a group - it is put into the active layer.
    */
+  num_items = g_list_length (items);
   for (item = items; item != NULL; item = g_list_next (item)) {
     DiaObject *obj = (DiaObject *)item->data;
     gchar *name = NULL;
 
-    if (IS_GROUP(obj) && ((name = dia_object_get_meta (obj, "id")) != NULL)) {
+    if (num_items > 1 && IS_GROUP(obj) && ((name = dia_object_get_meta (obj, "id")) != NULL)) {
       DiaObject *group = (DiaObject *)item->data;
       /* new_layer() is taking ownership of the name */
       Layer *layer = new_layer (g_strdup (name), dia);
@@ -1394,6 +1420,7 @@ import_svg(const gchar *filename, DiagramData *dia, DiaContext *ctx, void* user_
       /* Just as before: throw it in the active layer */
       DiaObject *obj = (DiaObject *)item->data;
       layer_add_object(dia->active_layer, obj);
+      layer_update_extents(dia->active_layer);
     }
 
   }
@@ -1416,8 +1443,9 @@ static const gchar *extensions[] = {"svg", NULL };
 DiaImportFilter svg_import_filter = {
 	N_("Scalable Vector Graphics"),
 	extensions,
-	import_svg,
+	import_file_svg,
 	NULL, /* user_data */
-	"dia-svg"
+	"dia-svg",
+	0, /* flags */
+	import_memory_svg
 };
-



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]