Index: src/org/openstreetmap/josm/gui/download/OsmMapControl.java
===================================================================
--- src/org/openstreetmap/josm/gui/download/OsmMapControl.java	(revision 1588)
+++ src/org/openstreetmap/josm/gui/download/OsmMapControl.java	(working copy)
@@ -1,121 +1,306 @@
-// This code has been adapted and copied from code that has been written by Immanuel Scholz and others for JOSM.
-// License: GPL. Copyright 2007 by Tim Haussmann
-package org.openstreetmap.josm.gui.download;
-
-import java.awt.Point;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-import javax.swing.KeyStroke;
-
-/**
- * This class controls the user input by listening to mouse and key events.
- * Currently implemented is: - zooming in and out with scrollwheel - zooming in
- * and centering by double clicking - selecting an area by clicking and dragging
- * the mouse
- * 
- * @author Tim Haussmann
- */
-public class OsmMapControl extends MouseAdapter implements MouseMotionListener, MouseListener {
-
-    // start and end point of selection rectangle
-    private Point iStartSelectionPoint;
-    private Point iEndSelectionPoint;
-
-    // the SlippyMapChooserComponent
-    private final SlippyMapChooser iSlippyMapChooser;
-
-    private SizeButton iSizeButton = null;
-    private SourceButton iSourceButton = null;
-
-    /**
-     * Create a new OsmMapControl
-     */
-    public OsmMapControl(SlippyMapChooser navComp, JPanel contentPane, SizeButton sizeButton, SourceButton sourceButton) {
-        this.iSlippyMapChooser = navComp;
-        iSlippyMapChooser.addMouseListener(this);
-        iSlippyMapChooser.addMouseMotionListener(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]);
-            }
-        }
-        iSizeButton = sizeButton;
-        iSourceButton = sourceButton;
-    }
-
-    /**
-     * Start drawing the selection rectangle if it was the 1st button (left
-     * button)
-     */
-    @Override
-    public void mousePressed(MouseEvent e) {
-        if (e.getButton() == MouseEvent.BUTTON1) {
-            if (!iSizeButton.hit(e.getPoint())) {
-                iStartSelectionPoint = e.getPoint();
-                iEndSelectionPoint = e.getPoint();
-            }
-        }
-        
-    }
-
-    public void mouseDragged(MouseEvent e) {        
-        if((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK){
-            if (iStartSelectionPoint != null) {             
-                iEndSelectionPoint = e.getPoint();
-                iSlippyMapChooser.setSelection(iStartSelectionPoint, iEndSelectionPoint);
-            }
-        }
-    }
-
-    /**
-     * When dragging the map change the cursor back to it's pre-move cursor. If
-     * a double-click occurs center and zoom the map on the clicked location.
-     */
-    @Override
-    public void mouseReleased(MouseEvent e) {
-        if (e.getButton() == MouseEvent.BUTTON1) {
-            
-            int sourceButton = iSourceButton.hit(e.getPoint());
-            
-            if (iSizeButton.hit(e.getPoint())) {
-                iSizeButton.toggle();
-                iSlippyMapChooser.resizeSlippyMap();
-            }
-            else if(sourceButton == SourceButton.HIDE_OR_SHOW) {
-                iSourceButton.toggle();
-                iSlippyMapChooser.repaint();
-                
-            }else if(sourceButton == SourceButton.MAPNIK || sourceButton == SourceButton.OSMARENDER || sourceButton == SourceButton.CYCLEMAP) {
-                iSlippyMapChooser.toggleMapSource(sourceButton);
-            }
-            else {
-                if (e.getClickCount() == 1) {
-                    iSlippyMapChooser.setSelection(iStartSelectionPoint, e.getPoint());
-
-                    // reset the selections start and end
-                    iEndSelectionPoint = null;
-                    iStartSelectionPoint = null;
-                }
-            }
-            
-        }
-    }
-
-    public void mouseMoved(MouseEvent e) {
-    }
-
-}
+// This code has been adapted and copied from code that has been written by Immanuel Scholz and others for JOSM.
+// License: GPL. Copyright 2007 by Tim Haussmann
+package org.openstreetmap.josm.gui.download;
+
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.KeyStroke;
+
+/**
+ * This class controls the user input by listening to mouse and key events.
+ * Currently implemented is: - zooming in and out with scrollwheel - zooming in
+ * and centering by double clicking - selecting an area by clicking and dragging
+ * the mouse
+ * 
+ * @author Tim Haussmann
+ */
+public class OsmMapControl extends MouseAdapter implements MouseMotionListener, MouseListener {
+
+    /** A Timer for smoothly moving the map area */
+    private static final Timer timer = new Timer(true);
+
+    /** Does the moving */
+    private MoveTask moveTask = new MoveTask();
+
+    /** How often to do the moving (milliseconds) */
+    private static long timerInterval = 20;
+
+    /** The maximum speed (pixels per timer interval) */
+    private static final double MAX_SPEED = 20;
+
+    /** The speed increase per timer interval when a cursor button is clicked */
+    private static final double ACCELERATION = 0.10;
+
+    // start and end point of selection rectangle
+    private Point iStartSelectionPoint;
+    private Point iEndSelectionPoint;
+
+    // the SlippyMapChooserComponent
+    private final SlippyMapChooser iSlippyMapChooser;
+
+    private SizeButton iSizeButton = null;
+    private SourceButton iSourceButton = null;
+
+    /**
+     * Create a new OsmMapControl
+     */
+    public OsmMapControl(SlippyMapChooser navComp, JPanel contentPane, SizeButton sizeButton, SourceButton sourceButton) {
+        this.iSlippyMapChooser = navComp;
+        iSlippyMapChooser.addMouseListener(this);
+        iSlippyMapChooser.addMouseMotionListener(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]);
+            }
+        }
+        iSizeButton = sizeButton;
+        iSourceButton = sourceButton;
+
+        InputMap inputMap = navComp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+        ActionMap actionMap = navComp.getActionMap();
+
+        // map moving
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "MOVE_RIGHT");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "MOVE_LEFT");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "MOVE_UP");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "MOVE_DOWN");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "STOP_MOVE_HORIZONTALLY");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "STOP_MOVE_HORIZONTALLY");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "STOP_MOVE_VERTICALLY");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "STOP_MOVE_VERTICALLY");
+
+        // zooming. To avoid confusion about which modifier key to use,
+        // we just add all keys left of the space bar
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_IN");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.META_DOWN_MASK, false), "ZOOM_IN");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK, false), "ZOOM_IN");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_OUT");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.META_DOWN_MASK, false), "ZOOM_OUT");
+        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK, false), "ZOOM_OUT");
+
+        // action mapping
+        actionMap.put("MOVE_RIGHT", new MoveXAction(1));
+        actionMap.put("MOVE_LEFT", new MoveXAction(-1));
+        actionMap.put("MOVE_UP", new MoveYAction(-1));
+        actionMap.put("MOVE_DOWN", new MoveYAction(1));
+        actionMap.put("STOP_MOVE_HORIZONTALLY", new MoveXAction(0));
+        actionMap.put("STOP_MOVE_VERTICALLY", new MoveYAction(0));
+        actionMap.put("ZOOM_IN", new ZoomInAction());
+        actionMap.put("ZOOM_OUT", new ZoomOutAction());
+    }
+
+    /**
+     * Start drawing the selection rectangle if it was the 1st button (left
+     * button)
+     */
+    @Override
+    public void mousePressed(MouseEvent e) {
+        if (e.getButton() == MouseEvent.BUTTON1) {
+            if (!iSizeButton.hit(e.getPoint())) {
+                iStartSelectionPoint = e.getPoint();
+                iEndSelectionPoint = e.getPoint();
+            }
+        }
+
+    }
+
+    public void mouseDragged(MouseEvent e) {
+        if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK) {
+            if (iStartSelectionPoint != null) {
+                iEndSelectionPoint = e.getPoint();
+                iSlippyMapChooser.setSelection(iStartSelectionPoint, iEndSelectionPoint);
+            }
+        }
+    }
+
+    /**
+     * When dragging the map change the cursor back to it's pre-move cursor. If
+     * a double-click occurs center and zoom the map on the clicked location.
+     */
+    @Override
+    public void mouseReleased(MouseEvent e) {
+        if (e.getButton() == MouseEvent.BUTTON1) {
+
+            int sourceButton = iSourceButton.hit(e.getPoint());
+
+            if (iSizeButton.hit(e.getPoint())) {
+                iSizeButton.toggle();
+                iSlippyMapChooser.resizeSlippyMap();
+            } else if (sourceButton == SourceButton.HIDE_OR_SHOW) {
+                iSourceButton.toggle();
+                iSlippyMapChooser.repaint();
+
+            } else if (sourceButton == SourceButton.MAPNIK || sourceButton == SourceButton.OSMARENDER
+                    || sourceButton == SourceButton.CYCLEMAP) {
+                iSlippyMapChooser.toggleMapSource(sourceButton);
+            } else {
+                if (e.getClickCount() == 1) {
+                    iSlippyMapChooser.setSelection(iStartSelectionPoint, e.getPoint());
+
+                    // reset the selections start and end
+                    iEndSelectionPoint = null;
+                    iStartSelectionPoint = null;
+                }
+            }
+
+        }
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+
+    private class MoveXAction extends AbstractAction {
+
+        int direction;
+
+        public MoveXAction(int direction) {
+            this.direction = direction;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            moveTask.setDirectionX(direction);
+        }
+    }
+
+    private class MoveYAction extends AbstractAction {
+
+        int direction;
+
+        public MoveYAction(int direction) {
+            this.direction = direction;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            moveTask.setDirectionY(direction);
+        }
+    }
+
+    /** Moves the map depending on which cursor keys are pressed (or not) */
+    private class MoveTask extends TimerTask {
+        /** The current x speed (pixels per timer interval) */
+        private double speedX = 1;
+
+        /** The current y speed (pixels per timer interval) */
+        private double speedY = 1;
+
+        /** The horizontal direction of movement, -1:left, 0:stop, 1:right */
+        private int directionX = 0;
+
+        /** The vertical direction of movement, -1:up, 0:stop, 1:down */
+        private int directionY = 0;
+
+        /**
+         * Indicated if <code>moveTask</code> is currently enabled (periodically
+         * executed via timer) or disabled
+         */
+        protected boolean scheduled = false;
+
+        protected void setDirectionX(int directionX) {
+            this.directionX = directionX;
+            updateScheduleStatus();
+        }
+
+        protected void setDirectionY(int directionY) {
+            this.directionY = directionY;
+            updateScheduleStatus();
+        }
+
+        private void updateScheduleStatus() {
+            boolean newMoveTaskState = !(directionX == 0 && directionY == 0);
+
+            if (newMoveTaskState != scheduled) {
+                scheduled = newMoveTaskState;
+                if (newMoveTaskState)
+                    timer.schedule(this, 0, timerInterval);
+                else {
+                    // We have to create a new instance because rescheduling a
+                    // once canceled TimerTask is not possible
+                    moveTask = new MoveTask();
+                    cancel(); // Stop this TimerTask
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            // update the x speed
+            switch (directionX) {
+            case -1:
+                if (speedX > -1)
+                    speedX = -1;
+                if (speedX > -1 * MAX_SPEED)
+                    speedX -= ACCELERATION;
+                break;
+            case 0:
+                speedX = 0;
+                break;
+            case 1:
+                if (speedX < 1)
+                    speedX = 1;
+                if (speedX < MAX_SPEED)
+                    speedX += ACCELERATION;
+                break;
+            }
+
+            // update the y speed
+            switch (directionY) {
+            case -1:
+                if (speedY > -1)
+                    speedY = -1;
+                if (speedY > -1 * MAX_SPEED)
+                    speedY -= ACCELERATION;
+                break;
+            case 0:
+                speedY = 0;
+                break;
+            case 1:
+                if (speedY < 1)
+                    speedY = 1;
+                if (speedY < MAX_SPEED)
+                    speedY += ACCELERATION;
+                break;
+            }
+
+            // move the map
+            int moveX = (int) Math.floor(speedX);
+            int moveY = (int) Math.floor(speedY);
+            if (moveX != 0 || moveY != 0)
+                iSlippyMapChooser.moveMap(moveX, moveY);
+        }
+    }
+
+    private class ZoomInAction extends AbstractAction {
+
+        public void actionPerformed(ActionEvent e) {
+            iSlippyMapChooser.zoomIn();
+        }
+    }
+
+    private class ZoomOutAction extends AbstractAction {
+
+        public void actionPerformed(ActionEvent e) {
+            iSlippyMapChooser.zoomOut();
+        }
+    }
+
+}
Index: src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java
===================================================================
--- src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java	(revision 1588)
+++ src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java	(working copy)
@@ -104,8 +104,9 @@
         slipyyMapTabPanel = new JPanel();
         slipyyMapTabPanel.setLayout(new BorderLayout());
         slipyyMapTabPanel.add(this, BorderLayout.CENTER);
-        slipyyMapTabPanel.add(new JLabel((tr("Zoom: Mousewheel or double click.   "
-                + "Move map: Hold right mousebutton and move mouse.   Select: Click."))), BorderLayout.SOUTH);
+        String labelText = "<b>Zoom:</b> Mousewheel, double click or Ctrl + Up/Down "
+                + "<b>Move map:</b> Hold right mousebutton and move mouse or use cursor keys. <b>Select:</b> Click.";
+        slipyyMapTabPanel.add(new JLabel("<html>" + tr(labelText) + "</html>"), BorderLayout.SOUTH);
         iGui.tabpane.add(slipyyMapTabPanel, tr("Slippy map"));
         new OsmMapControl(this, slipyyMapTabPanel, iSizeButton, iSourceButton);
     }
