Index: src/org/openstreetmap/josm/actions/PreferencesAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/PreferencesAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/PreferencesAction.java	(working copy)
@@ -14,6 +14,7 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Open the Preferences dialog.
@@ -26,7 +27,8 @@
 	 * Create the preference action with "&Preferences" as label.
 	 */
 	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: src/org/openstreetmap/josm/actions/OpenAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/OpenAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/OpenAction.java	(working copy)
@@ -24,20 +24,22 @@
 import org.openstreetmap.josm.io.NmeaReader;
 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));
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/AlignInCircleAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/AlignInCircleAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/AlignInCircleAction.java	(working copy)
@@ -19,6 +19,7 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Aligns all selected nodes within a circle. (Useful for roundabouts)
@@ -28,7 +29,8 @@
 public final class AlignInCircleAction extends JosmAction {
 
 	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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
@@ -39,7 +41,7 @@
 			if (osm instanceof Node)
 				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))
 			for (OsmPrimitive osm : sel)
@@ -66,7 +68,7 @@
 		// Node "avn" now is central to all selected nodes.
 
 		// 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()];
 		double avdist = 0, latd, lond;
Index: src/org/openstreetmap/josm/actions/MoveAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/MoveAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/MoveAction.java	(working copy)
@@ -16,39 +16,62 @@
 import org.openstreetmap.josm.data.osm.Node;
 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
  */
 public class MoveAction extends JosmAction {
 
 	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;
 			break;
@@ -61,10 +84,10 @@
 		default:
 			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: src/org/openstreetmap/josm/actions/JosmAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/JosmAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/JosmAction.java	(working copy)
@@ -1,6 +1,7 @@
 // License: GPL. Copyright 2007 by Immanuel Scholz and others
 package org.openstreetmap.josm.actions;
 
+import java.awt.event.InputEvent;
 
 import javax.swing.AbstractAction;
 import javax.swing.JComponent;
@@ -10,43 +11,91 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 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();
 	}
@@ -57,14 +106,14 @@
 	public void pasteBufferChanged(DataSet newPasteBuffer) {
 		return;
 	}
-	
+
 	/**
 	 * needs to be overridden to be useful
 	 */
 	public void addListener(JosmAction a) {
 		return;
 	}
-	
+
 	private void setHelpId() {
 		String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
 		if (helpId.endsWith("Action"))
Index: src/org/openstreetmap/josm/actions/SelectAllAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SelectAllAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/SelectAllAction.java	(working copy)
@@ -7,12 +7,14 @@
 import java.awt.event.KeyEvent;
 
 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) {
 		Main.ds.setSelected(Main.ds.allNonDeletedPhysicalPrimitives());
Index: src/org/openstreetmap/josm/actions/ExitAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/ExitAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/ExitAction.java	(working copy)
@@ -7,10 +7,11 @@
 import java.awt.event.KeyEvent;
 
 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
  */
 public class ExitAction extends JosmAction {
@@ -18,7 +19,8 @@
 	 * Construct the action with "Exit" as label
 	 */
 	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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/CopyAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/CopyAction.java	(working copy)
@@ -23,15 +23,16 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 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);
 		listeners = new LinkedList<JosmAction>();
@@ -40,12 +41,12 @@
 	@Override public void addListener(JosmAction a) {
 		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;
 		}
 
@@ -54,12 +55,12 @@
 		final HashMap<OsmPrimitive,OsmPrimitive> map = new HashMap<OsmPrimitive,OsmPrimitive>();
 		/* temporarily maps old nodes to new so we can do a true deep copy */
 
-		/* scan the selected objects, mapping them to copies; when copying a way or relation, 
+		/* 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; }
 				Node nnew = new Node(n);
@@ -106,7 +107,7 @@
 
 		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: src/org/openstreetmap/josm/actions/UnselectAllAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UnselectAllAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/UnselectAllAction.java	(working copy)
@@ -9,17 +9,20 @@
 import javax.swing.JComponent;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class UnselectAllAction extends JosmAction {
 
 	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
 		/*
@@ -28,8 +31,8 @@
 		 * for now this is a reasonable approximation.
 		 */
 		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"));
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/ZoomInAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/ZoomInAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/ZoomInAction.java	(working copy)
@@ -7,12 +7,13 @@
 import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ZoomInAction extends JosmAction {
 
 	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: src/org/openstreetmap/josm/actions/SplitWayAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SplitWayAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/SplitWayAction.java	(working copy)
@@ -32,10 +32,11 @@
 import org.openstreetmap.josm.data.osm.Way;
 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.
  */
@@ -49,13 +50,14 @@
 	 * Create a new SplitWayAction.
 	 */
 	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);
 	}
 
 	/**
 	 * 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.
 	 */
@@ -84,7 +86,7 @@
 				// enties are not considered
 			}
 		};
-		
+
 		for (OsmPrimitive p : selection)
 			p.visit(splitVisitor);
 
@@ -110,7 +112,7 @@
 				}
 			}
 			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()));
 				return;
@@ -139,7 +141,7 @@
 				nds.remove(n);
 			}
 			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()));
 				return;
@@ -150,13 +152,13 @@
 		splitWay();
 	}
 
-	/** 
+	/**
 	 * 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;
 		boolean node = false;
@@ -229,9 +231,9 @@
 		// build a list of commands, and also a new selection list
 		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);
 		changedWay.nodes.clear();
Index: src/org/openstreetmap/josm/actions/ZoomOutAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/ZoomOutAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/ZoomOutAction.java	(working copy)
@@ -7,12 +7,13 @@
 import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ZoomOutAction extends JosmAction {
 
 	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: src/org/openstreetmap/josm/actions/SaveAsAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SaveAsAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/SaveAsAction.java	(working copy)
@@ -8,22 +8,24 @@
 import java.io.File;
 
 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.
 	 * @param layer Save this layer.
 	 */
 	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: src/org/openstreetmap/josm/actions/audio/AudioBackAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(working copy)
@@ -10,13 +10,15 @@
 import org.openstreetmap.josm.actions.JosmAction;
 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"));
 		} catch (NumberFormatException e) {
Index: src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java	(working copy)
@@ -10,13 +10,15 @@
 import org.openstreetmap.josm.actions.JosmAction;
 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"));
 		} catch (NumberFormatException e) {
Index: src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java	(working copy)
@@ -6,11 +6,24 @@
 import org.openstreetmap.josm.Main;
 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);
 		try {
@@ -18,13 +31,13 @@
 		} catch (NumberFormatException e) {
 			multiplier = 1.3;
 		}
-		if (! fast) 
+		if (! fast)
 			multiplier = 1.0 / multiplier;
 	}
 
 	public void actionPerformed(ActionEvent e) {
 		double speed = AudioPlayer.speed();
-		if (speed * multiplier <= 0.1) 
+		if (speed * multiplier <= 0.1)
 			return;
 		try {
 			if (AudioPlayer.playing() || AudioPlayer.paused())
Index: src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java	(working copy)
@@ -10,11 +10,13 @@
 import org.openstreetmap.josm.actions.JosmAction;
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
@@ -34,5 +36,5 @@
 		} catch (Exception ex) {
 			AudioPlayer.audioMalfunction(ex);
 		}
-	}	
+	}
 }
