Index: /trunk/build.xml
===================================================================
--- /trunk/build.xml	(revision 1022)
+++ /trunk/build.xml	(revision 1023)
@@ -37,5 +37,5 @@
 		<jar destfile="dist/josm-custom.jar" basedir="build">
 			<manifest>
-				<attribute name="Main-class" value="org.openstreetmap.josm.gui.MainApplication" />
+				<attribute name="Main-class" value="JOSM" />
 			</manifest>
 		</jar>
Index: /trunk/src/JOSM.java
===================================================================
--- /trunk/src/JOSM.java	(revision 1023)
+++ /trunk/src/JOSM.java	(revision 1023)
@@ -0,0 +1,12 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+//Licence: GPL
+
+// Note: The name of the main class will be the name of the application menu on OS X.
+//       so instead of exposing "org.openstreetmap.josm.gui.MainApplication" to the
+//       user, we subclass it with a nicer name "JOSM".
+//       An alternative would be to set the name in the plist file---but JOSM usually
+//       is not delivered as an OS X Application Bundle, so we have no plist file.
+
+import org.openstreetmap.josm.gui.MainApplication;
+
+public class JOSM extends MainApplication {}
Index: /trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/Main.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/Main.java	(revision 1023)
@@ -59,4 +59,9 @@
 import org.openstreetmap.josm.plugins.PluginProxy;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.PlatformHook;
+import org.openstreetmap.josm.tools.PlatformHookUnixoid;
+import org.openstreetmap.josm.tools.PlatformHookWindows;
+import org.openstreetmap.josm.tools.PlatformHookOsx;
+import org.openstreetmap.josm.tools.ShortCut;
 
 abstract public class Main {
@@ -131,4 +136,12 @@
 		System.out.println(msg);
 	}
+
+	/**
+	 * Platform specific code goes in here.
+	 * Plugins may replace it, however, some hooks will be called before any plugins have been loeaded.
+	 * So if you need to hook into those early ones, split your class and send the one with the early hooks
+	 * to the JOSM team for inclusion.
+	 */
+	public static PlatformHook platform;
 
 	/**
@@ -178,7 +191,8 @@
 	}
 
-
 	public Main() {
 		main = this;
+//		platform = determinePlatformHook();
+		platform.startupHook();
 		contentPane.add(panel, BorderLayout.CENTER);
 		panel.add(new GettingStarted(), BorderLayout.CENTER);
@@ -186,9 +200,9 @@
 
 		undoRedo.listenerCommands.add(redoUndoListener);
-		
+
 		// creating toolbar
 		contentPane.add(toolbar.control, BorderLayout.NORTH);
 
-		contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "Help");
+		contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ShortCut.registerShortCut("system:help", tr("Help"), KeyEvent.VK_F1, ShortCut.GROUP_DIRECT).getKeyStroke(), "Help");
 		contentPane.getActionMap().put("Help", menu.help);
 
@@ -235,5 +249,5 @@
 				plugins.add("lang-"+lang);
 		}
-		
+
 		if (plugins.isEmpty())
 			return;
@@ -250,9 +264,9 @@
 				if (early)
 					System.out.println("Plugin not found: "+pluginName); // do not translate
-				else	
+				else
 					JOptionPane.showMessageDialog(Main.parent, tr("Plugin not found: {0}.", pluginName));
 			}
 		}
-		
+
 		// iterate all plugins and collect all libraries of all plugins:
 		List<URL> allPluginLibraries = new ArrayList<URL>();
@@ -290,5 +304,5 @@
 						plugins.remove(info.name);
 						String plist = null;
-						for (String pn : plugins) { 
+						for (String pn : plugins) {
 							if (plist==null) plist=""; else plist=plist+",";
 							plist=plist+pn;
@@ -415,4 +429,5 @@
 
 	public static boolean breakBecauseUnsavedChanges() {
+		ShortCut.savePrefs();
 		if (map != null) {
 			boolean modified = false;
@@ -471,3 +486,21 @@
 		main.menu.open.openFile(new File(s));
 	}
+
+	protected static void determinePlatformHook() {
+		String os = System.getProperty("os.name");
+		if (os == null) {
+			System.err.println("Your operating system has no name, so I'm guessing its some kind of *nix.");
+			platform = new PlatformHookUnixoid();
+		} else if (os.toLowerCase().startsWith("windows")) {
+			platform = new PlatformHookWindows();
+		} else if (os.equals("Linux") || os.equals("Solaris") || os.equals("SunOS") || os.equals("AIX") || os.equals("FreeBSD")) {
+			platform = new PlatformHookUnixoid();
+		} else if (os.toLowerCase().startsWith("mac os x")) {
+			platform = new PlatformHookOsx();
+		} else {
+			System.err.println("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix.");
+			platform = new PlatformHookUnixoid();
+		}
+	}
+
 }
Index: /trunk/src/org/openstreetmap/josm/actions/AboutAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AboutAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/AboutAction.java	(revision 1023)
@@ -34,11 +34,12 @@
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.UrlLabel;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Nice about screen. I guess every application need one these days.. *sigh*
- * 
- * The REVISION resource is read and if present, it shows the revision 
+ *
+ * The REVISION resource is read and if present, it shows the revision
  * information of the jar-file.
- * 
+ *
  * @author imi
  */
@@ -65,7 +66,7 @@
 		return version;
 	}
