Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 10604)
@@ -84,4 +84,5 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapFrameListener;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.io.SaveLayersDialog;
@@ -178,10 +179,14 @@
     /**
      * The global paste buffer.
-     */
+     * @deprecated Use swing CCP instead. See {@link OsmTransferHandler}
+     */
+    @Deprecated
     public static final PrimitiveDeepCopy pasteBuffer = new PrimitiveDeepCopy();
 
     /**
      * The layer source from which {@link Main#pasteBuffer} data comes from.
-     */
+     * @deprecated During a copy operation, the layer should be added. See {@link OsmLayerTransferData}.
+     */
+    @Deprecated
     public static Layer pasteSource;
 
Index: trunk/src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 10604)
@@ -9,13 +9,15 @@
 import java.awt.event.KeyEvent;
 import java.util.Collection;
+import java.util.Collections;
 
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
 import org.openstreetmap.josm.tools.Shortcut;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -23,9 +25,5 @@
  * @since 404
  */
-public final class CopyAction extends JosmAction {
-
-    /** regular expression that matches text clipboard contents after copying */
-    public static final String CLIPBOARD_REGEXP = "((node|way|relation)\\s\\d+,)*(node|way|relation)\\s\\d+";
-
+public class CopyAction extends JosmAction {
     /**
      * Constructs a new {@code CopyAction}.
@@ -43,8 +41,12 @@
     @Override
     public void actionPerformed(ActionEvent e) {
-        if (isEmptySelection()) return;
-        Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
+        DataSet set = getLayerManager().getEditDataSet();
+        Collection<OsmPrimitive> selection = set == null ? Collections.<OsmPrimitive>emptySet() : set.getSelected();
+        if (selection.isEmpty()) {
+            showEmptySelectionWarning();
+            return;
+        }
 
-        copy(getLayerManager().getEditLayer(), selection);
+        copy(selection);
     }
 
@@ -52,22 +54,9 @@
      * Copies the given primitive ids to the clipboard. The output by this function
      * looks similar to: node 1089302677,node 1089303458,way 93793372
-     * @param source The OSM data layer source
      * @param primitives The OSM primitives to copy
      */
-    public static void copy(OsmDataLayer source, Collection<OsmPrimitive> primitives) {
+    public static void copy(Collection<OsmPrimitive> primitives) {
         // copy ids to the clipboard
-        String ids = getCopyString(primitives);
-        Utils.copyToClipboard(ids);
-
-        Main.pasteBuffer.makeCopy(primitives);
-        Main.pasteSource = source;
-    }
-
-    static String getCopyString(Collection<? extends OsmPrimitive> primitives) {
-        StringBuilder idsBuilder = new StringBuilder();
-        for (OsmPrimitive p : primitives) {
-            idsBuilder.append(OsmPrimitiveType.from(p).getAPIName()).append(' ').append(p.getId()).append(',');
-        }
-        return idsBuilder.substring(0, idsBuilder.length() - 1);
+        ClipboardUtils.copy(new PrimitiveTransferable(PrimitiveTransferData.getDataWithReferences(primitives)));
     }
 
@@ -82,16 +71,11 @@
     }
 
-    private boolean isEmptySelection() {
-        Collection<OsmPrimitive> sel = getLayerManager().getEditDataSet().getSelected();
-        if (sel.isEmpty()) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("Please select something to copy."),
-                    tr("Information"),
-                    JOptionPane.INFORMATION_MESSAGE
-            );
-            return true;
-        }
-        return false;
+    protected void showEmptySelectionWarning() {
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                tr("Please select something to copy."),
+                tr("Information"),
+                JOptionPane.INFORMATION_MESSAGE
+        );
     }
 }
Index: trunk/src/org/openstreetmap/josm/actions/CopyCoordinatesAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CopyCoordinatesAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/actions/CopyCoordinatesAction.java	(revision 10604)
@@ -12,4 +12,5 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.Utils;
@@ -35,5 +36,5 @@
             s.append('\n');
         }
-        Utils.copyToClipboard(s.toString().trim());
+        ClipboardUtils.copyString(s.toString().trim());
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 10604)
@@ -12,7 +12,12 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
+import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
 import org.openstreetmap.josm.tools.Shortcut;
 
+/**
+ * An action that dupplicates the given nodes. They are not added to the clipboard.
+ */
 public final class DuplicateAction extends JosmAction {
 
@@ -22,5 +27,5 @@
     public DuplicateAction() {
         super(tr("Duplicate"), "duplicate",
-                tr("Duplicate selection by copy and immediate paste."),
+                tr("Duplicate selection."),
                 Shortcut.registerShortcut("system:duplicate", tr("Edit: {0}", tr("Duplicate")), KeyEvent.VK_D, Shortcut.CTRL), true);
         putValue("help", ht("/Action/Duplicate"));
@@ -29,6 +34,6 @@
     @Override
     public void actionPerformed(ActionEvent e) {
-        Main.main.menu.paste.pasteData(
-                new PrimitiveDeepCopy(getLayerManager().getEditDataSet().getSelected()), getLayerManager().getEditLayer(), e);
+        PrimitiveTransferData data = PrimitiveTransferData.getDataWithReferences(getLayerManager().getEditDataSet().getSelected());
+        new OsmTransferHandler().pasteOn(Main.getLayerManager().getEditLayer(), data.getCenter(), new PrimitiveTransferable(data));
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/MapRectifierWMSmenuAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/MapRectifierWMSmenuAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/actions/MapRectifierWMSmenuAction.java	(revision 10604)
@@ -23,4 +23,5 @@
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
 import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
@@ -28,5 +29,4 @@
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Shortcut;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -124,5 +124,5 @@
         JosmTextField tfWmsUrl = new JosmTextField(30);
 
-        String clip = Utils.getClipboardContent();
+        String clip = ClipboardUtils.getClipboardStringContent();
         clip = clip == null ? "" : clip.trim();
         ButtonGroup group = new ButtonGroup();
Index: trunk/src/org/openstreetmap/josm/actions/PasteAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 10604)
@@ -8,24 +8,13 @@
 import java.awt.MouseInfo;
 import java.awt.Point;
+import java.awt.datatransfer.FlavorEvent;
+import java.awt.datatransfer.FlavorListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.command.AddPrimitivesCommand;
 import org.openstreetmap.josm.data.coor.EastNorth;
-import org.openstreetmap.josm.data.osm.NodeData;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
-import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
-import org.openstreetmap.josm.data.osm.RelationData;
-import org.openstreetmap.josm.data.osm.RelationMemberData;
-import org.openstreetmap.josm.data.osm.WayData;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -34,5 +23,7 @@
  * @since 404
  */
-public final class PasteAction extends JosmAction implements PasteBufferChangedListener {
+public final class PasteAction extends JosmAction implements FlavorListener {
+
+    private final OsmTransferHandler transferHandler;
 
     /**
@@ -46,57 +37,10 @@
         Main.registerActionShortcut(this,
                 Shortcut.registerShortcut("system:paste:cua", tr("Edit: {0}", tr("Paste")), KeyEvent.VK_INSERT, Shortcut.SHIFT));
-        Main.pasteBuffer.addPasteBufferChangedListener(this);
+        transferHandler = new OsmTransferHandler();
+        ClipboardUtils.getClipboard().addFlavorListener(this);
     }
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        if (!isEnabled())
-            return;
-        pasteData(Main.pasteBuffer, Main.pasteSource, e);
-    }
-
-    /**
-     * Paste OSM primitives from the given paste buffer and OSM data layer source to the current edit layer.
-     * @param pasteBuffer The paste buffer containing primitive ids to copy
-     * @param source The OSM data layer used to look for primitive ids
-     * @param e The ActionEvent that triggered this operation
-     */
-    public void pasteData(PrimitiveDeepCopy pasteBuffer, Layer source, ActionEvent e) {
-        /* Find the middle of the pasteBuffer area */
-        double maxEast = -1E100;
-        double minEast = 1E100;
-        double maxNorth = -1E100;
-        double minNorth = 1E100;
-        boolean incomplete = false;
-        for (PrimitiveData data : pasteBuffer.getAll()) {
-            if (data instanceof NodeData) {
-                NodeData n = (NodeData) data;
-                if (n.getEastNorth() != null) {
-                    double east = n.getEastNorth().east();
-                    double north = n.getEastNorth().north();
-                    if (east > maxEast) {
-                        maxEast = east;
-                    }
-                    if (east < minEast) {
-                        minEast = east;
-                    }
-                    if (north > maxNorth) {
-                        maxNorth = north;
-                    }
-                    if (north < minNorth) {
-                        minNorth = north;
-                    }
-                }
-            }
-            if (data.isIncomplete()) {
-                incomplete = true;
-            }
-        }
-
-        // Allow to cancel paste if there are incomplete primitives
-        if (incomplete && !confirmDeleteIncomplete()) {
-            return;
-        }
-
         // default to paste in center of map (pasted via menu or cursor not in MapView)
         EastNorth mPosition = Main.map.mapView.getCenter();
@@ -113,101 +57,14 @@
         }
 
-        double offsetEast = mPosition.east() - (maxEast + minEast)/2.0;
-        double offsetNorth = mPosition.north() - (maxNorth + minNorth)/2.0;
-
-        // Make a copy of pasteBuffer and map from old id to copied data id
-        List<PrimitiveData> bufferCopy = new ArrayList<>();
-        List<PrimitiveData> toSelect = new ArrayList<>();
-        Map<Long, Long> newNodeIds = new HashMap<>();
-        Map<Long, Long> newWayIds = new HashMap<>();
-        Map<Long, Long> newRelationIds = new HashMap<>();
-        for (PrimitiveData data: pasteBuffer.getAll()) {
-            if (data.isIncomplete()) {
-                continue;
-            }
-            PrimitiveData copy = data.makeCopy();
-            copy.clearOsmMetadata();
-            if (data instanceof NodeData) {
-                newNodeIds.put(data.getUniqueId(), copy.getUniqueId());
-            } else if (data instanceof WayData) {
-                newWayIds.put(data.getUniqueId(), copy.getUniqueId());
-            } else if (data instanceof RelationData) {
-                newRelationIds.put(data.getUniqueId(), copy.getUniqueId());
-            }
-            bufferCopy.add(copy);
-            if (pasteBuffer.getDirectlyAdded().contains(data)) {
-                toSelect.add(copy);
-            }
-        }
-
-        // Update references in copied buffer
-        for (PrimitiveData data:bufferCopy) {
-            if (data instanceof NodeData) {
-                NodeData nodeData = (NodeData) data;
-                if (Main.getLayerManager().getEditLayer() == source) {
-                    nodeData.setEastNorth(nodeData.getEastNorth().add(offsetEast, offsetNorth));
-                }
-            } else if (data instanceof WayData) {
-                List<Long> newNodes = new ArrayList<>();
-                for (Long oldNodeId: ((WayData) data).getNodes()) {
-                    Long newNodeId = newNodeIds.get(oldNodeId);
-                    if (newNodeId != null) {
-                        newNodes.add(newNodeId);
-                    }
-                }
-                ((WayData) data).setNodes(newNodes);
-            } else if (data instanceof RelationData) {
-                List<RelationMemberData> newMembers = new ArrayList<>();
-                for (RelationMemberData member: ((RelationData) data).getMembers()) {
-                    OsmPrimitiveType memberType = member.getMemberType();
-                    Long newId;
-                    switch (memberType) {
-                    case NODE:
-                        newId = newNodeIds.get(member.getMemberId());
-                        break;
-                    case WAY:
-                        newId = newWayIds.get(member.getMemberId());
-                        break;
-                    case RELATION:
-                        newId = newRelationIds.get(member.getMemberId());
-                        break;
-                    default: throw new AssertionError();
-                    }
-                    if (newId != null) {
-                        newMembers.add(new RelationMemberData(member.getRole(), memberType, newId));
-                    }
-                }
-                ((RelationData) data).setMembers(newMembers);
-            }
-        }
-
-        /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
-        Main.main.undoRedo.add(new AddPrimitivesCommand(bufferCopy, toSelect));
-        Main.map.mapView.repaint();
-    }
-
-    private static boolean confirmDeleteIncomplete() {
-        ExtendedDialog ed = new ExtendedDialog(Main.parent,
-                tr("Delete incomplete members?"),
-                new String[] {tr("Paste without incomplete members"), tr("Cancel")});
-        ed.setButtonIcons(new String[] {"dialogs/relation/deletemembers", "cancel"});
-        ed.setContent(tr("The copied data contains incomplete objects.  "
-                + "When pasting the incomplete objects are removed.  "
-                + "Do you want to paste the data without the incomplete objects?"));
-        ed.showDialog();
-        return ed.getValue() == 1;
+        transferHandler.pasteOn(Main.getLayerManager().getEditLayer(), mPosition);
     }
 
     @Override
     protected void updateEnabledState() {
-        if (getLayerManager().getEditDataSet() == null || Main.pasteBuffer == null) {
-            setEnabled(false);
-            return;
-        }
-        setEnabled(!Main.pasteBuffer.isEmpty());
+        setEnabled(getLayerManager().getEditDataSet() != null && transferHandler.isDataAvailable());
     }
 
     @Override
-    public void pasteBufferChanged(PrimitiveDeepCopy pasteBuffer) {
+    public void flavorsChanged(FlavorEvent e) {
         updateEnabledState();
     }
Index: trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 10604)
@@ -26,8 +26,8 @@
 import org.openstreetmap.josm.data.osm.TagCollection;
 import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
 import org.openstreetmap.josm.tools.I18n;
 import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.TextTagParser;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -42,4 +42,5 @@
 
     private static final String help = ht("/Action/PasteTags");
+    private final OsmTransferHandler transferHandler = new OsmTransferHandler();
 
     /**
@@ -54,4 +55,7 @@
     }
 
+    /**
+     * Used to update the tags.
+     */
     public static class TagPaster {
 
@@ -259,11 +263,5 @@
             return;
 
-        String buf = Utils.getClipboardContent();
-        if (buf == null || buf.isEmpty() || buf.matches(CopyAction.CLIPBOARD_REGEXP)) {
-            pasteTagsFromJOSMBuffer(selection);
-        } else {
-            // Paste tags from arbitrary text
-            pasteTagsFromText(selection, buf);
-        }
+        transferHandler.pasteTags(selection);
     }
 
@@ -290,22 +288,4 @@
         commitCommands(selection, commands);
         return !commands.isEmpty();
-    }
-
-    /**
-     * Paste tags from JOSM buffer
-     * @param selection objects that will have the tags
-     * @return false if JOSM buffer was empty
-     */
-    public static boolean pasteTagsFromJOSMBuffer(Collection<OsmPrimitive> selection) {
-        List<PrimitiveData> directlyAdded = Main.pasteBuffer.getDirectlyAdded();
-        if (directlyAdded == null || directlyAdded.isEmpty()) return false;
-
-        PasteTagsAction.TagPaster tagPaster = new PasteTagsAction.TagPaster(directlyAdded, selection);
-        List<Command> commands = new ArrayList<>();
-        for (Tag tag : tagPaster.execute()) {
-            commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue()) ? null : tag.getValue()));
-        }
-        commitCommands(selection, commands);
-        return true;
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 10604)
@@ -81,4 +81,5 @@
         oos.writeInt(changesetId);
         oos.writeInt(timestamp);