Index: src/org/openstreetmap/josm/actions/audio/AudioFasterAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioFasterAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioFasterAction.java	(working copy)
@@ -2,12 +2,14 @@
 package org.openstreetmap.josm.actions.audio;
 
 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: src/org/openstreetmap/josm/actions/audio/AudioSlowerAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioSlowerAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioSlowerAction.java	(working copy)
@@ -4,10 +4,12 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 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: src/org/openstreetmap/josm/actions/audio/AudioPrevAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioPrevAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioPrevAction.java	(working copy)
@@ -8,11 +8,13 @@
 
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/audio/AudioNextAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/audio/AudioNextAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/audio/AudioNextAction.java	(working copy)
@@ -8,11 +8,13 @@
 
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/JoinNodeWayAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/JoinNodeWayAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/JoinNodeWayAction.java	(working copy)
@@ -22,11 +22,12 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/search/SearchAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/search/SearchAction.java	(working copy)
@@ -21,6 +21,7 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class SearchAction extends JosmAction {
 
@@ -35,8 +36,8 @@
     private static SearchSetting lastSearch = null;
 
     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);
     }
 
     public void actionPerformed(ActionEvent e) {
@@ -104,9 +105,9 @@
     }
 
     /**
-     * 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
      */
     public static void searchWithHistory(SearchSetting s) {
Index: src/org/openstreetmap/josm/actions/AlignInLineAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/AlignInLineAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/AlignInLineAction.java	(working copy)
@@ -17,18 +17,20 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Aligns all selected nodes into a straight line (useful for
  * roads that should be straight, but have side roads and
  * therefore need multiple nodes)
- * 
+ *
  * @author Matthew Newton
  */
 public final class AlignInLineAction extends JosmAction {
 
 	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);
 	}
 
 	/**
@@ -45,7 +47,7 @@
 				nodes.add((Node)osm);
 				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))
 			for (OsmPrimitive osm : sel)
Index: src/org/openstreetmap/josm/actions/ReverseWayAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/ReverseWayAction.java	(working copy)
@@ -24,13 +24,13 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/UnGlueAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/UnGlueAction.java	(working copy)
@@ -25,6 +25,7 @@
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Dupe a node that is used my multiple ways, so each way has its own node.
@@ -43,7 +44,8 @@
 	 * Create a new SplitWayAction.
 	 */
 	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);
 	}
 
@@ -84,14 +86,14 @@
 	 * out from the selectionChanged listener).
 	 */
 	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) {
 				selectedNode = (Node) p;
@@ -99,11 +101,11 @@
 					return size == 1 || selectedWay.nodes.contains(selectedNode);
 			} 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;
 	}
 
@@ -132,7 +134,7 @@
 
 		return firstway;
 	}
-	
+
 	/**
 	 * see above
 	 */
@@ -142,13 +144,13 @@
 		List<Node> newNodes = new LinkedList<Node>();
 
 		if (selectedWay == null) {
-			
+
 			boolean firstway = true;
 			// modify all ways containing the nodes
 			for (Way w : Main.ds.ways) {
 				if (w.deleted || w.incomplete || w.nodes.size() < 1) continue;
 				if (!w.nodes.contains(selectedNode)) continue;
-	
+
 				firstway = modifyWay(firstway, w, cmds, newNodes);
 			}
 		} else {
Index: src/org/openstreetmap/josm/actions/GpxExportAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/GpxExportAction.java	(working copy)
@@ -33,6 +33,7 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.io.GpxWriter;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Exports data to gpx.
@@ -44,7 +45,8 @@
 	private final Layer layer;
 
 	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;
 	}
 
@@ -70,7 +72,7 @@
 			fn += ".gpx";
 			file = new File(fn);
 		}
-		
+
 		// open the dialog asking for options
 		JPanel p = new JPanel(new GridBagLayout());
 
@@ -79,7 +81,7 @@
 		desc.setWrapStyleWord(true);
 		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);
 		p.add(author, GBC.eol());
@@ -104,7 +106,7 @@
 		JLabel warning = new JLabel("<html><font size='-2'>&nbsp;</html");
 		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();
 		p.add(keywords, GBC.eop().fill(GBC.HORIZONTAL));
@@ -112,7 +114,7 @@
 		int answer = JOptionPane.showConfirmDialog(Main.parent, p, tr("Export options"), JOptionPane.OK_CANCEL_OPTION);
 		if (answer != JOptionPane.OK_OPTION)
 			return;
-		
+
 		Main.pref.put("lastAddAuthor", author.isSelected());
 		if (authorName.getText().length() != 0)
 			Main.pref.put("lastAuthorName", authorName.getText());
@@ -135,19 +137,19 @@
 		} catch (IOException x) {
 			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,
 			final JTextField copyright,
@@ -158,7 +160,7 @@
 			final JLabel copyrightLabel,
 			final JLabel copyrightYearLabel,
 			final JLabel warning) {
-		
+
 		ActionListener authorActionListener = new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
 				boolean b = author.isSelected();
@@ -182,7 +184,7 @@
 					}
 				};
 		authorName.addKeyListener(authorNameListener);
-		
+
 		predefined.addActionListener(new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
 				JList l = new JList(new String[]{"Creative Commons By-SA", "public domain", "GNU Lesser Public License (LGPL)", "BSD License (MIT/X11)"});
Index: src/org/openstreetmap/josm/actions/DeleteAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/DeleteAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/DeleteAction.java	(working copy)
@@ -7,12 +7,13 @@
 import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class DeleteAction extends JosmAction {
 
 	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: src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(working copy)
@@ -15,12 +15,13 @@
 import org.openstreetmap.josm.data.osm.WaySegment;
 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}.
  *
@@ -29,7 +30,7 @@
  *
  * If the user enters the mapmode and any object is selected, all selected
  * objects that can be deleted will.
- * 
+ *
  * @author imi
  */
 public class DeleteAction extends MapMode {
@@ -40,10 +41,10 @@
 	 */
 	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"));
 	}
 
@@ -57,7 +58,7 @@
 		Main.map.mapView.removeMouseListener(this);
 	}
 