-	
+
 	public AboutAction() {
-		super(tr("About"), "about",tr("Display the about screen."), KeyEvent.VK_F1, KeyEvent.SHIFT_DOWN_MASK, true);
+		super(tr("About"), "about", tr("Display the about screen."), ShortCut.registerShortCut("system:about", tr("About..."), KeyEvent.VK_F1, ShortCut.GROUP_DIRECT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java	(revision 1023)
@@ -20,4 +20,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -29,5 +30,6 @@
 
 	public AlignInCircleAction() {
-		super(tr("Align Nodes in Circle"), "aligncircle", tr("Move the selected nodes into a circle."), KeyEvent.VK_O, 0, true);
+		super(tr("Align Nodes in Circle"), "aligncircle", tr("Move the selected nodes into a circle."),
+		ShortCut.registerShortCut("tools:aligncircle", tr("Tool: Align in circle"), KeyEvent.VK_O, ShortCut.GROUP_EDIT), true);
 	}
 
@@ -40,5 +42,5 @@
 				nodes.add((Node)osm);
 
-		// special case if no single nodes are selected and exactly one way is: 
+		// special case if no single nodes are selected and exactly one way is:
 		// then use the way's nodes
 		if ((nodes.size() == 0) && (sel.size() == 1))
@@ -67,5 +69,5 @@
 
 		// Now calculate the average distance to each node from the
-		// centre.  This method is ok as long as distances are short 
+		// centre.  This method is ok as long as distances are short
 		// relative to the distance from the N or S poles.
 		double distances[] = new double[nodes.size()];
Index: /trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java	(revision 1023)
@@ -18,4 +18,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -23,5 +24,5 @@
  * roads that should be straight, but have side roads and
  * therefore need multiple nodes)
- * 
+ *
  * @author Matthew Newton
  */
@@ -29,5 +30,6 @@
 
 	public AlignInLineAction() {
-		super(tr("Align Nodes in Line"), "alignline", tr("Move the selected nodes onto a line."), KeyEvent.VK_L, 0, true);
+		super(tr("Align Nodes in Line"), "alignline", tr("Move the selected nodes onto a line."),
+		ShortCut.registerShortCut("tools:alignline", tr("Tool: Align in line"), KeyEvent.VK_L, ShortCut.GROUP_EDIT), true);
 	}
 
@@ -46,5 +48,5 @@
 				itnodes.add((Node)osm);
 			}
-		// special case if no single nodes are selected and exactly one way is: 
+		// special case if no single nodes are selected and exactly one way is:
 		// then use the way's nodes
 		if ((nodes.size() == 0) && (sel.size() == 1))
Index: /trunk/src/org/openstreetmap/josm/actions/AlignInRectangleAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AlignInRectangleAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/AlignInRectangleAction.java	(revision 1023)
@@ -20,16 +20,17 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
- * Aligns all selected nodes within a rectangle. 
- * 
+ * Aligns all selected nodes within a rectangle.
+ *
  * There are many ways this could be done, for example:
  * - find smallest rectangle to contain all points (rectangular hull) OR
  * - find largest rectangle to fit inside OR
  * - find both and compute the average
- * 
+ *
  * Also, it would be possible to let the user specify more input, e.g.
  * two nodes that should remain where they are.
- * 
+ *
  * This method uses the following algorithm:
  * 1. compute "heading" of all four edges
@@ -40,5 +41,6 @@
 
 	public AlignInRectangleAction() {
-		super(tr("Align Nodes in Rectangle"), "alignrect", tr("Move the selected nodes into a rectangle."), KeyEvent.VK_Q, 0, true);
+		super(tr("Align Nodes in Rectangle"), "alignrect", tr("Move the selected nodes into a rectangle."),
+		ShortCut.registerShortCut("tools:alignrect", tr("Tool: Align in rectangle"), KeyEvent.VK_Q, ShortCut.GROUP_EDIT), true);
 	}
 
@@ -46,9 +48,9 @@
 		Collection<OsmPrimitive> sel = Main.ds.getSelected();
 		Way myWay = null;
-		if (sel.size() == 1) 
+		if (sel.size() == 1)
 			for (OsmPrimitive osm : sel)
 				if (osm instanceof Way)
 					myWay = (Way) osm;
-		
+
 		if ((myWay == null) || (myWay.nodes.size() != 5) || (!myWay.nodes.get(0).equals(myWay.nodes.get(4)))) {
 			JOptionPane.showMessageDialog(Main.parent, tr("Please select one circular way of exactly four nodes."));
@@ -67,7 +69,7 @@
 		}
 		avg_angle /= 4;
-		
+
 		// select edge that is closest to average, and use it as the base for the following
-		double best_dist = 0; 
+		double best_dist = 0;
 		int base = 0;
 		for (int i=0; i<4; i++)
@@ -86,5 +88,5 @@
 		EastNorth next = en[(base+2)%4]; // node following the second node of the base seg
 		EastNorth prev= en[(base+3)%4];  // node before the first node of the base seg
-		
+
 		// find a parallel to the base segment
 		double base_slope = (end.north() - begin.north()) / (end.east() - begin.east());
@@ -102,22 +104,22 @@
 		u = ((prev.east()-begin.east())*(end.east()-begin.east()) + (prev.north()-begin.north())*(end.north()-begin.north()))/end.distanceSq(begin);
 		EastNorth begin2 = new EastNorth(begin.east()+u*(end.east()-begin.east()), begin.north()+u*(end.north()-begin.north()));
-		
-		// new "begin" and "end" points are halfway between their old position and 
+
+		// new "begin" and "end" points are halfway between their old position and
 		// the base points found above
 		end = new EastNorth((end2.east()+end.east())/2, (end2.north()+end.north())/2);
 		begin = new EastNorth((begin2.east()+begin.east())/2, (begin2.north()+begin.north())/2);
-		
+
 		double other_slope = -1 / base_slope;
 		double next_b = end.north() - other_slope * end.east();
 		double prev_b = begin.north() - other_slope * begin.east();
-		
+
 		double x = (opposite_b-next_b)/(other_slope-base_slope);
 		double y = opposite_b + base_slope * x;
 		next = new EastNorth(x, y);
-		
+
 		x = (opposite_b-prev_b)/(other_slope-base_slope);
 		y = opposite_b + base_slope * x;
 		prev = new EastNorth(x, y);
-		
+
 		Collection<Command> cmds = new LinkedList<Command>();
 		for (int i=0; i<4; i++) {
Index: /trunk/src/org/openstreetmap/josm/actions/AutoScaleAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AutoScaleAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/AutoScaleAction.java	(revision 1023)
@@ -15,4 +15,5 @@
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -46,5 +47,5 @@
     public AutoScaleAction(String mode) {
         super(tr("Zoom to {0}", tr(mode)), "dialogs/autoscale/" + mode, tr("Zoom the view to {0}.", tr(mode)),
-                AutoScaleAction.getModeShortcut(mode), 0, true);
+				ShortCut.registerShortCut("view:zoom"+mode, tr("View: Zoom to {0}", tr(mode)), getModeShortcut(mode), ShortCut.GROUP_EDIT), true);
         String modeHelp = Character.toUpperCase(mode.charAt(0)) + mode.substring(1);
         putValue("help", "Action/AutoScale/" + modeHelp);
@@ -79,6 +80,6 @@
             for (OsmPrimitive osm : sel)
                 osm.visit(v);
-            // increase bbox by 0.001 degrees on each side. this is required 
-            // especially if the bbox contains one single node, but helpful 
+            // increase bbox by 0.001 degrees on each side. this is required
+            // especially if the bbox contains one single node, but helpful
             // in most other cases as well.
             v.enlargeBoundingBox();
Index: /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 1023)
@@ -40,8 +40,9 @@
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Combines multiple ways into one.
- * 
+ *
  * @author Imi
  */
@@ -49,5 +50,6 @@
 
 	public CombineWayAction() {
-		super(tr("Combine Way"), "combineway", tr("Combine several ways into one."), KeyEvent.VK_C, 0, true);
+		super(tr("Combine Way"), "combineway", tr("Combine several ways into one."),
+		ShortCut.registerShortCut("tools:combineway", tr("Tool: Combine ways"), KeyEvent.VK_C, ShortCut.GROUP_EDIT), true);
 		DataSet.selListeners.add(this);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 1023)
@@ -24,13 +24,14 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class CopyAction extends JosmAction implements SelectionChangedListener {
 
 	private LinkedList<JosmAction> listeners;
-	
+
 	public CopyAction() {
 		super(tr("Copy"), "copy",
 				tr("Copy selected objects to paste buffer."),
-				KeyEvent.VK_C, KeyEvent.CTRL_MASK, true);
+				ShortCut.registerShortCut("system:copy", tr("Edit: Copy"), KeyEvent.VK_C, ShortCut.GROUP_MENU), true);
 		setEnabled(false);
 		DataSet.selListeners.add(this);
@@ -41,10 +42,10 @@
 		listeners.add(a);
 	}
-	
+
 	public void actionPerformed(ActionEvent e) {
 		Collection<OsmPrimitive> sel = Main.ds.getSelected();
-		if (sel.isEmpty()) { 
+		if (sel.isEmpty()) {
 			JOptionPane.showMessageDialog(Main.parent,
-					tr("Please select something to copy."));	
+					tr("Please select something to copy."));
 			return;
 		}
@@ -55,10 +56,10 @@
 		/* 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, 
+		/* 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 
+				/* 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; }
@@ -107,5 +108,5 @@
 		Main.pasteBuffer = pasteBuffer;
 		Main.main.menu.paste.setEnabled(true); /* now we have a paste buffer we can make paste available */
-		
+
 		for(JosmAction a : listeners) {
 			a.pasteBufferChanged(Main.pasteBuffer);
Index: /trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java	(revision 1023)
@@ -20,9 +20,12 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Create a new circle from three selected nodes--or a way with 3 nodes. (Useful for roundabouts)
+ *
  * Note: If a way is selected, it is changed. If nodes are selected a new way is created.
  *       So if you've got a way with 3 nodes it makes a difference between running this on the way or the nodes!
+ *
  * BTW: Someone might want to implement projection corrections for this...
  *
@@ -32,5 +35,6 @@
 
 	public CreateCircleAction() {
-		super(tr("Create Circle"), "createcircle", tr("Create a circle from three selected nodes."), KeyEvent.VK_O, KeyEvent.CTRL_MASK, true);
+		super(tr("Create Circle"), "createcircle", tr("Create a circle from three selected nodes."),
+		ShortCut.registerShortCut("tools:createcircle", tr("Tool: Create circle"), KeyEvent.VK_O, ShortCut.GROUP_EDIT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/DeleteAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/DeleteAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/DeleteAction.java	(revision 1023)
@@ -8,4 +8,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class DeleteAction extends JosmAction {
@@ -13,5 +14,5 @@
 	public DeleteAction() {
 		super(tr("Delete"), "dialogs/delete", tr("Delete selected objects."),
-		        KeyEvent.VK_DELETE, 0, true);
+		ShortCut.registerShortCut("system:delete", tr("Edit: Delete"), KeyEvent.VK_DELETE, ShortCut.GROUP_DIRECT), true);
 		setEnabled(true);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/DiskAccessAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/DiskAccessAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/DiskAccessAction.java	(revision 1023)
@@ -10,4 +10,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -16,8 +17,13 @@
 abstract public class DiskAccessAction extends JosmAction {
 
+	public DiskAccessAction(String name, String iconName, String tooltip, ShortCut shortCut) {
+		super(name, iconName, tooltip, shortCut, true);
+	}
+
+	@Deprecated
 	public DiskAccessAction(String name, String iconName, String tooltip, int shortCut, int modifiers) {
 		super(name, iconName, tooltip, shortCut, modifiers, true);
 	}
-	
+
 	protected static JFileChooser createAndOpenFileChooser(boolean open, boolean multiple, String title) {
 		String curDir = Main.pref.get("lastDirectory");
@@ -32,9 +38,9 @@
 			fc.addChoosableFileFilter(ExtensionFileFilter.filters[i]);
 		fc.setAcceptAllFileFilterUsed(true);
-	
+
 		int answer = open ? fc.showOpenDialog(Main.parent) : fc.showSaveDialog(Main.parent);
 		if (answer != JFileChooser.APPROVE_OPTION)
 			return null;
-		
+
 		if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir))
 			Main.pref.put("lastDirectory", fc.getCurrentDirectory().getAbsolutePath());
@@ -42,9 +48,9 @@
 		if (!open) {
 			File file = fc.getSelectedFile();
-			if (file == null || (file.exists() && JOptionPane.YES_OPTION != 
+			if (file == null || (file.exists() && JOptionPane.YES_OPTION !=
 					JOptionPane.showConfirmDialog(Main.parent, tr("File exists. Overwrite?"), tr("Overwrite"), JOptionPane.YES_NO_OPTION)))
 				return null;
 		}
-		
+
 		return fc;
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/DownloadAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 1023)
@@ -17,4 +17,5 @@
 import org.openstreetmap.josm.gui.download.DownloadDialog.DownloadTask;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -27,14 +28,15 @@
  */
 public class DownloadAction extends JosmAction {
-	
+
 	public DownloadDialog dialog;
-	
+
 	public DownloadAction() {
-		super(tr("Download from OSM ..."), "download", tr("Download map data from the OSM server."), KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK, true);
+		super(tr("Download from OSM ..."), "download", tr("Download map data from the OSM server."),
+		ShortCut.registerShortCut("file:download", tr("File: Download"), KeyEvent.VK_D, ShortCut.GROUPS_ALT1+ShortCut.GROUP_HOTKEY), true);
 	}
 
 	public void actionPerformed(ActionEvent e) {
 		dialog = new DownloadDialog();
-		
+
 		JPanel downPanel = new JPanel(new GridBagLayout());
 		downPanel.add(dialog, GBC.eol().fill(GBC.BOTH));
Index: /trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 1023)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class DuplicateAction extends JosmAction implements SelectionChangedListener {
@@ -19,7 +20,7 @@
     	super(tr("Duplicate"), "duplicate",
 			tr("Duplicate selection by copy and immediate paste."),
-			KeyEvent.VK_D, KeyEvent.CTRL_MASK, true);
+			ShortCut.registerShortCut("system:duplicate", tr("Edit: Duplicate selection"), KeyEvent.VK_D, ShortCut.GROUP_MENU), true);
     	setEnabled(false);
-		DataSet.selListeners.add(this);
+			DataSet.selListeners.add(this);
     }
 
@@ -28,5 +29,5 @@
 		Main.main.menu.paste.actionPerformed(e);
     }
-	
+
 	public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
 		setEnabled(! newSelection.isEmpty());
Index: /trunk/src/org/openstreetmap/josm/actions/ExitAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ExitAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/ExitAction.java	(revision 1023)
@@ -8,8 +8,9 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Exit the application. May ask for permission first (if something has changed).
- *  
+ *
  * @author imi
  */
@@ -19,5 +20,6 @@
 	 */
 	public ExitAction() {
-		super(tr("Exit"), "exit", tr("Exit the application."), KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK, true);
+		super(tr("Exit"), "exit", tr("Exit the application."),
+		ShortCut.registerShortCut("system:menuexit", tr("Quit JOSM"), KeyEvent.VK_Q, ShortCut.GROUP_MENU), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 1023)
@@ -34,4 +34,5 @@
 import org.openstreetmap.josm.io.GpxWriter;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -45,5 +46,6 @@
 
 	public GpxExportAction(Layer layer) {
-		super(tr("Export to GPX ..."), "exportgpx", tr("Export the data to GPX file."), KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK);
+		super(tr("Export to GPX ..."), "exportgpx", tr("Export the data to GPX file."),
+		ShortCut.registerShortCut("file:exportgpx", tr("Export to GPX"), KeyEvent.VK_E, ShortCut.GROUP_MENU));
 		this.layer = layer;
 	}
@@ -71,5 +73,5 @@
 			file = new File(fn);
 		}
-		
+
 		// open the dialog asking for options
 		JPanel p = new JPanel(new GridBagLayout());
@@ -80,5 +82,5 @@
 		desc.setLineWrap(true);
 		p.add(new JScrollPane(desc), GBC.eop().fill(GBC.BOTH));
-		
+
 		JCheckBox author = new JCheckBox(tr("Add author information"), Main.pref.getBoolean("lastAddAuthor", true));
 		author.setSelected(true);
@@ -105,5 +107,5 @@
 		p.add(warning, GBC.eol().fill(GBC.HORIZONTAL).insets(15,0,0,0));
 		addDependencies(author, authorName, email, copyright, predefined, copyrightYear, nameLabel, emailLabel, copyrightLabel, copyrightYearLabel, warning);
-		
+
 		p.add(new JLabel(tr("Keywords")), GBC.eol());
 		JTextField keywords = new JTextField();
@@ -113,5 +115,5 @@
 		if (answer != JOptionPane.OK_OPTION)
 			return;
-		
+
 		Main.pref.put("lastAddAuthor", author.isSelected());
 		if (authorName.getText().length() != 0)
@@ -136,17 +138,17 @@
 			x.printStackTrace();
 			JOptionPane.showMessageDialog(Main.parent, tr("Error while exporting {0}", fn)+":\n"+x.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
-		}		
-	}
-	
+		}
+	}
+
 	/**
 	 * Add all those listeners to handle the enable state of the fields.
-	 * @param copyrightYearLabel 
-	 * @param copyrightLabel 
-	 * @param emailLabel 
-	 * @param nameLabel 
-	 * @param warning 
+	 * @param copyrightYearLabel
+	 * @param copyrightLabel
+	 * @param emailLabel
+	 * @param nameLabel
+	 * @param warning
 	 */
 	private static void addDependencies(
-			final JCheckBox author, 
+			final JCheckBox author,
 			final JTextField authorName,
 			final JTextField email,
@@ -159,5 +161,5 @@
 			final JLabel copyrightYearLabel,
 			final JLabel warning) {
-		
+
 		ActionListener authorActionListener = new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
@@ -183,5 +185,5 @@
 				};
 		authorName.addKeyListener(authorNameListener);
-		
+
 		predefined.addActionListener(new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
Index: /trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 1023)
@@ -14,9 +14,11 @@
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
 import org.openstreetmap.josm.tools.OpenBrowser;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class HistoryInfoAction extends JosmAction {
 
 	public HistoryInfoAction() {
-		super(tr("OSM History Information"), "about",tr("Display history information about OSM ways or nodes."), KeyEvent.VK_H, KeyEvent.SHIFT_DOWN_MASK, true);
+		super(tr("OSM History Information"), "about",tr("Display history information about OSM ways or nodes."),
+		ShortCut.registerShortCut("core:history", tr("Display history"), KeyEvent.VK_H, ShortCut.GROUP_HOTKEY), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/JoinNodeWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/JoinNodeWayAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/JoinNodeWayAction.java	(revision 1023)
@@ -23,9 +23,10 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class JoinNodeWayAction extends JosmAction {
 	public JoinNodeWayAction() {
-	    super(tr("Join node to way"), "joinnodeway",
-			tr("Join a node into the nearest way segments"), KeyEvent.VK_J, 0, true);
+	    super(tr("Join node to way"), "joinnodeway", tr("Join a node into the nearest way segments"),
+			ShortCut.registerShortCut("tools:joinnodeway", tr("Tool: Join node to way"), KeyEvent.VK_J, ShortCut.GROUP_EDIT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/JosmAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/JosmAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/JosmAction.java	(revision 1023)
@@ -2,4 +2,5 @@
 package org.openstreetmap.josm.actions;
 
+import java.awt.event.InputEvent;
 
 import javax.swing.AbstractAction;
@@ -11,41 +12,89 @@
 import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.ShortCutLabel;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Base class helper for all Actions in JOSM. Just to make the life easier.
- * 
+ *
  * destroy() from interface Destroyable is called e.g. for MapModes, when the last layer has
  * been removed and so the mapframe will be destroyed. For other JosmActions, destroy() may never
  * be called (currently).
- * 
+ *
  * @author imi
  */
 abstract public class JosmAction extends AbstractAction implements Destroyable {
 
+	@Deprecated
 	public KeyStroke shortCut;
+	protected ShortCut sc;
 
+	public ShortCut getShortCut() {
+		if (sc == null) {
+			sc = ShortCut.registerShortCut("core:none", "No Shortcut", 0, ShortCut.GROUP_NONE);
+			sc.setAutomatic(); // as this shortcut is shared by all action that don't want to have a shortcut,
+			                   // we shouldn't allow the user to change it...
+		}
+		return sc;
+	}
+
+	@Deprecated
 	public JosmAction(String name, String iconName, String tooltip, int shortCut, int modifier, boolean register) {
 		super(name, iconName == null ? null : ImageProvider.get(iconName));
 		setHelpId();
-		String scl = ShortCutLabel.name(shortCut, modifier);
-		putValue(SHORT_DESCRIPTION, "<html>"+tooltip+" <font size='-2'>"+scl+"</font>"+(scl.equals("")?"":"&nbsp;")+"</html>");
 		if (shortCut != 0) {
-			this.shortCut = KeyStroke.getKeyStroke(shortCut, modifier);
-			Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(this.shortCut, name);
+			int group = ShortCut.GROUP_LAYER; //GROUP_NONE;
+			if (((modifier & InputEvent.CTRL_MASK) != 0) || ((modifier & InputEvent.CTRL_DOWN_MASK) != 0)) {
+				group = ShortCut.GROUP_MENU;
+			} else if (modifier == 0) {
+				group = ShortCut.GROUP_EDIT;
+			}
+			sc = ShortCut.registerShortCut("auto:"+name, name, shortCut, group);
+			this.shortCut = sc.getKeyStroke();
+			Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sc.getKeyStroke(), name);
 			Main.contentPane.getActionMap().put(name, this);
 		}
-        putValue("toolbar", iconName);
-        if (register)
-        	Main.toolbar.register(this);
+		putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
+		putValue("toolbar", iconName);
+    if (register)
+    	Main.toolbar.register(this);
+	}
+
+	/**
+	 * The new super for all actions.
+	 *
+	 * Use this super constructor to setup your action. It takes 5 parameters:
+	 *
+	 * name - the action's text as displayed on the menu (if it is added to a menu)
+	 * iconName - the filename of the icon to use
+	 * tooltip - a longer description of the action that will be displayed in the tooltip. Please note
+	 *           that html is not supported for menu action on some platforms
+	 * shortCut - a ready-created shortcut object or null if you don't want a shortcut. But you always
+	 *            do want a shortcut, remember you can alway register it with group=none, so you
+	 *            won't be assigned a shurtcut unless the user configures one. If you pass null here,
+	 *            the user CANNOT configure a shortcut for your action.
+	 * register - register this action for the toolbar preferences?
+	 */
+	public JosmAction(String name, String iconName, String tooltip, ShortCut shortCut, boolean register) {
+		super(name, iconName == null ? null : ImageProvider.get(iconName));
+		setHelpId();
+		sc = shortCut;
+		if (sc != null) {
+			this.shortCut = sc.getKeyStroke();
+			Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sc.getKeyStroke(), name);
+			Main.contentPane.getActionMap().put(name, this);
+		}
+		putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
+		putValue("toolbar", iconName);
+    if (register)
+    	Main.toolbar.register(this);
 	}
 
 	public void destroy() {
 		if (shortCut != null) {
-			Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortCut);
-			Main.contentPane.getActionMap().remove(shortCut);
+			Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(sc.getKeyStroke());
+			Main.contentPane.getActionMap().remove(sc.getKeyStroke());
 		}
 	}
-	
+
 	public JosmAction() {
 		setHelpId();
@@ -58,5 +107,5 @@
 		return;
 	}
-	
+
 	/**
 	 * needs to be overridden to be useful
@@ -65,5 +114,5 @@
 		return;
 	}
-	
+
 	private void setHelpId() {
 		String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
Index: /trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 1023)
@@ -40,4 +40,5 @@
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.ShortCut;
 
 
@@ -45,5 +46,5 @@
  * Merge two or more nodes into one node.
  * (based on Combine ways)
- * 
+ *
  * @author Matthew Newton
  *
@@ -52,5 +53,6 @@
 
 	public MergeNodesAction() {
-		super(tr("Merge Nodes"), "mergenodes", tr("Merge nodes into one."), KeyEvent.VK_M, 0, true);
+		super(tr("Merge Nodes"), "mergenodes", tr("Merge nodes into the oldest one."),
+		ShortCut.registerShortCut("tools:mergenodes", tr("Tool: Merge nodes"), KeyEvent.VK_M, ShortCut.GROUP_EDIT), true);
 		DataSet.selListeners.add(this);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/MoveAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/MoveAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/MoveAction.java	(revision 1023)
@@ -17,8 +17,9 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Moves the selection
- * 
+ *
  * @author Frederik Ramm
  */
@@ -27,27 +28,49 @@
 	public enum Direction { UP, LEFT, RIGHT, DOWN }
 	private Direction myDirection;
-	
+
+	// any better idea?
+	private static Object calltosupermustbefirststatementinconstructor(Direction dir, boolean text) {
+		ShortCut sc;
+		String directiontext;
+		if        (dir == Direction.UP)   {
+			directiontext = tr("up");
+			sc = ShortCut.registerShortCut("core:moveup",    tr("Move objects {0}", directiontext), KeyEvent.VK_UP,    ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT);
+		} else if (dir == Direction.DOWN)  {
+			directiontext = tr("down");
+			sc = ShortCut.registerShortCut("core:movedown",  tr("Move objects {0}", directiontext), KeyEvent.VK_DOWN,  ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT);
+		} else if (dir == Direction.LEFT)  {
+			directiontext = tr("left");
+			sc = ShortCut.registerShortCut("core:moveleft",  tr("Move objects {0}", directiontext), KeyEvent.VK_LEFT,  ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT);
+		} else { //dir == Direction.RIGHT) {
+			directiontext = tr("right");
+			sc = ShortCut.registerShortCut("core:moveright", tr("Move objects {0}", directiontext), KeyEvent.VK_RIGHT, ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT);
+		}
+		if (text) {
+			return directiontext;
+		} else {
+			return sc;
+		}
+	}
+
 	public MoveAction(Direction dir) {
-		super(tr("Move"), null, tr("Moves Objects"), 
-		(dir == Direction.UP) ? KeyEvent.VK_UP : 
-		(dir == Direction.DOWN) ? KeyEvent.VK_DOWN :
-		(dir == Direction.LEFT) ? KeyEvent.VK_LEFT :
-		KeyEvent.VK_RIGHT, 0, true);
+		super(tr("Move {0}", calltosupermustbefirststatementinconstructor(dir, true)), null,
+		      tr("Moves Objects {0}", calltosupermustbefirststatementinconstructor(dir, true)),
+		      (ShortCut)calltosupermustbefirststatementinconstructor(dir, false), true);
 		myDirection = dir;
 	}
 
 	public void actionPerformed(ActionEvent event) {
-		
+
 		// find out how many "real" units the objects have to be moved in order to
 		// achive an 1-pixel movement
-		
+
 		EastNorth en1 = Main.map.mapView.getEastNorth(100, 100);
 		EastNorth en2 = Main.map.mapView.getEastNorth(101, 101);
-		
+
 		double distx = en2.east() - en1.east();
 		double disty = en2.north() - en1.north();
-		
+
 		switch (myDirection) {
-		case UP: 
+		case UP:
 			distx = 0;
 			disty = -disty;
@@ -62,8 +85,8 @@
 			disty = 0;
 		}
-		
+
 		Collection<OsmPrimitive> selection = Main.ds.getSelected();
 		Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
-		
+
 		Command c = !Main.main.undoRedo.commands.isEmpty()
 		? Main.main.undoRedo.commands.getLast() : null;
Index: /trunk/src/org/openstreetmap/josm/actions/NewAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/NewAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/NewAction.java	(revision 1023)
@@ -11,9 +11,11 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class NewAction extends JosmAction {
 
 	public NewAction() {
-		super(tr("New"), "new", tr("Create a new map."), KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK, true);
+		super(tr("New"), "new", tr("Create a new map."),
+		ShortCut.registerShortCut("system:new", tr("File: New"), KeyEvent.VK_N, ShortCut.GROUP_MENU), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/OpenAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OpenAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/OpenAction.java	(revision 1023)
@@ -25,18 +25,20 @@
 import org.openstreetmap.josm.io.OsmReader;
 import org.xml.sax.SAXException;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Open a file chooser dialog and select an file to import. Then call the gpx-import
  * driver. Finally open an internal frame into the main window with the gpx data shown.
- * 
+ *
  * @author imi
  */
 public class OpenAction extends DiskAccessAction {
-	
+
 	/**
 	 * Create an open action. The name is "Open a file".
 	 */
 	public OpenAction() {
-		super(tr("Open ..."), "open", tr("Open a file."), KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK);
+		super(tr("Open ..."), "open", tr("Open a file."),
+		ShortCut.registerShortCut("system:open", tr("File: Open..."), KeyEvent.VK_O, ShortCut.GROUP_MENU));
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/PasteAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 1023)
@@ -24,12 +24,12 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.tools.ShortCut;
 
 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);
+    	super(tr("Paste"), "paste", tr("Paste contents of paste buffer."),
+			ShortCut.registerShortCut("system:paste", tr("Edit: Paste"), KeyEvent.VK_V, ShortCut.GROUP_MENU), true);
+			setEnabled(false);
     }
 
@@ -37,13 +37,13 @@
 		DataSet pasteBuffer = Main.pasteBuffer;
 
-		/* Find the middle of the pasteBuffer area */ 
+		/* Find the middle of the pasteBuffer area */
 		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; } 
+			if (east > maxEast) { maxEast = east; }
+			if (east < minEast) { minEast = east; }
+			if (north > maxNorth) { maxNorth = north; }
+			if (north < minNorth) { minNorth = north; }
 		}
 
@@ -57,8 +57,8 @@
 		double offsetEast  = mPosition.east() - (maxEast + minEast)/2.0;
 		double offsetNorth = mPosition.north() - (maxNorth + minNorth)/2.0;
-		
-		HashMap<OsmPrimitive,OsmPrimitive> map = new HashMap<OsmPrimitive,OsmPrimitive>(); 
+
+		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) {
@@ -96,5 +96,5 @@
 			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();
Index: /trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 1023)
@@ -20,4 +20,5 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class PasteTagsAction extends JosmAction implements SelectionChangedListener {
@@ -26,5 +27,5 @@
 		super(tr("Paste Tags"), "pastetags",
 			tr("Apply tags of contents of paste buffer to all selected items."),
-			KeyEvent.VK_V, KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK, true);
+			ShortCut.registerShortCut("system:pastestyle", tr("Edit: Paste tags"), KeyEvent.VK_V, ShortCut.GROUP_MENU), true);
 		DataSet.selListeners.add(this);
 		copyAction.addListener(this);
Index: /trunk/src/org/openstreetmap/josm/actions/PreferencesAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/PreferencesAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/PreferencesAction.java	(revision 1023)
@@ -15,4 +15,5 @@
 import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -27,5 +28,6 @@
 	 */
 	public PreferencesAction() {
-		super(tr("Preferences ..."), "preference", tr("Open a preferences page for global settings."), KeyEvent.VK_F12, 0, true);
+		super(tr("Preferences ..."), "preference", tr("Open a preferences page for global settings."),
+		ShortCut.registerShortCut("system:preferences", tr("Preferences"), KeyEvent.VK_F12, ShortCut.GROUP_DIRECT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/RedoAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/RedoAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/RedoAction.java	(revision 1023)
@@ -9,9 +9,9 @@
 
 import org.openstreetmap.josm.Main;
-
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Redoes the last command.
- * 
+ *
  * @author imi
  */
@@ -22,5 +22,6 @@
 	 */
 	public RedoAction() {
-		super(tr("Redo"), "redo", tr("Redo the last undone action."), KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK, true);
+		super(tr("Redo"), "redo", tr("Redo the last undone action."),
+		ShortCut.registerShortCut("system:redo", tr("Edit: Redo"), KeyEvent.VK_Y, ShortCut.GROUP_MENU), true);
 		setEnabled(false);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/ReverseWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 1023)
@@ -25,11 +25,11 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ReverseWayAction extends JosmAction {
 
 	public ReverseWayAction() {
-		super(tr("Reverse ways"), "wayflip",
-		        tr("Reverse the direction of all selected ways."),
-		        KeyEvent.VK_R, 0, true);
+		super(tr("Reverse ways"), "wayflip", tr("Reverse the direction of all selected ways."),
+		ShortCut.registerShortCut("tools:reverse", tr("Tool: Reverse way"), KeyEvent.VK_R, ShortCut.GROUP_EDIT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/SaveAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SaveAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/SaveAction.java	(revision 1023)
@@ -11,12 +11,13 @@
 import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Export the data as an OSM xml file.
- * 
+ *
  * @author imi
  */
 public class SaveAction extends SaveActionBase {
-    
+
 	/**
 	 * Construct the action with "Save" as label.
@@ -24,7 +25,8 @@
 	 */
 	public SaveAction(Layer layer) {
-		super(tr("Save"), "save", tr("Save the current data."), KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK, layer);
+		super(tr("Save"), "save", tr("Save the current data."),
+		ShortCut.registerShortCut("system:save", tr("File: Save"), KeyEvent.VK_S, ShortCut.GROUP_MENU), layer);
 	}
-	
+
 	@Override public File getFile(Layer layer) {
 		if (layer instanceof OsmDataLayer) {
Index: /trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 1023)
@@ -22,4 +22,5 @@
 import org.openstreetmap.josm.io.OsmWriter;
 import org.openstreetmap.josm.io.GpxWriter;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public abstract class SaveActionBase extends DiskAccessAction {
@@ -27,4 +28,10 @@
 	private Layer layer;
 
+	public SaveActionBase(String name, String iconName, String tooltip, ShortCut shortCut, Layer layer) {
+		super(name, iconName, tooltip, shortCut);
+		this.layer = layer;
+	}
+
+	@Deprecated
 	public SaveActionBase(String name, String iconName, String tooltip, int shortCut, int modifiers, Layer layer) {
 		super(name, iconName, tooltip, shortCut, modifiers);
@@ -79,5 +86,5 @@
 		}
 		if (!Main.map.conflictDialog.conflicts.isEmpty()) {
-			int answer = JOptionPane.showConfirmDialog(Main.parent, 
+			int answer = JOptionPane.showConfirmDialog(Main.parent,
 					tr("There are unresolved conflicts. Conflicts will not be saved and handled as if you rejected all. Continue?"),tr("Conflicts"), JOptionPane.YES_NO_OPTION);
 			if (answer != JOptionPane.YES_OPTION)
@@ -216,7 +223,7 @@
 	/**
 	 * Check the data set if it would be empty on save. It is empty, if it contains
-	 * no objects (after all objects that are created and deleted without being 
+	 * no objects (after all objects that are created and deleted without being
 	 * transfered to the server have been removed).
-	 *  
+	 *
 	 * @return <code>true</code>, if a save result in an empty data set.
 	 */
Index: /trunk/src/org/openstreetmap/josm/actions/SaveAsAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SaveAsAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/SaveAsAction.java	(revision 1023)
@@ -9,12 +9,13 @@
 
 import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Export the data.
- * 
+ *
  * @author imi
  */
 public class SaveAsAction extends SaveActionBase {
-    
+
 	/**
 	 * Construct the action with "Save" as label.
@@ -22,7 +23,8 @@
 	 */
 	public SaveAsAction(Layer layer) {
-		super(tr("Save as ..."), "save_as", tr("Save the current data to a new file."), KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK, layer);
+		super(tr("Save as ..."), "save_as", tr("Save the current data to a new file."),
+		ShortCut.registerShortCut("system:saveas", tr("File: Save as..."), KeyEvent.VK_S, ShortCut.GROUP_MENU), layer);
 	}
-	
+
 	@Override protected File getFile(Layer layer) {
 		return openFileDialog(layer);
Index: /trunk/src/org/openstreetmap/josm/actions/SelectAllAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SelectAllAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/SelectAllAction.java	(revision 1023)
@@ -8,10 +8,12 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class SelectAllAction extends JosmAction {
 
 	public SelectAllAction() {
-		super(tr("Select All"),"selectall", tr("Select all undeleted objects in the data layer. This selects incomplete objects too."), KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK, true);
-    }
+		super(tr("Select All"),"selectall", tr("Select all undeleted objects in the data layer. This selects incomplete objects too."),
+		ShortCut.registerShortCut("system:selectall", tr("Edit: Select all"), KeyEvent.VK_A, ShortCut.GROUP_MENU), true);
+	}
 
 	public void actionPerformed(ActionEvent e) {
Index: /trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java	(revision 1023)
@@ -33,8 +33,9 @@
 import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Splits a way into multiple ways (all identical except for their node list).
- * 
+ *
  * Ways are just split at the selected nodes.  The nodes remain in their
  * original order.  Selected nodes at the end of a way are ignored.
@@ -50,5 +51,6 @@
 	 */
 	public SplitWayAction() {
-		super(tr("Split Way"), "splitway", tr("Split a way at the selected node."), KeyEvent.VK_P, 0, true);
+		super(tr("Split Way"), "splitway", tr("Split a way at the selected node."),
+		ShortCut.registerShortCut("tools:splitway", tr("Tool: Split way"), KeyEvent.VK_P, ShortCut.GROUP_EDIT), true);
 		DataSet.selListeners.add(this);
 	}
@@ -56,5 +58,5 @@
 	/**
 	 * Called when the action is executed.
-	 * 
+	 *
 	 * This method performs an expensive check whether the selection clearly defines one
 	 * of the split actions outlined above, and if yes, calls the splitWay method.
@@ -85,5 +87,5 @@
 			}
 		};
-		
+
 		for (OsmPrimitive p : selection)
 			p.visit(splitVisitor);
@@ -111,5 +113,5 @@
 			}
 			if (wayOccurenceCounter.isEmpty()) {
-				JOptionPane.showMessageDialog(Main.parent, 
+				JOptionPane.showMessageDialog(Main.parent,
 						trn("The selected node is no inner part of any way.",
 								"The selected nodes are no inner part of any way.", selectedNodes.size()));
@@ -140,5 +142,5 @@
 			}
 			if (!nds.isEmpty()) {
-				JOptionPane.showMessageDialog(Main.parent, 
+				JOptionPane.showMessageDialog(Main.parent,
 						trn("The selected way does not contain the selected node.",
 								"The selected way does not contain all the selected nodes.", selectedNodes.size()));
@@ -151,11 +153,11 @@
 	}
 
-	/** 
+	/**
 	 * Checks if the selection consists of something we can work with.
 	 * Checks only if the number and type of items selected looks good;
-	 * does not check whether the selected items are really a valid 
+	 * does not check whether the selected items are really a valid
 	 * input for splitting (this would be too expensive to be carried
 	 * out from the selectionChanged listener).
-	 */	
+	 */
 	private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
 		boolean way = false;
@@ -230,7 +232,7 @@
 		Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
 		Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size());
-		
+
 		Iterator<List<Node>> chunkIt = wayChunks.iterator();
-		
+
 		// First, change the original way
 		Way changedWay = new Way(selectedWay);
Index: /trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 1023)
@@ -26,4 +26,5 @@
 import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -44,5 +45,6 @@
 	 */
 	public UnGlueAction() {
-		super(tr("UnGlue Ways"), "unglueways", tr("Duplicate the selected node so each way using it has its own copy."), KeyEvent.VK_G, 0, true);
+		super(tr("UnGlue Ways"), "unglueways", tr("Duplicate the selected node so each way using it has its own copy."),
+		ShortCut.registerShortCut("tools:unglue", tr("Tool: Unglue"), KeyEvent.VK_G, ShortCut.GROUP_EDIT), true);
 		DataSet.selListeners.add(this);
 	}
@@ -85,12 +87,12 @@
 	 */
 	private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
-		
+
 		int size = selection.size();
 		if (size < 1 || size > 2)
 			return false;
-		
+
 		selectedNode = null;
 		selectedWay = null;
-		
+
 		for (OsmPrimitive p : selection) {
 			if (p instanceof Node) {
@@ -100,9 +102,9 @@
 			} else if (p instanceof Way) {
 				selectedWay = (Way) p;
-				if (size == 2 && selectedNode != null) 
+				if (size == 2 && selectedNode != null)
 					return selectedWay.nodes.contains(selectedNode);
 			}
 		}
-		
+
 		return false;
 	}
@@ -133,5 +135,5 @@
 		return firstway;
 	}
-	
+
 	/**
 	 * see above
@@ -143,5 +145,5 @@
 
 		if (selectedWay == null) {
-			
+
 			boolean firstway = true;
 			// modify all ways containing the nodes
@@ -149,5 +151,5 @@
 				if (w.deleted || w.incomplete || w.nodes.size() < 1) continue;
 				if (!w.nodes.contains(selectedNode)) continue;
-	
+
 				firstway = modifyWay(firstway, w, cmds, newNodes);
 			}
Index: /trunk/src/org/openstreetmap/josm/actions/UndoAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/UndoAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/UndoAction.java	(revision 1023)
@@ -9,9 +9,9 @@
 
 import org.openstreetmap.josm.Main;
-
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Undoes the last command.
- * 
+ *
  * @author imi
  */
@@ -22,5 +22,6 @@
 	 */
 	public UndoAction() {
-		super(tr("Undo"), "undo", tr("Undo the last action."), KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK, true);
+		super(tr("Undo"), "undo", tr("Undo the last action."),
+		ShortCut.registerShortCut("system:undo", tr("Edit: Undo"), KeyEvent.VK_Z, ShortCut.GROUP_MENU), true);
 		setEnabled(false);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/UnselectAllAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/UnselectAllAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/UnselectAllAction.java	(revision 1023)
@@ -10,4 +10,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class UnselectAllAction extends JosmAction {
@@ -15,10 +16,12 @@
 	public UnselectAllAction() {
 		super(tr("Unselect All"), "unselectall", tr("Unselect all objects."),
-		        KeyEvent.VK_U, 0, true);
+		ShortCut.registerShortCut("edit:unselectall", tr("Edit: Unselect all"), KeyEvent.VK_U, ShortCut.GROUP_EDIT), true);
+		// this is not really GROUP_EDIT, but users really would complain if the yhad to reconfigure because we put
+		// the correct group in
 
 		// Add extra shortcut C-S-a
 		Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
-		        KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK
-		                | KeyEvent.SHIFT_DOWN_MASK), tr("Unselect All"));
+		ShortCut.registerShortCut("edit:unselectall2", tr("Edit: Unselect all (2)"), KeyEvent.VK_A, ShortCut.GROUP_MENU).getKeyStroke(),
+		tr("Unselect All"));
 
 		// Add extra shortcut ESCAPE
@@ -29,6 +32,6 @@
 		 */
 		Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
-		        KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
-		        tr("Unselect All"));
+		ShortCut.registerShortCut("edit:unselectall3", tr("Edit: Unselect all (3)"), KeyEvent.VK_ESCAPE, ShortCut.GROUP_DIRECT).getKeyStroke(),
+		tr("Unselect All"));
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 1023)
@@ -24,4 +24,5 @@
 import org.openstreetmap.josm.tools.GBC;
 import org.xml.sax.SAXException;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -34,5 +35,5 @@
  */
 public class UploadAction extends JosmAction {
-	
+
 	/** Upload Hook */
 	public interface UploadHook {
@@ -46,10 +47,10 @@
 		public boolean checkUpload(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete);
 	}
-	
+
 	/**
 	 * The list of upload hooks. These hooks will be called one after the other
 	 * when the user wants to upload data. Plugins can insert their own hooks here
 	 * if they want to be able to veto an upload.
-	 * 
+	 *
 	 * Be default, the standard upload dialog is the only element in the list.
 	 * Plugins should normally insert their code before that, so that the upload
@@ -60,5 +61,6 @@
 
 	public UploadAction() {
-		super(tr("Upload to OSM ..."), "upload", tr("Upload all changes to the OSM server."), KeyEvent.VK_U, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK, true);
+		super(tr("Upload to OSM ..."), "upload", tr("Upload all changes to the OSM server."),
+		ShortCut.registerShortCut("file:upload", tr("File: Upload"), KeyEvent.VK_U, ShortCut.GROUPS_ALT1+ShortCut.GROUP_HOTKEY), true);
 
 		/**
@@ -129,5 +131,5 @@
 				delete.addFirst(osm);
 		}
-		
+
 		if (add.isEmpty() && update.isEmpty() && delete.isEmpty()) {
 			JOptionPane.showMessageDialog(Main.parent,tr("No changes to upload."));
@@ -140,5 +142,5 @@
 			if(!hook.checkUpload(add, update, delete))
 				return;
-		
+
 		final OsmServerWriter server = new OsmServerWriter();
 		final Collection<OsmPrimitive> all = new LinkedList<OsmPrimitive>();
Index: /trunk/src/org/openstreetmap/josm/actions/ZoomInAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ZoomInAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/ZoomInAction.java	(revision 1023)
@@ -8,4 +8,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ZoomInAction extends JosmAction {
@@ -13,5 +14,5 @@
 	public ZoomInAction() {
 		super(tr("Zoom in"), "dialogs/zoomin", tr("Zoom in"),
-		        KeyEvent.VK_PLUS, 0, true);
+		ShortCut.registerShortCut("view:zoomin", tr("View: Zoom in"), KeyEvent.VK_PLUS, ShortCut.GROUP_DIRECT), true);
 		setEnabled(true);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/ZoomOutAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ZoomOutAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/ZoomOutAction.java	(revision 1023)
@@ -8,4 +8,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ZoomOutAction extends JosmAction {
@@ -13,5 +14,5 @@
 	public ZoomOutAction() {
 		super(tr("Zoom out"), "dialogs/zoomout", tr("Zoom out"),
-		        KeyEvent.VK_MINUS, 0, true);
+		ShortCut.registerShortCut("view:zoomout", tr("View: Zoom out"), KeyEvent.VK_MINUS, ShortCut.GROUP_DIRECT), true);
 		setEnabled(true);
 	}
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 1023)
@@ -11,11 +11,13 @@
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class AudioBackAction extends JosmAction {
 
 	private double amount; // note, normally negative, i.e. jump backwards in time
-	
+
 	public AudioBackAction() {
-		super(tr("Back"), "audio-back", tr("Jump back."), KeyEvent.VK_F6, 0, true);
+		super(tr("Back"), "audio-back", tr("Jump back."),
+		ShortCut.registerShortCut("audio:back", tr("Audio: Back"), KeyEvent.VK_F6, ShortCut.GROUP_DIRECT), true);
 		try {
 			amount = - Double.parseDouble(Main.pref.get("audio.forwardbackamount","10.0"));
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java	(revision 1023)
@@ -7,9 +7,22 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 abstract public class AudioFastSlowAction extends JosmAction {
 
 	private double multiplier;
-	
+
+	public AudioFastSlowAction(String name, String iconName, String tooltip, ShortCut shortcut, boolean fast) {
+		super(name, iconName, tooltip, shortcut, true);
+		try {
+			multiplier = Double.parseDouble(Main.pref.get("audio.fastfwdmultiplier","1.3"));
+		} catch (NumberFormatException e) {
+			multiplier = 1.3;
+		}
+		if (! fast)
+			multiplier = 1.0 / multiplier;
+	}
+
+	@Deprecated
 	public AudioFastSlowAction(String name, String iconName, String tooltip, int shortcut, int modifier, boolean fast) {
 		super(name, iconName, tooltip, shortcut, modifier, true);
@@ -19,5 +32,5 @@
 			multiplier = 1.3;
 		}
-		if (! fast) 
+		if (! fast)
 			multiplier = 1.0 / multiplier;
 	}
@@ -25,5 +38,5 @@
 	public void actionPerformed(ActionEvent e) {
 		double speed = AudioPlayer.speed();
-		if (speed * multiplier <= 0.1) 
+		if (speed * multiplier <= 0.1)
 			return;
 		try {
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioFasterAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioFasterAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioFasterAction.java	(revision 1023)
@@ -3,11 +3,13 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ShortCut;
 
 import java.awt.event.KeyEvent;
 
 public class AudioFasterAction extends AudioFastSlowAction {
-	
+
 	public AudioFasterAction() {
-		super(tr("Faster"), "audio-faster", tr("Faster Forward"), KeyEvent.VK_F9, 0, true);
+		super(tr("Faster"), "audio-faster", tr("Faster Forward"),
+		ShortCut.registerShortCut("audio:faster", tr("Audio: Faster"), KeyEvent.VK_F9, ShortCut.GROUP_DIRECT), true);
 	}
 }
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java	(revision 1023)
@@ -11,11 +11,13 @@
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class AudioFwdAction extends JosmAction {
 
 	private double amount;
-	
+
 	public AudioFwdAction() {
-		super(tr("Forward"), "audio-fwd", tr("Jump forward"), KeyEvent.VK_F7, 0, true);
+		super(tr("Forward"), "audio-fwd", tr("Jump forward"),
+		ShortCut.registerShortCut("audio:forward", tr("Audio: Forward"), KeyEvent.VK_F7, ShortCut.GROUP_DIRECT), true);
 		try {
 			amount = Double.parseDouble(Main.pref.get("audio.forwardbackamount","10.0"));
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioNextAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioNextAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioNextAction.java	(revision 1023)
@@ -9,9 +9,11 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class AudioNextAction extends JosmAction {
 
 	public AudioNextAction() {
-		super(tr("Next Marker"), "audio-next", tr("Play next marker."), KeyEvent.VK_F8, 0, true);
+		super(tr("Next Marker"), "audio-next", tr("Play next marker."),
+		ShortCut.registerShortCut("audio:next", tr("Audio: Next"), KeyEvent.VK_F8, ShortCut.GROUP_DIRECT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java	(revision 1023)
@@ -11,9 +11,11 @@
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class AudioPlayPauseAction extends JosmAction {
 
 	public AudioPlayPauseAction() {
-		super(tr("Play/pause"), "audio-playpause", tr("Play/pause audio."), KeyEvent.VK_PERIOD, 0, true);
+		super(tr("Play/pause"), "audio-playpause", tr("Play/pause audio."),
+		ShortCut.registerShortCut("audio:pause", tr("Audio: Play/Pause"), KeyEvent.VK_PERIOD, ShortCut.GROUP_DIRECT), true);
 	}
 
@@ -35,4 +37,4 @@
 			AudioPlayer.audioMalfunction(ex);
 		}
-	}	
+	}
 }
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioPrevAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioPrevAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioPrevAction.java	(revision 1023)
@@ -9,9 +9,11 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class AudioPrevAction extends JosmAction {
 
 	public AudioPrevAction() {
-		super(tr("Previous Marker"), "audio-prev", tr("Play previous marker."), KeyEvent.VK_F5, 0, true);
+		super(tr("Previous Marker"), "audio-prev", tr("Play previous marker."),
+		ShortCut.registerShortCut("audio:prev", tr("Audio: Previous"), KeyEvent.VK_F5, ShortCut.GROUP_DIRECT), true);
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioSlowerAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioSlowerAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioSlowerAction.java	(revision 1023)
@@ -5,9 +5,11 @@
 
 import java.awt.event.KeyEvent;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class AudioSlowerAction extends AudioFastSlowAction {
-	
+
 	public AudioSlowerAction() {
-		super(tr("Slower"), "audio-slower", tr("Slower Forward"), KeyEvent.VK_F9, KeyEvent.SHIFT_MASK, false);
+		super(tr("Slower"), "audio-slower", tr("Slower Forward"),
+		ShortCut.registerShortCut("audio:slower", tr("Audio: Slower"), KeyEvent.VK_F9, ShortCut.GROUP_DIRECT), true);
 	}
 }
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 1023)
@@ -16,10 +16,11 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * An action that enables the user to delete nodes and other objects.
  *
- * The user can click on an object, which gets deleted if possible. When Ctrl is 
- * pressed when releasing the button, the objects and all its references are 
+ * The user can click on an object, which gets deleted if possible. When Ctrl is
+ * pressed when releasing the button, the objects and all its references are
  * deleted. The exact definition of "all its references" are in
  * {@link #deleteWithReferences deleteWithReferences}.
@@ -30,5 +31,5 @@
  * If the user enters the mapmode and any object is selected, all selected
  * objects that can be deleted will.
- * 
+ *
  * @author imi
  */
@@ -41,8 +42,8 @@
 	public DeleteAction(MapFrame mapFrame) {
 		super(tr("Delete Mode"),
-				"delete", 
-				tr("Delete nodes or ways."), 
-				KeyEvent.VK_D, 
-				mapFrame, 
+				"delete",
+				tr("Delete nodes or ways."),
+				ShortCut.registerShortCut("mapmode:delete", tr("Delete mode"), KeyEvent.VK_D, ShortCut.GROUP_EDIT),
+				mapFrame,
 				ImageProvider.getCursor("normal", "delete"));
 	}
@@ -58,5 +59,5 @@
 	}
 
-	
+
 	@Override public void actionPerformed(ActionEvent e) {
 		super.actionPerformed(e);
@@ -97,5 +98,5 @@
 		boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
 		boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
-		
+
 		OsmPrimitive sel = Main.map.mapView.getNearestNode(e.getPoint());
 		Command c = null;
@@ -104,5 +105,5 @@
 			if (ws != null) {
 				if (shift) {
-					c = DeleteCommand.deleteWaySegment(ws); 
+					c = DeleteCommand.deleteWaySegment(ws);
 				} else if (ctrl) {
 					c = DeleteCommand.deleteWithReferences(Collections.singleton((OsmPrimitive)ws.way));
@@ -122,5 +123,5 @@
 		Main.map.mapView.repaint();
 	}
-	
+
 	@Override public String getModeHelpText() {
 		return tr("Click to delete. Shift: delete way segment. Alt: don't delete unused nodes when deleting a way. Ctrl: delete referring objects.");
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 1023)
@@ -52,10 +52,11 @@
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  *
- */ 
+ */
 public class DrawAction extends MapMode implements MapViewPaintable, SelectionChangedListener, AWTEventListener {
-		
+
 	private static Node lastUsedNode = null;
 	private double PHI=Math.toRadians(90);
@@ -68,19 +69,20 @@
 	private Point mousePos;
 	private Color selectedColor;
-	
+
 	private Node currentBaseNode;
 	private EastNorth currentMouseEastNorth;
-	
+
 	public DrawAction(MapFrame mapFrame) {
 		super(tr("Draw"), "node/autonode", tr("Draw nodes"),
-			KeyEvent.VK_A, mapFrame, getCursor());
+				ShortCut.registerShortCut("mapmode:draw", tr("Draw mode"), KeyEvent.VK_A, ShortCut.GROUP_EDIT),
+				mapFrame, getCursor());
 
 		// Add extra shortcut N
 		Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
-			KeyStroke.getKeyStroke(KeyEvent.VK_N, 0), tr("Draw"));
-		
+			ShortCut.registerShortCut("mapmode:draw2", tr("Draw mode (2)"), KeyEvent.VK_N, ShortCut.GROUP_EDIT).getKeyStroke(), tr("Draw"));
+
 		//putValue("help", "Action/AddNode/Autnode");
 		selectedColor = Main.pref.getColor(marktr("selected"), Color.YELLOW);
-		
+
 		drawHelperLine = Main.pref.getBoolean("draw.helper-line", true);
 	}
@@ -118,5 +120,5 @@
 		}
 	}
-	
+
 	/**
 	 * redraw to (possibly) get rid of helper line if selection changes.
@@ -144,5 +146,5 @@
 	 * position.
 	 *
-	 * If in nodeway mode, insert the node into the way. 
+	 * If in nodeway mode, insert the node into the way.
 	 */
 	@Override public void mouseClicked(MouseEvent e) {
@@ -160,5 +162,5 @@
 		shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
 		mousePos = e.getPoint();
-		
+
 		Collection<OsmPrimitive> selection = Main.ds.getSelected();
 		Collection<Command> cmds = new LinkedList<Command>();
@@ -168,9 +170,9 @@
 		boolean newNode = false;
 		Node n = null;
-		
+
 		if (!ctrl) {
 			n = Main.map.mapView.getNearestNode(mousePos);
 		}
-		
+
 		if (n != null) {
 			// user clicked on node
@@ -182,5 +184,5 @@
 				return;
 			}
-			
+
 		} else {
 			// no node found in clicked area
@@ -232,20 +234,20 @@
 			}
 		}
-		
+
 		// This part decides whether or not a "segment" (i.e. a connection) is made to an
 		// existing node.
-		
+
 		// For a connection to be made, the user must either have a node selected (connection
 		// is made to that node), or he must have a way selected *and* one of the endpoints
 		// of that way must be the last used node (connection is made to last used node), or
 		// he must have a way and a node selected (connection is made to the selected node).
-		
+
 		boolean extendedWay = false;
 
 		if (!shift && selection.size() > 0 && selection.size() < 3) {
-			
+
 			Node selectedNode = null;
 			Way selectedWay = null;
-			
+
 			for (OsmPrimitive p : selection) {
 				if (p instanceof Node) {
@@ -257,8 +259,8 @@
 				}
 			}
-			
+
 			// the node from which we make a connection
 			Node n0 = null;
-			
+
 			if (selectedNode == null) {
 				if (selectedWay == null) return;
@@ -271,15 +273,15 @@
 				if (selectedNode == selectedWay.nodes.get(0) || selectedNode == selectedWay.nodes.get(selectedWay.nodes.size()-1)) {
 					n0 = selectedNode;
-				}			
-			}
-			
+				}
+			}
+
 			if (n0 == null || n0 == n) {
 				return; // Don't create zero length way segments.
 			}
 
-			// Ok we know now that we'll insert a line segment, but will it connect to an 
+			// Ok we know now that we'll insert a line segment, but will it connect to an
 			// existing way or make a new way of its own? The "alt" modifier means that the
 			// user wants a new way.
-			
+
 			Way way = alt ? null : (selectedWay != null) ? selectedWay : getWayForNode(n0);
 			if (way == null) {
@@ -328,5 +330,5 @@
 
 		Command c = new SequenceCommand(title, cmds);
-	
+
 		Main.main.undoRedo.add(c);
 		lastUsedNode = n;
@@ -334,5 +336,5 @@
 		Main.map.mapView.repaint();
 	}
-	
+
 	@Override public void mouseMoved(MouseEvent e) {
 		if(!Main.map.mapView.isDrawableLayer())
@@ -342,13 +344,13 @@
 		// AWTEvent didn't make it through the security manager. Unclear
 		// if that can ever happen but better be safe.
-		
+
 		ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
 		alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
 		shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
 		mousePos = e.getPoint();
-		
+
 		computeHelperLine();
 	}
-	
+
 	/**
 	 * This method prepares data required for painting the "helper line" from
@@ -363,5 +365,5 @@
 			return;
 		}
-		
+
 		double distance = -1;
 		double angle = -1;
@@ -381,5 +383,5 @@
 			currentMouseNode = Main.map.mapView.getNearestNode(mousePos);
 		}
-		
+
 		if (currentMouseNode != null) {
 			// user clicked on node
@@ -391,5 +393,5 @@
 			currentMouseEastNorth = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
 		}
-		
+
 		for (OsmPrimitive p : selection) {
 			if (p instanceof Node) {
@@ -401,9 +403,9 @@
 			}
 		}
-		
+
 		// the node from which we make a connection
 		currentBaseNode = null;
 		Node previousNode = null;
-		
+
 		if (selectedNode == null) {
 			if (selectedWay == null) return;
@@ -419,7 +421,7 @@
 			if (selectedNode == selectedWay.nodes.get(0) || selectedNode == selectedWay.nodes.get(selectedWay.nodes.size()-1)) {
 				currentBaseNode = selectedNode;
-			}			
-		}
-		
+			}
+		}
+
 		if (currentBaseNode == null || currentBaseNode == currentMouseNode) {
 			return; // Don't create zero length way segments.
@@ -443,5 +445,5 @@
 		Main.map.mapView.repaint();
 	}
-	
+
 	/**
 	 * Repaint on mouse exit so that the helper line goes away.
@@ -453,7 +455,7 @@
 		Main.map.mapView.repaint();
 	}
-	
+
 	/**
-	 * @return If the node is the end of exactly one way, return this. 
+	 * @return If the node is the end of exactly one way, return this.
 	 * 	<code>null</code> otherwise.
 	 */
@@ -491,18 +493,18 @@
 	 * Adjusts the position of a node to lie on a segment (or a segment
 	 * intersection).
-	 * 
+	 *
 	 * If one or more than two segments are passed, the node is adjusted
 	 * to lie on the first segment that is passed.
-	 * 
+	 *
 	 * If two segments are passed, the node is adjusted to be at their
 	 * intersection.
-	 * 
+	 *
 	 * No action is taken if no segments are passed.
-	 * 
+	 *
 	 * @param segs the segments to use as a reference when adjusting
 	 * @param n the node to adjust
 	 */
 	private static void adjustNode(Collection<Pair<Node,Node>> segs, Node n) {
-		
+
 		switch (segs.size()) {
 		case 0:
@@ -511,5 +513,5 @@
 			// algorithm used here is a bit clumsy, anyone's welcome to replace
 			// it by something else. All it does it compute the intersection between
-			// the two segments and adjust the node position. The code doesnt 
+			// the two segments and adjust the node position. The code doesnt
 			Iterator<Pair<Node,Node>> i = segs.iterator();
 			Pair<Node,Node> seg = i.next();
@@ -528,5 +530,5 @@
 					det(A.east() - B.east(), A.north() - B.north(), C.east() - D.east(), C.north() - D.north())
 			);
-			
+
 			// only adjust to intersection if within 10 pixel of mouse click; otherwise
 			// fall through to default action.
@@ -536,5 +538,5 @@
 				return;
 			}
-		
+
 		default:
 			EastNorth P = n.eastNorth;
@@ -551,5 +553,5 @@
 		}
 	}
-	
+
 	// helper for adjustNode
 	static double det(double a, double b, double c, double d)
@@ -557,24 +559,24 @@
 		return a * d - b * c;
 	}
-	
+
 	public void paint(Graphics g, MapView mv) {
 
 		// don't draw line if disabled in prefs
 		if (!drawHelperLine) return;
-		
+
 		// sanity checks
 		if (Main.map.mapView == null) return;
 		if (mousePos == null) return;
-		
+
 		// if shift key is held ("no auto-connect"), don't draw a line
 		if (shift) return;
-		
+
 		// don't draw line if we don't know where from or where to
 		if (currentBaseNode == null) return;
 		if (currentMouseEastNorth == null) return;
-		
+
 		// don't draw line if mouse is outside window
 		if (!Main.map.mapView.getBounds().contains(mousePos)) return;
-		
+
 		Graphics2D g2 = (Graphics2D) g;
 		g2.setColor(selectedColor);
@@ -585,7 +587,7 @@
 
 		double t = Math.atan2(p2.y-p1.y, p2.x-p1.x) + Math.PI;
-		
+
 		b.moveTo(p1.x,p1.y); b.lineTo(p2.x, p2.y);
-		
+
 		// if alt key is held ("start new way"), draw a little perpendicular line
 		if (alt) {
@@ -593,23 +595,23 @@
 			b.lineTo((int)(p1.x + 8*Math.cos(t-PHI)), (int)(p1.y + 8*Math.sin(t-PHI)));
 		}
-		
+
 		g2.draw(b);
-		g2.setStroke(new BasicStroke(1));	
-
-	}
-	
+		g2.setStroke(new BasicStroke(1));
+
+	}
+
 	@Override public String getModeHelpText() {
 		String rv;
-		
+
 		if (currentBaseNode != null && !shift) {
 			if (mouseOnExistingNode) {
 				if (alt && /* FIXME: way exists */true)
 				    rv = tr("Click to create a new way to the existing node.");
-				else	
+				else
 					rv =tr("Click to make a connection to the existing node.");
 			} else {
 				if (alt && /* FIXME: way exists */true)
 				    rv = tr("Click to insert a node and create a new way.");
-				else	
+				else
 					rv = tr("Click to insert a new node and make a connection.");
 			}
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 1023)
@@ -30,7 +30,9 @@
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
+
 /**
  * Makes a rectangle from a line, or modifies a rectangle.
- * 
+ *
  * This class currently contains some "sleeping" code copied from DrawAction (move and rotate)
  * which can eventually be removed, but it may also get activated here and removed in DrawAction.
@@ -47,5 +49,5 @@
 	double yoff;
 	double distance;
-	
+
 	/**
 	 * The old cursor before the user pressed the mouse button.
@@ -53,8 +55,8 @@
 	private Cursor oldCursor;
 	/**
-	 * The current position of the mouse 
+	 * The current position of the mouse
 	 */
 	private Point mousePos;
-	/** 
+	/**
 	 * The position of the mouse cursor when the drag action was initiated.
 	 */
@@ -72,5 +74,6 @@
 	public ExtrudeAction(MapFrame mapFrame) {
 		super(tr("Extrude"), "extrude/extrude", tr("Create areas"),
-			KeyEvent.VK_X, mapFrame,
+				ShortCut.registerShortCut("mapmode:extrude", tr("Extrude mode"), KeyEvent.VK_X, ShortCut.GROUP_EDIT),
+			mapFrame,
 			getCursor("normal", "selection", Cursor.DEFAULT_CURSOR));
 		putValue("help", "Action/Extrude/Extrude");
@@ -100,5 +103,5 @@
 		}
 	}
-	
+
 	@Override public void enterMode() {
 		super.enterMode();
@@ -122,5 +125,5 @@
 	@Override public void mouseDragged(MouseEvent e) {
 		if (mode == Mode.select) return;
-		
+
 		// do not count anything as a move if it lasts less than 100 milliseconds.
 		if ((mode == Mode.EXTRUDE) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return;
@@ -137,5 +140,5 @@
 			return;
 		}
-		
+
 		Main.map.mapView.repaint();
 		mousePos = e.getPoint();
@@ -147,32 +150,32 @@
 			Node n1 = selectedSegment.way.nodes.get(selectedSegment.lowerIndex);
 			Node n2 = selectedSegment.way.nodes.get(selectedSegment.lowerIndex+1);
-			
+
 			EastNorth en1 = n1.eastNorth;
 			EastNorth en2 = n2.eastNorth;
 			if (en1.east() < en2.east()) { en2 = en1; en1 = n2.eastNorth; }
 			EastNorth en3 = mv.getEastNorth(mousePos.x, mousePos.y);
-			
+
 			double u = ((en3.east()-en1.east())*(en2.east()-en1.east()) + (en3.north()-en1.north())*(en2.north()-en1.north()))/en2.distanceSq(en1);
 			// the point on the segment from which the distance to mouse pos is shortest
 			EastNorth base = new EastNorth(en1.east()+u*(en2.east()-en1.east()), en1.north()+u*(en2.north()-en1.north()));
-			
+
 			// the distance, in projection units, between the base point and the mouse cursor
 			double len = base.distance(en3);
-			
+
 			// find out the distance, in metres, between the base point and the mouse cursor
 			distance = Main.proj.eastNorth2latlon(base).greatCircleDistance(Main.proj.eastNorth2latlon(en3));
 			Main.map.statusLine.setDist(distance);
 			updateStatusLine();
-			
+
 			// compute the angle at which the segment is drawn
 			// and use it to compute the x and y offsets for the
-			// corner points. 
+			// corner points.
 			double sin_alpha = (en2.north()-en1.north())/en2.distance(en1);
-			
+
 			// this is a kludge because sometimes extrusion just goes the wrong direction
 			if ((en3.east()>base.east()) ^ (sin_alpha < 0)) len=-len;
 			xoff = sin_alpha * len;
 			yoff = Math.sqrt(1-sin_alpha*sin_alpha) * len;
-			
+
 			Graphics2D g2 = (Graphics2D) g;
 			g2.setColor(selectedColor);
@@ -183,13 +186,13 @@
 			Point p3=mv.getPoint(en1.add(-xoff, -yoff));
 			Point p4=mv.getPoint(en2.add(-xoff, -yoff));
-			
+
 			b.moveTo(p1.x,p1.y); b.lineTo(p3.x, p3.y);
 			b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y);
 			b.lineTo(p1.x,p1.y);
 			g2.draw(b);
-			g2.setStroke(new BasicStroke(1));	
-		}
-	}
-	
+			g2.setStroke(new BasicStroke(1));
+		}
+	}
+
 	/**
 	 */
@@ -201,7 +204,7 @@
 		// boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
 		// boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
-		
+
 		mouseDownTime = System.currentTimeMillis();
-		
+
 		selectedSegment =
 			Main.map.mapView.getNearestWaySegment(e.getPoint());
@@ -242,9 +245,9 @@
 			Main.main.undoRedo.add(c);
 		}
-		
+
 		Main.map.mapView.removeTemporaryLayer(this);
 		mode = null;
 		updateStatusLine();
-		Main.map.mapView.repaint();	
+		Main.map.mapView.repaint();
 	}
 
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/MapMode.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/MapMode.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/MapMode.java	(revision 1023)
@@ -12,4 +12,5 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -17,7 +18,7 @@
  * As example scrolling the map is a MapMode, connecting Nodes to new Ways
  * is another.
- * 
+ *
  * MapModes should register/deregister all necessary listeners on the map's view
- * control. 
+ * control.
  */
 abstract public class MapMode extends JosmAction implements MouseListener, MouseMotionListener {
@@ -28,4 +29,14 @@
 	 * Constructor for mapmodes without an menu
 	 */
+	public MapMode(String name, String iconName, String tooltip, ShortCut shortCut, MapFrame mapFrame, Cursor cursor) {
+		super(name, "mapmode/"+iconName, tooltip, shortCut, false);
+		this.cursor = cursor;
+		putValue("active", false);
+	}
+
+	/**
+	 * Constructor for mapmodes without an menu
+	 */
+	 @Deprecated
 	public MapMode(String name, String iconName, String tooltip, int keystroke, MapFrame mapFrame, Cursor cursor) {
 		super(name, "mapmode/"+iconName, tooltip, keystroke, 0, false);
@@ -59,5 +70,5 @@
 		Main.map.statusLine.repaint();
 	}
-	
+
 	public String getModeHelpText() {
 		return "";
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java	(revision 1023)
@@ -12,8 +12,9 @@
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Singleton marker class to track position of audio.
- * 
+ *
  * @author david.earl
  *
@@ -25,10 +26,11 @@
 	private Point mouseStart = null;
 	private PlayHeadMarker playHeadMarker = null;
-	
+
 	public PlayHeadDragMode(PlayHeadMarker m) {
-		super("play head drag", "playheaddrag", "play head trag", 0, Main.map, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+		super("play head drag", "playheaddrag", "play head drag", null,
+		Main.map, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
 		playHeadMarker = m;
 	}
-	
+
 	@Override public void enterMode() {
 		super.enterMode();
@@ -74,5 +76,5 @@
 			playHeadMarker.synchronize(en);
 		}
-		mousePos = null;	
+		mousePos = null;
 		dragging = false;
 
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(revision 1023)
@@ -36,4 +36,6 @@
 import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
+
 /**
  * Move is an action that can move all kind of OsmPrimitives (except keys for now).
@@ -43,5 +45,5 @@
  * and will be moved.
  * If no object is under the mouse, move all selected objects (if any)
- * 
+ *
  * @author imi
  */
@@ -65,5 +67,5 @@
 	private Point mousePos;
 	private SelectionManager selectionManager;
-	
+
 	/**
 	 * The time which needs to pass between click and release before something
@@ -84,11 +86,12 @@
 	public SelectAction(MapFrame mapFrame) {
 		super(tr("Select"), "move/move", tr("Select, move and rotate objects"),
-			KeyEvent.VK_S, mapFrame,
+			ShortCut.registerShortCut("mapmode:select", tr("Select mode"), KeyEvent.VK_S, ShortCut.GROUP_EDIT),
+			mapFrame,
 			getCursor("normal", "selection", Cursor.DEFAULT_CURSOR));
 		putValue("help", "Action/Move/Move");
-		selectionManager = new SelectionManager(this, false, mapFrame.mapView);		
+		selectionManager = new SelectionManager(this, false, mapFrame.mapView);
 		try { initialMoveDelay = Integer.parseInt(Main.pref.get("edit.initial-move-delay","200")); } catch (NumberFormatException x) {}
 		try { initialMoveThreshold = Integer.parseInt(Main.pref.get("edit.initial-move-threshold","5")); } catch (NumberFormatException x) {}
-		
+
 	}
 
@@ -114,5 +117,5 @@
 		}
 	}
-	
+
 	@Override public void enterMode() {
 		super.enterMode();
@@ -153,5 +156,5 @@
 			return;
 		}
-		
+
 		if (!initialMoveThresholdExceeded) {
 			int dxp = mousePos.x - e.getX();
@@ -161,5 +164,5 @@
 			initialMoveThresholdExceeded = true;
 		}
-		
+
 		EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());
 		EastNorth mouseStartEN = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
@@ -184,7 +187,7 @@
 			Collection<OsmPrimitive> selection = Main.ds.getSelected();
 			Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
-		
+
 			// when rotating, having only one node makes no sense - quit silently
-			if (mode == Mode.rotate && affectedNodes.size() < 2) 
+			if (mode == Mode.rotate && affectedNodes.size() < 2)
 				return;
 
@@ -232,5 +235,5 @@
 		virtualWay = null;
 		virtualNode = null;
-		
+
 		if (osm == null)
 		{
@@ -257,5 +260,5 @@
 			}
 		}
-		if (osm == null) 
+		if (osm == null)
 			return Collections.emptySet();
 		return Collections.singleton(osm);
@@ -265,8 +268,8 @@
 	 * Look, whether any object is selected. If not, select the nearest node.
 	 * If there are no nodes in the dataset, do nothing.
-	 * 
+	 *
 	 * If the user did not press the left mouse button, do nothing.
-	 * 
-	 * Also remember the starting position of the movement and change the mouse 
+	 *
+	 * Also remember the starting position of the movement and change the mouse
 	 * cursor to movement.
 	 */
@@ -278,5 +281,5 @@
 		// boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
 		boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
-		
+
 		mouseDownTime = System.currentTimeMillis();
 		didMove = false;
@@ -377,5 +380,5 @@
 		Main.map.mapView.repaint();
 	}
-	
+
 	@Override public String getModeHelpText() {
 		if (mode == Mode.select) {
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java	(revision 1023)
@@ -13,16 +13,17 @@
 import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
- * Enable the zoom mode within the MapFrame. 
- * 
- * Holding down the left mouse button select a rectangle with the same aspect 
+ * Enable the zoom mode within the MapFrame.
+ *
+ * Holding down the left mouse button select a rectangle with the same aspect
  * ratio than the current map view.
  * Holding down left and right let the user move the former selected rectangle.
  * Releasing the left button zoom to the selection.
- * 
- * Rectangle selections with either height or width smaller than 3 pixels 
+ *
+ * Rectangle selections with either height or width smaller than 3 pixels
  * are ignored.
- * 
+ *
  * @author imi
  */
@@ -45,5 +46,7 @@
 	 */
 	public ZoomAction(MapFrame mapFrame) {
-		super(tr("Zoom"), "zoom", tr("Zoom and move map"), KeyEvent.VK_Z, mapFrame, ImageProvider.getCursor("normal", "zoom"));
+		super(tr("Zoom"), "zoom", tr("Zoom and move map"),
+		ShortCut.registerShortCut("mapmode:zoom", tr("Zoom mode"), KeyEvent.VK_Z, ShortCut.GROUP_EDIT),
+		mapFrame, ImageProvider.getCursor("normal", "zoom"));
 		mv = mapFrame.mapView;
 		selectionManager = new SelectionManager(this, true, mv);
@@ -70,5 +73,5 @@
 		selectionManager.unregister(mv);
 	}
-	
+
 	@Override public String getModeHelpText() {
 		return tr("Zoom by dragging or Ctrl+. or Ctrl+,; move with Ctrl+up,left,down,right; move zoom with right button");
Index: /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 1023)
@@ -22,4 +22,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class SearchAction extends JosmAction {
@@ -36,6 +37,6 @@
 
     public SearchAction() {
-        super(tr("Search ..."), "dialogs/search", tr("Search for objects."), KeyEvent.VK_F, KeyEvent.CTRL_DOWN_MASK,
-                true);
+        super(tr("Search ..."), "dialogs/search", tr("Search for objects."),
+        ShortCut.registerShortCut("system:find", tr("Search..."), KeyEvent.VK_F, ShortCut.GROUP_HOTKEY), true);
     }
 
@@ -105,7 +106,7 @@
 
     /**
-     * Adds the search specified by the settings in <code>s</code> to the 
+     * Adds the search specified by the settings in <code>s</code> to the
      * search history and performs the search.
-     * 
+     *
      * @param s
      */
Index: /trunk/src/org/openstreetmap/josm/gui/MainApplet.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainApplet.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/MainApplet.java	(revision 1023)
@@ -29,4 +29,5 @@
 import org.openstreetmap.josm.data.ServerSidePreferences;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class MainApplet extends JApplet {
@@ -34,5 +35,6 @@
 	public static final class UploadPreferencesAction extends JosmAction {
 		public UploadPreferencesAction() {
-			super(tr("Upload Preferences"), "upload-preferences", tr("Upload the current preferences to the server"), KeyEvent.VK_U, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK, true);
+			super(tr("Upload Preferences"), "upload-preferences", tr("Upload the current preferences to the server"),
+			ShortCut.registerShortCut("applet:uploadprefs", tr("Upload preferences"), KeyEvent.VK_U, ShortCut.GROUP_HOTKEY), true);
         }
 	    public void actionPerformed(ActionEvent e) {
@@ -100,5 +102,5 @@
 		Main.parent = this;
 		new MainCaller().postConstructorProcessCmdLine(args);
-		
+
 		MainMenu m = Main.main.menu; // shortcut
 
Index: /trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 1023)
@@ -30,4 +30,9 @@
  */
 public class MainApplication extends Main {
+	/**
+	 * Allow subclassing (see JOSM.java)
+	 */
+	public MainApplication() {}
+
 	/**
 	 * Construct an main frame, ready sized and operating. Does not
@@ -66,4 +71,9 @@
 		Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler());
 
+		// initialize the plaform hook, and
+		Main.determinePlatformHook();
+		// call the really early hook before we anything else
+		Main.platform.preStartupHook();
+
 		// construct argument table
 		List<String> argList = Arrays.asList(argArray);
@@ -138,5 +148,5 @@
 			        tr("Plugins"), JOptionPane.ERROR_MESSAGE);
 		}
-		
+
 		// load the early plugins
 		splash.setStatus(tr("Loading early plugins"));
@@ -144,4 +154,5 @@
 
 		if (argList.contains("--help") || argList.contains("-?") || argList.contains("-h")) {
+			// TODO: put in a platformHook for system that have no console by default
 			System.out.println(tr("Java OpenStreetMap Editor")+"\n\n"+
 					tr("usage")+":\n"+
@@ -192,3 +203,4 @@
 		});
 	}
+
 }
Index: /trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 1023)
@@ -13,4 +13,5 @@
 import javax.swing.JMenuItem;
 import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
@@ -59,4 +60,5 @@
 import org.openstreetmap.josm.actions.search.SearchAction;
 import org.openstreetmap.josm.data.DataSetChecker;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -79,5 +81,5 @@
 	public final JosmAction upload = new UploadAction();
 	public final JosmAction exit = new ExitAction();
-    
+
 	/* Edit menu */
 	public final UndoAction undo = new UndoAction();
@@ -86,6 +88,6 @@
 	public final JosmAction paste = new PasteAction();
 	public final JosmAction delete = new DeleteAction();
-	public final JosmAction pasteTags = new PasteTagsAction(copy); 
-	public final JosmAction duplicate = new DuplicateAction(); 
+	public final JosmAction pasteTags = new PasteTagsAction(copy);
+	public final JosmAction duplicate = new DuplicateAction();
 	public final JosmAction selectAll = new SelectAllAction();
 	public final JosmAction unselectAll = new UnselectAllAction();
@@ -93,7 +95,7 @@
 	public final JosmAction search = new SearchAction();
 	public final JosmAction preferences = new PreferencesAction();
-        
+
 	/* View menu */
-    
+
 	/* Tools menu */
 	public final JosmAction splitWay = new SplitWayAction();
@@ -121,5 +123,5 @@
 	public final JosmAction about = new AboutAction();
 	public final HistoryInfoAction historyinfo = new HistoryInfoAction();
-	
+
 	public final JMenu fileMenu = new JMenu(tr("File"));
 	public final JMenu editMenu = new JMenu(tr("Edit"));
@@ -129,154 +131,128 @@
 	public final JMenu presetsMenu = new JMenu(tr("Presets"));
 	public final JMenu helpMenu = new JMenu(tr("Help"));
-        
+
+	/**
+	 * Add a JosmAction to a menu.
+	 *
+	 * This method handles all the shortcut handling.
+	 * It also makes sure that actions that are handled by the
+	 * OS are not duplicated on the menu.
+	 */
+	public static void add(JMenu menu, JosmAction action) {
+		if (!action.getShortCut().getAutomatic()) {
+			JMenuItem menuitem = menu.add(action);
+			KeyStroke ks = action.getShortCut().getKeyStroke();
+			if (ks != null) {
+				menuitem.setAccelerator(ks);
+			}
+		}
+	}
+
+	/**
+	 * Add a menu to the main menu.
+	 *
+	 * This method handles all the shortcut handling.
+	 */
+	public void add(JMenu menu, int mnemonicKey, String shortName) {
+		ShortCut.registerShortCut("menu:"+shortName, shortName+" menu", mnemonicKey, ShortCut.GROUP_MNEMONIC).setMnemonic(menu);
+		add(menu);
+	}
 
 	public MainMenu() {
-        JMenuItem current;
-        
-		fileMenu.setMnemonic('F');
-		current = fileMenu.add(newAction);
-		current.setAccelerator(newAction.shortCut);
-		current = fileMenu.add(open);
-		current.setAccelerator(open.shortCut);
+		JMenuItem current;
+
+		add(fileMenu, newAction);
+		add(fileMenu, open);
 		fileMenu.addSeparator();
-		current = fileMenu.add(save);
-		current.setAccelerator(save.shortCut);
-		current = fileMenu.add(saveAs);
-		current.setAccelerator(saveAs.shortCut);
-		current = fileMenu.add(gpxExport);
-		current.setAccelerator(gpxExport.shortCut);
+		add(fileMenu, save);
+		add(fileMenu, saveAs);
+		add(fileMenu, gpxExport);
 		fileMenu.addSeparator();
-		current = fileMenu.add(download);
-		current.setAccelerator(download.shortCut);
-		current = fileMenu.add(upload);
-		current.setAccelerator(upload.shortCut);
-		fileMenu.addSeparator();
-		current = fileMenu.add(exit);
-		current.setAccelerator(exit.shortCut);
-		add(fileMenu);
-
-		editMenu.setMnemonic('E');
-		current = editMenu.add(undo);
-		current.setAccelerator(undo.shortCut);
-		current = editMenu.add(redo);
-		current.setAccelerator(redo.shortCut);
-		editMenu.addSeparator();
-		current = editMenu.add(copy);
-		current.setAccelerator(copy.shortCut);
-		current = editMenu.add(delete);
-		current.setAccelerator(delete.shortCut);
-		current = editMenu.add(paste);
-		current.setAccelerator(paste.shortCut);
-		current = editMenu.add(pasteTags);
-		current.setAccelerator(pasteTags.shortCut);
-		current = editMenu.add(duplicate);
-		current.setAccelerator(duplicate.shortCut);
-		editMenu.addSeparator();
-		current = editMenu.add(selectAll);
-		current.setAccelerator(selectAll.shortCut);
-		current = editMenu.add(unselectAll);
-		current.setAccelerator(unselectAll.shortCut);
-		editMenu.addSeparator();
-		current = editMenu.add(search);
-		current.setAccelerator(search.shortCut);
-		editMenu.addSeparator();
-		current = editMenu.add(preferences);
-		current.setAccelerator(preferences.shortCut);
-		add(editMenu);
-		
-		viewMenu.setMnemonic('V');
-        for (String mode : AutoScaleAction.modes) {
-            JosmAction autoScaleAction = new AutoScaleAction(mode);
-			current = viewMenu.add(autoScaleAction);
-		    current.setAccelerator(autoScaleAction.shortCut);
-        }
-        viewMenu.addSeparator();
-        JosmAction a = new ZoomOutAction();
-		viewMenu.add(a).setAccelerator(a.shortCut);
-		a = new ZoomInAction();
-		viewMenu.add(a).setAccelerator(a.shortCut);
-
+		add(fileMenu, download);
+		add(fileMenu, upload);
+		add(fileMenu, exit);
+		add(fileMenu, KeyEvent.VK_F, "file");
+
+		add(editMenu, undo);
+		add(editMenu, redo);
+		editMenu.addSeparator();
+		add(editMenu, copy);
+		add(editMenu, delete);
+		add(editMenu, paste);
+		add(editMenu, pasteTags);
+		add(editMenu, duplicate);
+		editMenu.addSeparator();
+		add(editMenu, selectAll);
+		add(editMenu, unselectAll);
+		editMenu.addSeparator();
+		add(editMenu, search);
+		editMenu.addSeparator();
+		add(editMenu, preferences);
+		add(editMenu, KeyEvent.VK_E, "edit");
+
+		for (String mode : AutoScaleAction.modes) {
+			JosmAction autoScaleAction = new AutoScaleAction(mode);
+			add(viewMenu, autoScaleAction);
+		}
 		viewMenu.addSeparator();
-
+		add(viewMenu, new ZoomOutAction());
+		add(viewMenu, new ZoomInAction());
+		viewMenu.addSeparator();
 		// TODO move code to an "action" like the others?
-        final JCheckBoxMenuItem wireframe = new JCheckBoxMenuItem(tr("Wireframe view"));
+		final JCheckBoxMenuItem wireframe = new JCheckBoxMenuItem(tr("Wireframe view"));
 		wireframe.setSelected(Main.pref.getBoolean("draw.wireframe", false));
-        wireframe.setAccelerator(KeyStroke.getKeyStroke("ctrl W"));
-        wireframe.addActionListener(new ActionListener() {
-        	public void actionPerformed(ActionEvent ev) {
-        		Main.pref.put("draw.wireframe", wireframe.isSelected());
-        		if (Main.map != null) {
+		wireframe.setAccelerator(ShortCut.registerShortCut("menu:view:wireframe", "Toggle Wireframe view", KeyEvent.VK_W, ShortCut.GROUP_MENU).getKeyStroke());
+		wireframe.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent ev) {
+				Main.pref.put("draw.wireframe", wireframe.isSelected());
+				if (Main.map != null) {
 					Main.map.mapView.repaint();
 				}
-        	}
-        });
-        viewMenu.add(wireframe);
-        
-		add(viewMenu);
-
-		toolsMenu.setMnemonic('T');
-		current = toolsMenu.add(splitWay);
-		current.setAccelerator(splitWay.shortCut);
-		current = toolsMenu.add(combineWay);
-		current.setAccelerator(combineWay.shortCut);
-		toolsMenu.addSeparator();
-		current = toolsMenu.add(reverseWay);
-		current.setAccelerator(reverseWay.shortCut);
-		toolsMenu.addSeparator();
-		current = toolsMenu.add(alignInCircle);
-		current.setAccelerator(alignInCircle.shortCut);
-		current = toolsMenu.add(alignInLine);
-		current.setAccelerator(alignInLine.shortCut);
-		current = toolsMenu.add(alignInRect);
-		current.setAccelerator(alignInRect.shortCut);
-		toolsMenu.addSeparator();
-		current = toolsMenu.add(createCircle);
-		current.setAccelerator(createCircle.shortCut);
-		toolsMenu.addSeparator();
-		current = toolsMenu.add(mergeNodes);
-		current.setAccelerator(mergeNodes.shortCut);
-		current = toolsMenu.add(joinNodeWay);
-		current.setAccelerator(joinNodeWay.shortCut);
-		current = toolsMenu.add(unglueNodes);
-		current.setAccelerator(unglueNodes.shortCut);
-		add(toolsMenu);
+			}
+		});
+		viewMenu.add(wireframe);
+		add(viewMenu, KeyEvent.VK_V, "view");
+
+		add(toolsMenu, splitWay);
+		add(toolsMenu, combineWay);
+		toolsMenu.addSeparator();
+		add(toolsMenu, reverseWay);
+		toolsMenu.addSeparator();
+		add(toolsMenu, alignInCircle);
+		add(toolsMenu, alignInLine);
+		add(toolsMenu, alignInRect);
+		toolsMenu.addSeparator();
+		add(toolsMenu, createCircle);
+		toolsMenu.addSeparator();
+		add(toolsMenu, mergeNodes);
+		add(toolsMenu, joinNodeWay);
+		add(toolsMenu, unglueNodes);
+		add(toolsMenu, KeyEvent.VK_T, "tools");
 
 		if (! Main.pref.getBoolean("audio.menuinvisible")) {
-			audioMenu.setMnemonic('A');
-			current = audioMenu.add(audioPlayPause);
-			current.setAccelerator(audioPlayPause.shortCut);
-			current = audioMenu.add(audioNext);
-			current.setAccelerator(audioNext.shortCut);
-			current = audioMenu.add(audioPrev);
-			current.setAccelerator(audioPrev.shortCut);
-			current = audioMenu.add(audioFwd);
-			current.setAccelerator(audioFwd.shortCut);
-			current = audioMenu.add(audioBack);
-			current.setAccelerator(audioBack.shortCut);
-			current = audioMenu.add(audioSlower);
-			current.setAccelerator(audioSlower.shortCut);
-			current = audioMenu.add(audioFaster);
-			current.setAccelerator(audioFaster.shortCut);
-			add(audioMenu);
+			add(audioMenu, audioPlayPause);
+			add(audioMenu, audioNext);
+			add(audioMenu, audioPrev);
+			add(audioMenu, audioFwd);
+			add(audioMenu, audioBack);
+			add(audioMenu, audioSlower);
+			add(audioMenu, audioFaster);
+			add(audioMenu, KeyEvent.VK_A, "audio");
 		}
 
-		add(presetsMenu);
-		presetsMenu.setMnemonic('P');
-		
-		helpMenu.setMnemonic('H');
+		add(presetsMenu, KeyEvent.VK_P, "presets");
+
 		JMenuItem check = new JMenuItem("DEBUG: Check Dataset");
 		check.addActionListener(new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
 				DataSetChecker.check();
-            }
+			}
 		});
-		current = helpMenu.add(check);
-		current = helpMenu.add(help);
-		//current.setAccelerator(help.shortCut);
-		current = helpMenu.add(about);
-		current.setAccelerator(about.shortCut);
-		current = helpMenu.add(historyinfo);
-		current.setAccelerator(historyinfo.shortCut);
-		add(helpMenu);
+		helpMenu.add(check);
+		current = helpMenu.add(help); // why is help not a JosmAction?
+		current.setAccelerator(ShortCut.registerShortCut("system:help", tr("Help"), KeyEvent.VK_F1, ShortCut.GROUP_DIRECT).getKeyStroke());
+		add(helpMenu, about);
+		add(helpMenu, historyinfo);
+		add(helpMenu, KeyEvent.VK_H, "help");
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/MapMover.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MapMover.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/MapMover.java	(revision 1023)
@@ -16,4 +16,6 @@
 import javax.swing.JPanel;
 import javax.swing.KeyStroke;
+import org.openstreetmap.josm.tools.ShortCut;
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import org.openstreetmap.josm.data.coor.EastNorth;
@@ -78,13 +80,35 @@
 		nc.addMouseMotionListener(this);
 		nc.addMouseWheelListener(this);
-		
-		String[] n = {",",".","up","right","down","left"};
-		int[] k = {KeyEvent.VK_COMMA, KeyEvent.VK_PERIOD, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT};
 
 		if (contentPane != null) {
-			for (int i = 0; i < n.length; ++i) {
-				contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(k[i], KeyEvent.CTRL_DOWN_MASK), "MapMover.Zoomer."+n[i]);
-				contentPane.getActionMap().put("MapMover.Zoomer."+n[i], new ZoomerAction(n[i]));
-			}
+			contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+				ShortCut.registerShortCut("system:movefocusright", tr("Map: Move right"), KeyEvent.VK_RIGHT, ShortCut.GROUP_HOTKEY).getKeyStroke(),
+				"MapMover.Zoomer.right");
+			contentPane.getActionMap().put("MapMover.Zoomer.right", new ZoomerAction("right"));
+
+			contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+				ShortCut.registerShortCut("system:movefocusleft", tr("Map: Move left"), KeyEvent.VK_LEFT, ShortCut.GROUP_HOTKEY).getKeyStroke(),
+				"MapMover.Zoomer.left");
+			contentPane.getActionMap().put("MapMover.Zoomer.left", new ZoomerAction("left"));
+
+			contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+				ShortCut.registerShortCut("system:movefocusup", tr("Map: Move up"), KeyEvent.VK_UP, ShortCut.GROUP_HOTKEY).getKeyStroke(),
+				"MapMover.Zoomer.up");
+			contentPane.getActionMap().put("MapMover.Zoomer.up", new ZoomerAction("up"));
+
+			contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+				ShortCut.registerShortCut("system:movefocusdown", tr("Map: Move down"), KeyEvent.VK_DOWN, ShortCut.GROUP_HOTKEY).getKeyStroke(),
+				"MapMover.Zoomer.down");
+			contentPane.getActionMap().put("MapMover.Zoomer.down", new ZoomerAction("down"));
+
+			contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+				ShortCut.registerShortCut("view:zoominalternate", tr("Map: Zoom in"), KeyEvent.VK_COMMA, ShortCut.GROUP_HOTKEY).getKeyStroke(),
+				"MapMover.Zoomer.in");
+			contentPane.getActionMap().put("MapMover.Zoomer.in", new ZoomerAction(","));
+
+			contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+				ShortCut.registerShortCut("view:zoomoutalternate", tr("Map: Zoom out"), KeyEvent.VK_PERIOD, ShortCut.GROUP_HOTKEY).getKeyStroke(),
+				"MapMover.Zoomer.out");
+			contentPane.getActionMap().put("MapMover.Zoomer.out", new ZoomerAction("."));
 		}
 	}
