Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 403)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 404)
@@ -83,4 +83,8 @@
 	public static DataSet ds = new DataSet();
 	/**
+	 * The global paste buffer.
+	 */
+	public static DataSet pasteBuffer = new DataSet();
+	/**
 	 * The projection method used.
 	 */
Index: trunk/src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 404)
+++ trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 404)
@@ -0,0 +1,106 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+// Author: David Earl
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+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.Collection;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.Visitor;
+
+public final class CopyAction extends JosmAction implements SelectionChangedListener {
+
+	public CopyAction() {
+		super(tr("Copy"), "copy",
+				tr("Copy selected objects to paste buffer."),
+				KeyEvent.VK_C, KeyEvent.CTRL_MASK, true);
+		setEnabled(false);
+		DataSet.selListeners.add(this);
+	}
+
+	public void actionPerformed(ActionEvent e) {
+		Collection<OsmPrimitive> sel = Main.ds.getSelected();
+		if (sel.isEmpty()) { 
+			JOptionPane.showMessageDialog(Main.parent,
+					tr("Please select something to copy."));	
+			return;
+		}
+
+		/* New pasteBuffer - will be assigned to the global one at the end */
+		final DataSet pasteBuffer = new DataSet();
+		final HashMap<OsmPrimitive,OsmPrimitive> map = new HashMap<OsmPrimitive,OsmPrimitive>();
+		/* temporarily maps old nodes to new so we can do a true deep copy */
+
+		/* scan the selected objects, mapping them to copies; when copying a way or relation, 
+		 * the copy references the copies of their child objects */
+		new Visitor(){
+			public void visit(Node n) {
+				/* check if already in pasteBuffer - e.g. two ways are selected which share a node; 
+				 * or a way and a node in that way is selected, we'll see it twice, once via the 
+				 * way and once directly; and so on. */
+				if (map.containsKey(n)) { return; }
+				Node nnew = new Node(n);
+				map.put(n, nnew);
+				pasteBuffer.addPrimitive(nnew);
+			}
+			public void visit(Way w) {
+				/* check if already in pasteBuffer - could have come from a relation, and directly etc. */
+				if (map.containsKey(w)) { return; }
+				Way wnew = new Way();
+				wnew.cloneFrom(w);
+				wnew.nodes.clear();
+				List<Node> nodes = new ArrayList<Node>();
+				for (Node n : w.nodes) {
+					if (! map.containsKey(n)) {
+						n.visit(this);
+					}
+					nodes.add((Node)map.get(n));
+				}
+				wnew.nodes.clear();
+				wnew.nodes.addAll(nodes);
+				pasteBuffer.addPrimitive(wnew);
+			}
+			public void visit(Relation e) {
+				if (map.containsKey(e)) { return; }
+				Relation enew = new Relation(e);
+				List<RelationMember> members = new ArrayList<RelationMember>();
+				for (RelationMember m : e.members) {
+					if (! map.containsKey(m.member)) {
+						m.member.visit(this);
+					}
+					RelationMember mnew = new RelationMember(m);
+					mnew.member = map.get(m.member);
+					members.add(mnew);
+				}
+				enew.members.addAll(members);
+				pasteBuffer.addPrimitive(enew);
+			}
+			public void visitAll() {
+				for (OsmPrimitive osm : Main.ds.getSelected())
+					osm.visit(this);
+			}
+		}.visitAll();
+
+		Main.pasteBuffer = pasteBuffer;
+		Main.main.menu.paste.setEnabled(true); /* now we have a paste buffer we can make paste available */
+	}
+
+	public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+		setEnabled(! newSelection.isEmpty());
+	}
+}
Index: trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 404)
+++ trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 404)
@@ -0,0 +1,34 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+// Author: David Earl
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+
+public final class DuplicateAction extends JosmAction implements SelectionChangedListener {
+
+    public DuplicateAction() {
+    	super(tr("Duplicate"), "duplicate",
+			tr("Duplicate selection by Copy and immediate Paste."),
+			KeyEvent.VK_D, KeyEvent.CTRL_MASK, true);
+    	setEnabled(false);
+		DataSet.selListeners.add(this);
+    }
+
+	public void actionPerformed(ActionEvent e) {
+		Main.main.menu.copy.actionPerformed(e);
+		Main.main.menu.paste.actionPerformed(e);
+    }
+	
+	public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+		setEnabled(! newSelection.isEmpty());
+	}
+}
Index: trunk/src/org/openstreetmap/josm/actions/PasteAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 404)
+++ trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 404)
@@ -0,0 +1,103 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+// Author: David Earl
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.coor.EastNorth;
+
+public final class PasteAction extends JosmAction {
+
+    public PasteAction() {
+    	super(tr("Paste"), "paste",
+			tr("Paste contents of paste buffer."),
+			KeyEvent.VK_V, KeyEvent.CTRL_MASK, true);
+		setEnabled(false);
+    }
+
+	public void actionPerformed(ActionEvent e) {
+		DataSet pasteBuffer = Main.pasteBuffer;
+
+		/* Find the middle of the pasteBuffer area */ 
+		EastNorth centre = Main.map.mapView.getCenter();
+		double maxEast = -1E100, minEast = 1E100, maxNorth = -1E100, minNorth = 1E100;
+		for (Node n : pasteBuffer.nodes) {
+			double east = n.eastNorth.east();
+			double north = n.eastNorth.north();
+			if (east > maxEast) { maxEast = east; } 
+			if (east < minEast) { minEast = east; } 
+			if (north > maxNorth) { maxNorth = north; } 
+			if (north < minNorth) { minNorth = north; } 
+		}
+		double offsetEast = centre.east() - (maxEast + minEast)/2.0;
+		double offsetNorth = centre.north() - (maxNorth + minNorth)/2.0; 
+		
+		HashMap<OsmPrimitive,OsmPrimitive> map = new HashMap<OsmPrimitive,OsmPrimitive>(); 
+		  /* temporarily maps old nodes to new so we can do a true deep copy */
+		
+		/* do the deep copy of the paste buffer contents, leaving the pasteBuffer unchanged */
+		for (Node n : pasteBuffer.nodes) {
+			Node nnew = new Node(n);
+			nnew.id = 0;
+			/* adjust the coordinates to the middle of the visible map area */
+			nnew.eastNorth = new EastNorth(nnew.eastNorth.east() + offsetEast, nnew.eastNorth.north() + offsetNorth);
+			nnew.coor = Main.proj.eastNorth2latlon(nnew.eastNorth);
+			map.put(n, nnew);
+		}
+		for (Way w : pasteBuffer.ways) {
+			Way wnew = new Way();
+			wnew.cloneFrom(w);
+			wnew.id = 0;
+			/* make sure we reference the new nodes corresponding to the old ones */
+			List<Node> nodes = new ArrayList<Node>();
+			for (Node n : w.nodes) {
+				nodes.add((Node)map.get(n));
+			}
+			wnew.nodes.clear();
+			wnew.nodes.addAll(nodes);
+			map.put(w, wnew);
+		}
+		for (Relation r : pasteBuffer.relations) {
+			Relation rnew = new Relation(r);
+			rnew.id = 0;
+			List<RelationMember> members = new ArrayList<RelationMember>();
+			for (RelationMember m : r.members) {
+				RelationMember mnew = new RelationMember(m);
+				mnew.member = map.get(m.member);
+				members.add(mnew);
+			}
+			rnew.members.clear();
+			rnew.members.addAll(members);
+			map.put(r, rnew);
+		}
+		
+		/* Now execute the commands to add the dupicated contents of the paste buffer to the map */
+		Collection<OsmPrimitive> osms = map.values();
+		Collection<Command> clist = new LinkedList<Command>();
+		for (OsmPrimitive osm : osms) {
+			clist.add(new AddCommand(osm));
+		}
+
+		Main.main.undoRedo.add(new SequenceCommand(tr("Paste"), clist));
+		Main.ds.setSelected(osms);
+		Main.map.mapView.repaint();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 403)
+++ trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 404)
@@ -4,16 +4,11 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.BorderLayout;
-import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.util.Vector;
 
 import javax.swing.Action;
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.JMenu;
 import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