-	
+
 	@Override public void actionPerformed(ActionEvent e) {
 		super.actionPerformed(e);
 		if(!Main.map.mapView.isDrawableLayer())
@@ -96,14 +97,14 @@
 		boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
 		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;
 		if (sel == null) {
 			WaySegment ws = Main.map.mapView.getNearestWaySegment(e.getPoint());
 			if (ws != null) {
 				if (shift) {
-					c = DeleteCommand.deleteWaySegment(ws); 
+					c = DeleteCommand.deleteWaySegment(ws);
 				} else if (ctrl) {
 					c = DeleteCommand.deleteWithReferences(Collections.singleton((OsmPrimitive)ws.way));
 				} else {
@@ -121,7 +122,7 @@
 
 		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: src/org/openstreetmap/josm/actions/mapmode/MapMode.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/MapMode.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/MapMode.java	(working copy)
@@ -11,14 +11,15 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * A class implementing MapMode is able to be selected as an mode for map editing.
  * 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 {
 	private final Cursor cursor;
@@ -27,6 +28,16 @@
 	/**
 	 * 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);
 		this.cursor = cursor;
@@ -58,7 +69,7 @@
 		Main.map.statusLine.setHelpText(getModeHelpText());
 		Main.map.statusLine.repaint();
 	}
-	
+
 	public String getModeHelpText() {
 		return "";
 	}
Index: src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java	(working copy)
@@ -12,18 +12,19 @@
 import org.openstreetmap.josm.gui.SelectionManager;
 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
  */
 public class ZoomAction extends MapMode implements SelectionEnded {
@@ -44,7 +45,9 @@
 	 * @param mapFrame The MapFrame, whose zoom mode should be enabled.
 	 */
 	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);
 	}
@@ -69,7 +72,7 @@
 		super.exitMode();
 		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: src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java	(working copy)
@@ -11,10 +11,11 @@
 import org.openstreetmap.josm.Main;
 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
  *
  */
@@ -24,12 +25,13 @@
 	private Point mousePos = null;
 	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();
 		Main.map.mapView.addMouseListener(this);
@@ -73,7 +75,7 @@
 		} else {
 			playHeadMarker.synchronize(en);
 		}
-		mousePos = null;	
+		mousePos = null;
 		dragging = false;
 
 	/*
Index: src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(working copy)
@@ -51,12 +51,13 @@
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
 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);
 
@@ -67,21 +68,22 @@
 	private boolean drawHelperLine;
 	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);
 	}
 
@@ -117,7 +119,7 @@
 		} catch (SecurityException ex) {
 		}
 	}
-	
+
 	/**
 	 * redraw to (possibly) get rid of helper line if selection changes.
 	 */
@@ -143,7 +145,7 @@
 	 * If user clicked with the left button, add a node at the current mouse
 	 * 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) {
 
@@ -159,7 +161,7 @@
 		alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
 		shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
 		mousePos = e.getPoint();
-		
+
 		Collection<OsmPrimitive> selection = Main.ds.getSelected();
 		Collection<Command> cmds = new LinkedList<Command>();
 
@@ -167,11 +169,11 @@
 			replacedWays = new ArrayList<Way>();
 		boolean newNode = false;
 		Node n = null;
-		
+
 		if (!ctrl) {
 			n = Main.map.mapView.getNearestNode(mousePos);
 		}
-		
+
 		if (n != null) {
 			// user clicked on node
 			if (shift || selection.isEmpty()) {
@@ -181,7 +183,7 @@
 				Main.ds.setSelected(n);
 				return;
 			}
-			
+
 		} else {
 			// no node found in clicked area
 			n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
@@ -231,22 +233,22 @@
 				adjustNode(segSet, n);
 			}
 		}
-		
+
 		// 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) {
 					if (selectedNode != null) return;
@@ -256,10 +258,10 @@
 					selectedWay = (Way) p;
 				}
 			}
-			
+
 			// the node from which we make a connection
 			Node n0 = null;
-			
+
 			if (selectedNode == null) {
 				if (selectedWay == null) return;
 				if (lastUsedNode == selectedWay.nodes.get(0) || lastUsedNode == selectedWay.nodes.get(selectedWay.nodes.size()-1)) {
@@ -270,17 +272,17 @@
 			} else {
 				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) {
 				way = new Way();
@@ -327,13 +329,13 @@
 		}
 
 		Command c = new SequenceCommand(title, cmds);
-	
+
 		Main.main.undoRedo.add(c);
 		lastUsedNode = n;
 		computeHelperLine();
 		Main.map.mapView.repaint();
 	}
-	
+
 	@Override public void mouseMoved(MouseEvent e) {
 		if(!Main.map.mapView.isDrawableLayer())
 			return;
@@ -341,15 +343,15 @@
 		// we copy ctrl/alt/shift from the event just in case our global
 		// 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
 	 * the last used position to the mouse cursor. It duplicates some code from
@@ -362,7 +364,7 @@
 			currentBaseNode = null;
 			return;
 		}
-		
+
 		double distance = -1;
 		double angle = -1;
 
@@ -380,7 +382,7 @@
 		if (!ctrl && mousePos != null) {
 			currentMouseNode = Main.map.mapView.getNearestNode(mousePos);
 		}
-		
+
 		if (currentMouseNode != null) {
 			// user clicked on node
 			if (selection.isEmpty()) return;
@@ -390,7 +392,7 @@
 			// no node found in clicked area
 			currentMouseEastNorth = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
 		}
-		
+
 		for (OsmPrimitive p : selection) {
 			if (p instanceof Node) {
 				if (selectedNode != null) return;
@@ -400,11 +402,11 @@
 				selectedWay = (Way) p;
 			}
 		}
-		
+
 		// the node from which we make a connection
 		currentBaseNode = null;
 		Node previousNode = null;
-		
+
 		if (selectedNode == null) {
 			if (selectedWay == null) return;
 			if (lastUsedNode == selectedWay.nodes.get(0) || lastUsedNode == selectedWay.nodes.get(selectedWay.nodes.size()-1)) {
@@ -418,9 +420,9 @@
 		} else {
 			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.
 		}
@@ -442,7 +444,7 @@
 
 		Main.map.mapView.repaint();
 	}
-	
+
 	/**
 	 * Repaint on mouse exit so that the helper line goes away.
 	 */
@@ -452,9 +454,9 @@
 		mousePos = e.getPoint();
 		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.
 	 */
 	public static Way getWayForNode(Node n) {
@@ -490,27 +492,27 @@
 	/**
 	 * 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:
 			return;
 		case 2:
 			// 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();
 			EastNorth A = seg.a.eastNorth;
@@ -527,7 +529,7 @@
 					det(C.east(), C.north(), D.east(), D.north()), C.north() - D.north())/
 					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.
 			// (for semi-parallel lines, intersection might be miles away!)
@@ -535,7 +537,7 @@
 				n.eastNorth = intersection;
 				return;
 			}
-		
+
 		default:
 			EastNorth P = n.eastNorth;
 			seg = segs.iterator().next();
@@ -550,32 +552,32 @@
 				B.north() + q * (A.north() - B.north()));
 		}
 	}
-	
+
 	// helper for adjustNode
 	static double det(double a, double b, double c, double d)
 	{
 		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);
 		g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
@@ -584,33 +586,33 @@
 		Point p2=mv.getPoint(currentMouseEastNorth);
 
 		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) {
 			b.moveTo((int)(p1.x + 8*Math.cos(t+PHI)), (int)(p1.y + 8*Math.sin(t+PHI)));
 			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: src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(working copy)
@@ -35,6 +35,8 @@
 import org.openstreetmap.josm.gui.SelectionManager;
 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).
  *
@@ -42,7 +44,7 @@
  * If an unselected object is under the mouse when dragging, it becomes selected
  * and will be moved.
  * If no object is under the mouse, move all selected objects (if any)
- * 
+ *
  * @author imi
  */
 public class SelectAction extends MapMode implements SelectionEnded {
@@ -64,7 +66,7 @@
 	 */
 	private Point mousePos;
 	private SelectionManager selectionManager;
-	
+
 	/**
 	 * The time which needs to pass between click and release before something
 	 * counts as a move, in milliseconds
@@ -83,13 +85,14 @@
 	 */
 	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) {}
-		
+
 	}
 
 	private static Cursor getCursor(String name, String mod, int def) {
@@ -113,7 +116,7 @@
 			oldCursor = null;
 		}
 	}