@@ -169,5 +193,5 @@
 		double centerx = e.getX() - (e.getX()-w/2)*newHalfWidth*2/w;
 		double centery = e.getY() - (e.getY()-h/2)*newHalfHeight*2/h;
-		EastNorth newCenter = nc.getEastNorth((int)centerx, (int)centery); 
+		EastNorth newCenter = nc.getEastNorth((int)centerx, (int)centery);
 
 		nc.zoomTo(newCenter, nc.getScale()*zoom);
Index: /trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 1023)
@@ -26,4 +26,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.actions.MoveAction;
 import org.openstreetmap.josm.data.Bounds;
@@ -84,5 +85,5 @@
 	 */
 	private Layer activeLayer;
-	
+
 	/**
 	 * The last event performed by mouse.
@@ -91,7 +92,7 @@
 
 	private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
-	
+
 	private BufferedImage offscreenBuffer;
-	
+
 	/**
 	 * The listener of the active layer changes.
@@ -109,14 +110,25 @@
 
 				new MapMover(MapView.this, Main.contentPane);
-				Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, java.awt.event.InputEvent.SHIFT_MASK), "UP");
-				Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, java.awt.event.InputEvent.SHIFT_MASK), "DOWN");
-				Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, java.awt.event.InputEvent.SHIFT_MASK), "LEFT");
-				Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, java.awt.event.InputEvent.SHIFT_MASK), "RIGHT");
-
-				Main.contentPane.getActionMap().put("UP", new MoveAction(MoveAction.Direction.UP));
-				Main.contentPane.getActionMap().put("DOWN", new MoveAction(MoveAction.Direction.DOWN));
-				Main.contentPane.getActionMap().put("LEFT", new MoveAction(MoveAction.Direction.LEFT));
-				Main.contentPane.getActionMap().put("RIGHT", new MoveAction(MoveAction.Direction.RIGHT));
-				
+				JosmAction mv;
+				mv = new MoveAction(MoveAction.Direction.UP);
+				if (mv.getShortCut() != null) {
+					Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortCut().getKeyStroke(), "UP");
+					Main.contentPane.getActionMap().put("UP", mv);
+				}
+				mv = new MoveAction(MoveAction.Direction.DOWN);
+				if (mv.getShortCut() != null) {
+					Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortCut().getKeyStroke(), "DOWN");
+					Main.contentPane.getActionMap().put("DOWN", mv);
+				}
+				mv = new MoveAction(MoveAction.Direction.LEFT);
+				if (mv.getShortCut() != null) {
+					Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortCut().getKeyStroke(), "LEFT");
+					Main.contentPane.getActionMap().put("LEFT", mv);
+				}
+				mv = new MoveAction(MoveAction.Direction.RIGHT);
+				if (mv.getShortCut() != null) {
+					Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortCut().getKeyStroke(), "RIGHT");
+					Main.contentPane.getActionMap().put("RIGHT", mv);
+				}
 
 				MapSlider zoomSlider = new MapSlider(MapView.this);
@@ -278,5 +290,5 @@
 			mvp.paint(tempG, this);
 		}
-		
+
 		// draw world borders
 		tempG.setColor(Color.WHITE);
@@ -290,5 +302,5 @@
 		if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
 			tempG.drawRect(x1, y1, x2-x1+1, y2-y1+1);
-		
+
 		if (playHeadMarker != null)
 			playHeadMarker.paint(tempG, this);
@@ -435,10 +447,10 @@
 		return false;
 	}
-	
+
 	public boolean addTemporaryLayer(MapViewPaintable mvp) {
 		if (temporaryLayers.contains(mvp)) return false;
 		return temporaryLayers.add(mvp);
 	}
-	
+
 	public boolean removeTemporaryLayer(MapViewPaintable mvp) {
 		return temporaryLayers.remove(mvp);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 1023)
@@ -20,4 +20,5 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class CommandStackDialog extends ToggleDialog implements CommandQueueListener {
@@ -27,7 +28,8 @@
 
 	public CommandStackDialog(final MapFrame mapFrame) {
-		super(tr("Command Stack"), "commandstack", tr("Open a list of all commands (undo buffer)."), KeyEvent.VK_O, 100);
+		super(tr("Command Stack"), "commandstack", tr("Open a list of all commands (undo buffer)."),
+		ShortCut.registerShortCut("subwindow:commandstack", tr("Toggle command stack"), KeyEvent.VK_O, ShortCut.GROUP_LAYER), 100);
 		Main.main.undoRedo.listenerCommands.add(this);
-			
+
 		tree.setRootVisible(false);
 		tree.setShowsRootHandles(true);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java	(revision 1023)
@@ -43,4 +43,5 @@
 import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ConflictDialog extends ToggleDialog {
@@ -51,5 +52,6 @@
 
 	public ConflictDialog() {
-		super(tr("Conflict"), "conflict", tr("Merging conflicts."), KeyEvent.VK_C, 100);
+		super(tr("Conflict"), "conflict", tr("Merging conflicts."),
+		ShortCut.registerShortCut("subwindow:conflict", tr("Toggle conflict window"), KeyEvent.VK_C, ShortCut.GROUP_LAYER), 100);
 		displaylist.setCellRenderer(new OsmPrimitivRenderer());
 		displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 1023)
@@ -38,4 +38,5 @@
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -87,5 +88,6 @@
 
 	public HistoryDialog() {
-		super(tr("History"), "history", tr("Display the history of all selected items."), KeyEvent.VK_H, 150);
+		super(tr("History"), "history", tr("Display the history of all selected items."),
+		ShortCut.registerShortCut("subwindow:history", tr("Toggle history window"), KeyEvent.VK_H, ShortCut.GROUP_LAYER), 150);
 		historyPane.setVisible(false);
 		notLoaded.setVisible(true);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 1023)
@@ -40,4 +40,5 @@
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.ImageProvider.OverlayPosition;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -158,5 +159,6 @@
 	 */
 	public LayerListDialog(MapFrame mapFrame) {
-		super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."), KeyEvent.VK_L, 100);
+		super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
+		ShortCut.registerShortCut("subwindow:layers", tr("Toggle layer window"), KeyEvent.VK_L, ShortCut.GROUP_LAYER), 100);
 		instance = new JList(model);
 		listScrollPane = new JScrollPane(instance);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java	(revision 1023)
