Index: trunk/src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 10601)
+++ 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 10601)
+++ 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 10601)
+++ 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 10601)
+++ 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 10601)
+++ 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 10601)
+++ 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;
     }
 