-	
+
 	@Override public void enterMode() {
 		super.enterMode();
 		Main.map.mapView.addMouseListener(this);
@@ -152,7 +155,7 @@
 			mousePos = e.getPoint();
 			return;
 		}
-		
+
 		if (!initialMoveThresholdExceeded) {
 			int dxp = mousePos.x - e.getX();
 			int dyp = mousePos.y - e.getY();
@@ -160,7 +163,7 @@
 			if (dp < initialMoveThreshold) return;
 			initialMoveThresholdExceeded = true;
 		}
-		
+
 		EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());
 		EastNorth mouseStartEN = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
 		double dx = mouseEN.east() - mouseStartEN.east();
@@ -183,9 +186,9 @@
 		} else {
 			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;
 
 			Command c = !Main.main.undoRedo.commands.isEmpty()
@@ -231,7 +234,7 @@
 		OsmPrimitive osm = c.getNearestNode(p);
 		virtualWay = null;
 		virtualNode = null;
-		
+
 		if (osm == null)
 		{
 			WaySegment nearestWaySeg = c.getNearestWaySegment(p);
@@ -256,7 +259,7 @@
 				}
 			}
 		}
-		if (osm == null) 
+		if (osm == null)
 			return Collections.emptySet();
 		return Collections.singleton(osm);
 	}
@@ -264,10 +267,10 @@
 	/**
 	 * 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.
 	 */
 	@Override public void mousePressed(MouseEvent e) {
@@ -277,7 +280,7 @@
 		boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
 		// boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
 		boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
-		
+
 		mouseDownTime = System.currentTimeMillis();
 		didMove = false;
 		initialMoveThresholdExceeded = false;
@@ -376,7 +379,7 @@
 		Main.ds.setSelected(curSel);
 		Main.map.mapView.repaint();
 	}
-	
+
 	@Override public String getModeHelpText() {
 		if (mode == Mode.select) {
 			return tr("Release the mouse button to select the objects in the rectangle.");
Index: src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(working copy)
@@ -29,9 +29,11 @@
 import org.openstreetmap.josm.gui.MapView;
 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.
  */
@@ -46,16 +48,16 @@
 	double xoff;
 	double yoff;
 	double distance;
-	
+
 	/**
 	 * The old cursor before the user pressed the mouse button.
 	 */
 	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.
 	 */
 	private Point initialMousePos;
@@ -71,7 +73,8 @@
 	 */
 	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");
 		initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
@@ -99,7 +102,7 @@
 			oldCursor = null;
 		}
 	}
-	
+
 	@Override public void enterMode() {
 		super.enterMode();
 		Main.map.mapView.addMouseListener(this);
@@ -121,7 +124,7 @@
 	 */
 	@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;
 
@@ -136,7 +139,7 @@
 			mousePos = e.getPoint();
 			return;
 		}
-		
+
 		Main.map.mapView.repaint();
 		mousePos = e.getPoint();
 
@@ -146,34 +149,34 @@
 		if (selectedSegment != null) {
 			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);
 			g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
@@ -182,15 +185,15 @@
 			Point p2=mv.getPoint(en2);
 			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));
 		}
 	}
-	
+
 	/**
 	 */
 	@Override public void mousePressed(MouseEvent e) {
@@ -200,9 +203,9 @@
 		// boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
 		// 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());
 
@@ -241,11 +244,11 @@
 			Command c = new SequenceCommand(tr("Extrude Way"), cmds);
 			Main.main.undoRedo.add(c);
 		}
-		
+
 		Main.map.mapView.removeTemporaryLayer(this);
 		mode = null;
 		updateStatusLine();
-		Main.map.mapView.repaint();	
+		Main.map.mapView.repaint();
 	}
 
 	@Override public String getModeHelpText() {
Index: src/org/openstreetmap/josm/actions/UndoAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UndoAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/UndoAction.java	(working copy)
@@ -8,11 +8,11 @@
 import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
-
 /**
  * Undoes the last command.
- * 
+ *
  * @author imi
  */
 public class UndoAction extends JosmAction {
@@ -21,7 +21,8 @@
 	 * Construct the action with "Undo" as label.
 	 */
 	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: src/org/openstreetmap/josm/actions/AlignInRectangleAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/AlignInRectangleAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/AlignInRectangleAction.java	(working copy)
@@ -19,18 +19,19 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.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
  * 2. select the edge that is oriented closest to the average of all headings
@@ -39,17 +40,18 @@
 public final class AlignInRectangleAction extends JosmAction {
 
 	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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
 		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."));
 			return;
@@ -66,9 +68,9 @@
 			avg_angle += angle[i];
 		}
 		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++)
 		{
@@ -85,7 +87,7 @@
 		EastNorth end = en[(base+1)%4]; // second node of base segment
 		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());
 		// base intercept of parallels that go through "next" and "prev" points
@@ -101,24 +103,24 @@
 		// same for "prev"
 		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++) {
 			Node n = myWay.nodes.get(i);
Index: src/org/openstreetmap/josm/actions/DownloadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/DownloadAction.java	(working copy)
@@ -16,6 +16,7 @@
 import org.openstreetmap.josm.gui.download.DownloadDialog;
 import org.openstreetmap.josm.gui.download.DownloadDialog.DownloadTask;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Action that opens a connection to the osm server and downloads map data.
@@ -26,16 +27,17 @@
  * @author imi
  */
 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: src/org/openstreetmap/josm/actions/DuplicateAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/DuplicateAction.java	(working copy)
@@ -12,22 +12,23 @@
 import org.openstreetmap.josm.data.SelectionChangedListener;
 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 {
 
     public DuplicateAction() {
     	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);
     }
 
 	public void actionPerformed(ActionEvent e) {
 		Main.main.menu.copy.actionPerformed(e);
 		Main.main.menu.paste.actionPerformed(e);
     }
-	
+
 	public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
 		setEnabled(! newSelection.isEmpty());
 	}
Index: src/org/openstreetmap/josm/actions/AboutAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/AboutAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/AboutAction.java	(working copy)
@@ -33,13 +33,14 @@
 import org.openstreetmap.josm.tools.GBC;
 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
  */
 public class AboutAction extends JosmAction {
@@ -64,9 +65,9 @@
 	static public String getVersion() {
 		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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/PasteAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/PasteAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/PasteAction.java	(working copy)
@@ -23,28 +23,28 @@
 import org.openstreetmap.josm.data.osm.RelationMember;
 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);
     }
 
 	public void actionPerformed(ActionEvent e) {
 		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; }
 		}
 
 		EastNorth mPosition;