@@ -64,4 +64,5 @@
 import org.openstreetmap.josm.tools.AutoCompleteComboBox;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -88,5 +89,5 @@
 	 */
 	private NameVisitor nameVisitor = new NameVisitor();
-	
+
 	/**
 	 * Watches for double clicks and from editing or new property, depending on the
@@ -134,5 +135,5 @@
 		}
 		String msg = "<html>"+trn("This will change up to {0} object.", "This will change up to {0} objects.", sel.size(), sel.size())+"<br><br>("+tr("An empty value deletes the key.", key)+")</html>";
-		
+
 		JPanel panel = new JPanel(new BorderLayout());
 		panel.add(new JLabel(msg), BorderLayout.NORTH);
@@ -159,5 +160,5 @@
 			    	String str = null;
 			    		str=(String) value;
-			    		if (valueCount.containsKey(objKey)){ 
+			    		if (valueCount.containsKey(objKey)){
 			    			Map<String, Integer> m=valueCount.get(objKey);
 			    			if (m.containsKey(str)) {
@@ -269,9 +270,9 @@
 	 * This simply fires up an relation editor for the relation shown; everything else
 	 * is the editor's business.
-	 * 
+	 *
 	 * @param row
 	 */
-	void membershipEdit(int row) {	
-		final RelationEditor editor = new RelationEditor((Relation)membershipData.getValueAt(row, 0), 
+	void membershipEdit(int row) {
+		final RelationEditor editor = new RelationEditor((Relation)membershipData.getValueAt(row, 0),
 				(Collection<RelationMember>) membershipData.getValueAt(row, 1) );
 		editor.setVisible(true);
@@ -296,5 +297,5 @@
 		keys.setPossibleItems(allData.keySet());
 		keys.setEditable(true);
-		
+
 		p.add(keys, BorderLayout.CENTER);
 
@@ -403,5 +404,5 @@
 		}
 	};
-	
+
 	/**
 	 * The properties list.
@@ -417,11 +418,12 @@
 	 */
 	public PropertiesDialog(MapFrame mapFrame) {
-		super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."), KeyEvent.VK_P, 150);
+		super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
+		ShortCut.registerShortCut("subwindow:properies", tr("Toggle properties window"), KeyEvent.VK_P, ShortCut.GROUP_LAYER), 150);
 
 		// ---------------------------------------
-		// This drop-down is really deprecated but we offer people a chance to 
-		// activate it if they really want. Presets should be used from the 
+		// This drop-down is really deprecated but we offer people a chance to
+		// activate it if they really want. Presets should be used from the
 		// menu.
-		if (TaggingPresetPreference.taggingPresets.size() > 0 && 
+		if (TaggingPresetPreference.taggingPresets.size() > 0 &&
 				Main.pref.getBoolean("taggingpreset.in-properties-dialog", false)) {
 			Vector<ActionListener> allPresets = new Vector<ActionListener>();
@@ -448,8 +450,8 @@
 
 		// setting up the properties table
-		
+
 		propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
 		propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-	
+
 		propertyTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer(){
 			@Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
@@ -476,10 +478,10 @@
 			}
 		});
-		
+
 		// setting up the membership table
-		
+
 		membershipData.setColumnIdentifiers(new String[]{tr("Member Of"),tr("Role")});
 		membershipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-		
+
 		membershipTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
 			@Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
@@ -492,5 +494,5 @@
 			}
 		});