+        oos.writeObject(keys);
         oos.defaultWriteObject();
     }
@@ -92,4 +93,5 @@
         changesetId = ois.readInt();
         timestamp = ois.readInt();
+        keys = (String[]) ois.readObject();
         ois.defaultReadObject();
     }
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java	(revision 10604)
@@ -2,28 +2,26 @@
 package org.openstreetmap.josm.data.osm;
 
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
 
-import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
 
 /**
  * This class allows to create and keep a deep copy of primitives. Provides methods to access directly added
  * primitives and reference primitives
+ * <p>
+ * To be removed end of 2016
  * @since 2305
+ * @deprecated This has been replaced by Swing Copy+Paste support. Use {@link OsmTransferHandler} instead.
  */
+@Deprecated
 public class PrimitiveDeepCopy {
-
-    @FunctionalInterface
-    public interface PasteBufferChangedListener {
-        void pasteBufferChanged(PrimitiveDeepCopy pasteBuffer);
-    }
-
-    private final List<PrimitiveData> directlyAdded = new ArrayList<>();
-    private final List<PrimitiveData> referenced = new ArrayList<>();
-    private final CopyOnWriteArrayList<PasteBufferChangedListener> listeners = new CopyOnWriteArrayList<>();
 
     /**
@@ -35,99 +33,19 @@
 
     /**
-     * Constructs a new {@code PrimitiveDeepCopy} of given OSM primitives.
-     * @param primitives OSM primitives to copy
-     * @since 7961
+     * Gets the list of primitives that were explicitly added to this copy.
+     * @return The added primitives
      */
-    public PrimitiveDeepCopy(final Collection<? extends OsmPrimitive> primitives) {
-        makeCopy(primitives);
-    }
-
-    /**
-     * Replace content of the object with copy of provided primitives.
-     * @param primitives OSM primitives to copy
-     * @since 7961
-     */
-    public final void makeCopy(final Collection<? extends OsmPrimitive> primitives) {
-        directlyAdded.clear();
-        referenced.clear();
-
-        final Set<Long> visitedNodeIds = new HashSet<>();
-        final Set<Long> visitedWayIds = new HashSet<>();
-        final Set<Long> visitedRelationIds = new HashSet<>();
-
-        new AbstractVisitor() {
-            private boolean firstIteration;
-
-            @Override
-            public void visit(Node n) {
-                if (!visitedNodeIds.add(n.getUniqueId()))
-                    return;
-                (firstIteration ? directlyAdded : referenced).add(n.save());
-            }
-
-            @Override
-            public void visit(Way w) {
-                if (!visitedWayIds.add(w.getUniqueId()))
-                    return;
-                (firstIteration ? directlyAdded : referenced).add(w.save());
-                firstIteration = false;
-                for (Node n : w.getNodes()) {
-                    visit(n);
-                }
-            }
-
-            @Override
-            public void visit(Relation r) {
-                if (!visitedRelationIds.add(r.getUniqueId()))
-                    return;
-                (firstIteration ? directlyAdded : referenced).add(r.save());
-                firstIteration = false;
-                for (RelationMember m : r.getMembers()) {
-                    m.getMember().accept(this);
-                }
-            }
-
-            public void visitAll() {
-                for (OsmPrimitive osm : primitives) {
-                    firstIteration = true;
-                    osm.accept(this);
-                }
-            }
-        }.visitAll();
-
-        firePasteBufferChanged();
-    }
-
     public List<PrimitiveData> getDirectlyAdded() {
-        return directlyAdded;
-    }
-
-    public List<PrimitiveData> getReferenced() {
-        return referenced;
-    }
-
-    public List<PrimitiveData> getAll() {
-        List<PrimitiveData> result = new ArrayList<>(directlyAdded.size() + referenced.size());
-        result.addAll(directlyAdded);
-        result.addAll(referenced);
-        return result;
+        try {
+            PrimitiveTransferData data = (PrimitiveTransferData) ClipboardUtils.getClipboard().getData(PrimitiveTransferData.DATA_FLAVOR);
+            return new ArrayList<>(data.getDirectlyAdded());
+        } catch (UnsupportedFlavorException | IOException e) {
+            Main.debug(e);
+            return Collections.emptyList();
+        }
     }
 
     public boolean isEmpty() {
-        return directlyAdded.isEmpty() && referenced.isEmpty();
-    }
-
-    private void firePasteBufferChanged() {
-        for (PasteBufferChangedListener listener: listeners) {
-            listener.pasteBufferChanged(this);
-        }
-    }
-
-    public void addPasteBufferChangedListener(PasteBufferChangedListener listener) {
-        listeners.addIfAbsent(listener);
-    }
-
-    public void removePasteBufferChangedListener(PasteBufferChangedListener listener) {
-        listeners.remove(listener);
+        return !ClipboardUtils.getClipboard().isDataFlavorAvailable(PrimitiveTransferData.DATA_FLAVOR);
     }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/TagMap.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/TagMap.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/data/osm/TagMap.java	(revision 10604)
@@ -2,9 +2,13 @@
 package org.openstreetmap.josm.data.osm;
 
+import java.io.Serializable;
 import java.util.AbstractMap;
 import java.util.AbstractSet;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.ConcurrentModificationException;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -17,5 +21,6 @@
  * @author Michael Zangl
  */
-public class TagMap extends AbstractMap<String, String> {
+public class TagMap extends AbstractMap<String, String> implements Serializable {
+    static final long serialVersionUID = 1;
     /**
      * We use this array every time we want to represent an empty map.
@@ -108,5 +113,23 @@
      */
     public TagMap() {
-        this(null);
+        this((String[]) null);
+    }
+
+    /**
+     * Create a new tag map and load it from the other map.
+     * @param tags The map to load from.
+     * @since 10604
+     */
+    public TagMap(Map<String, String> tags) {
+        putAll(tags);
+    }
+
+    /**
+     * Copy constructor.
+     * @param tagMap The map to copy from.
+     * @since 10604
+     */
+    public TagMap(TagMap tagMap) {
+        this(tagMap.tags);
     }
 
@@ -210,4 +233,17 @@
 
     /**
+     * Gets a list of all tags contained in this map.
+     * @return The list of tags in the order they were added.
+     * @since 10604
+     */
+    public List<Tag> getTags() {
+        List<Tag> tagList = new ArrayList<>();
+        for (int i = 0; i < tags.length; i += 2) {
+            tagList.add(new Tag(tags[i], tags[i+1]));
+        }
+        return tagList;
+    }
+
+    /**
      * Finds a key in an array that is structured like the {@link #tags} array and returns the position.
      * <p>
Index: trunk/src/org/openstreetmap/josm/gui/MapFrame.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapFrame.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/MapFrame.java	(revision 10604)
@@ -9,5 +9,4 @@
 import java.awt.Dimension;
 import java.awt.Font;
-import java.awt.GraphicsEnvironment;
 import java.awt.GridBagLayout;
 import java.awt.Rectangle;
@@ -197,7 +196,4 @@
 
         mapView = new MapView(Main.getLayerManager(), contentPane, viewportData);
-        if (!GraphicsEnvironment.isHeadless()) {
-            new FileDrop(mapView);
-        }
 
         splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
Index: trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 10604)
@@ -53,4 +53,5 @@
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
 import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
 import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
@@ -581,4 +582,5 @@
             add(c);
         }
+        setTransferHandler(new OsmTransferHandler());
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/ClipboardUtils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/ClipboardUtils.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/ClipboardUtils.java	(revision 10604)
@@ -0,0 +1,154 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer;
+
+import java.awt.GraphicsEnvironment;
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.ClipboardOwner;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * This is a utility class that provides methods useful for general data transfer support.
+ *
+ * @author Michael Zangl
+ * @since 10604
+ */
+public final class ClipboardUtils {
+    private static final class DoNothingClipboardOwner implements ClipboardOwner {
+        @Override
+        public void lostOwnership(Clipboard clpbrd, Transferable t) {
+            // Do nothing
+        }
+    }
+
+    private static Clipboard clipboard;
+
+    private ClipboardUtils() {
+    }
+
+    /**
+     * This method should be used from all of JOSM to access the clipboard.
+     * <p>
+     * It will default to the system clipboard except for cases where that clipboard is not accessible.
+     * @return A clipboard.
+     * @see #getClipboardContent()
+     */
+    public static synchronized Clipboard getClipboard() {
+        // Might be unsupported in some more cases, we need a fake clipboard then.
+        if (clipboard == null) {
+            try {
+                clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+            } catch (HeadlessException e) {
+                Main.warn("Headless. Using fake clipboard.", e);
+                clipboard = new Clipboard("fake");
+            }
+        }
+        return clipboard;
+    }
+
+    /**
+     * Gets the singleton instance of the system selection as a <code>Clipboard</code> object.
+     * This allows an application to read and modify the current, system-wide selection.
+     * @return the system selection as a <code>Clipboard</code>, or <code>null</code> if the native platform does not
+     *         support a system selection <code>Clipboard</code> or if GraphicsEnvironment.isHeadless() returns true
+     * @see Toolkit#getSystemSelection
+     */
+    public static Clipboard getSystemSelection() {
+        if (GraphicsEnvironment.isHeadless()) {
+            return null;
+        } else {
+            return Toolkit.getDefaultToolkit().getSystemSelection();
+        }
+    }
+
+    /**
+     * Gets the clipboard content as string.
+     * @return the content if available, <code>null</code> otherwise.
+     */
+    public static String getClipboardStringContent() {
+        try {
+            Transferable t = getClipboardContent();
+            if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+                return (String) t.getTransferData(DataFlavor.stringFlavor);
+            }
+        } catch (UnsupportedFlavorException | IOException ex) {
+            Main.error(ex);
+        }
+        return null;
+    }
+
+    /**
+     * Extracts clipboard content as {@code Transferable} object. Using this method avoids some problems on some platforms.
+     * @return The content or <code>null</code> if it is not available
+     */
+    public static synchronized Transferable getClipboardContent() {
+        return getClipboardContent(getClipboard());
+    }
+
+    /**
+     * Extracts clipboard content as {@code Transferable} object. Using this method avoids some problems on some platforms.
+     * @param clipboard clipboard from which contents are retrieved
+     * @return clipboard contents if available, {@code null} otherwise.
+     */
+    public static Transferable getClipboardContent(Clipboard clipboard) {
+        Transferable t = null;
+        for (int tries = 0; t == null && tries < 10; tries++) {
+            try {
+                t = clipboard.getContents(null);
+            } catch (IllegalStateException e) {
+                // Clipboard currently unavailable.
+                // On some platforms, the system clipboard is unavailable while it is accessed by another application.
+                Main.trace("Clipboard unavailable.", e);
+                try {
+                    Thread.sleep(1);
+                } catch (InterruptedException ex) {
+                    Main.warn(ex, "InterruptedException in " + Utils.class.getSimpleName()
+                            + " while getting clipboard content");
+                }
+            } catch (NullPointerException e) {
+                // JDK-6322854: On Linux/X11, NPE can happen for unknown reasons, on all versions of Java
+                Main.error(e);
+            }
+        }
+        return t;
+    }
+
+    /**
+     * Copy the given string to the clipboard.
+     * @param s The string to copy.
+     * @return True if the  copy was successful
+     */
+    public static boolean copyString(String s) {
+        return copy(new StringSelection(s));
+    }
+
+    /**
+     * Copies the given transferable to the clipboard. Handles state problems that occur on some platforms.
+     * @param transferable The transferable to copy.
+     * @return True if the copy was successful
+     */
+    public static boolean copy(final Transferable transferable) {
+        return GuiHelper.runInEDTAndWaitAndReturn(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                try {
+                    getClipboard().setContents(transferable, new DoNothingClipboardOwner());
+                    return true;
+                } catch (IllegalStateException ex) {
+                    Main.error(ex);
+                    return false;
+                }
+            }
+        });
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java	(revision 10604)
@@ -0,0 +1,122 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.swing.TransferHandler;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.datatransfer.importers.AbstractOsmDataPaster;
+import org.openstreetmap.josm.gui.datatransfer.importers.FilePaster;
+import org.openstreetmap.josm.gui.datatransfer.importers.PrimitiveDataPaster;
+import org.openstreetmap.josm.gui.datatransfer.importers.TagTransferPaster;
+import org.openstreetmap.josm.gui.datatransfer.importers.TextTagPaster;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * This transfer hanlder provides the ability to transfer OSM data. It allows you to receive files, primitives or tags.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public class OsmTransferHandler extends TransferHandler {
+
+    private static final Collection<AbstractOsmDataPaster> SUPPORTED = Arrays.asList(
+            new FilePaster(), new PrimitiveDataPaster(),
+            new TagTransferPaster(), new TextTagPaster());
+
+    @Override
+    public boolean canImport(TransferSupport support) {
+        // import everything for now, only support copy.
+        for (AbstractOsmDataPaster df : SUPPORTED) {
+            if (df.supports(support)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean importData(TransferSupport support) {
+        return importData(support, Main.getLayerManager().getEditLayer(), null);
+    }
+
+    private boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth center) {
+        for (AbstractOsmDataPaster df : SUPPORTED) {
+            if (df.supports(support)) {
+                try {
+                    if (df.importData(support, layer, center)) {
+                        return true;
+                    }
+                } catch (UnsupportedFlavorException | IOException e) {
+                    Main.warn(e);
+                }
+            }
+        }
+        return super.importData(support);
+    }
+
+    private boolean importTags(TransferSupport support, Collection<? extends OsmPrimitive> primitives) {
+        for (AbstractOsmDataPaster df : SUPPORTED) {
+            if (df.supports(support)) {
+                try {
+                    if (df.importTagsOn(support, primitives)) {
+                        return true;
+                    }
+                } catch (UnsupportedFlavorException | IOException e) {
+                    Main.warn(e);
+                }
+            }
+        }
+        return super.importData(support);
+    }
+
+    /**
+     * Paste the current clipboard current at the given position
+     * @param editLayer The layer to paste on.
+     * @param mPosition The position to paste at.
+     */
+    public void pasteOn(OsmDataLayer editLayer, EastNorth mPosition) {
+        Transferable transferable = ClipboardUtils.getClipboard().getContents(null);
+        pasteOn(editLayer, mPosition, transferable);
+    }
+
+    /**
+     * Paste the given clipboard current at the given position
+     * @param editLayer The layer to paste on.
+     * @param mPosition The position to paste at.
+     * @param transferable The transferable to use.
+     */
+    public void pasteOn(OsmDataLayer editLayer, EastNorth mPosition, Transferable transferable) {
+        importData(new TransferSupport(Main.panel, transferable), editLayer, mPosition);
+    }
+
+    /**
+     * Paste the given tags on the primitives.
+     * @param primitives The primitives to paste on.
+     */
+    public void pasteTags(Collection<? extends OsmPrimitive> primitives) {
+        Transferable transferable = ClipboardUtils.getClipboard().getContents(null);
+        importTags(new TransferSupport(Main.panel, transferable), primitives);
+    }
+
+    /**
+     * Check if any primitive data or any other supported data is available in the clipboard.
+     * @return <code>true</code> if any flavor is supported.
+     */
+    public boolean isDataAvailable() {
+        Collection<DataFlavor> available = Arrays.asList(ClipboardUtils.getClipboard().getAvailableDataFlavors());
+        for (AbstractOsmDataPaster s : SUPPORTED) {
+            if (s.supports(available)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java	(revision 10604)
@@ -5,45 +5,26 @@
 import java.awt.datatransfer.Transferable;
 import java.awt.datatransfer.UnsupportedFlavorException;
-import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Arrays;
+import java.util.List;
 
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.gui.DefaultNameFormatter;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
+import org.openstreetmap.josm.gui.datatransfer.data.TagTransferData;
 
 /**
- * Transferable objects for {@link PrimitiveData}.
+ * Transferable objects for {@link PrimitiveTransferData} objects
  * @since 9369
+ * @since 10604 Complete rework
  */
 public class PrimitiveTransferable implements Transferable {
 
     /**
-     * A wrapper for a collection of {@link PrimitiveData}.
+     * The flavors that are available for normal primitives.
      */
-    public static final class Data implements Serializable {
-        private static final long serialVersionUID = -1485089993600213704L;
-        private final Collection<PrimitiveData> primitiveData;
-
-        private Data(Collection<PrimitiveData> primitiveData) {
-            CheckParameterUtil.ensureThat(primitiveData instanceof Serializable, "primitiveData must be instanceof Serializable");
-            this.primitiveData = primitiveData;
-        }
-
-        /**
-         * Returns the contained {@link PrimitiveData}
-         * @return the contained {@link PrimitiveData}
-         */
-        public Collection<PrimitiveData> getPrimitiveData() {
-            return primitiveData;
-        }
-    }
-
-    /**
-     * Data flavor for {@link PrimitiveData} which is wrapped in {@link Data}.
-     */
-    public static final DataFlavor PRIMITIVE_DATA = new DataFlavor(Data.class, Data.class.getName());
-    private final Collection<? extends OsmPrimitive> primitives;
+    private static final List<DataFlavor> PRIMITIVE_FLAVORS = Arrays.asList(PrimitiveTransferData.DATA_FLAVOR,
+            TagTransferData.FLAVOR, DataFlavor.stringFlavor);
+    private final PrimitiveTransferData primitives;
 
     /**
@@ -51,5 +32,5 @@
      * @param primitives collection of OSM primitives
      */
-    public PrimitiveTransferable(Collection<? extends OsmPrimitive> primitives) {
+    public PrimitiveTransferable(PrimitiveTransferData primitives) {
         this.primitives = primitives;
     }
@@ -57,10 +38,17 @@
     @Override
     public DataFlavor[] getTransferDataFlavors() {
-        return new DataFlavor[]{PRIMITIVE_DATA, DataFlavor.stringFlavor};
+        ArrayList<DataFlavor> flavors = new ArrayList<>(PRIMITIVE_FLAVORS);
+        return flavors.toArray(new DataFlavor[flavors.size()]);
     }
 
     @Override
     public boolean isDataFlavorSupported(DataFlavor flavor) {
-        return flavor == PRIMITIVE_DATA;
+        DataFlavor[] flavors = getTransferDataFlavors();
+        for (DataFlavor f : flavors) {
+            if (flavor.equals(f)) {
+                return true;
+            }
+        }
+        return false;
     }
 
@@ -69,27 +57,22 @@
         if (DataFlavor.stringFlavor.equals(flavor)) {
             return getStringData();
-        } else if (PRIMITIVE_DATA.equals(flavor)) {
-            return getPrimitiveData();
+        } else if (PrimitiveTransferData.DATA_FLAVOR.equals(flavor)) {
+            return primitives;
+        } else if (TagTransferData.FLAVOR.equals(flavor)) {
+            return new TagTransferData(primitives.getDirectlyAdded());
+        } else {
+            throw new UnsupportedFlavorException(flavor);
         }
-        throw new UnsupportedFlavorException(flavor);
     }
 
     protected String getStringData() {
         final StringBuilder sb = new StringBuilder();
-        for (OsmPrimitive primitive : primitives) {
-            sb.append(primitive.getType())
-              .append(' ').append(primitive.getUniqueId())
-              .append(" # ").append(primitive.getDisplayName(DefaultNameFormatter.getInstance()))
-              .append('\n');
+        for (PrimitiveData primitive : primitives.getAll()) {
+            if (sb.length() > 0) {
+                sb.append("\n");
+            }
+            sb.append(OsmPrimitiveType.from(primitive).getAPIName()).append(' ').append(primitive.getId());
         }
         return sb.toString().replace("\u200E", "").replace("\u200F", "");
     }
-
-    protected Data getPrimitiveData() {
-        final Collection<PrimitiveData> r = new ArrayList<>(primitives.size());
-        for (OsmPrimitive primitive : primitives) {
-            r.add(primitive.save());
-        }
-        return new Data(r);
-    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferable.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferable.java	(revision 10604)
@@ -7,9 +7,14 @@
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
 
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.RelationMemberData;
 import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
@@ -37,5 +42,18 @@
          */
         public Collection<RelationMemberData> getRelationMemberData() {
-            return relationMemberDatas;
+            return Collections.unmodifiableCollection(relationMemberDatas);
+        }
+
+        /**
+         * Gets the Data for the given list of members.
+         * @param members The collection. The order is preserved.
+         * @return The data.
+         */
+        public static Data getData(Collection<RelationMember> members) {
+            final Collection<RelationMemberData> r = new ArrayList<>(members.size());
+            for (RelationMember member : members) {
+                r.add(new RelationMemberData(member.getRole(), member.getType(), member.getUniqueId()));
+            }
+            return new Data(r);
         }
     }
@@ -44,5 +62,5 @@
      * Data flavor for {@link RelationMemberData} which is wrapped in {@link Data}.
      */
-    public static final DataFlavor RELATION_MEMBER_DATA = new DataFlavor(Data.class, Data.class.getName());
+    public static final DataFlavor RELATION_MEMBER_DATA = new DataFlavor(Data.class, "Relation member");
     private final Collection<RelationMember> members;
 
@@ -52,15 +70,15 @@
      */
     public RelationMemberTransferable(Collection<RelationMember> members) {
-        this.members = members;
+        this.members = new ArrayList<>(members);
     }
 
     @Override
     public DataFlavor[] getTransferDataFlavors() {
-        return new DataFlavor[]{RELATION_MEMBER_DATA, DataFlavor.stringFlavor};
+        return new DataFlavor[]{RELATION_MEMBER_DATA, PrimitiveTransferData.DATA_FLAVOR, DataFlavor.stringFlavor};
     }
 
     @Override
     public boolean isDataFlavorSupported(DataFlavor flavor) {
-        return flavor == RELATION_MEMBER_DATA;
+        return Arrays.asList(getTransferDataFlavors()).contains(flavor);
     }
 
@@ -71,6 +89,16 @@
         } else if (RELATION_MEMBER_DATA.equals(flavor)) {
             return getRelationMemberData();
+        } else if (PrimitiveTransferData.DATA_FLAVOR.equals(flavor)) {
+            return getPrimitiveData();
         }
         throw new UnsupportedFlavorException(flavor);
+    }
+
+    private PrimitiveTransferData getPrimitiveData() {
+        Collection<OsmPrimitive> primitives = new HashSet<>();
+        for (RelationMember member : members) {
+            primitives.add(member.getMember());
+        }
+        return PrimitiveTransferData.getData(primitives);
     }
 
@@ -88,9 +116,5 @@
 
     protected Data getRelationMemberData() {
-        final Collection<RelationMemberData> r = new ArrayList<>(members.size());
-        for (RelationMember member : members) {
-            r.add(new RelationMemberData(member.getRole(), member.getType(), member.getUniqueId()));
-        }
-        return new Data(r);
+        return Data.getData(members);
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTransferData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTransferData.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTransferData.java	(revision 10604)
@@ -0,0 +1,159 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.data;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.NodeData;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+
+/**
+ * A list of primitives that are transfered. The list allows you to implicitly add primitives.
+ * It distinguishes between primitives that were directly added and implicitly added ones.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public final class PrimitiveTransferData implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The data flavor used to represent this class.
+     */
+    public static final DataFlavor DATA_FLAVOR = new DataFlavor(PrimitiveTransferData.class, "OSM Primitives");
+
+    private static final class GetReferences implements ReferenceGetter {
+        @Override
+        public Collection<? extends OsmPrimitive> getReferedPrimitives(OsmPrimitive primitive) {
+            if (primitive instanceof Way) {
+                return ((Way) primitive).getNodes();
+            } else if (primitive instanceof Relation) {
+                return ((Relation) primitive).getMemberPrimitives();
+            } else {
+                return Collections.emptyList();
+            }
+        }
+    }
+
+    @FunctionalInterface
+    private interface ReferenceGetter {
+        Collection<? extends OsmPrimitive> getReferedPrimitives(OsmPrimitive primitive);
+    }
+
+    private final ArrayList<PrimitiveData> direct;
+    private final ArrayList<PrimitiveData> referenced;
+
+    /**
+     * Create the new transfer data.
+     * @param primitives The primitives to transfer
+     * @param referencedGetter A function that allows to get the primitives referenced by the primitives variable.
+     * It will be queried recursively.
+     */
+    private PrimitiveTransferData(Collection<? extends OsmPrimitive> primitives, ReferenceGetter referencedGetter) {
+        // convert to hash set first to remove dupplicates
+        HashSet<OsmPrimitive> visited = new HashSet<>(primitives);
+        this.direct = new ArrayList<>(visited.size());
+
+        this.referenced = new ArrayList<>();
+        Queue<OsmPrimitive> toCheck = new LinkedList<>();
+        for (OsmPrimitive p : visited) {
+            direct.add(p.save());
+            toCheck.addAll(referencedGetter.getReferedPrimitives(p));
+        }
+        while (!toCheck.isEmpty()) {
+            OsmPrimitive p = toCheck.poll();
+            if (visited.add(p)) {
+                referenced.add(p.save());
+                toCheck.addAll(referencedGetter.getReferedPrimitives(p));
+            }
+        }
+    }
+
+    /**
+     * Gets all primitives directly added.
+     * @return The primitives
+     */
+    public Collection<PrimitiveData> getDirectlyAdded() {
+        return Collections.unmodifiableList(direct);
+    }
+
+    /**
+     * Gets all primitives that were added because they were referenced.
+     * @return The primitives
+     */
+    public Collection<PrimitiveData> getReferenced() {
+        return Collections.unmodifiableList(referenced);
+    }
+
+    /**
+     * Gets a List of all primitives added to this set.
+     * @return That list.
+     */
+    public Collection<PrimitiveData> getAll() {
+        ArrayList<PrimitiveData> list = new ArrayList<>();
+        list.addAll(direct);
+        list.addAll(referenced);
+        return list;
+    }
+
+    /**
+     * Creates a new {@link PrimitiveTransferData} object that only contains the primitives.
+     * @param primitives The primitives to contain.
+     * @return That set.
+     */
+    public static PrimitiveTransferData getData(Collection<? extends OsmPrimitive> primitives) {
+        return new PrimitiveTransferData(primitives, primitive -> Collections.emptyList());
+    }
+
+    /**
+     * Creates a new {@link PrimitiveTransferData} object that contains the primitives and all references.
+     * @param primitives The primitives to contain.
+     * @return That set.
+     */
+    public static PrimitiveTransferData getDataWithReferences(Collection<? extends OsmPrimitive> primitives) {
+        return new PrimitiveTransferData(primitives, new GetReferences());
+    }
+
+    /**
+     * Compute the center of all nodes.
+     * @return The center or null if this buffer has no location.
+     */
+    public EastNorth getCenter() {
+        BoundingXYVisitor visitor = new BoundingXYVisitor();
+        for (PrimitiveData pd : getAll()) {
+            if (pd instanceof NodeData && !pd.isIncomplete()) {
+                visitor.visit(((NodeData) pd).getEastNorth());
+            }
+        }
+        ProjectionBounds bounds = visitor.getBounds();
+        if (bounds == null) {
+            return null;
+        } else {
+            return bounds.getCenter();
+        }
+    }
+
+    /**
+     * Tests wheter this set contains any primitives that have invalid data.
+     * @return <code>true</code> if invalid data is contained in this set.
+     */
+    public boolean hasIncompleteData() {
+        for (PrimitiveData pd : getAll()) {
+            if (pd.isIncomplete()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java	(revision 10604)
@@ -0,0 +1,48 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.data;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.osm.TagMap;
+import org.openstreetmap.josm.data.osm.Tagged;
+
+/**
+ * This is a special transfer type that only transfers tag data.
+ * <p>
+ * It currently contains all tags contained in the selection that was copied.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public class TagTransferData implements Serializable {
+
+    private static final long serialVersionUID = 1;
+
+    /**
+     * This is a data flavor added
+     */
+    public static final DataFlavor FLAVOR = new DataFlavor(TagTransferData.class, "OSM Tags");
+
+    private final TagMap tags = new TagMap();
+
+    /**
+     * Creates a new {@link TagTransferData} object for the given objects.
+     * @param tagged The tags to transfer.
+     */
+    public TagTransferData(Collection<? extends Tagged> tagged) {
+        for (Tagged t : tagged) {
+            tags.putAll(t.getKeys());
+        }
+    }
+
+    /**
+     * Gets all tags contained in this data.
+     * @return The tags.
+     */
+    public Map<String, String> getTags() {
+        return Collections.unmodifiableMap(tags);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractOsmDataPaster.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractOsmDataPaster.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractOsmDataPaster.java	(revision 10604)
@@ -0,0 +1,80 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.importers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.swing.TransferHandler;
+import javax.swing.TransferHandler.TransferSupport;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * This is an abstract class that helps implementing the transfer support required by swing.
+ * <p>
+ * It implements a mechanism to import a given data flavor into the current OSM data layer.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public abstract class AbstractOsmDataPaster {
+    protected final DataFlavor df;
+
+    /**
+     * Create a new {@link AbstractOsmDataPaster}
+     * @param df The data flavor that this support supports.
+     */
+    protected AbstractOsmDataPaster(DataFlavor df) {
+        this.df = df;
+    }
+
+    /**
+     * Checks if this supports importing the given transfer support.
+     * @param support The support that should be supported.
+     * @return True if we support that transfer.
+     */
+    public boolean supports(TransferSupport support) {
+        return support.isDataFlavorSupported(df) && isCopy(support);
+    }
+
+    /**
+     * Checks if this supports any of the available flavors.
+     * @param available The flavors that should be supported
+     * @return True if any of them is supported.
+     */
+    public boolean supports(Collection<DataFlavor> available) {
+        return available.contains(df);
+    }
+
+    private static boolean isCopy(TransferSupport support) {
+        return !support.isDrop() || (TransferHandler.COPY & support.getSourceDropActions()) == TransferHandler.COPY;
+    }
+
+    /**
+     * Attempts to import the given transfer data.
+     * @param support The transfer support to import from.
+     * @param layer The layer to paste at.
+     * @param pasteAt The position to paste at.
+     * @return <code>true</code> if the import was successful.
+     * @throws UnsupportedFlavorException if the requested data flavor is not supported
+     * @throws IOException if an I/O error occurs
+     */
+    public abstract boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth pasteAt)
+            throws UnsupportedFlavorException, IOException;
+
+    /**
+     * Imports only if this import changes the tags only. Does nothing if more than tags would be changed.
+     * @param support The support
+     * @param selection The primitives to apply on.
+     * @return <code>true</code> if an import was done.
+     * @throws UnsupportedFlavorException if the requested data flavor is not supported
+     * @throws IOException if an I/O error occurs
+     */
+    public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection)
+            throws UnsupportedFlavorException, IOException {
+        return false;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java	(revision 10604)
@@ -0,0 +1,56 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.importers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.swing.TransferHandler.TransferSupport;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * This transfer support allows us to transfer tags to the selected primitives
+ * @author Michael Zangl
+ * @since 10604
+ */
+public abstract class AbstractTagPaster extends AbstractOsmDataPaster {
+
+    AbstractTagPaster(DataFlavor df) {
+        super(df);
+    }
+
+    @Override
+    public boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth pasteAt)
+            throws UnsupportedFlavorException, IOException {
+        Collection<OsmPrimitive> selection = layer.data.getSelected();
+        if (selection.isEmpty()) {
+            return false;
+        }
+
+        return importTagsOn(support, selection);
+    }
+
+    @Override
+    public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection)
+            throws UnsupportedFlavorException, IOException {
+        ChangePropertyCommand command = new ChangePropertyCommand(selection, getTags(support));
+        Main.main.undoRedo.add(command);
+        return true;
+    }
+
+    /**
+     * Gets the tags that should be pasted.
+     * @param support The TransferSupport to get the tags from.
+     * @return The tags
+     * @throws UnsupportedFlavorException if the requested data flavor is not supported
+     * @throws IOException if an I/O error occurs
+     */
+    protected abstract Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException;
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/FilePaster.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/FilePaster.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/FilePaster.java	(revision 10604)
@@ -0,0 +1,40 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.importers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import javax.swing.TransferHandler.TransferSupport;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.OpenFileAction;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * This transfer support allows us to import a file that is dropped / copied on to the map.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public final class FilePaster extends AbstractOsmDataPaster {
+    /**
+     * Create a new {@link FilePaster}
+     */
+    public FilePaster() {
+        super(DataFlavor.javaFileListFlavor);
+    }
+
+    @Override
+    public boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth pasteAt)
+            throws UnsupportedFlavorException, IOException {
+        @SuppressWarnings("unchecked")
+        List<File> files = (List<File>) support.getTransferable().getTransferData(df);
+        OpenFileAction.OpenFileTask task = new OpenFileAction.OpenFileTask(files, null);
+        task.setRecordHistory(true);
+        Main.worker.submit(task);
+        return true;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java	(revision 10604)
@@ -0,0 +1,140 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.importers;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.TransferHandler.TransferSupport;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddPrimitivesCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.NodeData;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
+import org.openstreetmap.josm.data.osm.RelationData;
+import org.openstreetmap.josm.data.osm.RelationMemberData;
+import org.openstreetmap.josm.data.osm.WayData;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * This transfer support allows us to transfer primitives. This is the default paste action when primitives were copied.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public final class PrimitiveDataPaster extends AbstractOsmDataPaster {
+    /**
+     * Create a new {@link PrimitiveDataPaster}
+     */
+    public PrimitiveDataPaster() {
+        super(PrimitiveTransferData.DATA_FLAVOR);
+    }
+
+    @Override
+    public boolean importData(TransferSupport support, final OsmDataLayer layer, EastNorth pasteAt)
+            throws UnsupportedFlavorException, IOException {
+        PrimitiveTransferData pasteBuffer = (PrimitiveTransferData) support.getTransferable().getTransferData(df);
+        // Allow to cancel paste if there are incomplete primitives
+        if (pasteBuffer.hasIncompleteData() && !confirmDeleteIncomplete()) {
+            return false;
+        }
+
+        EastNorth center = pasteBuffer.getCenter();
+        EastNorth offset = center == null ? null : pasteAt.subtract(center);
+
+        AddPrimitivesCommand command = createNewPrimitives(pasteBuffer, offset, layer);
+
+        /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
+        Main.main.undoRedo.add(command);
+        return true;
+    }
+
+    private static AddPrimitivesCommand createNewPrimitives(PrimitiveTransferData pasteBuffer, EastNorth offset, OsmDataLayer layer) {
+        // Make a copy of pasteBuffer and map from old id to copied data id
+        List<PrimitiveData> bufferCopy = new ArrayList<>();
+        List<PrimitiveData> toSelect = new ArrayList<>();
+        EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = generateNewPrimitives(pasteBuffer, bufferCopy, toSelect);
+
+        // Update references in copied buffer
+        for (PrimitiveData data : bufferCopy) {
+            if (data instanceof NodeData) {
+                NodeData nodeData = (NodeData) data;
+                nodeData.setEastNorth(nodeData.getEastNorth().add(offset));
+            } else if (data instanceof WayData) {
+                updateNodes(newIds.get(OsmPrimitiveType.NODE), data);
+            } else if (data instanceof RelationData) {
+                updateMembers(newIds, data);
+            }
+        }
+        return new AddPrimitivesCommand(bufferCopy, toSelect, layer);
+    }
+
+    private static EnumMap<OsmPrimitiveType, Map<Long, Long>> generateNewPrimitives(PrimitiveTransferData pasteBuffer,
+            List<PrimitiveData> bufferCopy, List<PrimitiveData> toSelect) {
+        EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = new EnumMap<>(OsmPrimitiveType.class);
+        newIds.put(OsmPrimitiveType.NODE, new HashMap<Long, Long>());
+        newIds.put(OsmPrimitiveType.WAY, new HashMap<Long, Long>());
+        newIds.put(OsmPrimitiveType.RELATION, new HashMap<Long, Long>());
+
+        for (PrimitiveData data : pasteBuffer.getAll()) {
+            if (data.isIncomplete()) {
+                continue;
+            }
+            PrimitiveData copy = data.makeCopy();
+            // don't know why this is reset, but we need it to not crash on copying incomplete nodes.
+            boolean wasIncomplete = copy.isIncomplete();
+            copy.clearOsmMetadata();
+            copy.setIncomplete(wasIncomplete);
+            newIds.get(data.getType()).put(data.getUniqueId(), copy.getUniqueId());
+
+            bufferCopy.add(copy);
+            if (pasteBuffer.getDirectlyAdded().contains(data)) {
+                toSelect.add(copy);
+            }
+        }
+        return newIds;
+    }
+
+    private static void updateMembers(EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds, PrimitiveData data) {
+        List<RelationMemberData> newMembers = new ArrayList<>();
+        for (RelationMemberData member : ((RelationData) data).getMembers()) {
+            OsmPrimitiveType memberType = member.getMemberType();
+            Long newId = newIds.get(memberType).get(member.getMemberId());
+            if (newId != null) {
+                newMembers.add(new RelationMemberData(member.getRole(), memberType, newId));
+            }
+        }
+        ((RelationData) data).setMembers(newMembers);
+    }
+
+    private static void updateNodes(Map<Long, Long> newNodeIds, PrimitiveData data) {
+        List<Long> newNodes = new ArrayList<>();
+        for (Long oldNodeId : ((WayData) data).getNodes()) {
+            Long newNodeId = newNodeIds.get(oldNodeId);
+            if (newNodeId != null) {
+                newNodes.add(newNodeId);
+            }
+        }
+        ((WayData) data).setNodes(newNodes);
+    }
+
+    private static boolean confirmDeleteIncomplete() {
+        ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Delete incomplete members?"),
+                new String[] {tr("Paste without incomplete members"), tr("Cancel")});
+        ed.setButtonIcons(new String[] {"dialogs/relation/deletemembers", "cancel"});
+        ed.setContent(tr(
+                "The copied data contains incomplete objects.  " + "When pasting the incomplete objects are removed.  "
+                        + "Do you want to paste the data without the incomplete objects?"));
+        ed.showDialog();
+        return ed.getValue() == 1;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/TagTransferPaster.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/TagTransferPaster.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/TagTransferPaster.java	(revision 10604)
@@ -0,0 +1,30 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.importers;
+
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.swing.TransferHandler.TransferSupport;
+
+import org.openstreetmap.josm.gui.datatransfer.data.TagTransferData;
+
+/**
+ * This transfer support allows us to transfer tags from the copied primitives on to the selected ones.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public final class TagTransferPaster extends AbstractTagPaster {
+    /**
+     * Create a new {@link TagTransferPaster}
+     */
+    public TagTransferPaster() {
+        super(TagTransferData.FLAVOR);
+    }
+
+    @Override
+    protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
+        TagTransferData data = (TagTransferData) support.getTransferable().getTransferData(df);
+        return data.getTags();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java	(revision 10604)
+++ trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java	(revision 10604)
@@ -0,0 +1,42 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.datatransfer.importers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.swing.TransferHandler.TransferSupport;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.TextTagParser;
+
+/**
+ * This transfer support allows us to import tags from the text that was copied to the clipboard.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public final class TextTagPaster extends AbstractTagPaster {
+
+    /**
+     * Create a new {@link TextTagPaster}
+     */
+    public TextTagPaster() {
+        super(DataFlavor.stringFlavor);
+    }
+
+    @Override
+    public boolean supports(TransferSupport support) {
+        try {
+            return super.supports(support) && getTags(support) != null;
+        } catch (UnsupportedFlavorException | IOException e) {
+            Main.warn(e);
+            return false;
+        }
+    }
+
+    @Override
+    protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
+        return TextTagParser.readTagsFromText((String) support.getTransferable().getTransferData(df));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java	(revision 10604)
@@ -34,4 +34,5 @@
 import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
 import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
 import org.openstreetmap.josm.gui.widgets.HtmlPanel;
@@ -202,5 +203,5 @@
 
     protected void tryToPasteFromClipboard(OsmIdTextField tfId, OsmPrimitiveTypesComboBox cbType) {
-        String buf = Utils.getClipboardContent();
+        String buf = ClipboardUtils.getClipboardStringContent();
         if (buf == null || buf.isEmpty()) return;
         if (buf.length() > Main.pref.getInteger("downloadprimitive.max-autopaste-length", 2000)) return;
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 10604)
@@ -70,4 +70,5 @@
 import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
 import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
@@ -881,5 +882,5 @@
         @Override
         protected Transferable createTransferable(JComponent c) {
-            return new PrimitiveTransferable(getSelectedPrimitives());
+            return new PrimitiveTransferable(PrimitiveTransferData.getData(getSelectedPrimitives()));
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 10604)
@@ -87,4 +87,5 @@
 import org.openstreetmap.josm.gui.PopupMenuHandler;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
@@ -1265,5 +1266,5 @@
             String key = editHelper.getDataKey(tagTable.getSelectedRow());
             Collection<OsmPrimitive> sel = Main.main.getInProgressSelection();
-            String clipboard = Utils.getClipboardContent();
+            String clipboard = ClipboardUtils.getClipboardStringContent();
             if (sel.isEmpty() || clipboard == null)
                 return;
@@ -1295,5 +1296,5 @@
             }
             if (!values.isEmpty()) {
-                Utils.copyToClipboard(Utils.join("\n", values));
+                ClipboardUtils.copyString(Utils.join("\n", values));
             }
         }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java	(revision 10604)
@@ -77,4 +77,5 @@
 import org.openstreetmap.josm.data.preferences.StringProperty;
 import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
@@ -584,7 +585,7 @@
         private void selectACComboBoxSavingUnixBuffer(AutoCompletingComboBox cb) {
             // select combobox with saving unix system selection (middle mouse paste)
-            Clipboard sysSel = GuiHelper.getSystemSelection();
+            Clipboard sysSel = ClipboardUtils.getSystemSelection();
             if (sysSel != null) {
-                Transferable old = Utils.getTransferableContent(sysSel);
+                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
                 cb.requestFocusInWindow();
                 cb.getEditor().selectAll();
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 10604)
@@ -12,4 +12,6 @@
 import java.awt.GridBagLayout;
 import java.awt.Window;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.FlavorListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.FocusAdapter;
@@ -62,4 +64,5 @@
 import org.openstreetmap.josm.gui.DefaultNameFormatter;
 import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction;
@@ -153,4 +156,8 @@
      */
     private final CancelAction cancelAction;
+    /**
+     * A list of listeners that need to be notified on clipboard content changes.
+     */
+    private final ArrayList<FlavorListener> clipboardListeners = new ArrayList<>();
 
     /**
@@ -274,5 +281,5 @@
         // CHECKSTYLE.ON: LineLength
 
-        registerCopyPasteAction(new PasteMembersAction(memberTableModel, getLayer(), this) {
+        registerCopyPasteAction(new PasteMembersAction(memberTable, getLayer(), this) {
             @Override
             public void actionPerformed(ActionEvent e) {
@@ -743,4 +750,5 @@
         }
         super.setVisible(visible);
+        Clipboard clipboard = ClipboardUtils.getClipboard();
         if (visible) {
             leftButtonToolbar.sortBelowButton.setVisible(ExpertToggleAction.isExpert());
@@ -750,4 +758,7 @@
             }
             tagEditorPanel.requestFocusInWindow();
+            for (FlavorListener listener : clipboardListeners) {
+                clipboard.addFlavorListener(listener);
+            }
         } else {
             // make sure all registered listeners are unregistered
@@ -760,4 +771,7 @@
                 Main.main.menu.windowMenu.remove(windowMenuItem);
                 windowMenuItem = null;
+            }
+            for (FlavorListener listener : clipboardListeners) {
+                clipboard.removeFlavorListener(listener);
             }
             dispose();
@@ -824,5 +838,5 @@
     }
 
-    private static void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut,
+    private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut,
             JRootPane rootPane, JTable... tables) {
         int mods = shortcut.getModifiers();
@@ -840,4 +854,7 @@
             table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
             table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
+        }
+        if (action instanceof FlavorListener) {
+            clipboardListeners.add((FlavorListener) action);
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java	(revision 10604)
@@ -86,4 +86,5 @@
         zoomToGap = new ZoomToGapAction();
         registerListeners();
+        menu.addSeparator();
         getSelectionModel().addListSelectionListener(zoomToGap);
         menu.add(zoomToGap);
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 10604)
@@ -480,7 +480,5 @@
         }
         invalidateConnectionType();
-        final List<Integer> selection = getSelectedIndices();
         fireTableRowsInserted(index, idx - 1);
-        setSelectedMembersIdx(selection);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java	(revision 10604)
@@ -17,11 +17,16 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.PrimitiveData;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.RelationMemberData;
-import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
 import org.openstreetmap.josm.gui.datatransfer.RelationMemberTransferable;
-import org.openstreetmap.josm.tools.Utils.Function;
+import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
 
-class MemberTransferHandler extends TransferHandler {
+/**
+ * A transfer handler that helps with importing / exporting members for relations.
+ * @author Michael Zangl
+ * @since 10604
+ */
+public class MemberTransferHandler extends TransferHandler {
 
     @Override
@@ -38,19 +43,45 @@
     @Override
     public boolean canImport(TransferSupport support) {
-        support.setShowDropLocation(true);
+        if (support.isDrop()) {
+            support.setShowDropLocation(true);
+        }
         return support.isDataFlavorSupported(RelationMemberTransferable.RELATION_MEMBER_DATA)
-                || support.isDataFlavorSupported(PrimitiveTransferable.PRIMITIVE_DATA);
+                || support.isDataFlavorSupported(PrimitiveTransferData.DATA_FLAVOR);
     }
 
     @Override
     public boolean importData(TransferSupport support) {
-        final MemberTable destination = (MemberTable) support.getComponent();
-        final int insertRow = ((JTable.DropLocation) support.getDropLocation()).getRow();
+        MemberTable destination = (MemberTable) support.getComponent();
+        int insertRow = computeInsertionRow(support, destination);
 
+        return importDataAt(support, destination, insertRow);
+    }
+
+    private int computeInsertionRow(TransferSupport support, MemberTable destination) {
+        final int insertRow;
+        if (support.isDrop()) {
+            insertRow = ((JTable.DropLocation) support.getDropLocation()).getRow();
+        } else {
+            int selection = destination.getSelectedRow();
+            if (selection < 0) {
+                // no selection, add at the end.
+                insertRow = destination.getRowCount();
+            } else {
+                insertRow = selection;
+            }
+        }
+        return insertRow;
+    }
+
+    private boolean importDataAt(TransferSupport support, MemberTable destination, int insertRow) {
         try {
             if (support.isDataFlavorSupported(RelationMemberTransferable.RELATION_MEMBER_DATA)) {
                 importRelationMemberData(support, destination, insertRow);
-            } else if (support.isDataFlavorSupported(PrimitiveTransferable.PRIMITIVE_DATA)) {
+                return true;
+            } else if (support.isDataFlavorSupported(PrimitiveTransferData.DATA_FLAVOR)) {
                 importPrimitiveData(support, destination, insertRow);
+                return true;
+            } else {
+                return false;
             }
         } catch (IOException | UnsupportedFlavorException e) {
@@ -58,6 +89,4 @@
             return false;
         }
-
-        return true;
     }
 
@@ -66,14 +95,8 @@
         final RelationMemberTransferable.Data memberData = (RelationMemberTransferable.Data)
                 support.getTransferable().getTransferData(RelationMemberTransferable.RELATION_MEMBER_DATA);
-        importData(destination, insertRow, memberData.getRelationMemberData(), new Function<RelationMemberData, RelationMember>() {
+        importData(destination, insertRow, memberData.getRelationMemberData(), new AbstractRelationMemberConverter<RelationMemberData>() {
             @Override
-            public RelationMember apply(RelationMemberData member) {
-                final OsmPrimitive p = destination.getLayer().data.getPrimitiveById(member.getUniqueId(), member.getType());
-                if (p == null) {
-                    Main.warn(tr("Cannot add {0} since it is not part of dataset", member));
-                    return null;
-                } else {
-                    return new RelationMember(member.getRole(), p);
-                }
+            protected RelationMember getMember(MemberTable destination, RelationMemberData data, OsmPrimitive p) {
+                return new RelationMember(data.getRole(), p);
             }
         });
@@ -82,25 +105,19 @@
     protected void importPrimitiveData(TransferSupport support, final MemberTable destination, int insertRow)
             throws UnsupportedFlavorException, IOException {
-        final PrimitiveTransferable.Data data = (PrimitiveTransferable.Data)
-                support.getTransferable().getTransferData(PrimitiveTransferable.PRIMITIVE_DATA);
-        importData(destination, insertRow, data.getPrimitiveData(), new Function<PrimitiveData, RelationMember>() {
+        final PrimitiveTransferData data = (PrimitiveTransferData)
+                support.getTransferable().getTransferData(PrimitiveTransferData.DATA_FLAVOR);
+        importData(destination, insertRow, data.getDirectlyAdded(), new AbstractRelationMemberConverter<PrimitiveData>() {
             @Override
-            public RelationMember apply(PrimitiveData data) {
-                final OsmPrimitive p = destination.getLayer().data.getPrimitiveById(data);
-                if (p == null) {
-                    Main.warn(tr("Cannot add {0} since it is not part of dataset", data));
-                    return null;
-                } else {
-                    return destination.getMemberTableModel().getRelationMemberForPrimitive(p);
-                }
+            protected RelationMember getMember(MemberTable destination, PrimitiveData data, OsmPrimitive p) {
+                return destination.getMemberTableModel().getRelationMemberForPrimitive(p);
             }
         });
     }
 
-    protected <T> void importData(MemberTable destination, int insertRow,
-                                  Collection<T> memberData, Function<T, RelationMember> toMemberFunction) {
+    protected <T extends PrimitiveId> void importData(MemberTable destination, int insertRow,
+                                  Collection<T> memberData, AbstractRelationMemberConverter<T> toMemberFunction) {
         final Collection<RelationMember> membersToAdd = new ArrayList<>(memberData.size());
-        for (T i : memberData) {
-            final RelationMember member = toMemberFunction.apply(i);
+        for (T data : memberData) {
+            final RelationMember member = toMemberFunction.importPrimitive(destination, data);
             if (member != null) {
                 membersToAdd.add(member);
@@ -120,3 +137,17 @@
         model.selectionChanged(null);
     }
+
+    private abstract static class AbstractRelationMemberConverter<T extends PrimitiveId> {
+        protected RelationMember importPrimitive(MemberTable destination, T data) {
+            final OsmPrimitive p = destination.getLayer().data.getPrimitiveById(data);
+            if (p == null) {
+                Main.warn(tr("Cannot add {0} since it is not part of dataset", data));
+                return null;
+            } else {
+                return getMember(destination, data, p);
+            }
+        }
+
+        protected abstract RelationMember getMember(MemberTable destination, T data, OsmPrimitive p);
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java	(revision 10604)
@@ -5,6 +5,7 @@
 import java.util.Collection;
 
-import org.openstreetmap.josm.actions.CopyAction;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.gui.datatransfer.RelationMemberTransferable;
 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
 import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
@@ -29,7 +30,8 @@
     @Override
     public void actionPerformed(ActionEvent e) {
-        final Collection<OsmPrimitive> primitives = memberTableModel.getSelectedChildPrimitives();
-        if (!primitives.isEmpty()) {
-            CopyAction.copy(layer, primitives);
+        final Collection<RelationMember> members = memberTableModel.getSelectedMembers();
+
+        if (!members.isEmpty()) {
+            ClipboardUtils.copy(new RelationMemberTransferable(members));
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java	(revision 10604)
@@ -2,19 +2,14 @@
 package org.openstreetmap.josm.gui.dialogs.relation.actions;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
+import java.awt.datatransfer.FlavorEvent;
+import java.awt.datatransfer.FlavorListener;
+import java.awt.event.ActionEvent;
 
-import java.awt.event.ActionEvent;
-import java.util.ArrayList;
-import java.util.List;
+import javax.swing.TransferHandler.TransferSupport;
 
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
-import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberTransferHandler;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 
@@ -23,59 +18,34 @@
  * @since 9496
  */
-public class PasteMembersAction extends AddFromSelectionAction {
+public class PasteMembersAction extends AddFromSelectionAction implements FlavorListener {
 
     /**
      * Constructs a new {@code PasteMembersAction}.
-     * @param memberTableModel member table model
+     * @param memberTable member table
      * @param layer OSM data layer
      * @param editor relation editor
      */
-    public PasteMembersAction(MemberTableModel memberTableModel, OsmDataLayer layer, IRelationEditor editor) {
-        super(null, memberTableModel, null, null, null, layer, editor);
+    public PasteMembersAction(MemberTable memberTable, OsmDataLayer layer, IRelationEditor editor) {
+        super(memberTable, null, null, null, null, layer, editor);
+        updateEnabledState();
     }
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        try {
-            List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
-            DataSet ds = layer.data;
-            List<OsmPrimitive> toAdd = new ArrayList<>();
-            boolean hasNewInOtherLayer = false;
+        new MemberTransferHandler().importData(getSupport());
+    }
 
-            for (PrimitiveData primitive: primitives) {
-                OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
-                if (primitiveInDs != null) {
-                    toAdd.add(primitiveInDs);
-                } else if (!primitive.isNew()) {
-                    OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
-                    ds.addPrimitive(p);
-                    toAdd.add(p);
-                } else {
-                    hasNewInOtherLayer = true;
-                    break;
-                }
-            }
-
-            if (hasNewInOtherLayer) {
-                JOptionPane.showMessageDialog(Main.parent,
-                        tr("Members from paste buffer cannot be added because they are not included in current layer"));
-                return;
-            }
-
-            toAdd = filterConfirmedPrimitives(toAdd);
-            int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
-            if (index == -1) {
-                index = memberTableModel.getRowCount() - 1;
-            }
-            memberTableModel.addMembersAfterIdx(toAdd, index);
-
-        } catch (AddAbortException ex) {
-            Main.trace(ex);
-        }
+    private TransferSupport getSupport() {
+        return new TransferSupport(memberTable, ClipboardUtils.getClipboard().getContents(null));
     }
 
     @Override
     protected void updateEnabledState() {
-        // Do nothing
+        setEnabled(new MemberTransferHandler().canImport(getSupport()));
+    }
+
+    @Override
+    public void flavorsChanged(FlavorEvent e) {
+        updateEnabledState();
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java	(revision 10604)
@@ -39,4 +39,5 @@
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
 import org.openstreetmap.josm.gui.help.HelpUtil;
@@ -48,5 +49,4 @@
 import org.openstreetmap.josm.tools.InputMapUtils;
 import org.openstreetmap.josm.tools.OsmUrlToBounds;
-import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.WindowGeometry;
 
@@ -243,5 +243,5 @@
             @Override
             public void actionPerformed(ActionEvent e) {
-                String clip = Utils.getClipboardContent();
+                String clip = ClipboardUtils.getClipboardStringContent();
                 if (clip == null) {
                     return;
Index: trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 10604)
@@ -31,4 +31,5 @@
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
@@ -41,5 +42,4 @@
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.date.DateUtils;
 
@@ -238,5 +238,5 @@
         if (SwingUtilities.isRightMouseButton(e) && noteData.getSelectedNote() != null) {
             final String url = OsmApi.getOsmApi().getBaseUrl() + "notes/" + noteData.getSelectedNote().getId();
-            Utils.copyToClipboard(url);
+            ClipboardUtils.copyString(url);
             return;
         } else if (!SwingUtilities.isLeftMouseButton(e)) {
Index: trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 10604)
@@ -53,4 +53,5 @@
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
@@ -725,5 +726,5 @@
     public void copyCurrentPhotoPath() {
         if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {
-            Utils.copyToClipboard(data.get(currentPhoto).getFile().toString());
+            ClipboardUtils.copyString(data.get(currentPhoto).getFile().toString());
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 10604)
@@ -8,4 +8,5 @@
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
@@ -25,4 +26,5 @@
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.data.osm.TagMap;
 import org.openstreetmap.josm.data.osm.Tagged;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
@@ -47,4 +49,6 @@
 
     private transient OsmPrimitive primitive;
+
+    private EndEditListener endEditListener;
 
     /**
@@ -167,4 +171,5 @@
      */
     public void clear() {
+        commitPendingEdit();
         boolean wasEmpty = tags.isEmpty();
         tags.clear();
@@ -183,4 +188,5 @@
      */
     public void add(TagModel tag) {
+        commitPendingEdit();
         CheckParameterUtil.ensureParameterNotNull(tag, "tag");
         tags.add(tag);
@@ -189,5 +195,15 @@
     }
 
+    /**
+     * Add a tag at the beginning of the table.
+     *
+     * @param tag The tag to add
+     *
+     * @throws IllegalArgumentException if tag is null
+     *
+     * @see #add(TagModel)
+     */
     public void prepend(TagModel tag) {
+        commitPendingEdit();
         CheckParameterUtil.ensureParameterNotNull(tag, "tag");
         tags.add(0, tag);
@@ -209,4 +225,5 @@
      */
     public void add(String name, String value) {
+        commitPendingEdit();
         String key = (name == null) ? "" : name;
         String val = (value == null) ? "" : value;
@@ -259,4 +276,5 @@
         if (tags == null)
             return;
+        commitPendingEdit();
         for (int tagIdx : tagIndices) {
             TagModel tag = tags.get(tagIdx);
@@ -277,4 +295,5 @@
         if (tags == null)
             return;
+        commitPendingEdit();
         for (int tagIdx : tagIndices) {
             TagModel tag = tags.get(tagIdx);
@@ -293,4 +312,5 @@
      */
     public void delete(String name) {
+        commitPendingEdit();
         if (name == null)
             return;
@@ -318,4 +338,5 @@
         if (tags == null)
             return;
+        commitPendingEdit();
         List<TagModel> toDelete = new ArrayList<>();
         for (int tagIdx : tagIndices) {
@@ -356,4 +377,5 @@
      */
     public void initFromPrimitive(Tagged primitive) {
+        commitPendingEdit();
         this.tags.clear();
         for (String key : primitive.keySet()) {
@@ -361,6 +383,6 @@
             this.tags.add(new TagModel(key, value));
         }
+        sort();
         TagModel tag = new TagModel();
-        sort();
         tags.add(tag);
         setDirty(false);
@@ -374,4 +396,5 @@
      */
     public void initFromTags(Map<String, String> tags) {
+        commitPendingEdit();
         this.tags.clear();
         for (Entry<String, String> entry : tags.entrySet()) {
@@ -391,4 +414,5 @@
      */
     public void initFromTags(TagCollection tags) {
+        commitPendingEdit();
         this.tags.clear();
         if (tags == null) {
@@ -424,5 +448,6 @@
      */
     private Map<String, String> applyToTags(boolean keepEmpty) {
-        Map<String, String> result = new HashMap<>();
+        // TagMap preserves the order of tags.
+        TagMap result = new TagMap();
         for (TagModel tag: this.tags) {
             // tag still holds an unchanged list of different values for the same key.
@@ -433,5 +458,4 @@
 
             // tag name holds an empty key. Don't apply it to the selection.
-            //
             if (!keepEmpty && (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty())) {
                 continue;
@@ -539,5 +563,5 @@
      */
     protected void sort() {
-        java.util.Collections.sort(
+        Collections.sort(
                 tags,
                 new Comparator<TagModel>() {
@@ -591,7 +615,8 @@
      */
     public void updateTags(List<Tag> tags) {
-         if (tags.isEmpty())
+        if (tags.isEmpty())
             return;
 
+        commitPendingEdit();
         Map<String, TagModel> modelTags = new HashMap<>();
         for (int i = 0; i < getRowCount(); i++) {
@@ -648,4 +673,18 @@
     }
 
+    /**
+     * Sets the listener that is notified when an edit should be aborted.
+     * @param endEditListener The listener to be notified when editing should be aborted.
+     */
+    public void setEndEditListener(EndEditListener endEditListener) {
+        this.endEditListener = endEditListener;
+    }
+
+    private void commitPendingEdit() {
+        if (endEditListener != null) {
+            endEditListener.endCellEditing();
+        }
+    }
+
     class SelectionStateMemento {
         private final int rowMin;
@@ -674,3 +713,15 @@
         }
     }
+
+    /**
+     * A listener that is called whenever the cells may be updated from outside the editor and the editor should thus be commited.
+     * @since 10604
+     */
+    @FunctionalInterface
+    public interface EndEditListener {
+        /**
+         * Requests to end the editing of any cells on this model
+         */
+        void endCellEditing();
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java	(revision 10604)
@@ -2,5 +2,4 @@
 package org.openstreetmap.josm.gui.tagging;
 
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
@@ -13,9 +12,6 @@
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EventObject;
-import java.util.List;
-import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -32,16 +28,12 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.CopyAction;
-import org.openstreetmap.josm.actions.PasteTagsAction;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
 import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.TagMap;
+import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel.EndEditListener;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
 import org.openstreetmap.josm.gui.widgets.JosmTable;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.TextTagParser;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -49,5 +41,5 @@
  * @since 1762
  */
-public class TagTable extends JosmTable {
+public class TagTable extends JosmTable implements EndEditListener {
     /** the table cell editor used by this table */
     private TagCellEditor editor;
@@ -211,10 +203,5 @@
             }
 
-            if (isEditing()) {
-                CellEditor cEditor = getCellEditor();
-                if (cEditor != null) {
-                    cEditor.cancelCellEditing();
-                }
-            }
+            endCellEditing();
 
             if (model.getRowCount() == 0) {
@@ -233,9 +220,5 @@
 
         protected final void updateEnabledState() {
-            if (isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
-                setEnabled(true);
-            } else if (!isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
-                setEnabled(true);
-            } else if (getSelectedColumnCount() > 1 || getSelectedRowCount() > 1) {
+            if (getSelectedColumnCount() >= 1 && getSelectedRowCount() >= 1) {
                 setEnabled(true);
             } else {
@@ -295,27 +278,6 @@
             Relation relation = new Relation();
             model.applyToPrimitive(relation);
-
-            String buf = Utils.getClipboardContent();
-            if (buf == null || buf.isEmpty() || buf.matches(CopyAction.CLIPBOARD_REGEXP)) {
-                List<PrimitiveData> directlyAdded = Main.pasteBuffer.getDirectlyAdded();
-                if (directlyAdded == null || directlyAdded.isEmpty()) return;
-                PasteTagsAction.TagPaster tagPaster = new PasteTagsAction.TagPaster(directlyAdded,
-                        Collections.<OsmPrimitive>singletonList(relation));
-                model.updateTags(tagPaster.execute());
-            } else {
-                 // Paste tags from arbitrary text
-                 Map<String, String> tags = TextTagParser.readTagsFromText(buf);
-                 if (tags == null || tags.isEmpty()) {
-                    TextTagParser.showBadBufferMessage(ht("/Action/PasteTags"));
-                 } else if (TextTagParser.validateTags(tags)) {
-                     List<Tag> newTags = new ArrayList<>();
-                     for (Map.Entry<String, String> entry: tags.entrySet()) {
-                        String k = entry.getKey();
-                        String v = entry.getValue();
-                        newTags.add(new Tag(k, v));
-                     }
-                     model.updateTags(newTags);
-                 }
-            }
+            new OsmTransferHandler().pasteTags(Collections.singleton(relation));
+            model.updateTags(new TagMap(relation.getKeys()).getTags());
         }
 
@@ -415,4 +377,5 @@
               model.getRowSelectionModel());
         this.model = model;
+        model.setEndEditListener(this);
         init(maxCharacters);
     }
@@ -488,7 +451,5 @@
      */
     public void setTagCellEditor(TagCellEditor editor) {
-        if (isEditing()) {
-            this.editor.cancelCellEditing();
-        }
+        endCellEditing();
         this.editor = editor;
         getColumnModel().getColumn(0).setCellEditor(editor);
@@ -546,4 +507,16 @@
         // delegate to the default implementation
         return super.editCellAt(row, column, e);
+    }
+
+    @Override
+    public void endCellEditing() {
+        if (isEditing()) {
+            CellEditor cEditor = getCellEditor();
+            if (cEditor != null) {
+                // First attempt to commit. If this does not work, cancel.
+                cEditor.stopCellEditing();
+                cEditor.cancelCellEditing();
+            }
+        }
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java	(revision 10604)
@@ -24,7 +24,6 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -134,7 +133,7 @@
             final JTextComponent editorComponent = comboBox.getEditorComponent();
             // save unix system selection (middle mouse paste)
-            Clipboard sysSel = GuiHelper.getSystemSelection();
+            Clipboard sysSel = ClipboardUtils.getSystemSelection();
             if (sysSel != null) {
-                Transferable old = Utils.getTransferableContent(sysSel);
+                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
                 editorComponent.select(start, end);
                 if (old != null) {
@@ -201,7 +200,7 @@
                         }
                         // save unix system selection (middle mouse paste)
-                        Clipboard sysSel = GuiHelper.getSystemSelection();
+                        Clipboard sysSel = ClipboardUtils.getSystemSelection();
                         if (sysSel != null) {
-                            Transferable old = Utils.getTransferableContent(sysSel);
+                            Transferable old = ClipboardUtils.getClipboardContent(sysSel);
                             editorComponent.selectAll();
                             if (old != null) {
Index: trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 10604)
@@ -21,5 +21,4 @@
 import java.awt.Toolkit;
 import java.awt.Window;
-import java.awt.datatransfer.Clipboard;
 import java.awt.event.ActionListener;
 import java.awt.event.HierarchyEvent;
@@ -497,16 +496,4 @@
 
     /**
-     * Gets the singleton instance of the system selection as a <code>Clipboard</code> object.
-     * This allows an application to read and modify the current, system-wide selection.
-     * @return the system selection as a <code>Clipboard</code>, or <code>null</code> if the native platform does not
-     *         support a system selection <code>Clipboard</code> or if GraphicsEnvironment.isHeadless() returns true
-     * @see Toolkit#getSystemSelection
-     * @since 9576
-     */
-    public static Clipboard getSystemSelection() {
-        return GraphicsEnvironment.isHeadless() ? null : Toolkit.getDefaultToolkit().getSystemSelection();
-    }
-
-    /**
      * Returns the first <code>Window</code> ancestor of event source, or
      * {@code null} if event source is not a component contained inside a <code>Window</code>.
Index: trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java	(revision 10604)
@@ -5,5 +5,5 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 
 /**
@@ -71,5 +71,5 @@
      */
     public void tryToPasteFromClipboard() {
-        tryToPasteFrom(Utils.getClipboardContent());
+        tryToPasteFrom(ClipboardUtils.getClipboardStringContent());
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/widgets/UrlLabel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/widgets/UrlLabel.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/gui/widgets/UrlLabel.java	(revision 10604)
@@ -11,6 +11,6 @@
 import javax.swing.SwingUtilities;
 
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.tools.OpenBrowser;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -112,5 +112,5 @@
             OpenBrowser.displayUrl(url);
         } else if (SwingUtilities.isRightMouseButton(e)) {
-            Utils.copyToClipboard(url);
+            ClipboardUtils.copyString(url);
         }
     }
Index: trunk/src/org/openstreetmap/josm/tools/TextTagParser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/TextTagParser.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/tools/TextTagParser.java	(revision 10604)
@@ -19,4 +19,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.widgets.UrlLabel;
@@ -291,5 +292,5 @@
         if (r == 0) r = 2;
         // clean clipboard if user asked
-        if (r == 3) Utils.copyToClipboard("");
+        if (r == 3) ClipboardUtils.copyString("");
         return r;
     }
@@ -326,5 +327,5 @@
         int r = ed.getValue();
         // clean clipboard if user asked
-        if (r == 2) Utils.copyToClipboard("");
+        if (r == 2) ClipboardUtils.copyString("");
     }
 }
Index: trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 10604)
@@ -8,12 +8,6 @@
 import java.awt.Color;
 import java.awt.Font;
-import java.awt.HeadlessException;
-import java.awt.Toolkit;
 import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.ClipboardOwner;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.StringSelection;
 import java.awt.datatransfer.Transferable;
-import java.awt.datatransfer.UnsupportedFlavorException;
 import java.awt.font.FontRenderContext;
 import java.awt.font.GlyphVector;
@@ -73,4 +67,5 @@
 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
@@ -645,18 +640,9 @@
      * @param s string to be copied to clipboard.
      * @return true if succeeded, false otherwise.
-     */
+     * @deprecated Use {@link ClipboardUtils#copyString(String)}. To be removed end of 2016.
+     */
+    @Deprecated
     public static boolean copyToClipboard(String s) {
-        try {
-            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() {
-                @Override
-                public void lostOwnership(Clipboard clpbrd, Transferable t) {
-                    // Do nothing
-                }
-            });
-            return true;
-        } catch (IllegalStateException | HeadlessException ex) {
-            Main.error(ex);
-            return false;
-        }
+        return ClipboardUtils.copyString(s);
     }
 
@@ -666,24 +652,9 @@
      * @return clipboard contents if available, {@code null} otherwise.
      * @since 8429
-     */
+     * @deprecated Use {@link ClipboardUtils#getClipboardContent(Clipboard)} instead. To be removed end of 2016.
+     */
+    @Deprecated
     public static Transferable getTransferableContent(Clipboard clipboard) {
-        Transferable t = null;
-        for (int tries = 0; t == null && tries < 10; tries++) {
-            try {
-                t = clipboard.getContents(null);
-            } catch (IllegalStateException e) {
-                // Clipboard currently unavailable.
-                // On some platforms, the system clipboard is unavailable while it is accessed by another application.
-                try {
-                    Thread.sleep(1);
-                } catch (InterruptedException ex) {
-                    Main.warn("InterruptedException in "+Utils.class.getSimpleName()+" while getting clipboard content");
-                }
-            } catch (NullPointerException e) {
-                // JDK-6322854: On Linux/X11, NPE can happen for unknown reasons, on all versions of Java
-                Main.error(e);
-            }
-        }
-        return t;
+        return ClipboardUtils.getClipboardContent(clipboard);
     }
 
@@ -691,16 +662,9 @@
      * Extracts clipboard content as string.
      * @return string clipboard contents if available, {@code null} otherwise.
-     */
+     * @deprecated Use {@link ClipboardUtils#getClipboardStringContent()}. To be removed end of 2016
+     */
+    @Deprecated
     public static String getClipboardContent() {
-        try {
-            Transferable t = getTransferableContent(Toolkit.getDefaultToolkit().getSystemClipboard());
-            if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
-                return (String) t.getTransferData(DataFlavor.stringFlavor);
-            }
-        } catch (UnsupportedFlavorException | IOException | HeadlessException ex) {
-            Main.error(ex);
-            return null;
-        }
-        return null;
+        return ClipboardUtils.getClipboardStringContent();
     }
 
Index: trunk/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java	(revision 10603)
+++ trunk/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java	(revision 10604)
@@ -6,4 +6,5 @@
 import javax.swing.JScrollPane;
 
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
 import org.openstreetmap.josm.gui.widgets.JosmTextArea;
 import org.openstreetmap.josm.tools.Utils;
@@ -61,9 +62,9 @@
 
     /**
-     * Copies the debug text to the clippboard. This includes the code tags for trac.
+     * Copies the debug text to the clipboard. This includes the code tags for trac.
      * @return <code>true</code> if copy was successful
      */
     public boolean copyToClippboard() {
-        return Utils.copyToClipboard(String.format(CODE_PATTERN, text));
+        return ClipboardUtils.copyString(String.format(CODE_PATTERN, text));
     }
 