@@ -56,10 +56,10 @@
 
 		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) {
 			Node nnew = new Node(n);
@@ -95,7 +95,7 @@
 			rnew.members.addAll(members);
 			map.put(r, rnew);
 		}
-		
+
 		/* Now execute the commands to add the dupicated contents of the paste buffer to the map */
 		Collection<OsmPrimitive> osms = map.values();
 		Collection<Command> clist = new LinkedList<Command>();
Index: src/org/openstreetmap/josm/actions/DiskAccessAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/DiskAccessAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/DiskAccessAction.java	(working copy)
@@ -9,16 +9,22 @@
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Helper class for all actions that access the disk
  */
 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");
 		if (curDir.equals(""))
@@ -31,21 +37,21 @@
 		for (int i = 0; i < ExtensionFileFilter.filters.length; ++i)
 			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());
 
 		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: src/org/openstreetmap/josm/actions/HistoryInfoAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(working copy)
@@ -13,11 +13,13 @@
 import org.openstreetmap.josm.data.osm.Way;
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/AutoScaleAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/AutoScaleAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/AutoScaleAction.java	(working copy)
@@ -14,6 +14,7 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Toggles the autoScale feature of the mapView
@@ -45,7 +46,7 @@
 
     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);
         this.mode = mode;
@@ -78,8 +79,8 @@
             }
             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: src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/UploadAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/UploadAction.java	(working copy)
@@ -23,6 +23,7 @@
 import org.openstreetmap.josm.io.OsmServerWriter;
 import org.openstreetmap.josm.tools.GBC;
 import org.xml.sax.SAXException;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * Action that opens a connection to the osm server and uploads all changes.
@@ -33,7 +34,7 @@
  * @author imi
  */
 public class UploadAction extends JosmAction {
-	
+
 	/** Upload Hook */
 	public interface UploadHook {
 		/**
@@ -45,12 +46,12 @@
 		 */
 		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
 	 * dialog is the last thing shown before upload really starts; on occasion
@@ -59,7 +60,8 @@
 	public final LinkedList<UploadHook> uploadHooks = new LinkedList<UploadHook>();
 
 	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);
 
 		/**
 		 * Displays a screen where the actions that would be taken are displayed and
@@ -128,7 +130,7 @@
 			else if (osm.deleted && osm.id != 0)
 				delete.addFirst(osm);
 		}
-		
+
 		if (add.isEmpty() && update.isEmpty() && delete.isEmpty()) {
 			JOptionPane.showMessageDialog(Main.parent,tr("No changes to upload."));
 			return;
@@ -139,7 +141,7 @@
 		for(UploadHook hook : uploadHooks)
 			if(!hook.checkUpload(add, update, delete))
 				return;
-		
+
 		final OsmServerWriter server = new OsmServerWriter();
 		final Collection<OsmPrimitive> all = new LinkedList<OsmPrimitive>();
 		all.addAll(add);
Index: src/org/openstreetmap/josm/actions/CreateCircleAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/CreateCircleAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/CreateCircleAction.java	(working copy)
@@ -19,11 +19,14 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.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...
  *
  * @author Henry Loenwind, based on much copy&Paste from other Actions.
@@ -31,7 +34,8 @@
 public final class CreateCircleAction extends JosmAction {
 
 	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);
 	}
 
 	private double calcang(double xc, double yc, double x, double y) {
Index: src/org/openstreetmap/josm/actions/MergeNodesAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/MergeNodesAction.java	(working copy)
@@ -39,19 +39,21 @@
 import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.ShortCut;
 
 
 /**
  * Merge two or more nodes into one node.
  * (based on Combine ways)
- * 
+ *
  * @author Matthew Newton
  *
  */
 public class MergeNodesAction extends JosmAction implements SelectionChangedListener {
 
 	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: src/org/openstreetmap/josm/actions/SaveActionBase.java
===================================================================
--- src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/SaveActionBase.java	(working copy)
@@ -21,11 +21,18 @@
 import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.io.OsmWriter;
 import org.openstreetmap.josm.io.GpxWriter;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public abstract class SaveActionBase extends DiskAccessAction {
 
 	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);
 		this.layer = layer;
@@ -78,7 +85,7 @@
 			return false;
 		}
 		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)
 				return false;
@@ -215,9 +222,9 @@
 
 	/**
 	 * 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.
 	 */
 	private boolean isDataSetEmpty(OsmDataLayer layer) {
Index: src/org/openstreetmap/josm/actions/RedoAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/RedoAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/RedoAction.java	(working copy)
@@ -8,11 +8,11 @@
 import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ShortCut;
 
-
 /**
  * Redoes the last command.
- * 
+ *
  * @author imi
  */
 public class RedoAction extends JosmAction {
@@ -21,7 +21,8 @@
 	 * Construct the action with "Redo" as label.
 	 */
 	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: src/org/openstreetmap/josm/actions/NewAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/NewAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/NewAction.java	(working copy)
@@ -10,11 +10,13 @@
 import org.openstreetmap.josm.Main;
 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);
 	}
 
 	public void actionPerformed(ActionEvent e) {
Index: src/org/openstreetmap/josm/actions/PasteTagsAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/PasteTagsAction.java	(working copy)
@@ -19,13 +19,14 @@
 import org.openstreetmap.josm.data.SelectionChangedListener;
 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 {
 
 	public PasteTagsAction(JosmAction copyAction) {
 		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);
 		setEnabled(false);
Index: src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/CombineWayAction.java	(working copy)
@@ -39,16 +39,18 @@
 import org.openstreetmap.josm.data.osm.Way;
 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
  */
 public class CombineWayAction extends JosmAction implements SelectionChangedListener {
 
 	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: src/org/openstreetmap/josm/actions/SaveAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SaveAction.java	(revision 1010)
+++ src/org/openstreetmap/josm/actions/SaveAction.java	(working copy)
@@ -10,22 +10,24 @@
 import org.openstreetmap.josm.gui.layer.Layer;
 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.
 	 * @param layer Save this layer.
 	 */
 	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) {
 			File f = ((OsmDataLayer)layer).associatedFile;
Index: src/org/openstreetmap/josm/tools/OpenBrowser.java
===================================================================
--- src/org/openstreetmap/josm/tools/OpenBrowser.java	(revision 1010)
+++ src/org/openstreetmap/josm/tools/OpenBrowser.java	(working copy)
@@ -11,6 +11,9 @@
 
 /**
  * Helper to open platform web browser on different platforms
+ *
+ * This now delegates the real work to a platform specific class.
+ *
  * @author Imi
  */
 public class OpenBrowser {
@@ -29,40 +32,12 @@
 			}
 		}
 
-		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();
 		}
 		return null;
 	}
 
-	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: src/org/openstreetmap/josm/tools/ShortCutLabel.java
===================================================================
--- src/org/openstreetmap/josm/tools/ShortCutLabel.java	(revision 1010)
+++ src/org/openstreetmap/josm/tools/ShortCutLabel.java	(working copy)
@@ -5,8 +5,9 @@
 
 import java.awt.event.KeyEvent;
 