-		
+
 		membershipTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
 			@Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
@@ -498,5 +500,5 @@
 				if (c instanceof JLabel) {
 					Collection<RelationMember> col = (Collection<RelationMember>) value;
-					
+
 					String text = null;
 					for (RelationMember r : col) {
@@ -509,5 +511,5 @@
 						}
 					}
-					
+
 					((JLabel)c).setText(text);
 				}
@@ -515,5 +517,5 @@
 			}
 		});
-		
+
 		// combine both tables and wrap them in a scrollPane
 		JPanel bothTables = new JPanel();
@@ -523,5 +525,5 @@
 		bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
 		bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH));
-		
+
 		DblClickWatch dblClickWatch = new DblClickWatch();
 		propertyTable.addMouseListener(dblClickWatch);
@@ -565,5 +567,5 @@
 							selectionChanged(sel); // update whole table
 						}
-						
+
 					}
 				}
@@ -587,5 +589,5 @@
 			}
 		};
-		
+
 		buttonPanel.add(new SideButton(marktr("Add"),"add","Properties",tr("Add a new key/value pair to all objects"), KeyEvent.VK_A, buttonAction));
 		buttonPanel.add(new SideButton(marktr("Edit"),"edit","Properties",tr("Edit the value of the selected key for all objects"), KeyEvent.VK_E, buttonAction));
@@ -611,5 +613,5 @@
 
 		// re-load property data
-		
+
 		propertyData.setRowCount(0);
 
@@ -639,11 +641,11 @@
 			propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
 		}
-		
+
 		// re-load membership data
 		// this is rather expensive since we have to walk through all members of all existing relationships.
 		// could use back references here for speed if necessary.
-		
+
 		membershipData.setRowCount(0);
-		
+
 		TreeMap<Relation, Collection<RelationMember>> roles = new TreeMap<Relation, Collection<RelationMember>>();
 		for (Relation r : Main.ds.relations) {
@@ -661,9 +663,9 @@
 			}
 		}
-		
+
 		for (Entry<Relation, Collection<RelationMember>> e : roles.entrySet()) {
 			membershipData.addRow(new Object[]{e.getKey(), e.getValue()});
 		}
-		
+
 		membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0);
 	}
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 1023)
@@ -32,9 +32,10 @@
 import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * A dialog showing all known relations, with buttons to add, edit, and
- * delete them. 
- * 
+ * delete them.
+ *
  * We don't have such dialogs for nodes, segments, and ways, becaus those
  * objects are visible on the map and can be selected there. Relations are not.
@@ -55,5 +56,6 @@
 
 	public RelationListDialog() {
-		super(tr("Relations"), "relationlist", tr("Open a list of all relations."), KeyEvent.VK_R, 150);
+		super(tr("Relations"), "relationlist", tr("Open a list of all relations."),
+		ShortCut.registerShortCut("subwindow:relations", tr("Toggle relations window"), KeyEvent.VK_R, ShortCut.GROUP_LAYER), 150);
 		displaylist.setCellRenderer(new OsmPrimitivRenderer());
 		displaylist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -71,5 +73,5 @@
 
 		JPanel buttonPanel = new JPanel(new GridLayout(1,4));
-		
+
 		buttonPanel.add(new SideButton(marktr("New"), "addrelation", "Selection", tr("Create a new relation"), new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
@@ -78,5 +80,5 @@
 			}
 		}), GBC.std());
-		
+
 		buttonPanel.add(new SideButton(marktr("Select"), "select", "Selection", tr("Select this relation"), new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
@@ -85,13 +87,13 @@
 			}
 		}), GBC.std());
-		
+
 		buttonPanel.add(new SideButton(marktr("Edit"), "edit", "Selection", tr( "Open an editor for the selected relation"), new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
 				Relation toEdit = (Relation) displaylist.getSelectedValue();
 				if (toEdit != null)
-					new RelationEditor(toEdit).setVisible(true);				
+					new RelationEditor(toEdit).setVisible(true);
 			}
 		}), GBC.std());
-		
+
 		buttonPanel.add(new SideButton(marktr("Delete"), "delete", "Selection", tr("Delete the selected relation"), new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
@@ -111,5 +113,5 @@
 		if (b) updateList();
 	}
-	
+
 	public void updateList() {
 		list.setSize(Main.ds.relations.size());
@@ -121,5 +123,5 @@
 		list.setSize(i);
 	}
-	
+
 	public void activeLayerChange(Layer a, Layer b) {
 		if ((a == null || a instanceof OsmDataLayer) && b instanceof OsmDataLayer) {
@@ -130,5 +132,5 @@
 		}
 	}
-	
+
 	public void layerRemoved(Layer a) {
 		if (a instanceof OsmDataLayer) {
@@ -140,13 +142,13 @@
 			((OsmDataLayer)a).listenerDataChanged.add(this);
 		}
-	}	
+	}
 	public void dataChanged(OsmDataLayer l) {
 		updateList();
 		repaint();
 	}
-	
+
 	/**
 	 * Returns the currently selected relation, or null.
-	 * 
+	 *
 	 * @return the currently selected relation, or null
 	 */
@@ -157,5 +159,5 @@
 	/**
 	 * Adds a selection listener to the relation list.
-	 * 
+	 *
 	 * @param listener the listener to add
 	 */
@@ -166,5 +168,5 @@
 	/**
 	 * Removes a selection listener from the relation list.
-	 * 
+	 *
 	 * @param listener the listener to remove
 	 */
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 1023)
@@ -41,4 +41,5 @@
 import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -70,11 +71,12 @@
 
     /**
-     * If the selection changed event is triggered with newSelection equals 
-     * this element, the newSelection will not be added to the selection history 
+     * If the selection changed event is triggered with newSelection equals
+     * this element, the newSelection will not be added to the selection history
      */
     private Collection<? extends OsmPrimitive> historyIgnoreSelection = null;
 
     public SelectionListDialog() {
-        super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."), KeyEvent.VK_T, 150);
+        super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."),
+        ShortCut.registerShortCut("subwindow:selection", tr("Toggle selection window"), KeyEvent.VK_T, ShortCut.GROUP_LAYER), 150);
 
         selectionHistory = new LinkedList<Collection<? extends OsmPrimitive>>();
