[dia] svg: allow to paste from clipboard via 'Edit/Paste Image'
- From: Hans Breuer <hans src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [dia] svg: allow to paste from clipboard via 'Edit/Paste Image'
- Date: Sun, 7 Oct 2012 13:03:39 +0000 (UTC)
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]