-
+@Deprecated
 public class ShortCutLabel {
+	@Deprecated
 	public static String name(int shortCut, int modifiers) {
 		if (shortCut == 0 && modifiers == 0)
 			return "";
Index: src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/ConflictDialog.java	(working copy)
@@ -42,6 +42,7 @@
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public final class ConflictDialog extends ToggleDialog {
 
@@ -50,7 +51,8 @@
 	private final JList displaylist = new JList(model);
 
 	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);
 		displaylist.addMouseListener(new MouseAdapter(){
Index: src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(working copy)
@@ -37,6 +37,7 @@
 import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * History dialog works like follows:
@@ -86,7 +87,8 @@
 	private JLabel notLoaded = new JLabel("<html><i>"+tr("Click Reload to refresh list")+"</i></html>");
 
 	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);
 		notLoaded.setHorizontalAlignment(JLabel.CENTER);
Index: src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(working copy)
@@ -40,6 +40,7 @@
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * A small tool dialog for displaying the current selection. The selection manager
@@ -69,13 +70,14 @@
     private JMenuItem zoomToElement;
 
     /**
-     * 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>>();
         popupMenu = new JPopupMenu();
@@ -190,7 +192,7 @@
     }
 
     /**
-     * Zooms to the element(s) selected in {@link #displaylist} 
+     * Zooms to the element(s) selected in {@link #displaylist}
      */
     public void zoomToSelectedElement() {
         BoundingXYVisitor box = new BoundingXYVisitor();
@@ -247,7 +249,7 @@
         if (selectionHistory != null && newSelection.size() > 0 && !newSelection.equals(historyIgnoreSelection)) {
             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))
                     return;
@@ -272,7 +274,7 @@
 
     /**
      * A specialized {@link JMenuItem} for presenting one entry of the selection history
-     *   
+     *
      * @author Jan Peter Stotz
      */
     protected class SelectionMenuItem extends JMenuItem implements ActionListener {
@@ -302,7 +304,7 @@
 
     /**
      * A specialized {@link JMenuItem} for presenting one entry of the search history
-     *   
+     *
      * @author Jan Peter Stotz
      */
     protected class SearchMenuItem extends JMenuItem implements ActionListener {
Index: src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java	(working copy)
@@ -26,6 +26,7 @@
 import org.openstreetmap.josm.actions.HelpAction.Helpful;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * This class is a toggle dialog that can be turned on and off. It is attached
@@ -39,8 +40,8 @@
 		public final String prefname;
 		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;
 		}
 
@@ -61,11 +62,22 @@
 	public JPanel parent;
 	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));
 		setLayout(new BorderLayout());
Index: src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(working copy)
@@ -19,6 +19,7 @@
 import org.openstreetmap.josm.command.Command;
 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 {
 
@@ -26,9 +27,10 @@
     private JTree tree = new JTree(treeModel);
 
 	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);
 		tree.expandRow(0);
Index: src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(working copy)
@@ -39,6 +39,7 @@
 import org.openstreetmap.josm.tools.DontShowAgainInfo;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.ImageProvider.OverlayPosition;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * A component that manages the list of all layers and react to selection changes
@@ -157,7 +158,8 @@
 	 * Create an layerlist and attach it to the given mapView.
 	 */
 	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);
 		add(listScrollPane, BorderLayout.CENTER);
Index: src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(working copy)
@@ -31,11 +31,12 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 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.
  *
@@ -54,7 +55,8 @@
 	private JList displaylist = new JList(list);
 
 	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);
 		displaylist.addMouseListener(new MouseAdapter(){
@@ -70,29 +72,29 @@
 		add(new JScrollPane(displaylist), BorderLayout.CENTER);
 
 		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) {
 				// call relation editor with null argument to create new relation
 				new RelationEditor(null).setVisible(true);
 			}
 		}), GBC.std());
-		
+
 		buttonPanel.add(new SideButton(marktr("Select"), "select", "Selection", tr("Select this relation"), new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
 				// replace selection with the relation from the list
 				Main.ds.setSelected((Relation)displaylist.getSelectedValue());
 			}
 		}), 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) {
 				Relation toDelete = (Relation) displaylist.getSelectedValue();
@@ -110,7 +112,7 @@
 		super.setVisible(b);
 		if (b) updateList();
 	}
-	
+
 	public void updateList() {
 		list.setSize(Main.ds.relations.size());
 		int i = 0;
@@ -120,7 +122,7 @@
 		}
 		list.setSize(i);
 	}
-	
+
 	public void activeLayerChange(Layer a, Layer b) {
 		if ((a == null || a instanceof OsmDataLayer) && b instanceof OsmDataLayer) {
 			if (a != null) ((OsmDataLayer)a).listenerDataChanged.remove(this);
@@ -129,7 +131,7 @@
 			repaint();
 		}
 	}
-	
+
 	public void layerRemoved(Layer a) {
 		if (a instanceof OsmDataLayer) {
 			((OsmDataLayer)a).listenerDataChanged.remove(this);
@@ -139,15 +141,15 @@
 		if (a instanceof OsmDataLayer) {
 			((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
 	 */
 	public Relation getCurrentRelation() {
@@ -156,7 +158,7 @@
 
 	/**
 	 * Adds a selection listener to the relation list.
-	 * 
+	 *
 	 * @param listener the listener to add
 	 */
 	public void addListSelectionListener(ListSelectionListener listener) {
@@ -165,7 +167,7 @@
 
 	/**
 	 * Removes a selection listener from the relation list.
-	 * 
+	 *
 	 * @param listener the listener to remove
 	 */
 	public void removeListSelectionListener(ListSelectionListener listener) {
Index: src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(working copy)
@@ -20,11 +20,12 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 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>
  */
 public class UserListDialog extends ToggleDialog implements SelectionChangedListener {
@@ -44,15 +45,16 @@
 	private JTable userTable = new JTable(data);
 
     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);
 	}
 
@@ -69,25 +71,25 @@
 	public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
 		if (!isVisible())
 			return;
-		
+
 		class UserCount {
 			User user;
 			int count;
 			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;
 		for (OsmPrimitive p : newSelection) {
             User u = p.user;
             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++;
             all++;
@@ -95,11 +97,11 @@
 		UserCount[] ucArr = new UserCount[counters.size()];
 		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: src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java	(working copy)
@@ -63,6 +63,7 @@
 import org.openstreetmap.josm.gui.tagging.TaggingPreset;
 import org.openstreetmap.josm.tools.AutoCompleteComboBox;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * This dialog displays the properties of the current selected primitives.
@@ -87,7 +88,7 @@
 	 * Used to display relation names in the membership table
 	 */
 	private NameVisitor nameVisitor = new NameVisitor();
-	
+
 	/**
 	 * Watches for double clicks and from editing or new property, depending on the
 	 * location, the click was.
@@ -133,7 +134,7 @@
 			return;
 		}
 		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);
 
@@ -158,7 +159,7 @@
 			    if (c instanceof JLabel) {
 			    	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)) {
 			    				str+="("+m.get(str)+")";
@@ -268,11 +269,11 @@
 	/**
 	 * 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);
 	}
@@ -295,7 +296,7 @@
 		final AutoCompleteComboBox keys = new AutoCompleteComboBox();
 		keys.setPossibleItems(allData.keySet());
 		keys.setEditable(true);
-		
+
 		p.add(keys, BorderLayout.CENTER);
 
 		JPanel p2 = new JPanel(new BorderLayout());
@@ -402,7 +403,7 @@
 			return String.class;
 		}
 	};
-	
+
 	/**
 	 * The properties list.
 	 */
@@ -416,13 +417,14 @@
 	 * Create a new PropertiesDialog
 	 */
 	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>();
 			for (final TaggingPreset p : TaggingPresetPreference.taggingPresets)
@@ -447,10 +449,10 @@
 		taggingPresets.setRenderer(new TaggingCellRenderer());
 
 		// 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) {
 				Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
@@ -475,12 +477,12 @@
 				return c;
 			}
 		});
-		
+
 		// 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) {
 				Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
@@ -491,13 +493,13 @@
 				return c;
 			}
 		});
-		
+
 		membershipTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
 			@Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
 				Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
 				if (c instanceof JLabel) {
 					Collection<RelationMember> col = (Collection<RelationMember>) value;
-					
+
 					String text = null;
 					for (RelationMember r : col) {
 						if (text == null) {
@@ -508,13 +510,13 @@
 							break;
 						}
 					}
-					
+
 					((JLabel)c).setText(text);
 				}
 				return c;
 			}
 		});