@@ -191,5 +193,5 @@
 
     /**
-     * Zooms to the element(s) selected in {@link #displaylist} 
+     * Zooms to the element(s) selected in {@link #displaylist}
      */
     public void zoomToSelectedElement() {
@@ -248,5 +250,5 @@
             historyIgnoreSelection = null;
             try {
-                // Check if the newSelection has already been added to the history 
+                // Check if the newSelection has already been added to the history
                 Collection<? extends OsmPrimitive> first = selectionHistory.getFirst();
                 if (first.equals(newSelection))
@@ -273,5 +275,5 @@
     /**
      * A specialized {@link JMenuItem} for presenting one entry of the selection history
-     *   
+     *
      * @author Jan Peter Stotz
      */
@@ -303,5 +305,5 @@
     /**
      * A specialized {@link JMenuItem} for presenting one entry of the search history
-     *   
+     *
      * @author Jan Peter Stotz
      */
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java	(revision 1023)
@@ -27,4 +27,5 @@
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
@@ -40,6 +41,6 @@
 		public AbstractButton button;
 
-		private ToggleDialogAction(String name, String iconName, String tooltip, int shortCut, int modifier, String prefname) {
-			super(name, iconName, tooltip, shortCut, modifier, false);
+		private ToggleDialogAction(String name, String iconName, String tooltip, ShortCut shortCut, String prefname) {
+			super(name, iconName, tooltip, shortCut, false);
 			this.prefname = prefname;
 		}
@@ -62,9 +63,20 @@
 	private final JPanel titleBar = new JPanel(new GridBagLayout());
 
+	@Deprecated
 	public ToggleDialog(final String name, String iconName, String tooltip, int shortCut, int preferredHeight) {
 		super(new BorderLayout());
 		this.prefName = iconName;
+		ToggleDialogInit(name, iconName, tooltip, ShortCut.registerShortCut("auto:"+name, tooltip, shortCut, ShortCut.GROUP_LAYER), preferredHeight);
+	}
+
+	public ToggleDialog(final String name, String iconName, String tooltip, ShortCut shortCut, int preferredHeight) {
+		super(new BorderLayout());
+		this.prefName = iconName;
+		ToggleDialogInit(name, iconName, tooltip, shortCut, preferredHeight);
+	}
+
+	private void ToggleDialogInit(final String name, String iconName, String tooltip, ShortCut shortCut, int preferredHeight) {
 		setPreferredSize(new Dimension(330,preferredHeight));
-		action = new ToggleDialogAction(name, "dialogs/"+iconName, tooltip, shortCut, KeyEvent.ALT_MASK, iconName);
+		action = new ToggleDialogAction(name, "dialogs/"+iconName, tooltip, shortCut, iconName);
 		String helpId = "Dialog/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
 		action.putValue("help", helpId.substring(0, helpId.length()-6));
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 1023)
@@ -21,9 +21,10 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.User;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
- * Displays a dialog with all users who have last edited something in the 
+ * Displays a dialog with all users who have last edited something in the
  * selection area, along with the number of objects.
- * 
+ *
  * @author Frederik Ramm <frederik@remote.org>
  */
@@ -45,13 +46,14 @@
 
     private static User anonymousUser = User.get("(anonymous users)");
-			
+
 	public UserListDialog() {
-		super(tr("Authors"), "userlist", tr("Open a list of people working on the selected objects."), KeyEvent.VK_A, 150);
-		
+		super(tr("Authors"), "userlist", tr("Open a list of people working on the selected objects."),
+		ShortCut.registerShortCut("subwindow:authors", tr("Toggle authors window"), KeyEvent.VK_A, ShortCut.GROUP_LAYER), 150);
+
 		data.setColumnIdentifiers(new String[]{tr("Author"),tr("# Objects"),"%"});
 		userTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 		add(new JScrollPane(userTable), BorderLayout.CENTER);
 		selectionChanged(Main.ds.getSelected());
-		
+
 		DataSet.selListeners.add(this);
 	}
@@ -70,5 +72,5 @@
 		if (!isVisible())
 			return;
-		
+
 		class UserCount {
 			User user;
@@ -76,10 +78,10 @@
 			UserCount(User user, int count) { this.user=user; this.count=count; }
 		}
-		
+
 		if (data == null)
 			return; // selection changed may be received in base class constructor before init
-		
+
 		data.setRowCount(0);
-		
+
 		HashMap<User,UserCount> counters = new HashMap<User,UserCount>();
 		int all = 0;
@@ -88,5 +90,5 @@
             if (u == null) u = anonymousUser;
             UserCount uc = counters.get(u);
-            if (uc == null) 
+            if (uc == null)
                 counters.put(u, uc = new UserCount(u, 0));
             uc.count++;
@@ -96,9 +98,9 @@
 		counters.values().toArray(ucArr);
 		Arrays.sort(ucArr, new Comparator<UserCount>() {
-			public int compare(UserCount a, UserCount b) { 
+			public int compare(UserCount a, UserCount b) {
 				return (a.count<b.count) ? 1 : (a.count>b.count) ? -1 : 0;
 			}
 		});
-		
+
 		for (UserCount uc : ucArr) {
 			data.addRow(new Object[] { uc.user.name, uc.count, uc.count * 100 / all });
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/LafPreference.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/LafPreference.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/LafPreference.java	(revision 1023)
@@ -5,4 +5,5 @@
 
 import java.awt.Component;
+import java.lang.reflect.*;
 
 import javax.swing.DefaultListCellRenderer;
@@ -26,5 +27,18 @@
 	public void addGui(PreferenceDialog gui) {
 		lafCombo = new JComboBox(UIManager.getInstalledLookAndFeels());
-		
+
+		// let's try to load additional LookAndFeels and put them into the list
+		try {
+			Class Cquaqua = Class.forName("ch.randelshofer.quaqua.QuaquaLookAndFeel");
+			Object Oquaqua = Cquaqua.getConstructor((Class[])null).newInstance((Object[])null);
+			// no exception? Then Go!
+			lafCombo.addItem(
+				new UIManager.LookAndFeelInfo(((javax.swing.LookAndFeel)Oquaqua).getName(), "ch.randelshofer.quaqua.QuaquaLookAndFeel")
+			);
+		} catch (Exception ex) {
+			// just ignore, Quaqua may not even be installed...
+			//System.out.println("Failed to load Quaqua: " + ex);
+		}
+
 		String laf = Main.pref.get("laf");
 		for (int i = 0; i < lafCombo.getItemCount(); ++i) {
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java	(revision 1023)
@@ -41,5 +41,5 @@
 	public final JPanel map = createPreferenceTab("map", I18n.tr("Map Settings"), I18n.tr("Settings for the map projection and data interpretation."));
 	public final JPanel audio = createPreferenceTab("audio", I18n.tr("Audio Settings"), I18n.tr("Settings for the audio player and audio markers."));
-	
+
 	/**
 	 * Construct a JPanel for the preference settings. Layout is GridBagLayout
@@ -111,5 +111,6 @@
 		settings.add(Main.toolbar);
 		settings.add(new AudioPreference());
-		
+		settings.add(new ShortcutPreference());
+
 		for (PluginProxy plugin : Main.plugins) {
 			PreferenceSetting p = plugin.getPreferenceSetting();
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/ShortcutPreference.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/ShortcutPreference.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/ShortcutPreference.java	(revision 1023)
@@ -0,0 +1,106 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.gui.preferences;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.GridBagLayout;
+import java.awt.Dimension;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.JTable;
+import javax.swing.JScrollPane;
+import javax.swing.table.TableModel;
+import javax.swing.table.AbstractTableModel;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
+import org.openstreetmap.josm.gui.preferences.prefJPanel;
+
+public class ShortcutPreference implements PreferenceSetting {
+
+	private DefaultTableModel model;
+
+	public void addGui(PreferenceDialog gui) {
+		// icon source: http://www.iconfinder.net/index.php?q=key&page=icondetails&iconid=8553&size=128&q=key&s12=on&s16=on&s22=on&s32=on&s48=on&s64=on&s128=on
+		// icon licence: GPL
+		// icon designer: Paolino, http://www.paolinoland.it/
+		// icon original filename: keyboard.png
+		// icon original size: 128x128
+		// modifications: icon was cropped, then resized
+		JPanel p = gui.createPreferenceTab("shortcuts", tr("Shortcut Preferences"), tr("Changing keyboard shortcuts manually."));
+
+		prefJPanel prefpanel = new prefJPanel(new scListModel());
+		p.add(prefpanel, GBC.eol().fill(GBC.BOTH));
+
+	}
+
+	public void ok() {
+   }
+
+	// Maybe move this to prefPanel? There's no need for it to be here.
+	private class scListModel extends AbstractTableModel {
+		private String[] columnNames = new String[]{tr("ID"),tr("Action"), tr("Group"), tr("Shortcut")};
+		private Collection<ShortCut> data;
+		public scListModel() {
+			data = ShortCut.listAll();
+		}
+		public int getColumnCount() {
+			return columnNames.length;
+		}
+		public int getRowCount() {
+			return data.size();
+		}
+		public String getColumnName(int col) {
+			return columnNames[col];
+		}
+		public Object getValueAt(int row, int col) {
+			ShortCut sc = (ShortCut)data.toArray()[row];
+			if (col == 0) {
+				return sc.getShortText();
+			} else if (col == 1) {
+				return sc.getLongText();
+			} else if (col == 2) {
+				if (sc.getRequestedGroup() == ShortCut.GROUP_NONE) {
+					return tr("None");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_HOTKEY) {
+					return tr("Hotkey");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_MENU) {
+					return tr("Menu");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_EDIT) {
+					return tr("Edit");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_LAYER) {
+					return tr("Subwindow");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_DIRECT) {
+					return tr("Direct");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_MNEMONIC) {
+					return tr("Mnemonic");
+				} else if (sc.getRequestedGroup() == ShortCut.GROUP_RESERVED) {
+					return tr("System");
+				} else {
+					return tr("Unknown");
+				}
+			} else if (col == 3) {
+				return sc.getKeyText();
+			} else {
+				// This is a kind of hack that allows the actions on the editing controls
+				// to access the underlying shortcut object without introducing another
+				// method. I opted to stay within the interface.
+				return sc;
+			}
+		}
+		public boolean isCellEditable(int row, int col) {
+			return false;
+		}
+	}
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/prefJPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/prefJPanel.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/prefJPanel.java	(revision 1023)
@@ -0,0 +1,552 @@
+/*
+ * prefJPanel.java
+ *
+ * Created on 28. September 2008, 17:47
+ */
+
+package org.openstreetmap.josm.gui.preferences;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import javax.swing.table.TableModel;
+import javax.swing.table.AbstractTableModel;
+import java.awt.event.KeyEvent;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.ListSelectionModel;
+import org.openstreetmap.josm.tools.ShortCut;
+import javax.swing.JEditorPane;
+import javax.swing.JScrollPane;
+import org.openstreetmap.josm.Main;
+
+/**
+ * This is the keyboard preferences content.
+ * If someone wants to merge it with ShortcutPreference.java, feel free.
+ */
+public class prefJPanel extends javax.swing.JPanel {
+
+		// table of shortcuts
+		private TableModel model;
+		// comboboxes of modifier groups, mapping selectedIndex to real data
+		private static int[] modifInts = new int[]{
+			-1,
+			0,
+			KeyEvent.SHIFT_DOWN_MASK,
+			KeyEvent.CTRL_DOWN_MASK,
+			KeyEvent.ALT_DOWN_MASK,
+			KeyEvent.META_DOWN_MASK,
+			KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK,
+			KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK,
+			KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK,
+			KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK,
+			KeyEvent.CTRL_DOWN_MASK | KeyEvent.META_DOWN_MASK,
+			KeyEvent.ALT_DOWN_MASK | KeyEvent.META_DOWN_MASK,
+			KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK,
+			KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK
+		};
+		// and here are the texts fro the comboboxes
+		private static String[] modifList = new String[] {
+			tr("disabled"),
+			tr("no modifier"),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[2]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[3]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[4]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[5]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[6]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[7]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[8]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[9]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[10]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[11]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[12]).getModifiers()),
+			KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, modifInts[13]).getModifiers())
+		};
+		// this are the display(!) texts for the checkboxes. Let the JVM do the i18n for us <g>.
+		// Ok, there's a real reason for this: The JVM should know best how the keys are labelled
+		// on the physical keyboard. What language pack is installed in JOSM is completely
+		// independent from the keyboard's labelling. But the operation system's locale
+		// usually matches the keyboard. This even works with my English Windows and my German
+		// keyboard.
+		private static String SHIFT = KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.SHIFT_DOWN_MASK).getModifiers());
+		private static String CTRL  = KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK).getModifiers());
+		private static String ALT   = KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.ALT_DOWN_MASK).getModifiers());
+		private static String META  = KeyEvent.getKeyModifiersText(javax.swing.KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.META_DOWN_MASK).getModifiers());
+
+		// A list of keys to present the user. Sadly this really is a list of keys Java knows about,
+		// not a list of real physical keys. If someone knows how to get that list?
+		private static Map<Integer, String> keyList = setKeyList();
+
+		private static Map<Integer, String> setKeyList() {
+			Map<Integer, String> list = new LinkedHashMap<Integer, String>();
+			// I hate this, but I found no alternative...
+			for (int i = 0; i < 65534; i++) {
+				String s = KeyEvent.getKeyText(i);
+				if (s != null && s.length() > 0 && !s.contains("Unknown")) {
+					list.put(new Integer(i), s);
+					//System.out.println(i+": "+s);
+				}
+			}
+			list.put(new Integer(-1), "");
+			return list;
+		}
+
+    /** Creates new form prefJPanel */
+    // Ain't those auto-generated comments helpful or what? <g>
+    public prefJPanel(TableModel model) {
+    	this.model = model;
+        initComponents();
+    }
+
+    private void initComponents() {
+        java.awt.GridBagConstraints gridBagConstraints;
+
+        // Did I mention auto-generated? That's the reason we
+        // have lots of properties here and not some arrays...
+        prefTabPane = new javax.swing.JTabbedPane();
+        shortcutTab = new javax.swing.JPanel();
+        listPane = new javax.swing.JPanel();
+        listScrollPane = new javax.swing.JScrollPane();
+        shortcutTable = new javax.swing.JTable();
+        shortcutEditPane = new javax.swing.JPanel();
+        cbDefault = new javax.swing.JCheckBox();
+        jLabel4 = new javax.swing.JLabel();
+        cbShift = new javax.swing.JCheckBox();
+        cbDisable = new javax.swing.JCheckBox();
+        cbCtrl = new javax.swing.JCheckBox();
+        tfKeyLabel = new javax.swing.JLabel();
+        cbAlt = new javax.swing.JCheckBox();
+        tfKey = new javax.swing.JComboBox();
+        cbMeta = new javax.swing.JCheckBox();
+        jLabel6 = new javax.swing.JLabel();
+        modifierTab = new javax.swing.JPanel();
+        editGroupPane = new javax.swing.JPanel();
+        jLabel1 = new javax.swing.JLabel();
+        bxPrim1 = new javax.swing.JComboBox();
+        jLabel2 = new javax.swing.JLabel();
+        bxSec1 = new javax.swing.JComboBox();
+        jLabel3 = new javax.swing.JLabel();
+        bxTer1 = new javax.swing.JComboBox();
+        menuGroupPane = new javax.swing.JPanel();
+        jLabel7 = new javax.swing.JLabel();
+        bxPrim2 = new javax.swing.JComboBox();
+        jLabel8 = new javax.swing.JLabel();
+        bxSec2 = new javax.swing.JComboBox();
+        jLabel9 = new javax.swing.JLabel();
+        bxTer2 = new javax.swing.JComboBox();
+        hotkeyGroupPane = new javax.swing.JPanel();
+        jLabel10 = new javax.swing.JLabel();
+        bxPrim3 = new javax.swing.JComboBox();
+        jLabel11 = new javax.swing.JLabel();
+        bxSec3 = new javax.swing.JComboBox();
+        jLabel12 = new javax.swing.JLabel();
+        bxTer3 = new javax.swing.JComboBox();
+        subwindowGroupPane = new javax.swing.JPanel();
+        jLabel13 = new javax.swing.JLabel();
+        bxPrim4 = new javax.swing.JComboBox();
+        jLabel14 = new javax.swing.JLabel();
+        bxSec4 = new javax.swing.JComboBox();
+        jLabel15 = new javax.swing.JLabel();
+        bxTer4 = new javax.swing.JComboBox();
+        infoTab = new javax.swing.JPanel();
+        cbAction action = new cbAction(this);
+        bxAction action2 = new bxAction();
+
+        setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
+
+				// If someone wants to move this text into some resource, feel free.
+				infoTab.setLayout(new javax.swing.BoxLayout(shortcutTab, javax.swing.BoxLayout.Y_AXIS));
+				JEditorPane editor = new JEditorPane();
+				editor.setEditable(false);
+				editor.setContentType("text/html");
+				editor.setText(
+					tr("<h1><a name=\"top\">Keyboard Shortcuts</a></h1>")+
+					tr("<p>Please note that shortcuts keys are assigned to the actions when JOSM is started. So you need to <b>restart</b> "
+					  +"JOSM to see your changes.</p>")+
+					tr("<p>Furthermore, the shortcuts are activated when the actions are assigned to a menu entry of button for the first "
+					  +"time. So some of your changes may become active even without restart --- but also without collistion handling. "
+					  +"This is another reason to <b>restart</b> JOSM after making any changes here.</p>")+
+					tr("<p>You may notice that the key selection list on the next page lists all keys that exist on all kinds of keyboards "
+					  +"Java knows about, not just those keys that exist on your keyboard. Please use only those values that correspond to "
+					  +"a real key on your keyboard. So if your keyboard has no 'Copy' key (PC keyboard don't have them, Sun keyboards do), "
+					  +"the do not use it. Also there will be 'keys' listed that correspond to a shortcut on your keyboard (e.g. ':'/Colon). "
+					  +"Please also do not use them, use the base key (';'/Semicolon on US keyboards, '.'/Period on German keyboards, ...) "
+					  +"instead. Not doing so may result in conflicts, as there is no way for JOSM to know that Ctrl+Shift+; and Ctrl+: "
+					  +"actually is the same thing on an US keyboard...</p>")+
+					tr("<p>Thank you for your understanding</p>")+
+					tr("<h1>Modifier Groups</h1>")+
+					tr("<p>The last page lists the modifier keys JOSM will automatically assign to shortcuts. For every of the four kinds "
+					  +"of shortcuts there are three alternatives. JOSM will try those alternative in the listed order when managing a "
+					  +"conflict. If all alternatives would result in shortcuts that are already taken, it will assign a random shortcut "
+					  +"instead.</p>")+
+					tr("<p>The pseudo-modifier 'disabled' will disable the shortcut when encountered.</p>")
+				);
+				editor.setCaretPosition(0); // scroll up
+				prefTabPane.addTab(tr("Read First"), new JScrollPane(editor));
+
+        shortcutTab.setLayout(new javax.swing.BoxLayout(shortcutTab, javax.swing.BoxLayout.Y_AXIS));
+
+        listPane.setLayout(new java.awt.GridLayout());
+
+        // This is the list of shortcuts:
+        shortcutTable.setModel(model);
+        shortcutTable.getSelectionModel().addListSelectionListener(new cbAction(this));
+        //shortcutTable.setFillsViewportHeight(true); Java 1.6
+        shortcutTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+        listScrollPane.setViewportView(shortcutTable);
+
+        listPane.add(listScrollPane);
+
+        shortcutTab.add(listPane);
+
+        // and here follows the edit area. I won't object to someone re-designing it, it looks, um, "minimalistic" ;)
+        shortcutEditPane.setLayout(new java.awt.GridLayout(5, 2));
+
+        cbDefault.setAction(action);
+        cbDefault.setText(tr("Use default"));
+        shortcutEditPane.add(cbDefault);
+
+        shortcutEditPane.add(jLabel4);
+
+        cbShift.setAction(action);
+        cbShift.setText(SHIFT); // see above for why no tr()
+        shortcutEditPane.add(cbShift);
+
+        cbDisable.setAction(action);
+        cbDisable.setText(tr("Disable"));
+        shortcutEditPane.add(cbDisable);
+
+        cbCtrl.setAction(action);
+        cbCtrl.setText(CTRL); // see above for why no tr()
+        shortcutEditPane.add(cbCtrl);
+
+        tfKeyLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
+        tfKeyLabel.setText(tr("Key:"));
+        shortcutEditPane.add(tfKeyLabel);
+
+        cbAlt.setAction(action);
+        cbAlt.setText(ALT); // see above for why no tr()
+        shortcutEditPane.add(cbAlt);
+
+        tfKey.setAction(action);
+        tfKey.setModel(new javax.swing.DefaultComboBoxModel(keyList.values().toArray()));
+        shortcutEditPane.add(tfKey);
+
+        cbMeta.setAction(action);
+        cbMeta.setText(META); // see above for why no tr()
+        shortcutEditPane.add(cbMeta);
+
+        jLabel6.setText(tr("Attention: Use real keyboard keys only!"));
+        shortcutEditPane.add(jLabel6);
+
+        action.actionPerformed(null); // init checkboxes
+
+        shortcutTab.add(shortcutEditPane);
+
+        prefTabPane.addTab(tr("Keyboard Shortcuts"), shortcutTab);
+
+        // next is the modfier group tab.
+        // Would be a nice array if I had done it by hand. But then, it would be finished next year or so...
+        modifierTab.setLayout(new java.awt.GridLayout(0, 1));
+
+        editGroupPane.setBorder(javax.swing.BorderFactory.createTitledBorder(tr("Edit Shortcuts")));
+        editGroupPane.setLayout(new java.awt.GridLayout(3, 5));
+
+        jLabel1.setText(tr("Primary"));
+        editGroupPane.add(jLabel1);
+
+        bxPrim1.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        editGroupPane.add(bxPrim1);
+
+        jLabel2.setText(tr("Secondary:"));
+        editGroupPane.add(jLabel2);
+
+        bxSec1.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        editGroupPane.add(bxSec1);
+
+        jLabel3.setText(tr("Tertiary:"));
+        editGroupPane.add(jLabel3);
+
+        bxTer1.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        editGroupPane.add(bxTer1);
+
+        modifierTab.add(editGroupPane);
+
+        menuGroupPane.setBorder(javax.swing.BorderFactory.createTitledBorder(tr("Menu Shortcuts")));
+        menuGroupPane.setLayout(new java.awt.GridLayout(3, 5));
+
+        jLabel7.setText(tr("Primary"));
+        menuGroupPane.add(jLabel7);
+
+        bxPrim2.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        menuGroupPane.add(bxPrim2);
+
+        jLabel8.setText(tr("Secondary:"));
+        menuGroupPane.add(jLabel8);
+
+        bxSec2.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        menuGroupPane.add(bxSec2);
+
+        jLabel9.setText(tr("Tertiary:"));
+        menuGroupPane.add(jLabel9);
+
+        bxTer2.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        menuGroupPane.add(bxTer2);
+
+        modifierTab.add(menuGroupPane);
+
+        hotkeyGroupPane.setBorder(javax.swing.BorderFactory.createTitledBorder(tr("Hotkey Shortcuts")));
+        hotkeyGroupPane.setLayout(new java.awt.GridLayout(3, 5));
+
+        jLabel10.setText(tr("Primary"));
+        hotkeyGroupPane.add(jLabel10);
+
+        bxPrim3.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        hotkeyGroupPane.add(bxPrim3);
+
+        jLabel11.setText(tr("Secondary:"));
+        hotkeyGroupPane.add(jLabel11);
+
+        bxSec3.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        hotkeyGroupPane.add(bxSec3);
+
+        jLabel12.setText(tr("Tertiary:"));
+        hotkeyGroupPane.add(jLabel12);
+
+        bxTer3.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        hotkeyGroupPane.add(bxTer3);
+
+        modifierTab.add(hotkeyGroupPane);
+
+        subwindowGroupPane.setBorder(javax.swing.BorderFactory.createTitledBorder(tr("Subwindow Shortcuts")));
+        subwindowGroupPane.setLayout(new java.awt.GridLayout(3, 5));
+
+        jLabel13.setText(tr("Primary"));
+        subwindowGroupPane.add(jLabel13);
+
+        bxPrim4.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        subwindowGroupPane.add(bxPrim4);
+
+        jLabel14.setText(tr("Secondary:"));
+        subwindowGroupPane.add(jLabel14);
+
+        bxSec4.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        subwindowGroupPane.add(bxSec4);
+
+        jLabel15.setText(tr("Tertiary:"));
+        subwindowGroupPane.add(jLabel15);
+
+        bxTer4.setModel(new javax.swing.DefaultComboBoxModel(modifList));
+        subwindowGroupPane.add(bxTer4);
+
+				initbx();
+				bxPrim1.setAction(action2);
+				bxSec1.setAction(action2);
+				bxTer1.setAction(action2);
+				bxPrim2.setAction(action2);
+				bxSec2.setAction(action2);
+				bxTer2.setAction(action2);
+				bxPrim3.setAction(action2);
+				bxSec3.setAction(action2);
+				bxTer3.setAction(action2);
+				bxPrim4.setAction(action2);
+				bxSec4.setAction(action2);
+				bxTer4.setAction(action2);
+
+        modifierTab.add(subwindowGroupPane);
+
+        prefTabPane.addTab(tr("Modifier Groups"), modifierTab);
+
+        add(prefTabPane);
+    }
+
+	// this allows to edit shortcuts. it:
+	//  * sets the edit controls to the selected shortcut
+	//  * enabled/disables the controls as needed
+	//  * writes the user's changes to the shortcut
+	// And after I finally had it working, I realized that those two methods
+	// are playing ping-pong (politically correct: table tennis, I know) and
+	// even have some duplicated code. Feel free to refactor, If you have
+	// more expirience with GUI coding than I have.
+	private class cbAction extends javax.swing.AbstractAction implements ListSelectionListener {
+		private prefJPanel panel;
+			public cbAction (prefJPanel panel) {
+				this.panel = panel;
+		}
+		public void valueChanged(ListSelectionEvent e) {
+			ListSelectionModel lsm = panel.shortcutTable.getSelectionModel(); // can't use e here
+			if (!lsm.isSelectionEmpty()) {
+				int row = lsm.getMinSelectionIndex();
+				ShortCut sc = (ShortCut)panel.model.getValueAt(row, -1);
+				panel.cbDefault.setSelected(!sc.getAssignedUser());
+				panel.cbDisable.setSelected(sc.getKeyStroke() == null);
+				panel.cbShift.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.SHIFT_DOWN_MASK) != 0);
+				panel.cbCtrl.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.CTRL_DOWN_MASK) != 0);
+				panel.cbAlt.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.ALT_DOWN_MASK) != 0);
+				panel.cbMeta.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.META_DOWN_MASK) != 0);
+				if (sc.getKeyStroke() != null) {
+					tfKey.setSelectedItem(keyList.get(sc.getKeyStroke().getKeyCode()));
+				} else {
+					tfKey.setSelectedItem(keyList.get(-1));
+				}
+				if (sc.getAutomatic()) {
+					panel.cbDefault.setEnabled(false);
+					panel.cbDisable.setEnabled(false);
+					panel.cbShift.setEnabled(false);
+					panel.cbCtrl.setEnabled(false);
+					panel.cbAlt.setEnabled(false);
+					panel.cbMeta.setEnabled(false);
+					panel.tfKey.setEnabled(false);
+				} else {
+					panel.cbDefault.setEnabled(true);
+					actionPerformed(null);
+				}
+			} else {
+				panel.cbDefault.setEnabled(false);
+				panel.cbDisable.setEnabled(false);
+				panel.cbShift.setEnabled(false);
+				panel.cbCtrl.setEnabled(false);
+				panel.cbAlt.setEnabled(false);
+				panel.cbMeta.setEnabled(false);
+				panel.tfKey.setEnabled(false);
+			}
+		}
+		public void actionPerformed(java.awt.event.ActionEvent e) {
+			ListSelectionModel lsm = panel.shortcutTable.getSelectionModel();
+			if (lsm != null && !lsm.isSelectionEmpty()) {
+				if (e != null) { // only if we've been called by a user action
+					int row = lsm.getMinSelectionIndex();
+					ShortCut sc = (ShortCut)panel.model.getValueAt(row, -1);
+					sc.setAssignedUser(!panel.cbDefault.isSelected());
+					if (panel.cbDisable.isSelected()) {
+						sc.setAssignedModifier(-1);
+					} else if (panel.tfKey.getSelectedItem().equals("")) {
+						sc.setAssignedModifier(KeyEvent.VK_CANCEL);
+					} else {
+						sc.setAssignedModifier(
+							(panel.cbShift.isSelected() ? KeyEvent.SHIFT_DOWN_MASK : 0) |
+							(panel.cbCtrl.isSelected() ? KeyEvent.CTRL_DOWN_MASK : 0) |
+							(panel.cbAlt.isSelected() ? KeyEvent.ALT_DOWN_MASK : 0) |
+							(panel.cbMeta.isSelected() ? KeyEvent.META_DOWN_MASK : 0)
+						);
+						for (Map.Entry<Integer, String> entry : keyList.entrySet()) {
+							if (entry.getValue().equals(panel.tfKey.getSelectedItem())) {
+								sc.setAssignedKey(entry.getKey());
+							}
+						}
+					}
+					valueChanged(null);
+				}
+				boolean state = !panel.cbDefault.isSelected();
+				panel.cbDisable.setEnabled(state);
+				state = state && !panel.cbDisable.isSelected();
+				panel.cbShift.setEnabled(state);
+				panel.cbCtrl.setEnabled(state);
+				panel.cbAlt.setEnabled(state);
+				panel.cbMeta.setEnabled(state);
+				panel.tfKey.setEnabled(state);
+			} else {
+				panel.cbDefault.setEnabled(false);
+				panel.cbDisable.setEnabled(false);
+				panel.cbShift.setEnabled(false);
+				panel.cbCtrl.setEnabled(false);
+				panel.cbAlt.setEnabled(false);
+				panel.cbMeta.setEnabled(false);
+				panel.tfKey.setEnabled(false);
+			}
+		}
+	}
+
+	// this handles the modifier groups
+	private class bxAction extends javax.swing.AbstractAction {
+		public void actionPerformed(java.awt.event.ActionEvent e) {
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_EDIT),    Integer.toString( modifInts[bxPrim1.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_EDIT),    Integer.toString( modifInts[ bxSec1.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_EDIT),    Integer.toString( modifInts[ bxTer1.getSelectedIndex()] ));
+
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MENU),    Integer.toString( modifInts[bxPrim2.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_MENU),    Integer.toString( modifInts[ bxSec2.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_MENU),    Integer.toString( modifInts[ bxTer2.getSelectedIndex()] ));
+
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_HOTKEY),  Integer.toString( modifInts[bxPrim3.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_HOTKEY),  Integer.toString( modifInts[ bxSec3.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_HOTKEY),  Integer.toString( modifInts[ bxTer3.getSelectedIndex()] ));
+
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_LAYER),   Integer.toString( modifInts[bxPrim4.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_LAYER),   Integer.toString( modifInts[ bxSec4.getSelectedIndex()] ));
+			Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_LAYER),   Integer.toString( modifInts[ bxTer4.getSelectedIndex()] ));
+		}
+	}
+
+	private void initbx() {
+		setBx(bxPrim1, "shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_EDIT));
+		setBx(bxSec1,  "shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_EDIT));
+		setBx(bxTer1,  "shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_EDIT));
+
+		setBx(bxPrim2, "shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MENU));
+		setBx(bxSec2,  "shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_MENU));
+		setBx(bxTer2,  "shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_MENU));
+
+		setBx(bxPrim3, "shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_HOTKEY));
+		setBx(bxSec3,  "shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_HOTKEY));
+		setBx(bxTer3,  "shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_HOTKEY));
+
+		setBx(bxPrim4, "shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_LAYER));
+		setBx(bxSec4,  "shortcut.groups."+(ShortCut.GROUPS_ALT1   +ShortCut.GROUP_LAYER));
+		setBx(bxTer4,  "shortcut.groups."+(ShortCut.GROUPS_ALT2   +ShortCut.GROUP_LAYER));
+	}
+	private void setBx(javax.swing.JComboBox bx, String key) {
+		int target = Main.pref.getInteger(key, -1);
+		for (int i = 0; i < modifInts.length; i++) {
+			if (modifInts[i] == target) {
+				bx.setSelectedIndex(i);
+			}
+		}
+	}
+
+    private javax.swing.JComboBox bxPrim1;
+    private javax.swing.JComboBox bxPrim2;
+    private javax.swing.JComboBox bxPrim3;
+    private javax.swing.JComboBox bxPrim4;
+    private javax.swing.JComboBox bxSec1;
+    private javax.swing.JComboBox bxSec2;
+    private javax.swing.JComboBox bxSec3;
+    private javax.swing.JComboBox bxSec4;
+    private javax.swing.JComboBox bxTer1;
+    private javax.swing.JComboBox bxTer2;
+    private javax.swing.JComboBox bxTer3;
+    private javax.swing.JComboBox bxTer4;
+    private javax.swing.JCheckBox cbAlt;
+    private javax.swing.JCheckBox cbCtrl;
+    private javax.swing.JCheckBox cbDefault;
+    private javax.swing.JCheckBox cbDisable;
+    private javax.swing.JCheckBox cbMeta;
+    private javax.swing.JCheckBox cbShift;
+    private javax.swing.JPanel editGroupPane;
+    private javax.swing.JPanel hotkeyGroupPane;
+    private javax.swing.JLabel jLabel1;
+    private javax.swing.JLabel jLabel10;
+    private javax.swing.JLabel jLabel11;
+    private javax.swing.JLabel jLabel12;
+    private javax.swing.JLabel jLabel13;
+    private javax.swing.JLabel jLabel14;
+    private javax.swing.JLabel jLabel15;
+    private javax.swing.JLabel jLabel2;
+    private javax.swing.JLabel jLabel3;
+    private javax.swing.JLabel jLabel4;
+    private javax.swing.JLabel jLabel6;
+    private javax.swing.JLabel jLabel7;
+    private javax.swing.JLabel jLabel8;
+    private javax.swing.JLabel jLabel9;
+    private javax.swing.JPanel listPane;
+    private javax.swing.JScrollPane listScrollPane;
+    private javax.swing.JPanel menuGroupPane;
+    private javax.swing.JPanel modifierTab;
+    private javax.swing.JTabbedPane prefTabPane;
+    private javax.swing.JPanel shortcutEditPane;
+    private javax.swing.JPanel shortcutTab;
+    private javax.swing.JTable shortcutTable;
+    private javax.swing.JPanel subwindowGroupPane;
+    private javax.swing.JComboBox tfKey;
+    private javax.swing.JLabel tfKeyLabel;
+    private javax.swing.JPanel infoTab;
+}
Index: /trunk/src/org/openstreetmap/josm/tools/OpenBrowser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/OpenBrowser.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/tools/OpenBrowser.java	(revision 1023)
@@ -12,4 +12,7 @@
 /**
  * Helper to open platform web browser on different platforms
+ *
+ * This now delegates the real work to a platform specific class.
+ *
  * @author Imi
  */
@@ -30,16 +33,6 @@
 		}
 
-		String os = System.getProperty("os.name");
-		if (os == null)
-			return "unknown operating system";
 		try {
-			if (os != null && os.startsWith("Windows"))
-				windows(url);
-			else if (os.equals("Linux") || os.equals("Solaris") || os.equals("SunOS") || os.equals("AIX") || os.equals("FreeBSD"))
-				linux(url);
-			else if (os.equals("Mac OS") || os.equals("Mac OS X"))
-				mac(url);
-			else
-				return "unknown operating system";
+			Main.platform.openUrl(url);
 		} catch (IOException e) {
 			return e.getMessage();
@@ -48,21 +41,3 @@
 	}
 
-	private static void windows(String url) throws IOException {
-		Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
-	}
-
-	private static void linux(String url) {
-		String[] programs = {"gnome-open", "kfmclient openURL", "firefox"};
-		for (String program : programs) {
-			try {
-				Runtime.getRuntime().exec(program+" "+url);
-				return;
-			} catch (IOException e) {
-            }
-		}
-	}
-
-	private static void mac(String url) throws IOException {
-		Runtime.getRuntime().exec("open " + url);
-	}
 }
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHook.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHook.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHook.java	(revision 1023)
@@ -0,0 +1,108 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ShortCut;
+
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.IOException;
+
+/**
+ * This interface allows platfrom (operating system) dependent code
+ * to be bundled into self-contained classes.
+ *
+ * For plugin authors:
+ * To implement your own PlatformHook class, implement this interface,
+ * then create the class when your plugin is loaded and store it in
+ * Main.platform. Please not that the two "startup" hooks will be
+ * called _before_ your plugin is loaded. If you need to hook there,
+ * split your class into two (one containing only the startup hooks,
+ * and one with the remainder) and send the startup class, together
+ * with propper OS detection code (see Main) for inclusion with
+ * JOSM to the JOSM team.
+ *
+ * Also, it might be a good idea to extend PlatformHookUnixoid.
+ * That class has a more or less neutral behaviour, that should
+ * work on all platforms supported by J2SE.
+ *
+ * Attention: At this time this interface is not to be considered
+ * complete.
+ */
+public interface PlatformHook {
+	/**
+	  * The preStartupHook will be called extremly early. It is
+	  * guaranteed to be called before the GUI setup has started.
+	  *
+	  * Reason: On OSX we need to inform the Swing libraries
+	  * that we want to be integrated with the OS before we setup
+	  * our GUI.
+	  */
+	public void preStartupHook();
+
+	/**
+	  * The startupHook will be called early, but after the GUI
+	  * setup has started.
+	  *
+	  * Reason: On OSX we need to register some callbacks with the
+	  * OS, so we'll receive events from the system menu.
+	  */
+	public void startupHook();
+
+	/**
+	  * The openURL hook will be used to open an URL in the
+	  * default webbrowser.
+	  */
+	public void openUrl(String url) throws IOException;
+
+	/**
+	  * The initShortCutGroups hook will be called by the
+	  * ShortCut class if it detects that there are no
+	  * groups in teh config file. So that will happen
+	  * once on each JOSM installation only.
+	  *
+	  * Please note that ShorCut will load its config on demand,
+	  * that is, at the moment the first shortcut is registered.
+	  *
+	  * In this hook, you have to fill the preferences with
+	  * data, not the internal structures! Also, do not try
+	  * to register any shortcuts from within.
+	  */
+	public void initShortCutGroups();
+
+	/**
+	  * The initSystemShortCuts hook will be called by the
+	  * ShortCut class after the modifier groups have been read
+	  * from the config, but before any shortcuts are read from
+	  * it or registered from within the application.
+	  *
+	  * Plese note that you are not allowed to register any
+	  * shortuts from this hook, but only "systemCuts"!
+	  *
+	  * BTW: SystemCuts should be named "system:<whatever>",
+	  * and it'd be best if sou'd recycle the names already used
+	  * by the Windows and OSX hooks. Especially the later has
+	  * really many of them.
+	  *
+	  * You should also register any and all shortcuts that the
+	  * operation system handles itself to block JOSM from trying
+	  * to use them---as that would just not work. Call setAutomatic
+	  * on them to prevent the keyboard preferences from allowing the
+	  * user to change them.
+	  */
+	public void initSystemShortCuts();
+
+	/**
+	  * The makeTooltip hook will be called whenever a tooltip for
+	  * a menu or button is created.
+	  *
+	  * Tooltips are usually not system dependent, unless the
+	  * JVM is to dumb to provide correct names for all the keys.
+	  *
+	  * Another reason not to use the implementation in the *nix
+	  * hook are LAFs that don't understand HTML, such as the OSX
+	  * LAFs.
+	  */
+	public String makeTooltip(String name, ShortCut sc);
+}
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 1023)
@@ -0,0 +1,250 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ShortCut;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.PlatformHookUnixoid;
+
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.Map;
+import java.lang.reflect.*;
+import java.io.IOException;
+import javax.swing.UIManager;
+
+/**
+  * see PlatformHook.java
+  */
+public class PlatformHookOsx extends PlatformHookUnixoid implements PlatformHook, InvocationHandler {
+	private static PlatformHookOsx ivhandler = new PlatformHookOsx();
+	public void preStartupHook(){
+		// This will merge our MenuBar into the system menu.
+		// MUST be set before Swing is initialized!
+		// And will not work when one of the system independet LAFs is used.
+		// They just insist on painting themselves...
+		System.setProperty("apple.laf.useScreenMenuBar", "true");
+	}
+	public void startupHook() {
+		// Here we register callbacks for the menu entries in the system menu
+		try {
+			Class Ccom_apple_eawt_Application = Class.forName("com.apple.eawt.Application");
+			Object Ocom_apple_eawt_Application = Ccom_apple_eawt_Application.getConstructor((Class[])null).newInstance((Object[])null);
+			Class Ccom_apple_eawt_ApplicationListener = Class.forName("com.apple.eawt.ApplicationListener");
+			Method MaddApplicationListener = Ccom_apple_eawt_Application.getDeclaredMethod("addApplicationListener", new Class[] { Ccom_apple_eawt_ApplicationListener });
+			Object Oproxy = Proxy.newProxyInstance(PlatformHookOsx.class.getClassLoader(), new Class[] { Ccom_apple_eawt_ApplicationListener }, ivhandler);
+			MaddApplicationListener.invoke(Ocom_apple_eawt_Application, new Object[] { Oproxy });
+			Method MsetEnabledPreferencesMenu = Ccom_apple_eawt_Application.getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class });
+			MsetEnabledPreferencesMenu.invoke(Ocom_apple_eawt_Application, new Object[] { Boolean.TRUE });
+		} catch (Exception ex) {
+			// Oops, what now?
+			// We'll just ignore this for now. The user will still be able to close JOSM
+			// by closing all its windows.
+			System.out.println("Failed to register with OSX: " + ex);
+		}
+	}
+	public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
+		Boolean handled = Boolean.TRUE;
+		//System.out.println("Going to handle method "+method+" (short: "+method.getName()+") with event "+args[0]);
+		if (method.getName().equals("handleQuit")) {
+			handled = !Main.main.breakBecauseUnsavedChanges();
+		} else if (method.getName().equals("handleAbout")) {
+			Main.main.menu.about.actionPerformed(null);
+		} else if (method.getName().equals("handlePreferences")) {
+			Main.main.menu.preferences.actionPerformed(null);
+		} else {
+			return null;
+		}
+		if (args[0] != null) {
+			try {
+				args[0].getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class }).invoke(args[0], new Object[] { handled });
+			} catch (Exception ex) {
+				System.out.println("Failed to report handled event: " + ex);
+			}
+		}
+		return null;
+	}
+	public void openUrl(String url) throws IOException {
+		// Ain't that KISS?
+		Runtime.getRuntime().exec("open " + url);
+	}
+	public void initShortCutGroups() {
+		// Everything but ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MENU is guesswork.
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_NONE),    Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_HOTKEY),  Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MENU),    Integer.toString(KeyEvent.META_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_EDIT),    Integer.toString(0));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_LAYER),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_DIRECT),  Integer.toString(0));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MNEMONIC),Integer.toString(KeyEvent.ALT_DOWN_MASK));
+
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_NONE),       Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_HOTKEY),     Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_MENU),       Integer.toString(KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_EDIT),       Integer.toString(KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_LAYER),      Integer.toString(KeyEvent.ALT_DOWN_MASK  | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT),     Integer.toString(KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_MNEMONIC),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_NONE),       Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_HOTKEY),     Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_MENU),       Integer.toString(KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_EDIT),       Integer.toString(KeyEvent.ALT_DOWN_MASK  | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_LAYER),      Integer.toString(KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_DIRECT),     Integer.toString(KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_MNEMONIC),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+	}
+	public void initSystemShortCuts() {
+		// Yeah, it's a long, long list. And people always complain that OSX has no shortcuts.
+		ShortCut.registerSystemCut("apple-reserved-01", "reserved", KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK).setAutomatic(); // Show or hide the Spotlight search field (when multiple languages are installed, may rotate through enabled script systems).
+		ShortCut.registerSystemCut("apple-reserved-02", "reserved", KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Apple reserved.
+		ShortCut.registerSystemCut("apple-reserved-03", "reserved", KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Show the Spotlight search results window (when multiple languages are installed, may rotate through keyboard layouts and input methods within a script).
+		ShortCut.registerSystemCut("apple-reserved-04", "reserved", KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK).setAutomatic(); //  | Apple reserved.
+		ShortCut.registerSystemCut("apple-reserved-05", "reserved", KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Navigate through controls in a reverse direction. See "Keyboard Focus and Navigation."
+		ShortCut.registerSystemCut("apple-reserved-06", "reserved", KeyEvent.VK_TAB, KeyEvent.META_DOWN_MASK).setAutomatic(); // Move forward to the next most recently used application in a list of open applications.
+		ShortCut.registerSystemCut("apple-reserved-07", "reserved", KeyEvent.VK_TAB, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Move backward through a list of open applications (sorted by recent use).
+		ShortCut.registerSystemCut("apple-reserved-08", "reserved", KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Move focus to the next grouping of controls in a dialog or the next table (when Tab moves to the next cell). See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-09", "reserved", KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Move focus to the previous grouping of controls. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-10", "reserved", KeyEvent.VK_ESCAPE, KeyEvent.META_DOWN_MASK).setAutomatic(); // Open Front Row.
+		ShortCut.registerSystemCut("apple-reserved-11", "reserved", KeyEvent.VK_ESCAPE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Open the Force Quit dialog.
+		ShortCut.registerSystemCut("apple-reserved-12", "reserved", KeyEvent.VK_F1, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Toggle full keyboard access on or off. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-13", "reserved", KeyEvent.VK_F2, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Move focus to the menu bar. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-14", "reserved", KeyEvent.VK_F3, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Move focus to the Dock. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-15", "reserved", KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Move focus to the active (or next) window. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-16", "reserved", KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Move focus to the previously active window. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-17", "reserved", KeyEvent.VK_F5, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Move focus to the toolbar. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-18", "reserved", KeyEvent.VK_F5, KeyEvent.META_DOWN_MASK).setAutomatic(); // Turn VoiceOver on or off. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-19", "reserved", KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Move focus to the first (or next) panel. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-20", "reserved", KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Move focus to the previous panel. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-21", "reserved", KeyEvent.VK_F7, KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Temporarily override the current keyboard access mode in windows and dialogs. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-22", "reserved", KeyEvent.VK_F9, 0).setAutomatic(); // Tile or untile all open windows.
+		ShortCut.registerSystemCut("apple-reserved-23", "reserved", KeyEvent.VK_F10, 0).setAutomatic(); // Tile or untile all open windows in the currently active application.
+		ShortCut.registerSystemCut("apple-reserved-24", "reserved", KeyEvent.VK_F11, 0).setAutomatic(); // Hide or show all open windows.
+		ShortCut.registerSystemCut("apple-reserved-25", "reserved", KeyEvent.VK_F12, 0).setAutomatic(); // Hide or display Dashboard.
+		ShortCut.registerSystemCut("apple-reserved-26", "reserved", KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK).setAutomatic(); // Activate the next open window in the frontmost application. See "Window Layering."
+		ShortCut.registerSystemCut("apple-reserved-27", "reserved", KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Activate the previous open window in the frontmost application. See "Window Layering."
+		ShortCut.registerSystemCut("apple-reserved-28", "reserved", KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Move focus to the window drawer.
+		ShortCut.registerSystemCut("apple-reserved-29", "reserved", KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK).setAutomatic(); // Decrease the size of the selected item (equivalent to the Smaller command). See "The Format Menu."
+		ShortCut.registerSystemCut("apple-reserved-30", "reserved", KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Zoom out when screen zooming is on. See Accessibility Overview.
+
+		ShortCut.registerSystemCut("system:align-left", "reserved", KeyEvent.VK_OPEN_BRACKET, KeyEvent.META_DOWN_MASK); // Left-align a selection (equivalent to the Align Left command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:align-right","reserved", KeyEvent.VK_CLOSE_BRACKET, KeyEvent.META_DOWN_MASK); // Right-align a selection (equivalent to the Align Right command). See "The Format Menu."
+		// I found no KeyEvent for |
+		//ShortCut.registerSystemCut("system:align-center", "reserved", '|', KeyEvent.META_DOWN_MASK); // Center-align a selection (equivalent to the Align Center command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:spelling", "reserved", KeyEvent.VK_COLON, KeyEvent.META_DOWN_MASK); // Display the Spelling window (equivalent to the Spelling command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:spellcheck", "reserved", KeyEvent.VK_SEMICOLON, KeyEvent.META_DOWN_MASK); // Find misspelled words in the document (equivalent to the Check Spelling command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:preferences", "reserved", KeyEvent.VK_COMMA, KeyEvent.META_DOWN_MASK).setAutomatic(); // Open the application's preferences window (equivalent to the Preferences command). See "The Application Menu."
+
+		ShortCut.registerSystemCut("apple-reserved-31", "reserved", KeyEvent.VK_COMMA, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Decrease screen contrast. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-32", "reserved", KeyEvent.VK_PERIOD, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Increase screen contrast. See Accessibility Overview.
+
+		// I found no KeyEvent for ?
+		//ShortCut.registerSystemCut("system:help", "reserved", '?', KeyEvent.META_DOWN_MASK).setAutomatic(); // Open the application's help in Help Viewer. See "The Help Menu."
+
+		ShortCut.registerSystemCut("apple-reserved-33", "reserved", KeyEvent.VK_SLASH, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Turn font smoothing on or off.
+		ShortCut.registerSystemCut("apple-reserved-34", "reserved", KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Increase the size of the selected item (equivalent to the Bigger command). See "The Format Menu."
+		ShortCut.registerSystemCut("apple-reserved-35", "reserved", KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Zoom in when screen zooming is on. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-36", "reserved", KeyEvent.VK_3, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Capture the screen to a file.
+		ShortCut.registerSystemCut("apple-reserved-37", "reserved", KeyEvent.VK_3, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Capture the screen to the Clipboard.
+		ShortCut.registerSystemCut("apple-reserved-38", "reserved", KeyEvent.VK_4, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Capture a selection to a file.
+		ShortCut.registerSystemCut("apple-reserved-39", "reserved", KeyEvent.VK_4, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Capture a selection to the Clipboard.
+		ShortCut.registerSystemCut("apple-reserved-40", "reserved", KeyEvent.VK_8, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Turn screen zooming on or off. See Accessibility Overview.
+		ShortCut.registerSystemCut("apple-reserved-41", "reserved", KeyEvent.VK_8, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Invert the screen colors. See Accessibility Overview.
+
+		ShortCut.registerSystemCut("system:selectall", "reserved", KeyEvent.VK_A, KeyEvent.META_DOWN_MASK); // Highlight every item in a document or window, or all characters in a text field (equivalent to the Select All command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:bold", "reserved", KeyEvent.VK_B, KeyEvent.META_DOWN_MASK); // Boldface the selected text or toggle boldfaced text on and off (equivalent to the Bold command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:copy", "reserved", KeyEvent.VK_C, KeyEvent.META_DOWN_MASK); // Duplicate the selected data and store on the Clipboard (equivalent to the Copy command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:colors", "reserved", KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Colors window (equivalent to the Show Colors command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:copystyle", "reserved", KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Copy the style of the selected text (equivalent to the Copy Style command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:copyformat", "reserved", KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK).setAutomatic(); // Copy the formatting settings of the selected item and store on the Clipboard (equivalent to the Copy Ruler command). See "The Format Menu."
+
+		ShortCut.registerSystemCut("apple-reserved-42", "reserved", KeyEvent.VK_D, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Show or hide the Dock. See "The Dock."
+
+		ShortCut.registerSystemCut("system:dictionarylookup", "reserved", KeyEvent.VK_D, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Display the definition of the selected word in the Dictionary application.
+		ShortCut.registerSystemCut("system:findselected", "reserved", KeyEvent.VK_E, KeyEvent.META_DOWN_MASK); // Use the selection for a find operation. See "Find Windows."
+		ShortCut.registerSystemCut("system:find", "reserved", KeyEvent.VK_F, KeyEvent.META_DOWN_MASK); // Open a Find window (equivalent to the Find command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:search", "reserved", KeyEvent.VK_F, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Jump to the search field control. See "Search Fields."
+		ShortCut.registerSystemCut("system:findnext", "reserved", KeyEvent.VK_G, KeyEvent.META_DOWN_MASK); // Find the next occurrence of the selection (equivalent to the Find Next command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:findprev", "reserved", KeyEvent.VK_G, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Find the previous occurrence of the selection (equivalent to the Find Previous command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:hide", "reserved", KeyEvent.VK_H, KeyEvent.META_DOWN_MASK).setAutomatic(); // Hide the windows of the currently running application (equivalent to the Hide ApplicationName command). See "The Application Menu."
+		ShortCut.registerSystemCut("system:hideothers", "reserved", KeyEvent.VK_H, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Hide the windows of all other running applications (equivalent to the Hide Others command). See "The Application Menu."
+		// What about applications that have italic text AND info windows?
+		//ShortCut.registerSystemCut("system:italic", "reserved", KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Italicize the selected text or toggle italic text on or off (equivalent to the Italic command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:info", "reserved", KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Display an Info window. See "Inspector Windows."
+		ShortCut.registerSystemCut("system:inspector", "reserved", KeyEvent.VK_I, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Display an inspector window. See "Inspector Windows."
+		ShortCut.registerSystemCut("system:toselection", "reserved", KeyEvent.VK_J, KeyEvent.META_DOWN_MASK); // Scroll to a selection.
+		ShortCut.registerSystemCut("system:minimize", "reserved", KeyEvent.VK_M, KeyEvent.META_DOWN_MASK); // Minimize the active window to the Dock (equivalent to the Minimize command). See "The Window Menu."
+		ShortCut.registerSystemCut("system:minimizeall", "reserved", KeyEvent.VK_M, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Minimize all windows of the active application to the Dock (equivalent to the Minimize All command). See "The Window Menu."
+		ShortCut.registerSystemCut("system:new", "reserved", KeyEvent.VK_N, KeyEvent.META_DOWN_MASK); // Open a new document (equivalent to the New command). See "The File Menu."
+		ShortCut.registerSystemCut("system:open", "reserved", KeyEvent.VK_O, KeyEvent.META_DOWN_MASK); // Display a dialog for choosing a document to open (equivalent to the Open command). See "The File Menu."
+		ShortCut.registerSystemCut("system:print", "reserved", KeyEvent.VK_P, KeyEvent.META_DOWN_MASK); // Display the Print dialog (equivalent to the Print command). See "The File Menu."
+		ShortCut.registerSystemCut("system:printsetup", "reserved", KeyEvent.VK_P, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display a dialog for specifying printing parameters (equivalent to the Page Setup command). See "The File Menu."
+		ShortCut.registerSystemCut("system:menuexit", "reserved", KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK).setAutomatic(); // Quit the application (equivalent to the Quit command). See "The Application Menu."
+
+		ShortCut.registerSystemCut("apple-reserved-43", "reserved", KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Log out the current user (equivalent to the Log Out command).
+		ShortCut.registerSystemCut("apple-reserved-44", "reserved", KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK).setAutomatic(); // Log out the current user without confirmation.
+
+		ShortCut.registerSystemCut("system:save", "reserved", KeyEvent.VK_S, KeyEvent.META_DOWN_MASK); // Save the active document (equivalent to the Save command). See "The File Menu."
+		ShortCut.registerSystemCut("system:saveas", "reserved", KeyEvent.VK_S, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Save dialog (equivalent to the Save As command). See "The File Menu."
+		ShortCut.registerSystemCut("system:fonts", "reserved", KeyEvent.VK_T, KeyEvent.META_DOWN_MASK); // Display the Fonts window (equivalent to the Show Fonts command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:toggletoolbar", "reserved", KeyEvent.VK_T, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Show or hide a toolbar (equivalent to the Show/Hide Toolbar command). See "The View Menu" and "Toolbars."
+		ShortCut.registerSystemCut("system:underline", "reserved", KeyEvent.VK_U, KeyEvent.META_DOWN_MASK); // Underline the selected text or turn underlining on or off (equivalent to the Underline command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:paste", "reserved", KeyEvent.VK_V, KeyEvent.META_DOWN_MASK); // Insert the Clipboard contents at the insertion point (equivalent to the Paste command). See "The File Menu."
+		ShortCut.registerSystemCut("system:pastestyle", "reserved", KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of one object to the selected object (equivalent to the Paste Style command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:pastemwithoutstyle", "reserved", KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of the surrounding text to the inserted object (equivalent to the Paste and Match Style command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:pasteformatting", "reserved", KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Apply formatting settings to the selected object (equivalent to the Paste Ruler command). See "The Format Menu."
+		ShortCut.registerSystemCut("system:closewindow", "reserved", KeyEvent.VK_W, KeyEvent.META_DOWN_MASK); // Close the active window (equivalent to the Close command). See "The File Menu."
+		ShortCut.registerSystemCut("system:closefile", "reserved", KeyEvent.VK_W, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Close a file and its associated windows (equivalent to the Close File command). See "The File Menu."
+		ShortCut.registerSystemCut("system:closeallwindows", "reserved", KeyEvent.VK_W, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Close all windows in the application (equivalent to the Close All command). See "The File Menu."
+		ShortCut.registerSystemCut("system:cut", "reserved", KeyEvent.VK_X, KeyEvent.META_DOWN_MASK); // Remove the selection and store on the Clipboard (equivalent to the Cut command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:undo", "reserved", KeyEvent.VK_Z, KeyEvent.META_DOWN_MASK); // Reverse the effect of the user's previous operation (equivalent to the Undo command). See "The Edit Menu."
+		ShortCut.registerSystemCut("system:redo", "reserved", KeyEvent.VK_Z, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Reverse the effect of the last Undo command (equivalent to the Redo command). See "The Edit Menu."
+
+		ShortCut.registerSystemCut("apple-reserved-45", "reserved", KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK).setAutomatic(); // Change the keyboard layout to current layout of Roman script.
+		//ShortCut.registerSystemCut("apple-reserved-46", "reserved", KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the next semantic unit, typically the end of the current line.
+		//ShortCut.registerSystemCut("apple-reserved-47", "reserved", KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection one character to the right.
+		//ShortCut.registerSystemCut("apple-reserved-48", "reserved", KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the end of the current word, then to the end of the next word.
+
+		ShortCut.registerSystemCut("system:movefocusright", "reserved", KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
+
+		ShortCut.registerSystemCut("apple-reserved-49", "reserved", KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK).setAutomatic(); // Change the keyboard layout to current layout of system script.
+		//ShortCut.registerSystemCut("apple-reserved-50", "reserved", KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the previous semantic unit, typically the beginning of the current line.
+		//ShortCut.registerSystemCut("apple-reserved-51", "reserved", KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection one character to the left.
+		//ShortCut.registerSystemCut("apple-reserved-52", "reserved", KeyEvent.VK_LEFT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the beginning of the current word, then to the beginning of the previous word.
+
+		ShortCut.registerSystemCut("system:movefocusleft", "reserved", KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
+
+		//ShortCut.registerSystemCut("apple-reserved-53", "reserved", KeyEvent.VK_UP, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection upward in the next semantic unit, typically the beginning of the document.
+		//ShortCut.registerSystemCut("apple-reserved-54", "reserved", KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the line above, to the nearest character boundary at the same horizontal location.
+		//ShortCut.registerSystemCut("apple-reserved-55", "reserved", KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the beginning of the current paragraph, then to the beginning of the next paragraph.
+
+		ShortCut.registerSystemCut("system:movefocusup", "reserved", KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
+
+		//ShortCut.registerSystemCut("apple-reserved-56", "reserved", KeyEvent.VK_DOWN, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection downward in the next semantic unit, typically the end of the document.
+		//ShortCut.registerSystemCut("apple-reserved-57", "reserved", KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the line below, to the nearest character boundary at the same horizontal location.
+		//ShortCut.registerSystemCut("apple-reserved-58", "reserved", KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).setAutomatic(); // Extend selection to the end of the current paragraph, then to the end of the next paragraph (include the blank line between paragraphs in cut, copy, and paste operations).
+
+		ShortCut.registerSystemCut("system:movefocusdown", "reserved", KeyEvent.VK_DOWN, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
+
+		ShortCut.registerSystemCut("system:about", "reserved", 0, -1).setAutomatic(); // About
+	}
+	public String makeTooltip(String name, ShortCut sc) {
+		String lafid = UIManager.getLookAndFeel().getID();
+		boolean canHtml = true;
+		// "Mac" is the native LAF, "Aqua" is Quaqua. Both use native menus with native tooltips.
+		if (lafid.contains("Mac") || lafid.contains("Aqua")) {
+			canHtml = false;
+		}
+		String result = "";
+		if (canHtml) result += "<html>";
+		result += name;
+		if (sc != null && sc.getKeyText().length() != 0) {
+			result += " ";
+			if (canHtml) result += "<font size='-2'>";
+			result += "("+sc.getKeyText()+")";
+			if (canHtml) result += "</font>";
+		}
+		if (canHtml) result += "&nbsp;</html>";
+		return result;
+	}
+}
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java	(revision 1023)
@@ -0,0 +1,81 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ShortCut;
+import org.openstreetmap.josm.Main;
+
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.IOException;
+import javax.swing.KeyStroke;
+
+/**
+  * see PlatformHook.java
+  *
+  * BTW: THIS IS A STUB. See comments below for details.
+  */
+public class PlatformHookUnixoid implements PlatformHook {
+	public void preStartupHook(){
+	}
+	public void startupHook() {
+	}
+	public void openUrl(String url) throws IOException {
+		String[] programs = {"gnome-open", "kfmclient openURL", "firefox"};
+		for (String program : programs) {
+			try {
+				Runtime.getRuntime().exec(program+" "+url);
+				return;
+			} catch (IOException e) {
+			}
+		}
+	}
+	public void initShortCutGroups() {
+		// This is the Windows list. Someone should look over it and make it more "*nix"...
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_NONE),    Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_HOTKEY),  Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MENU),    Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_EDIT),    Integer.toString(0));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_LAYER),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_DIRECT),  Integer.toString(0));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MNEMONIC),Integer.toString(KeyEvent.ALT_DOWN_MASK));
+
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_NONE),       Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_HOTKEY),     Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_MENU),       Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_EDIT),       Integer.toString(KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_LAYER),      Integer.toString(KeyEvent.ALT_DOWN_MASK  | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT),     Integer.toString(KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_MNEMONIC),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_NONE),       Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_HOTKEY),     Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_MENU),       Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_EDIT),       Integer.toString(KeyEvent.ALT_DOWN_MASK  | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_LAYER),      Integer.toString(KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_DIRECT),     Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_MNEMONIC),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+	}
+	public void initSystemShortCuts() {
+		// TODO: Insert system shortcuts here. See Windows and espacially OSX to see how to.
+	}
+	/**
+	  * This should work for all platforms. Yeah, should.
+	  * See PlatformHook.java for a list of reasons why
+	  * this is implemented here...
+	  */
+	public String makeTooltip(String name, ShortCut sc) {
+		String result = "";
+		result += "<html>";
+		result += name;
+		if (sc != null && sc.getKeyText().length() != 0) {
+			result += " ";
+			result += "<font size='-2'>";
+			result += "("+sc.getKeyText()+")";
+			result += "</font>";
+		}
+		result += "&nbsp;</html>";
+		return result;
+	}
+}
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java	(revision 1023)
@@ -0,0 +1,64 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ShortCut;
+import org.openstreetmap.josm.tools.PlatformHookUnixoid;
+import org.openstreetmap.josm.Main;
+
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.IOException;
+
+/**
+  * see PlatformHook.java
+  */
+public class PlatformHookWindows extends PlatformHookUnixoid implements PlatformHook {
+	public void preStartupHook(){
+	}
+	public void startupHook() {
+	}
+	public void openUrl(String url) throws IOException {
+		Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
+	}
+	public void initShortCutGroups() {
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_NONE),    Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_HOTKEY),  Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MENU),    Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_EDIT),    Integer.toString(0));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_LAYER),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_DIRECT),  Integer.toString(0));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_DEFAULT+ShortCut.GROUP_MNEMONIC),Integer.toString(KeyEvent.ALT_DOWN_MASK));
+
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_NONE),       Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_HOTKEY),     Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_MENU),       Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_EDIT),       Integer.toString(KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_LAYER),      Integer.toString(KeyEvent.ALT_DOWN_MASK  | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_DIRECT),     Integer.toString(KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT1+ShortCut.GROUP_MNEMONIC),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_NONE),       Integer.toString(-1));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_HOTKEY),     Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_MENU),       Integer.toString(KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_EDIT),       Integer.toString(KeyEvent.ALT_DOWN_MASK  | KeyEvent.SHIFT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_LAYER),      Integer.toString(KeyEvent.ALT_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_DIRECT),     Integer.toString(KeyEvent.CTRL_DOWN_MASK));
+		Main.pref.put("shortcut.groups."+(ShortCut.GROUPS_ALT2+ShortCut.GROUP_MNEMONIC),   Integer.toString(KeyEvent.ALT_DOWN_MASK));
+	}
+	public void initSystemShortCuts() {
+		// This list if far from complete!
+		ShortCut.registerSystemCut("system:exit", "unused", KeyEvent.VK_F4, KeyEvent.ALT_DOWN_MASK).setAutomatic(); // items with automatic shortcuts will not be added to the menu bar at all
+		ShortCut.registerSystemCut("system:menuexit", "unused", KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK);
+		ShortCut.registerSystemCut("system:copy", "unused", KeyEvent.VK_C, KeyEvent.CTRL_DOWN_MASK);
+		ShortCut.registerSystemCut("system:paste", "unused", KeyEvent.VK_V, KeyEvent.CTRL_DOWN_MASK);
+		ShortCut.registerSystemCut("system:cut", "unused", KeyEvent.VK_X, KeyEvent.CTRL_DOWN_MASK);
+		ShortCut.registerSystemCut("system:duplicate", "unused", KeyEvent.VK_D, KeyEvent.CTRL_DOWN_MASK); // not really system, but to avoid odd results
+		ShortCut.registerSystemCut("system:help", "unused", KeyEvent.VK_F1, 0);
+	}
+}
+
+
+
+
Index: /trunk/src/org/openstreetmap/josm/tools/ShortCut.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/ShortCut.java	(revision 1023)
+++ /trunk/src/org/openstreetmap/josm/tools/ShortCut.java	(revision 1023)
@@ -0,0 +1,438 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.Main;
+
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Collection;
+import javax.swing.KeyStroke;
+import javax.swing.JMenu;
+import javax.swing.JOptionPane;
+
+/**
+ * Global shortcut class.
+ *
+ * Note: This class represents a single shortcut, contains the factory to obtain
+ *       shortcut objects from, manages shortcuts and shortcut collisions, and
+ *       finally manages loading and saving shortcuts to/from the preferences.
+ *
+ * Action authors: You only need the registerShortCut() factory. Ignore everything
+ *                 else.
+ *
+ * All: Use only public methods that are also marked to be used. The others are
+ *      public so the shortcut preferences can use them.
+ *
+ */
+public class ShortCut {
+	private String shortText;        // the unique ID of the shortcut
+	private String longText;         // a human readable description that will be shown in the preferences
+	private int requestedKey;        // the key, the caller requested
+	private int requestedGroup;      // the group, the caller requested
+	private int assignedKey;         // the key that actually is used
+	private int assignedModifier;    // the modifiers that are used
+	private boolean assignedDefault; // true if it got assigned what was requested. (Note: modifiers will be ignored in favour of group when loading it from the preferences then.)
+	private boolean assignedUser;    // true if the user changed this shortcut
+	private boolean automatic;       // true if the user cannot change this shortcut (Note: it also will not be saved into the preferences)
+	private boolean reset;           // true if the user requested this shortcut to be set to its default value (will happen on next restart, as this shortcut will not be saved to the preferences)
+
+	// simple constructor
+	private ShortCut(String shortText, String longText, int requestedKey, int requestedGroup, int assignedKey, int assignedModifier, boolean assignedDefault, boolean assignedUser) {
+		this.shortText = shortText;
+		this.longText = longText;
+		this.requestedKey = requestedKey;
+		this.requestedGroup = requestedGroup;
+		this.assignedKey = assignedKey;
+		this.assignedModifier = assignedModifier;
+		this.assignedDefault = assignedDefault;
+		this.assignedUser = assignedUser;
+		this.automatic = false;
+		this.reset = false;
+	}
+
+	public String getShortText() {
+		return shortText;
+	}
+
+	public String getLongText() {
+		return longText;
+	}
+
+	// a shortcut will be renamed when it is handed out again, because the original name
+	// may be a dummy
+	private void setLongText(String longText) {
+		this.longText = longText;
+	}
+
+	private int getRequestedKey() {
+		return requestedKey;
+	}
+
+	public int getRequestedGroup() {
+		return requestedGroup;
+	}
+
+	public int getAssignedKey() {
+		return assignedKey;
+	}
+
+	public int getAssignedModifier() {
+		return assignedModifier;
+	}
+
+	public boolean getAssignedDefault() {
+		return assignedDefault;
+	}
+
+	public boolean getAssignedUser() {
+		return assignedUser;
+	}
+
+	public boolean getAutomatic() {
+		return automatic;
+	}
+
+	private boolean getReset() {
+		return reset;
+	}
+
+	/**
+	 * FOR PREF PANE ONLY
+	 */
+	public void setAutomatic() {
+		automatic = true;
+	}
+
+	/**
+	 * FOR PREF PANE ONLY
+	 */
+	public void setAssignedModifier(int assignedModifier) {
+		this.assignedModifier = assignedModifier;
+	}
+
+	/**
+	 * FOR PREF PANE ONLY
+	 */
+	public void setAssignedKey(int assignedKey) {
+		this.assignedKey = assignedKey;
+	}
+
+	/**
+	 * FOR PREF PANE ONLY
+	 */
+	public void setAssignedUser(boolean assignedUser) {
+		this.reset = (!this.assignedUser && assignedUser);
+		if (assignedUser) assignedDefault = false;
+		this.assignedUser = assignedUser;
+	}
+
+	/**
+	 * Use this to register the shortcut with Swing
+	 */
+	public KeyStroke getKeyStroke() {
+		if (assignedModifier != -1) {
+			return KeyStroke.getKeyStroke(assignedKey, assignedModifier);
+		} else {
+			return null;
+		}
+	}
+
+	private boolean isSame(int isKey, int isModifier) {
+		// -1 --- an unassigned shortcut is different from any other shortcut
+		return( isKey == assignedKey && isModifier == assignedModifier && assignedModifier != Groups.get(GROUP_NONE));
+	}
+
+	// create a shortcut object from an string as saved in the preferences
+	private ShortCut(String prefString) {
+		String[] s = prefString.split(";");
+		this.shortText = s[0];
+		this.longText = s[1];
+		this.requestedKey = Integer.parseInt(s[2]);
+		this.requestedGroup = Integer.parseInt(s[3]);
+		this.assignedKey = Integer.parseInt(s[4]);
+		this.assignedModifier = Integer.parseInt(s[5]);
+		this.assignedDefault = Boolean.parseBoolean(s[6]);
+		this.assignedUser = Boolean.parseBoolean(s[7]);
+	}
+
+	// get a string that can be put into the preferences
+	private String asPrefString() {
+		return shortText + ";" + longText + ";" + requestedKey + ";" + requestedGroup + ";" + assignedKey + ";" + assignedModifier + ";" + assignedDefault + ";" + assignedUser;
+	}
+
+	private boolean isSame(ShortCut other) {
+		return assignedKey == other.assignedKey && assignedModifier == other.assignedModifier;
+	}
+
+	/**
+	 * use this to set a menu's mnemonic
+	 */
+	public void setMnemonic(JMenu menu) {
+		if (requestedGroup == GROUP_MNEMONIC && assignedModifier == Groups.get(requestedGroup + GROUPS_DEFAULT) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
+			menu.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
+		}
+	}
+
+	/**
+	 * use this to get a human readable text for your shortcut
+	 */
+	 public String getKeyText() {
+	 	KeyStroke keyStroke = getKeyStroke();
+		if (keyStroke == null) return "";
+		String modifText = KeyEvent.getKeyModifiersText(keyStroke.getModifiers());
+		if ("".equals (modifText)) return KeyEvent.getKeyText (keyStroke.getKeyCode ());
+		return modifText + "+" + KeyEvent.getKeyText(keyStroke.getKeyCode ());
+	}
+
+	  ///////////////////////////////
+	 // everything's static below //
+	///////////////////////////////
+
+	// here we store our shortcuts
+	private static Map<String, ShortCut> ShortCuts = new LinkedHashMap<String, ShortCut>();
+
+	// and here our modifier groups
+	private static Map<Integer, Integer> Groups = new HashMap<Integer, Integer>();
+
+	// check if something collides with an existing shortcut
+	private static ShortCut findShortcut(int requestedKey, int modifier) {
+		if (modifier == Groups.get(GROUP_NONE)) {
+			return null;
+		}
+		for (ShortCut sc : ShortCuts.values()) {
+			if (sc.isSame(requestedKey, modifier)) {
+				return sc;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * FOR PREF PANE ONLY
+	 */
+	public static Collection<ShortCut> listAll() {
+		return ShortCuts.values();
+	}
+
+	// try to find an unused shortcut
+	private static ShortCut findRandomShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
+		int[] mods = {Groups.get(requestedGroup + GROUPS_DEFAULT), Groups.get(requestedGroup + GROUPS_ALT1), Groups.get(requestedGroup + GROUPS_ALT2)};
+		for (int m : mods) {
+			for (int k = KeyEvent.VK_A; k < KeyEvent.VK_Z; k++) { // we'll limit ourself to 100% safe keys
+				if ( findShortcut(k, m) == null ) {
+					return new ShortCut(shortText, longText, requestedKey, requestedGroup, k, m, false, false);
+				}
+			}
+		}
+		return new ShortCut(shortText, longText, requestedKey, requestedGroup, requestedKey, Groups.get(GROUP_NONE), false, false);
+	}
+
+	// use these constants to request shortcuts
+	public static final int GROUP_NONE = 0;          // no shortcut
+	public static final int GROUP_HOTKEY = 1;        // a button action, will use another modifier than MENU on system with a meta key
+	public static final int GROUP_MENU = 2;          // a menu action, e.g. "ctrl-e"/"cmd-e" (export)
+	public static final int GROUP_EDIT = 3;          // direct edit key, e.g. "a" (add)
+	public static final int GROUP_LAYER = 4;         // toggle one of the right-hand-side windows, e.g. "alt-l" (layers)
+	public static final int GROUP_DIRECT = 5;        // for non-letter keys, preferable without modifier, e.g. F5
+	public static final int GROUP_MNEMONIC = 6;      // for use with Menu.setMnemonic() only!
+	public static final int GROUP__MAX = 7;
+	public static final int GROUP_RESERVED = 1000;
+	public static final int GROUPS_DEFAULT = 0;
+	public static final int GROUPS_ALT1 = GROUP__MAX;
+	public static final int GROUPS_ALT2 = GROUP__MAX * 2;
+
+	// safely read a shortcut from the preferences
+	private static String[] getConfigStringArray(String key) {
+		String s = Main.pref.get(key, null);
+		if (s == null || s.equals("null") || s.equals(""))
+			return null;
+		return s.split(";");
+	}
+
+	// bootstrap
+	private static boolean initdone = false;
+	private static void doInit() {
+		if (initdone) return;
+		initdone = true;
+		// if we have no modifier groups in the config, we have to create them
+		if (Main.pref.get("shortcut.groups.configured", null) == null) {
+			Main.platform.initShortCutGroups();
+			Main.pref.put("shortcut.groups.configured", true);
+		}
+		// pull in the gorups
+		for (int i = GROUP_NONE; i < GROUP__MAX+GROUPS_ALT2*2; i++) { // fill more groups, so registering with e.g. ALT2+MNEMONIC won't NPE
+			Groups.put(new Integer(i), new Integer(Main.pref.getInteger("shortcut.groups."+i, -1)));
+		}
+		// (1) System reserved shortcuts
+		Main.platform.initSystemShortCuts();
+		// (2) User defined shortcuts
+		int i = 0;
+		String p = Main.pref.get("shortcut.shortcut."+i, null);
+		while (p != null) {
+			ShortCut sc = new ShortCut(p);
+			if (sc.getAssignedUser()) registerShortCut(sc);
+			i++;
+			p = Main.pref.get("shortcut.shortcut."+i, null);
+		}
+		// Shortcuts at their default values
+		i = 0;
+		p = Main.pref.get("shortcut.shortcut."+i, null);
+		while (p != null) {
+			ShortCut sc = new ShortCut(p);
+			if (!sc.getAssignedUser() && sc.getAssignedDefault()) registerShortCut(sc);
+			i++;
+			p = Main.pref.get("shortcut.shortcut."+i, null);
+		}
+		// Shortcuts that were automatically moved
+		i = 0;
+		p = Main.pref.get("shortcut.shortcut."+i, null);
+		while (p != null) {
+			ShortCut sc = new ShortCut(p);
+			if (!sc.getAssignedUser() && !sc.getAssignedDefault()) registerShortCut(sc);
+			i++;
+			p = Main.pref.get("shortcut.shortcut."+i, null);
+		}
+	}
+
+	// shutdown handling
+	public static void savePrefs() {
+// we save this directly from the preferences pane, so don't overwrite these values here
+//		for (int i = GROUP_NONE; i < GROUP__MAX+GROUPS_ALT2; i++) {
+//			Main.pref.put("shortcut.groups."+i, Groups.get(i).toString());
+//		}
+		int i = 0;
+		for (ShortCut sc : ShortCuts.values()) {
+			if (!sc.getAutomatic() && !sc.getReset()) {
+				Main.pref.put("shortcut.shortcut."+i, sc.asPrefString());
+				i++;
+			}
+		}
+		Main.pref.put("shortcut.shortcut."+i, "");
+	}
+
+	// this is used to register a shortcut that was read from the preferences
+	private static void registerShortCut(ShortCut sc) {
+		if (sc.getAssignedDefault()) { // a 100% default shortcut will go though unchanged -- unless the groups have been reconfigured
+			registerShortCut(sc.getShortText(), sc.getLongText(), sc.getRequestedKey(), sc.getRequestedGroup(), sc);
+		} else if (sc.getAssignedUser()) { // put a user configured shortcut in as-is -- unless there's a conflict
+			ShortCut potentialShortCut = findShortcut(sc.getAssignedKey(), sc.getAssignedModifier());
+			if (potentialShortCut == null) {
+				ShortCuts.put(sc.getShortText(), sc);
+			} else {
+				registerShortCut(sc.getShortText(), sc.getLongText(), sc.getRequestedKey(), sc.getRequestedGroup(), sc);
+			}
+		} else { // this shortcut was auto-moved before, re-register and warn if it changes
+			registerShortCut(sc.getShortText(), sc.getLongText(), sc.getRequestedKey(), sc.getRequestedGroup(), sc);
+		}
+	}
+
+	/**
+	 * FOR PLATFORMHOOK USE ONLY
+	 *
+	 * This registers a system shortcut. See PlatformHook for details.
+	 */
+	public static ShortCut registerSystemCut(String shortText, String longText, int key, int modifier) {
+		if (ShortCuts.containsKey(shortText)) {
+			return ShortCuts.get(shortText);
+		}
+		ShortCut potentialShortCut = findShortcut(key, modifier);
+		if (potentialShortCut != null) {
+			// this always is a logic error in the hook
+			System.err.println("CONFLICT WITH SYSTEM KEY "+shortText);
+			return null;
+		} else {
+			potentialShortCut = new ShortCut(shortText, longText, key, GROUP_RESERVED, key, modifier, true, false);
+			ShortCuts.put(shortText, potentialShortCut);
+			return potentialShortCut;
+		}
+	}
+
+	/**
+	 * Register a shortcut.
+	 *
+	 * Here you get your shortcuts from. The parameters are:
+	 *
+	 * shortText - an ID. re-use a "system:*" ID if possible, else use something unique.
+	 *             "menu:*" is reserved for menu mnemonics, "core:*" is reserved for
+	 *             actions that are part of JOSM's core. Use something like
+	 *             <pluginname>+":"+<actionname>
+	 * longText - this will be displayed in the shortcut preferences dialog. Better
+	 *            use soomething the user will recognize...
+	 * requestedKey - the key you'd prefer. Use a KeyEvent.VK_* constant here.
+	 * requestedGroup - the group this shortcut fits best. This will determine the
+	 *                  modifiers your shortcut will get assigned. Use the GROUP_*
+	 *                  constants defined above.
+	 */
+	public static ShortCut registerShortCut(String shortText, String longText, int requestedKey, int requestedGroup) {
+		return registerShortCut(shortText, longText, requestedKey, requestedGroup, null);
+	}
+
+	// and now the workhorse. same parameters as above, just one more: if originalShortCut is not null and
+	// is different from the shortcut that will be assigned, a popup warning will be displayed to the user.
+	// This is used when registering shortcuts that have been visible to the user before (read: have been
+	// read from the preferences file). New shortcuts will never warn, even when they land on some funny
+	// random fallback key like Ctrl+Alt+Shift+Z for "File Open..." <g>
+	private static ShortCut registerShortCut(String shortText, String longText, int requestedKey, int requestedGroup, ShortCut originalShortCut) {
+		doInit();
+		if (ShortCuts.containsKey(shortText)) { // a re-register? maybe a sc already read from the preferences?
+			ShortCut sc = ShortCuts.get(shortText);
+			sc.setLongText(longText); // or set by the platformHook, in this case the original longText doesn't match the real action
+			return sc;
+		}
+		Integer defaultModifier = Groups.get(requestedGroup + GROUPS_DEFAULT);
+		if (defaultModifier == null) { // garbage in, no shortcurt out
+			defaultModifier = Groups.get(GROUP_NONE + GROUPS_DEFAULT);
+		}
+		ShortCut conflictsWith = null;
+		ShortCut potentialShortCut = findShortcut(requestedKey, defaultModifier);
+		if (potentialShortCut != null) { // 3 stage conflict handling
+			conflictsWith = potentialShortCut;
+			defaultModifier = Groups.get(requestedGroup + GROUPS_ALT1);
+			if (defaultModifier == null) { // garbage in, no shortcurt out
+				defaultModifier = Groups.get(GROUP_NONE + GROUPS_DEFAULT);
+			}
+			potentialShortCut = findShortcut(requestedKey, defaultModifier);
+			if (potentialShortCut != null) {
+				defaultModifier = Groups.get(requestedGroup + GROUPS_ALT2);
+				if (defaultModifier == null) { // garbage in, no shortcurt out
+					defaultModifier = Groups.get(GROUP_NONE + GROUPS_DEFAULT);
+				}
+				potentialShortCut = findShortcut(requestedKey, defaultModifier);
+				if (potentialShortCut != null) { // if all 3 modifiers for a group are used, we give up
+					potentialShortCut = findRandomShortcut(shortText, longText, requestedKey, requestedGroup);
+				} else {
+					potentialShortCut = new ShortCut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, false, false);
+				}
+			} else {
+				potentialShortCut = new ShortCut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, false, false);
+			}
+			if (originalShortCut != null && !originalShortCut.isSame(potentialShortCut)) {
+				displayWarning(conflictsWith, potentialShortCut, shortText, longText);
+			} else if (originalShortCut == null) {
+				System.out.println("Silent shortcut conflict: '"+shortText+"' moved by '"+conflictsWith.getShortText()+"' to '"+potentialShortCut.getKeyText()+"'.");
+			}
+		} else {
+			potentialShortCut = new ShortCut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, true, false);
+		}
+		ShortCuts.put(shortText, potentialShortCut);
+		return potentialShortCut;
+	}
+
+	// a lengthy warning message
+	private static void displayWarning(ShortCut conflictsWith, ShortCut potentialShortCut, String shortText, String longText) {
+		JOptionPane.showMessageDialog(Main.parent, tr("Setting the keyboard shortcut ''{0}'' for the action ''{1}'' ({2}) failed\n"+
+		                                              "because the shortcut is already taken by the action ''{3}'' ({4}).\n\n",
+		                                              conflictsWith.getKeyText(), longText, shortText,
+		                                              conflictsWith.getLongText(), conflictsWith.getShortText())+
+		                                              (potentialShortCut.getKeyText().equals("") ?
+		                                              	tr("This action will have no shortcut.\n\n")
+		                                              :
+		                                              	tr("Using the shortcut ''{0}'' instead.\n\n", potentialShortCut.getKeyText())
+		                                              )+
+		                                              tr("(Hint: You can edit the shortcuts in the preferences.)")
+		                             );
+	}
+}
Index: /trunk/src/org/openstreetmap/josm/tools/ShortCutLabel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/ShortCutLabel.java	(revision 1022)
+++ /trunk/src/org/openstreetmap/josm/tools/ShortCutLabel.java	(revision 1023)
@@ -6,6 +6,7 @@
 import java.awt.event.KeyEvent;
 
-
+@Deprecated
 public class ShortCutLabel {
+	@Deprecated
 	public static String name(int shortCut, int modifiers) {
 		if (shortCut == 0 && modifiers == 0)