-import javax.swing.JPanel;
 
 import org.openstreetmap.josm.actions.AboutAction;
@@ -22,5 +17,7 @@
 import org.openstreetmap.josm.actions.AutoScaleAction;
 import org.openstreetmap.josm.actions.CombineWayAction;
+import org.openstreetmap.josm.actions.CopyAction;
 import org.openstreetmap.josm.actions.DownloadAction;
+import org.openstreetmap.josm.actions.DuplicateAction;
 import org.openstreetmap.josm.actions.ExitAction;
 import org.openstreetmap.josm.actions.GpxExportAction;
@@ -28,4 +25,5 @@
 import org.openstreetmap.josm.actions.NewAction;
 import org.openstreetmap.josm.actions.OpenAction;
+import org.openstreetmap.josm.actions.PasteAction;
 import org.openstreetmap.josm.actions.PreferencesAction;
 import org.openstreetmap.josm.actions.RedoAction;
@@ -40,9 +38,4 @@
 import org.openstreetmap.josm.actions.search.SearchAction;
 import org.openstreetmap.josm.data.DataSetChecker;
-import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
-import org.openstreetmap.josm.gui.tagging.ForwardActionListener;
-import org.openstreetmap.josm.gui.tagging.TaggingCellRenderer;
-import org.openstreetmap.josm.gui.tagging.TaggingPreset;
-import org.openstreetmap.josm.tools.GBC;
 
 /**
@@ -58,4 +51,7 @@
 	public final UndoAction undo = new UndoAction();
 	public final RedoAction redo = new RedoAction();
+	public final Action copy = new CopyAction();
+	public final Action paste = new PasteAction();
+	public final Action duplicate = new DuplicateAction(); 
 	public final Action selectAll = new SelectAllAction();
 	public final Action unselectAll = new UnselectAllAction();
@@ -104,4 +100,11 @@
 		editMenu.add(undo);
 		editMenu.add(redo);
+		editMenu.addSeparator();
+		editMenu.add(copy);
+		editMenu.add(paste);
+		editMenu.add(duplicate);
+		editMenu.addSeparator();
+		editMenu.add(selectAll);
+		
 		editMenu.addSeparator();
 		editMenu.add(selectAll);