-		
+
 		// combine both tables and wrap them in a scrollPane
 		JPanel bothTables = new JPanel();
 		bothTables.setLayout(new GridBagLayout());
@@ -522,7 +524,7 @@
 		bothTables.add(propertyTable, GBC.eol().fill(GBC.BOTH));
 		bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
 		bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH));
-		
+
 		DblClickWatch dblClickWatch = new DblClickWatch();
 		propertyTable.addMouseListener(dblClickWatch);
 		membershipTable.addMouseListener(dblClickWatch);
@@ -564,7 +566,7 @@
 							Main.main.undoRedo.add(new ChangeCommand(cur, rel));
 							selectionChanged(sel); // update whole table
 						}
-						
+
 					}
 				}
 				else
@@ -586,7 +588,7 @@
 				}
 			}
 		};
-		
+
 		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));
 		buttonPanel.add(new SideButton(marktr("Delete"),"delete","Properties",tr("Delete the selected key in all objects"), KeyEvent.VK_D, buttonAction));
@@ -610,7 +612,7 @@
 			propertyTable.getCellEditor().cancelCellEditing();
 
 		// re-load property data
-		
+
 		propertyData.setRowCount(0);
 
 		Map<String, Integer> keyCount = new HashMap<String, Integer>();
@@ -638,13 +640,13 @@
 			}
 			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) {
 			if (!r.deleted && !r.incomplete) {
@@ -660,11 +662,11 @@
 				}
 			}
 		}
-		
+
 		for (Entry<Relation, Collection<RelationMember>> e : roles.entrySet()) {
 			membershipData.addRow(new Object[]{e.getKey(), e.getValue()});
 		}
-		
+
 		membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0);
 	}
 }
Index: src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- src/org/openstreetmap/josm/gui/MainApplication.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/MainApplication.java	(working copy)
@@ -30,6 +30,11 @@
  */
 public class MainApplication extends Main {
 	/**
+	 * Allow subclassing (see JOSM.java)
+	 */
+	public MainApplication() {}
+
+	/**
 	 * Construct an main frame, ready sized and operating. Does not
 	 * display the frame.
 	 */
@@ -65,6 +70,11 @@
 
 		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);
 		final Map<String, Collection<String>> args = new HashMap<String, Collection<String>>();
@@ -137,12 +147,13 @@
 			        tr("Activating the updated plugins failed. Check if JOSM has the permission to overwrite the existing ones."),
 			        tr("Plugins"), JOptionPane.ERROR_MESSAGE);
 		}
-		
+
 		// load the early plugins
 		splash.setStatus(tr("Loading early plugins"));
 		Main.loadPlugins(true, language);
 
 		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"+
 					"\tjava -jar josm.jar <option> <option> <option>...\n\n"+
@@ -191,4 +202,5 @@
 			}
 		});
 	}
+
 }
Index: src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- src/org/openstreetmap/josm/gui/MapView.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/MapView.java	(working copy)
@@ -25,6 +25,7 @@
 
 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;
 import org.openstreetmap.josm.data.SelectionChangedListener;
@@ -82,16 +83,16 @@
 	 * The layer from the layers list that is currently active.
 	 */
 	private Layer activeLayer;
-	
+
 	/**
 	 * The last event performed by mouse.
 	 */
 	public MouseEvent lastMEvent;
 
 	private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
-	
+
 	private BufferedImage offscreenBuffer;
-	
+
 	/**
 	 * The listener of the active layer changes.
 	 * @deprecated Use Layer.listener instead.
@@ -106,17 +107,28 @@
 				new AutoScaleAction("data").actionPerformed(null);
 
 				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");
+				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);
+				}
 
-				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));
-				
-
 				MapSlider zoomSlider = new MapSlider(MapView.this);
 				add(zoomSlider);
 				zoomSlider.setBounds(3, 0, 114, 30);
@@ -275,7 +287,7 @@
 		for (MapViewPaintable mvp : temporaryLayers) {
 			mvp.paint(tempG, this);
 		}
-		
+
 		// draw world borders
 		tempG.setColor(Color.WHITE);
 		Bounds b = new Bounds();
@@ -287,7 +299,7 @@
 		int y2 = Math.max(min.y, max.y);
 		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);
 
@@ -403,12 +415,12 @@
 		if (oldScale != scale)
 			firePropertyChange("scale", oldScale, scale);
 	}
-	
+
 	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: src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- src/org/openstreetmap/josm/gui/MainMenu.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/MainMenu.java	(working copy)
@@ -12,6 +12,7 @@
 import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.AboutAction;
@@ -58,6 +59,7 @@
 import org.openstreetmap.josm.actions.audio.AudioSlowerAction;
 import org.openstreetmap.josm.actions.search.SearchAction;
 import org.openstreetmap.josm.data.DataSetChecker;
+import org.openstreetmap.josm.tools.ShortCut;
 
 /**
  * This is the JOSM main menu bar. It is overwritten to initialize itself and provide
@@ -78,23 +80,23 @@
 	public final DownloadAction download = new DownloadAction();
 	public final JosmAction upload = new UploadAction();
 	public final JosmAction exit = new ExitAction();
-    
+
 	/* Edit menu */
 	public final UndoAction undo = new UndoAction();
 	public final RedoAction redo = new RedoAction();
 	public final JosmAction copy = new CopyAction();
 	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();
     /* crashes when loading data, if using JosmAction for search */
 	public final JosmAction search = new SearchAction();
 	public final JosmAction preferences = new PreferencesAction();
-        
+
 	/* View menu */
-    
+
 	/* Tools menu */
 	public final JosmAction splitWay = new SplitWayAction();
 	public final JosmAction combineWay = new CombineWayAction();
@@ -120,7 +122,7 @@
 	public final HelpAction help = new HelpAction();
 	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"));
 	public final JMenu viewMenu = new JMenu(tr("View"));
@@ -128,155 +130,129 @@
 	public final JMenu audioMenu = new JMenu(tr("Audio"));
 	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);
+		add(fileMenu, download);
+		add(fileMenu, upload);
+		add(fileMenu, exit);
+		add(fileMenu, KeyEvent.VK_F, "file");
 
-		editMenu.setMnemonic('E');
-		current = editMenu.add(undo);
-		current.setAccelerator(undo.shortCut);
-		current = editMenu.add(redo);
-		current.setAccelerator(redo.shortCut);
+		add(editMenu, undo);
+		add(editMenu, redo);
 		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);
+		add(editMenu, copy);
+		add(editMenu, delete);
+		add(editMenu, paste);
+		add(editMenu, pasteTags);
+		add(editMenu, duplicate);
 		editMenu.addSeparator();
-		current = editMenu.add(selectAll);
-		current.setAccelerator(selectAll.shortCut);
-		current = editMenu.add(unselectAll);
-		current.setAccelerator(unselectAll.shortCut);
+		add(editMenu, selectAll);
+		add(editMenu, unselectAll);
 		editMenu.addSeparator();
-		current = editMenu.add(search);
-		current.setAccelerator(search.shortCut);
+		add(editMenu, search);
 		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(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);
+			}
+		});
+		viewMenu.add(wireframe);
+		add(viewMenu, KeyEvent.VK_V, "view");
 
-		toolsMenu.setMnemonic('T');
-		current = toolsMenu.add(splitWay);
-		current.setAccelerator(splitWay.shortCut);
-		current = toolsMenu.add(combineWay);
-		current.setAccelerator(combineWay.shortCut);
+		add(toolsMenu, splitWay);
+		add(toolsMenu, combineWay);
 		toolsMenu.addSeparator();
-		current = toolsMenu.add(reverseWay);
-		current.setAccelerator(reverseWay.shortCut);
+		add(toolsMenu, reverseWay);
 		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);
+		add(toolsMenu, alignInCircle);
+		add(toolsMenu, alignInLine);
+		add(toolsMenu, alignInRect);
 		toolsMenu.addSeparator();
-		current = toolsMenu.add(createCircle);
-		current.setAccelerator(createCircle.shortCut);
+		add(toolsMenu, createCircle);
 		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);
+		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: src/org/openstreetmap/josm/gui/preferences/LafPreference.java
===================================================================
--- src/org/openstreetmap/josm/gui/preferences/LafPreference.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/preferences/LafPreference.java	(working copy)
@@ -4,6 +4,7 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.Component;
+import java.lang.reflect.*;
 
 import javax.swing.DefaultListCellRenderer;
 import javax.swing.JComboBox;
@@ -25,7 +26,20 @@
 
 	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) {
 			if (((LookAndFeelInfo)lafCombo.getItemAt(i)).getClassName().equals(laf)) {
Index: src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java	(working copy)
@@ -40,7 +40,7 @@
 	public final JPanel connection = createPreferenceTab("connection", I18n.tr("Connection Settings"), I18n.tr("Connection Settings for the OSM server."));
 	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
 	 * and a centered title label and the description are added.
@@ -110,7 +110,8 @@
 		settings.add(new PluginPreference());
 		settings.add(Main.toolbar);
 		settings.add(new AudioPreference());
-		
+		settings.add(new ShortcutPreference());
+
 		for (PluginProxy plugin : Main.plugins) {
 			PreferenceSetting p = plugin.getPreferenceSetting();
 			if (p != null)
Index: src/org/openstreetmap/josm/gui/MapMover.java
===================================================================
--- src/org/openstreetmap/josm/gui/MapMover.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/MapMover.java	(working copy)
@@ -15,6 +15,8 @@
 import javax.swing.JComponent;
 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;
 
@@ -77,15 +79,37 @@
 		nc.addMouseListener(this);
 		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("."));
 		}
 	}
 
@@ -168,7 +192,7 @@
 		double newHalfHeight = h*zoomfactor - h/2;
 		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: src/org/openstreetmap/josm/gui/MainApplet.java
===================================================================
--- src/org/openstreetmap/josm/gui/MainApplet.java	(revision 1010)
+++ src/org/openstreetmap/josm/gui/MainApplet.java	(working copy)
@@ -28,12 +28,14 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.data.ServerSidePreferences;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ShortCut;
 
 public class MainApplet extends JApplet {
 
 	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) {
 	    	((ServerSidePreferences)Main.pref).upload();
@@ -99,7 +101,7 @@
 		Main.preConstructorInit(args);
 		Main.parent = this;
 		new MainCaller().postConstructorProcessCmdLine(args);
-		
+
 		MainMenu m = Main.main.menu; // shortcut
 
 		// remove offending stuff from JOSM (that would break the SecurityManager)
Index: src/org/openstreetmap/josm/Main.java
===================================================================
--- src/org/openstreetmap/josm/Main.java	(revision 1010)
+++ src/org/openstreetmap/josm/Main.java	(working copy)
@@ -58,6 +58,11 @@
 import org.openstreetmap.josm.plugins.PluginInformation;
 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 {
 	/**
@@ -132,6 +137,14 @@
 	}
 
 	/**
+	 * 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;
+
+	/**
 	 * Set or clear (if passed <code>null</code>) the map.
 	 */
 	public final void setMapFrame(final MapFrame map) {
@@ -177,19 +190,20 @@
 			setMapFrame(null);
 	}
 
-
 	public Main() {
 		main = this;
+//		platform = determinePlatformHook();
+		platform.startupHook();
 		contentPane.add(panel, BorderLayout.CENTER);
 		panel.add(new GettingStarted(), BorderLayout.CENTER);
 		menu = new MainMenu();
 
 		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);
 
 		TaggingPresetPreference.initialize();
@@ -234,7 +248,7 @@
 			if(!lang.equals("en"))
 				plugins.add("lang-"+lang);
 		}
-		
+
 		if (plugins.isEmpty())
 			return;
 		SortedMap<Integer, Collection<PluginInformation>> p = new TreeMap<Integer, Collection<PluginInformation>>();
@@ -249,11 +263,11 @@
 			} else {
 				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>();
 		for (Collection<PluginInformation> c : p.values())
@@ -289,7 +303,7 @@
 					if (remove) {
 						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;
 						}
@@ -414,6 +428,7 @@
 	}
 
 	public static boolean breakBecauseUnsavedChanges() {
+		ShortCut.savePrefs();
 		if (map != null) {
 			boolean modified = false;
 			boolean uploadedModified = false;
@@ -470,4 +485,22 @@
 
 		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: build.xml
===================================================================
--- build.xml	(revision 1010)
+++ build.xml	(working copy)
@@ -36,7 +36,7 @@
 		<delete file="dist/josm-custom.jar"/>
 		<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>
 	</target>
