Index: /trunk/src/org/openstreetmap/josm/gui/bbox/BBoxChooser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/bbox/BBoxChooser.java	(revision 3094)
+++ /trunk/src/org/openstreetmap/josm/gui/bbox/BBoxChooser.java	(revision 3094)
@@ -0,0 +1,35 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.bbox;
+
+import org.openstreetmap.josm.data.Bounds;
+
+/**
+ * A BBoxChooser is a component which provides a UI for choosing a
+ * bounding box.
+ * 
+ */
+public interface BBoxChooser {
+
+    /**
+     * A BBoxChooser emits {@see ProperyChangeEvent}s for this property
+     * if the current bounding box changes.
+     */
+    String BBOX_PROP = BBoxChooser.class.getName() + ".bbox";
+
+    /**
+     * Sets the current bounding box in this BboxChooser. If {@code bbox}
+     * is null the current bbox in this BBoxChooser is removed.
+     * 
+     * @param bbox the bounding box
+     */
+    public void setBoundingBox(Bounds bbox);
+
+    /**
+     * Replies the currently selected bounding box in this BBoxChooser.
+     * Replies null, if currently there isn't a bbox choosen in this
+     * BBoxChooser.
+     * 
+     * @return the currently selected bounding box
+     */
+    public Bounds getBoundingBox();
+}
Index: /trunk/src/org/openstreetmap/josm/gui/bbox/SizeButton.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/bbox/SizeButton.java	(revision 3094)
+++ /trunk/src/org/openstreetmap/josm/gui/bbox/SizeButton.java	(revision 3094)
@@ -0,0 +1,51 @@
+// License: GPL. Copyright 2007 by Tim Haussmann
+package org.openstreetmap.josm.gui.bbox;
+
+import java.awt.Graphics;
+import java.awt.Point;
+
+import javax.swing.ImageIcon;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * @author Tim Haussmann
+ */
+public class SizeButton{
+
+    private int x = 0;
+    private int y = 0;
+
+    private ImageIcon enlargeImage;
+    private ImageIcon shrinkImage;
+    private boolean isEnlarged = false;
+
+    public SizeButton(){
+        enlargeImage = ImageProvider.get("view-fullscreen.png");
+        shrinkImage = ImageProvider.get("view-fullscreen-revert.png");
+    }
+
+    public void paint(Graphics g){
+        if(isEnlarged){
+            if(shrinkImage != null)
+                g.drawImage(shrinkImage.getImage(),x,y, null);
+        }else{
+            if(enlargeImage != null)
+                g.drawImage(enlargeImage.getImage(),x,y, null);
+        }
+    }
+
+    public void toggle(){
+        isEnlarged = !isEnlarged;
+    }
+
+    public boolean hit(Point point){
+        if(x < point.x && point.x < x + enlargeImage.getIconWidth()){
+            if(y < point.y && point.y < y + enlargeImage.getIconHeight() ){
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java	(revision 3094)
+++ /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java	(revision 3094)
@@ -0,0 +1,268 @@
+package org.openstreetmap.josm.gui.bbox;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.util.Vector;
+
+import org.openstreetmap.gui.jmapviewer.Coordinate;
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
+import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
+import org.openstreetmap.gui.jmapviewer.OsmFileCacheTileLoader;
+import org.openstreetmap.gui.jmapviewer.OsmMercator;
+import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
+import org.openstreetmap.gui.jmapviewer.OsmTileSource;
+import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+
+public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser{
+    static private TileSource[] TILE_SOURCES = { new OsmTileSource.Mapnik(),
+        new OsmTileSource.TilesAtHome(), new OsmTileSource.CycleMap() };
+
+    // standard dimension
+    private Dimension iDownloadDialogDimension;
+
+    private TileLoader cachedLoader;
+    private TileLoader uncachedLoader;
+
+    private SizeButton iSizeButton = new SizeButton();
+    private SourceButton iSourceButton = new SourceButton();
+    private Bounds bbox;
+
+    // upper left and lower right corners of the selection rectangle (x/y on
+    // ZOOM_MAX)
+    Point iSelectionRectStart;
+    Point iSelectionRectEnd;
+
+    public SlippyMapBBoxChooser() {
+        super();
+        try {
+            cachedLoader = new OsmFileCacheTileLoader(this);
+        } catch (SecurityException e) {
+            // set to null if a SecurityException was thrown
+            // while creating the cachedLoader
+            //
+            cachedLoader = null;
+        }
+        uncachedLoader = new OsmTileLoader(this);
+        setZoomContolsVisible(false);
+        setMapMarkerVisible(false);
+        setMinimumSize(new Dimension(350, 350 / 2));
+        // We need to set an initial size - this prevents a wrong zoom selection
+        // for
+        // the area before the component has been displayed the first time
+        setBounds(new Rectangle(getMinimumSize()));
+        if (cachedLoader == null) {
+            setFileCacheEnabled(false);
+        } else {
+            setFileCacheEnabled(Main.pref.getBoolean("slippy_map_chooser.file_cache", true));
+        }
+        setMaxTilesInMemory(Main.pref.getInteger("slippy_map_chooser.max_tiles", 1000));
+
+        String mapStyle = Main.pref.get("slippy_map_chooser.mapstyle", "mapnik");
+        if (mapStyle.equals("osmarender")) {
+            iSourceButton.setMapStyle(SourceButton.OSMARENDER);
+            this.setTileSource(TILE_SOURCES[1]);
+        } else if (mapStyle.equals("cyclemap")) {
+            iSourceButton.setMapStyle(SourceButton.CYCLEMAP);
+            this.setTileSource(TILE_SOURCES[2]);
+        } else {
+            if (!mapStyle.equals("mapnik")) {
+                Main.pref.put("slippy_map_chooser", "mapnik");
+            }
+        }
+
+        new SlippyMapControler(this, this, iSizeButton, iSourceButton);
+    }
+
+    protected Point getTopLeftCoordinates() {
+        return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
+    }
+
+    /**
+     * Draw the map.
+     */
+    @Override
+    public void paint(Graphics g) {
+        try {
+            super.paint(g);
+
+            // draw selection rectangle
+            if (iSelectionRectStart != null && iSelectionRectEnd != null) {
+
+                int zoomDiff = MAX_ZOOM - zoom;
+                Point tlc = getTopLeftCoordinates();
+                int x_min = (iSelectionRectStart.x >> zoomDiff) - tlc.x;
+                int y_min = (iSelectionRectStart.y >> zoomDiff) - tlc.y;
+                int x_max = (iSelectionRectEnd.x >> zoomDiff) - tlc.x;
+                int y_max = (iSelectionRectEnd.y >> zoomDiff) - tlc.y;
+
+                int w = x_max - x_min;
+                int h = y_max - y_min;
+                g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
+                g.fillRect(x_min, y_min, w, h);
+
+                g.setColor(Color.BLACK);
+                g.drawRect(x_min, y_min, w, h);
+            }
+
+            iSizeButton.paint(g);
+            iSourceButton.paint(g);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void setFileCacheEnabled(boolean enabled) {
+        if (enabled) {
+            setTileLoader(cachedLoader);
+        } else {
+            setTileLoader(uncachedLoader);
+        }
+    }
+
+    public void setMaxTilesInMemory(int tiles) {
+        ((MemoryTileCache) getTileCache()).setCacheSize(tiles);
+    }
+
+
+    /**
+     * Callback for the OsmMapControl. (Re-)Sets the start and end point of the
+     * selection rectangle.
+     *
+     * @param aStart
+     * @param aEnd
+     */
+    public void setSelection(Point aStart, Point aEnd) {
+        if (aStart == null || aEnd == null || aStart.x == aEnd.x || aStart.y == aEnd.y)
+            return;
+
+        Point p_max = new Point(Math.max(aEnd.x, aStart.x), Math.max(aEnd.y, aStart.y));
+        Point p_min = new Point(Math.min(aEnd.x, aStart.x), Math.min(aEnd.y, aStart.y));
+
+        Point tlc = getTopLeftCoordinates();
+        int zoomDiff = MAX_ZOOM - zoom;
+        Point pEnd = new Point(p_max.x + tlc.x, p_max.y + tlc.y);
+        Point pStart = new Point(p_min.x + tlc.x, p_min.y + tlc.y);
+
+        pEnd.x <<= zoomDiff;
+        pEnd.y <<= zoomDiff;
+        pStart.x <<= zoomDiff;
+        pStart.y <<= zoomDiff;
+
+        iSelectionRectStart = pStart;
+        iSelectionRectEnd = pEnd;
+
+        Coordinate l1 = getPosition(p_max);
+        Coordinate l2 = getPosition(p_min);
+        Bounds b = new Bounds(
+                new LatLon(
+                        Math.min(l2.getLat(), l1.getLat()),
+                        Math.min(l1.getLon(), l2.getLon())
+                ),
+                new LatLon(
+                        Math.max(l2.getLat(), l1.getLat()),
+                        Math.max(l1.getLon(), l2.getLon()))
+        );
+        Bounds oldValue = this.bbox;
+        this.bbox = b;
+        firePropertyChange(BBOX_PROP, oldValue, this.bbox);
+        repaint();
+    }
+
+    /**
+     * Performs resizing of the DownloadDialog in order to enlarge or shrink the
+     * map.
+     */
+    public void resizeSlippyMap() {
+        int w, h;
+
+        // retrieve the size of the display
+        Dimension iScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+        // enlarge
+        if(iDownloadDialogDimension == null) {
+            // make the each dimension 90% of the absolute display size
+            w = iScreenSize.width * 90 / 100;
+            h = iScreenSize.height * 90 / 100;
+            //iDownloadDialogDimension = iGui.getSize();
+        }
+        // shrink
+        else {
+            // set the size back to the initial dimensions
+            w = iDownloadDialogDimension.width;
+            h = iDownloadDialogDimension.height;
+            iDownloadDialogDimension = null;
+        }
+        // resize and center the DownloadDialog
+        //iGui.setBounds((iScreenSize.width - w) / 2, (iScreenSize.height - h) / 2, w, h);
+
+        repaint();
+    }
+
+    public void toggleMapSource(int mapSource) {
+        this.tileController.setTileCache(new MemoryTileCache());
+        if (mapSource == SourceButton.MAPNIK) {
+            this.setTileSource(TILE_SOURCES[0]);
+            Main.pref.put("slippy_map_chooser.mapstyle", "mapnik");
+        } else if (mapSource == SourceButton.CYCLEMAP) {
+            this.setTileSource(TILE_SOURCES[2]);
+            Main.pref.put("slippy_map_chooser.mapstyle", "cyclemap");
+        } else {
+            this.setTileSource(TILE_SOURCES[1]);
+            Main.pref.put("slippy_map_chooser.mapstyle", "osmarender");
+        }
+    }
+
+    @Override
+    public Bounds getBoundingBox() {
+        return bbox;
+    }
+
+    @Override
+    public void setBoundingBox(Bounds bbox) {
+        if (bbox == null) {
+            Bounds oldValue = this.bbox;
+            this.bbox = null;
+            firePropertyChange(BBOX_PROP, oldValue, this.bbox);
+            return;
+        }
+        // test if a bounding box has been set
+        if (bbox.getMin().lat() == 0.0 && bbox.getMin().lon() == 0.0 && bbox.getMax().lat() == 0.0 && bbox.getMax().lon() == 0.0) {
+            Bounds oldValue = this.bbox;
+            this.bbox = null;
+            firePropertyChange(BBOX_PROP, oldValue, this.bbox);
+        }
+
+        int y1 = OsmMercator.LatToY(bbox.getMin().lat(), MAX_ZOOM);
+        int y2 = OsmMercator.LatToY(bbox.getMax().lat(), MAX_ZOOM);
+        int x1 = OsmMercator.LonToX(bbox.getMin().lon(), MAX_ZOOM);
+        int x2 = OsmMercator.LonToX(bbox.getMax().lon(), MAX_ZOOM);
+
+        iSelectionRectStart = new Point(Math.min(x1, x2), Math.min(y1, y2));
+        iSelectionRectEnd = new Point(Math.max(x1, x2), Math.max(y1, y2));
+
+        // calc the screen coordinates for the new selection rectangle
+        MapMarkerDot xmin_ymin = new MapMarkerDot(bbox.getMin().lat(), bbox.getMin().lon());
+        MapMarkerDot xmax_ymax = new MapMarkerDot(bbox.getMax().lat(), bbox.getMax().lon());
+
+        Vector<MapMarker> marker = new Vector<MapMarker>(2);
+        marker.add(xmin_ymin);
+        marker.add(xmax_ymax);
+        setMapMarkerList(marker);
+        setDisplayToFitMapMarkers();
+        zoomOut();
+
+        Bounds oldValue = this.bbox;
+        this.bbox = bbox;
+        firePropertyChange(BBOX_PROP, oldValue, this.bbox);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapControler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapControler.java	(revision 3094)
+++ /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapControler.java	(revision 3094)
@@ -0,0 +1,317 @@
+// 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.bbox;
+
+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 SlippyMapControler 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 SlippyMapBBoxChooser iSlippyMapChooser;
+
+    private SizeButton iSizeButton = null;
+    private SourceButton iSourceButton = null;
+
+    /**
+     * Create a new OsmMapControl
+     */
+    public SlippyMapControler(SlippyMapBBoxChooser 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();
+            }
+        }
+
+    }
+
+    @Override
+    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;
+                }
+            }
+
+        }
+    }
+
+    @Override
+    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: /trunk/src/org/openstreetmap/josm/gui/bbox/SourceButton.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/bbox/SourceButton.java	(revision 3094)
+++ /trunk/src/org/openstreetmap/josm/gui/bbox/SourceButton.java	(revision 3094)
@@ -0,0 +1,108 @@
+package org.openstreetmap.josm.gui.bbox;
+
+import java.awt.Graphics;
+import java.awt.Point;
+
+import javax.swing.ImageIcon;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class SourceButton {
+
+    private int x = 0;
+    private int y = 30;
+
+    private ImageIcon enlargeImage;
+    private ImageIcon shrinkImage;
+    private ImageIcon imageMapnik;
+    private ImageIcon imageOsmarender;
+    private ImageIcon imageCycleMap;
+
+    private boolean isEnlarged = false;
+
+    private int currentMap = MAPNIK;
+
+    public static final int HIDE_OR_SHOW = 1;
+    public static final int MAPNIK = 2;
+    public static final int OSMARENDER = 3;
+    public static final int CYCLEMAP = 4;
+
+    public SourceButton() {
+        enlargeImage = ImageProvider.get("layer-switcher-maximize.png");
+        shrinkImage = ImageProvider.get("layer-switcher-minimize.png");
+        imageMapnik = ImageProvider.get("blue_Mapnik.png");
+        imageOsmarender = ImageProvider.get("blue_Osmarender.png");
+        imageCycleMap = ImageProvider.get("blue_CycleMap.png");
+    }
+
+    public void paint(Graphics g) {
+
+        if (isEnlarged) {
+            if (currentMap == MAPNIK) {
+                g.drawImage(imageMapnik.getImage(), g.getClipBounds().width
+                        - imageMapnik.getIconWidth(), y, null);
+            }else if(currentMap == CYCLEMAP){
+                 g.drawImage(imageCycleMap.getImage(), g.getClipBounds().width
+                         - imageCycleMap.getIconWidth(), y, null);
+            }
+            else {
+                g.drawImage(imageOsmarender.getImage(), g.getClipBounds().width
+                        - imageMapnik.getIconWidth(), y, null);
+            }
+
+            if (shrinkImage != null) {
+                this.x = g.getClipBounds().width - shrinkImage.getIconWidth();
+                g.drawImage(shrinkImage.getImage(), x, y, null);
+            }
+
+        } else {
+            if (enlargeImage != null) {
+                this.x = g.getClipBounds().width - enlargeImage.getIconWidth();
+                g.drawImage(enlargeImage.getImage(), x, y, null);
+            }
+        }
+    }
+
+    public void toggle() {
+        this.isEnlarged = !this.isEnlarged;
+
+    }
+
+    public int hit(Point point) {
+        if (isEnlarged) {
+            if (x < point.x && point.x < x + shrinkImage.getIconWidth()) {
+                if (y < point.y && point.y < y + shrinkImage.getIconHeight()) {
+                    return HIDE_OR_SHOW;
+                }
+            } else if (x - imageMapnik.getIconWidth() < point.x && point.x < x) {
+                if (y < point.y && point.y < y + imageMapnik.getIconHeight() / 3) {
+                    currentMap = OSMARENDER;
+                    return OSMARENDER;
+                } else if (y + imageMapnik.getIconHeight() / 3 < point.y
+                        && point.y < y + imageMapnik.getIconHeight() *2/3) {
+                    currentMap = MAPNIK;
+                    return MAPNIK;
+                } else if (y + imageMapnik.getIconHeight()* 2/3 < point.y
+                        && point.y < y + imageMapnik.getIconHeight()) {
+                    currentMap = CYCLEMAP;
+                    return CYCLEMAP;
+                }
+            }
+        } else {
+            if (x < point.x && point.x < x + enlargeImage.getIconWidth()) {
+                if (y < point.y && point.y < y + enlargeImage.getIconHeight()) {
+                    return HIDE_OR_SHOW;
+                }
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * One of the constants OSMARENDER,MAPNIK or CYCLEMAP
+     */
+    public void setMapStyle (int style) {
+       currentMap = (style < 2 || style > 4) ? MAPNIK : style;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/bbox/TileSelectionBBoxChooser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/bbox/TileSelectionBBoxChooser.java	(revision 3094)
+++ /trunk/src/org/openstreetmap/josm/gui/bbox/TileSelectionBBoxChooser.java	(revision 3094)
@@ -0,0 +1,721 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.bbox;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.AWTKeyStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.KeyboardFocusManager;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Vector;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
+import org.openstreetmap.gui.jmapviewer.OsmMercator;
+import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * TileSelectionBBoxChooser allows to select a bounding box (i.e. for downloading) based
+ * on OSM tile numbers.
+ * 
+ * TileSelectionBBoxChooser can be embedded as component in a Swing container. Example:
+ * <pre>
+ *    JFrame f = new JFrame(....);
+ *    f.getContentPane().setLayout(new BorderLayout()));
+ *    TileSelectionBBoxChooser chooser = new TileSelectionBBoxChooser();
+ *    f.add(chooser, BorderLayout.CENTER);
+ *    chooser.addPropertyChangeListener(new PropertyChangeListener() {
+ *        public void propertyChange(PropertyChangeEvent evt) {
+ *            // listen for BBOX events
+ *            if (evt.getPropertyName().equals(BBoxChooser.BBOX_PROP)) {
+ *               System.out.println("new bbox based on OSM tiles selected: " + (Bounds)evt.getNewValue());
+ *            }
+ *        }
+ *    });
+ * 
+ *    // init the chooser with a bounding box
+ *    chooser.setBoundingBox(....);
+ * 
+ *    f.setVisible(true);
+ * </pre>
+ */
+public class TileSelectionBBoxChooser extends JPanel implements BBoxChooser{
+
+    static private final Logger logger = Logger.getLogger(TileSelectionBBoxChooser.class.getName());
+
+    /** the current bounding box */
+    private Bounds bbox;
+    /** the map viewer showing the selected bounding box */
+    private TileBoundsMapView mapViewer;
+    /** a panel for entering a bounding box given by a  tile grid and a zoom level */
+    private TileGridInputPanel pnlTileGrid;
+    /** a panel for entering a bounding box given by the address of an individual OSM tile at
+     *  a given zoom level
+     */
+    private TileAddressInputPanel pnlTileAddress;
+
+    /**
+     * builds the UI
+     */
+    protected void build() {
+        setLayout(new GridBagLayout());
+
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.weightx = 0.5;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(pnlTileGrid = new TileGridInputPanel(), gc);
+
+        gc.gridx = 1;
+        add(pnlTileAddress = new TileAddressInputPanel(), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = 2;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.insets = new Insets(2,2,2,2);
+        add(mapViewer = new TileBoundsMapView(), gc);
+        mapViewer.setFocusable(false);
+        mapViewer.setZoomContolsVisible(false);
+        mapViewer.setMapMarkerVisible(false);
+
+        pnlTileAddress.addPropertyChangeListener(pnlTileGrid);
+        pnlTileGrid.addPropertyChangeListener(new TileBoundsChangeListener());
+    }
+
+    public TileSelectionBBoxChooser() {
+        build();
+    }
+
+    /**
+     * Replies the current bounding box. null, if no valid bounding box is currently selected.
+     * 
+     */
+    @Override
+    public Bounds getBoundingBox() {
+        return bbox;
+    }
+
+    /**
+     * Sets the current bounding box.
+     * 
+     * @param bbox the bounding box. null, if this widget isn't initialized with a bounding box
+     */
+    @Override
+    public void setBoundingBox(Bounds bbox) {
+        pnlTileGrid.initFromBoundingBox(bbox);
+    }
+
+    protected void refreshMapView() {
+        if (bbox == null) return;
+
+        // calc the screen coordinates for the new selection rectangle
+        MapMarkerDot xmin_ymin = new MapMarkerDot(bbox.getMin().lat(), bbox.getMin().lon());
+        MapMarkerDot xmax_ymax = new MapMarkerDot(bbox.getMax().lat(), bbox.getMax().lon());
+
+        Vector<MapMarker> marker = new Vector<MapMarker>(2);
+        marker.add(xmin_ymin);
+        marker.add(xmax_ymax);
+        mapViewer.setBoundingBox(bbox);
+        mapViewer.setMapMarkerList(marker);
+        mapViewer.setDisplayToFitMapMarkers();
+        mapViewer.zoomOut();
+    }
+
+    /**
+     * Computes the bounding box given a tile grid.
+     * 
+     * @param tb the description of the tile grid
+     * @return the bounding box
+     */
+    protected Bounds convertTileBoundsToBoundingBox(TileBounds tb) {
+        LatLon min = getNorthWestLatLonOfTile(tb.min, tb.zoomLevel);
+        Point p = new Point(tb.max);
+        p.x++;
+        p.y++;
+        LatLon max = getNorthWestLatLonOfTile(p, tb.zoomLevel);
+        return new Bounds(max.lat(), min.lon(), min.lat(), max.lon());
+    }
+
+    /**
+     * Replies lat/lon of the north/west-corner of a tile at a specific zoom level
+     * 
+     * @param tile  the tile address (x,y)
+     * @param zoom the zoom level
+     * @return lat/lon of the north/west-corner of a tile at a specific zoom level
+     */
+    protected LatLon getNorthWestLatLonOfTile(Point tile, int zoom) {
+        double lon =  tile.x / Math.pow(2.0, zoom) * 360.0 - 180;
+        double lat =  Math.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * tile.y) / Math.pow(2.0, zoom))));
+        return new LatLon(lat, lon);
+    }
+
+    /**
+     * Listens to changes in the selected tile bounds, refreshes the map view and emits
+     * property change events for {@see BBoxChooser#BBOX_PROP}
+     */
+    class TileBoundsChangeListener implements PropertyChangeListener {
+        @Override
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (!evt.getPropertyName().equals(TileGridInputPanel.TILE_BOUNDS_PROP)) return;
+            TileBounds tb = (TileBounds)evt.getNewValue();
+            Bounds oldValue = TileSelectionBBoxChooser.this.bbox;
+            TileSelectionBBoxChooser.this.bbox = convertTileBoundsToBoundingBox(tb);
+            firePropertyChange(BBOX_PROP, oldValue, TileSelectionBBoxChooser.this.bbox);
+            refreshMapView();
+        }
+    }
+
+    /**
+     * A panel for describing a rectangular area of OSM tiles at a given zoom level.
+     * 
+     * The panel emits PropertyChangeEvents for the property {@see TileGridInputPanel#TILE_BOUNDS_PROP}
+     * when the user successfully enters a valid tile grid specification.
+     *
+     */
+    static private class TileGridInputPanel extends JPanel implements PropertyChangeListener{
+        static public final String TILE_BOUNDS_PROP = TileGridInputPanel.class.getName() + ".tileBounds";
+
+        private JTextField tfMaxY;
+        private JTextField tfMinY;
+        private JTextField tfMaxX;
+        private JTextField tfMinX;
+        private TileCoordinateValidator valMaxY;
+        private TileCoordinateValidator valMinY;
+        private TileCoordinateValidator valMaxX;
+        private TileCoordinateValidator valMinX;
+        private JSpinner spZoomLevel;
+        private TileBoundsBuilder tileBoundsBuilder = new TileBoundsBuilder();
+        private boolean doFireTileBoundChanged = true;
+
+        protected JPanel buildTextPanel() {
+            JPanel pnl = new JPanel(new BorderLayout());
+            HtmlPanel msg = new HtmlPanel();
+            msg.setText(tr("<html>Please select a <strong>range of OSM tiles</strong> at a given zoom level.</html>"));
+            pnl.add(msg);
+            return pnl;
+        }
+
+        protected JPanel buildZoomLevelPanel() {
+            JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+            pnl.add(new JLabel(tr("Zoom level:")));
+            pnl.add(spZoomLevel = new JSpinner(new SpinnerNumberModel(0,0,18,1)));
+            spZoomLevel.addChangeListener(new ZomeLevelChangeHandler());
+            spZoomLevel.addChangeListener(tileBoundsBuilder);
+            return pnl;
+        }
+
+        protected JPanel buildTileGridInputPanel() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            pnl.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.insets = new Insets(0, 0, 2, 2);
+
+            gc.gridwidth = 2;
+            gc.gridx = 1;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            pnl.add(buildZoomLevelPanel(), gc);
+
+            gc.gridwidth = 1;
+            gc.gridy = 1;
+            gc.gridx = 1;
+            pnl.add(new JLabel(tr("from tile")), gc);
+
+            gc.gridx = 2;
+            pnl.add(new JLabel(tr("up to tile")), gc);
+
+            gc.gridx = 0;
+            gc.gridy = 2;
+            gc.weightx = 0.0;
+            pnl.add(new JLabel("X:"), gc);
+
+
+            gc.gridx = 1;
+            gc.weightx = 0.5;
+            pnl.add(tfMinX = new JTextField(), gc);
+            valMinX = new TileCoordinateValidator(tfMinX);
+            SelectAllOnFocusGainedDecorator.decorate(tfMinX);
+            tfMinX.addActionListener(tileBoundsBuilder);
+            tfMinX.addFocusListener(tileBoundsBuilder);
+
+            gc.gridx = 2;
+            gc.weightx = 0.5;
+            pnl.add(tfMaxX = new JTextField(), gc);
+            valMaxX = new TileCoordinateValidator(tfMaxX);
+            SelectAllOnFocusGainedDecorator.decorate(tfMaxX);
+            tfMaxX.addActionListener(tileBoundsBuilder);
+            tfMaxX.addFocusListener(tileBoundsBuilder);
+
+            gc.gridx = 0;
+            gc.gridy = 3;
+            gc.weightx = 0.0;
+            pnl.add(new JLabel("Y:"), gc);
+
+            gc.gridx = 1;
+            gc.weightx = 0.5;
+            pnl.add(tfMinY = new JTextField(), gc);
+            valMinY = new TileCoordinateValidator(tfMinY);
+            SelectAllOnFocusGainedDecorator.decorate(tfMinY);
+            tfMinY.addActionListener(tileBoundsBuilder);
+            tfMinY.addFocusListener(tileBoundsBuilder);
+
+            gc.gridx = 2;
+            gc.weightx = 0.5;
+            pnl.add(tfMaxY = new JTextField(), gc);
+            valMaxY = new TileCoordinateValidator(tfMaxY);
+            SelectAllOnFocusGainedDecorator.decorate(tfMaxY);
+            tfMaxY.addActionListener(tileBoundsBuilder);
+            tfMaxY.addFocusListener(tileBoundsBuilder);
+
+            gc.gridy = 4;
+            gc.gridx = 0;
+            gc.gridwidth = 3;
+            gc.weightx = 1.0;
+            gc.weighty = 1.0;
+            gc.fill = GridBagConstraints.BOTH;
+            pnl.add(new JPanel(), gc);
+            return pnl;
+        }
+
+        protected void build() {
+            setLayout(new BorderLayout());
+            setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+            add(buildTextPanel(), BorderLayout.NORTH);
+            add(buildTileGridInputPanel(), BorderLayout.CENTER);
+
+            Set<AWTKeyStroke> forwardKeys = new HashSet<AWTKeyStroke>(getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
+            forwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
+            setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,forwardKeys);
+        }
+
+        public TileGridInputPanel() {
+            build();
+        }
+
+        public void initFromBoundingBox(Bounds bbox) {
+            if (bbox == null)
+                return;
+            TileBounds tb = new TileBounds();
+            tb.zoomLevel = (Integer) spZoomLevel.getValue();
+            tb.min = new Point(
+                    Math.max(0,lonToTileX(tb.zoomLevel, bbox.getMin().lon())),
+                    Math.max(0,latToTileY(tb.zoomLevel, bbox.getMax().lat()-.00001))
+            );
+            tb.max = new Point(
+                    Math.max(0,lonToTileX(tb.zoomLevel, bbox.getMax().lon())),
+                    Math.max(0,latToTileY(tb.zoomLevel, bbox.getMin().lat()-.00001))
+            );
+            doFireTileBoundChanged = false;
+            setTileBounds(tb);
+            doFireTileBoundChanged = true;
+        }
+
+        public static int latToTileY(int zoom, double lat) {
+            if ((zoom < 3) || (zoom > 18)) return -1;
+            double l = lat / 180 * Math.PI;
+            double pf = Math.log(Math.tan(l) + (1/Math.cos(l)));
+            return (int) ((1<<(zoom-1)) * (Math.PI - pf) / Math.PI);
+        }
+
+        public static int lonToTileX(int zoom, double lon) {
+            if ((zoom < 3) || (zoom > 18)) return -1;
+            return (int) ((1<<(zoom-3)) * (lon + 180.0) / 45.0);
+        }
+
+        public void setTileBounds(TileBounds tileBounds) {
+            tfMinX.setText(Integer.toString(tileBounds.min.x));
+            tfMinY.setText(Integer.toString(tileBounds.min.y));
+            tfMaxX.setText(Integer.toString(tileBounds.max.x));
+            tfMaxY.setText(Integer.toString(tileBounds.max.y));
+            spZoomLevel.setValue(tileBounds.zoomLevel);
+        }
+
+        @Override
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals(TileAddressInputPanel.TILE_BOUNDS_PROP)) {
+                TileBounds tb = (TileBounds)evt.getNewValue();
+                setTileBounds(tb);
+                fireTileBoundsChanged(tb);
+            }
+        }
+
+        protected void fireTileBoundsChanged(TileBounds tb) {
+            if (!doFireTileBoundChanged) return;
+            firePropertyChange(TILE_BOUNDS_PROP, null, tb);
+        }
+
+        class ZomeLevelChangeHandler implements ChangeListener {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                int zoomLevel = (Integer)spZoomLevel.getValue();
+                valMaxX.setZoomLevel(zoomLevel);
+                valMaxY.setZoomLevel(zoomLevel);
+                valMinX.setZoomLevel(zoomLevel);
+                valMinY.setZoomLevel(zoomLevel);
+            }
+        }
+
+        class TileBoundsBuilder implements ActionListener, FocusListener, ChangeListener {
+            protected void buildTileBounds() {
+                if (!valMaxX.isValid()) return;
+                if (!valMaxY.isValid()) return;
+                if (!valMinX.isValid()) return;
+                if (!valMinY.isValid()) return;
+                Point min = new Point(valMinX.getTileIndex(), valMinY.getTileIndex());
+                Point max = new Point(valMaxX.getTileIndex(), valMaxY.getTileIndex());
+                if (min.x > max.x) {
+
+                }
+                int zoomlevel = (Integer)spZoomLevel.getValue();
+                TileBounds tb = new TileBounds(min, max, zoomlevel);
+                fireTileBoundsChanged(tb);
+            }
+
+            @Override
+            public void focusGained(FocusEvent e) {/* irrelevant */}
+
+            @Override
+            public void focusLost(FocusEvent e) {
+                buildTileBounds();
+            }
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                buildTileBounds();
+            }
+
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                buildTileBounds();
+            }
+        }
+    }
+
+    /**
+     * A panel for entering the address of a single OSM tile at a given zoom level.
+     *
+     */
+    static private class TileAddressInputPanel extends JPanel {
+
+        static public final String TILE_BOUNDS_PROP = TileAddressInputPanel.class.getName() + ".tileBounds";
+
+        private JTextField tfTileAddress;
+        private TileAddressValidator valTileAddress;
+
+        protected JPanel buildTextPanel() {
+            JPanel pnl = new JPanel(new BorderLayout());
+            HtmlPanel msg = new HtmlPanel();
+            msg.setText(tr("<html>Alternatively you may enter a <strong>tile address</strong> for a single tile "
+                    + "in the format <i>zoomlevel/x/y</i>, i.e. <i>15/256/223</i>. Tile adresses "
+                    + "in the format <i>zoom,x,y</i> or <i>zoom;x;y</i> are valid too.</html>"));
+            pnl.add(msg);
+            return pnl;
+        }
+
+        protected JPanel buildTileAddressInputPanel() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 0.0;
+            gc.insets = new Insets(0,0,2,2);
+            pnl.add(new JLabel(tr("Tile address:")), gc);
+
+            gc.weightx = 1.0;
+            gc.gridx = 1;
+            pnl.add(tfTileAddress = new JTextField(), gc);
+            valTileAddress = new TileAddressValidator(tfTileAddress);
+            SelectAllOnFocusGainedDecorator.decorate(tfTileAddress);
+
+            gc.weightx = 0.0;
+            gc.gridx = 2;
+            ApplyTileAddressAction applyTileAddressAction = new ApplyTileAddressAction();
+            JButton btn = new JButton(applyTileAddressAction);
+            btn.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
+            pnl.add(btn, gc);
+            tfTileAddress.addActionListener(applyTileAddressAction);
+            return pnl;
+        }
+
+        protected void build() {
+            setLayout(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.anchor = GridBagConstraints.NORTHWEST;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            gc.insets = new Insets(0,0,5,0);
+            add(buildTextPanel(), gc);
+
+            gc.gridy = 1;
+            add(buildTileAddressInputPanel(), gc);
+
+            // filler - grab remaining space
+            gc.gridy = 2;
+            gc.fill = GridBagConstraints.BOTH;
+            gc.weighty = 1.0;
+            add(new JPanel(), gc);
+        }
+
+        public TileAddressInputPanel() {
+            setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+            build();
+        }
+
+        protected void fireTileBoundsChanged(TileBounds tb){
+            firePropertyChange(TILE_BOUNDS_PROP, null, tb);
+        }
+
+        class ApplyTileAddressAction extends AbstractAction {
+            public ApplyTileAddressAction() {
+                putValue(SMALL_ICON, ImageProvider.get("apply"));
+                putValue(SHORT_DESCRIPTION, tr("Apply the tile address"));
+            }
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                TileBounds tb = valTileAddress.getTileBounds();
+                if (tb != null) {
+                    fireTileBoundsChanged(tb);
+                }
+            }
+        }
+    }
+
+    /**
+     * Validates a tile address
+     */
+    static private class TileAddressValidator extends AbstractTextComponentValidator {
+
+        private TileBounds tileBounds = null;
+
+        public TileAddressValidator(JTextComponent tc) throws IllegalArgumentException {
+            super(tc);
+        }
+
+        @Override
+        public boolean isValid() {
+            String value = getComponent().getText().trim();
+            Matcher m = Pattern.compile("(\\d+)[^\\d]+(\\d+)[^\\d]+(\\d+)").matcher(value);
+            tileBounds = null;
+            if (!m.matches()) return false;
+            int zoom;
+            try {
+                zoom = Integer.parseInt(m.group(1));
+            } catch(NumberFormatException e){
+                return false;
+            }
+            if (zoom < 0 || zoom > 18) return false;
+
+            int x;
+            try {
+                x = Integer.parseInt(m.group(2));
+            } catch(NumberFormatException e){
+                return false;
+            }
+            if (x < 0 || x >= Math.pow(2, zoom -1)) return false;
+            int y;
+            try {
+                y = Integer.parseInt(m.group(3));
+            } catch(NumberFormatException e){
+                return false;
+            }
+            if (y < 0 || y >= Math.pow(2, zoom -1)) return false;
+
+            tileBounds = new TileBounds(new Point(x,y), new Point(x,y), zoom);
+            return true;
+        }
+
+        @Override
+        public void validate() {
+            if (isValid()) {
+                feedbackValid(tr("Please enter a tile address"));
+            } else {
+                feedbackInvalid(tr("The current value isn''t a valid tile address", getComponent().getText()));
+            }
+        }
+
+        public TileBounds getTileBounds() {
+            return tileBounds;
+        }
+    }
+
+    /**
+     * Validates the x- or y-coordinate of a tile at a given zoom level.
+     * 
+     */
+    static private class TileCoordinateValidator extends AbstractTextComponentValidator {
+        private int zoomLevel;
+        private int tileIndex;
+
+        public TileCoordinateValidator(JTextComponent tc) throws IllegalArgumentException {
+            super(tc);
+        }
+
+        public void setZoomLevel(int zoomLevel) {
+            this.zoomLevel = zoomLevel;
+            validate();
+        }
+
+        @Override
+        public boolean isValid() {
+            String value = getComponent().getText().trim();
+            try {
+                if (value.equals("")) {
+                    tileIndex = 0;
+                } else {
+                    tileIndex = Integer.parseInt(value);
+                }
+            } catch(NumberFormatException e) {
+                return false;
+            }
+            if (tileIndex < 0 || tileIndex >= Math.pow(2, zoomLevel)) return false;
+
+            return true;
+        }
+
+        @Override
+        public void validate() {
+            if (isValid()) {
+                feedbackValid(tr("Please enter a tile index"));
+            } else {
+                feedbackInvalid(tr("The current value isn''t a valid tile index for the given zoom level", getComponent().getText()));
+            }
+        }
+
+        public int getTileIndex() {
+            return tileIndex;
+        }
+    }
+
+    /**
+     * Represents a rectangular area of tiles at a given zoom level.
+     *
+     */
+    static private class TileBounds {
+        public Point min;
+        public Point max;
+        public int zoomLevel;
+
+        public TileBounds() {
+            zoomLevel = 0;
+            min = new Point(0,0);
+            max = new Point(0,0);
+        }
+
+        public TileBounds(Point min, Point max, int zoomLevel) {
+            this.min = min;
+            this.max = max;
+            this.zoomLevel = zoomLevel;
+        }
+
+        @Override
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append("min=").append(min.x).append(",").append(min.y).append(",");
+            sb.append("max=").append(max.x).append(",").append(max.y).append(",");
+            sb.append("zoom=").append(zoomLevel);
+            return sb.toString();
+        }
+    }
+
+    /**
+     * The map view used in this bounding box chooser
+     */
+    static private class TileBoundsMapView extends JMapViewer {
+        private Bounds bbox;
+        private Point min;
+        private Point max;
+
+        public TileBoundsMapView() {
+            setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
+        }
+
+        public void setBoundingBox(Bounds bbox){
+            this.bbox = bbox;
+            if (bbox == null) {
+                min = null;
+                max = null;
+            } else {
+                int y1 = OsmMercator.LatToY(bbox.getMin().lat(), MAX_ZOOM);
+                int y2 = OsmMercator.LatToY(bbox.getMax().lat(), MAX_ZOOM);
+                int x1 = OsmMercator.LonToX(bbox.getMin().lon(), MAX_ZOOM);
+                int x2 = OsmMercator.LonToX(bbox.getMax().lon(), MAX_ZOOM);
+
+                min = new Point(Math.min(x1, x2), Math.min(y1, y2));
+                max = new Point(Math.max(x1, x2), Math.max(y1, y2));
+            }
+            repaint();
+        }
+
+        protected Point getTopLeftCoordinates() {
+            return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
+        }
+
+        /**
+         * Draw the map.
+         */
+        @Override
+        public void paint(Graphics g) {
+            try {
+                super.paint(g);
+                if (min == null || max == null) return;
+                int zoomDiff = MAX_ZOOM - zoom;
+                Point tlc = getTopLeftCoordinates();
+                int x_min = (min.x >> zoomDiff) - tlc.x;
+                int y_min = (min.y >> zoomDiff) - tlc.y;
+                int x_max = (max.x >> zoomDiff) - tlc.x;
+                int y_max = (max.y >> zoomDiff) - tlc.y;
+
+                int w = x_max - x_min;
+                int h = y_max - y_min;
+                g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
+                g.fillRect(x_min, y_min, w, h);
+
+                g.setColor(Color.BLACK);
+                g.drawRect(x_min, y_min, w, h);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
Index: unk/src/org/openstreetmap/josm/gui/download/OsmMapControl.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/download/OsmMapControl.java	(revision 3093)
+++ 	(revision )
@@ -1,306 +1,0 @@
-// 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: unk/src/org/openstreetmap/josm/gui/download/SizeButton.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/download/SizeButton.java	(revision 3093)
+++ 	(revision )
@@ -1,51 +1,0 @@
-// License: GPL. Copyright 2007 by Tim Haussmann
-package org.openstreetmap.josm.gui.download;
-
-import java.awt.Graphics;
-import java.awt.Point;
-
-import javax.swing.ImageIcon;
-
-import org.openstreetmap.josm.tools.ImageProvider;
-
-/**
- * @author Tim Haussmann
- */
-public class SizeButton{
-
-    private int x = 0;
-    private int y = 0;
-
-    private ImageIcon enlargeImage;
-    private ImageIcon shrinkImage;
-    private boolean isEnlarged = false;
-
-    public SizeButton(){
-        enlargeImage = ImageProvider.get("view-fullscreen.png");
-        shrinkImage = ImageProvider.get("view-fullscreen-revert.png");
-    }
-
-    public void paint(Graphics g){
-        if(isEnlarged){
-            if(shrinkImage != null)
-                g.drawImage(shrinkImage.getImage(),x,y, null);
-        }else{
-            if(enlargeImage != null)
-                g.drawImage(enlargeImage.getImage(),x,y, null);
-        }
-    }
-
-    public void toggle(){
-        isEnlarged = !isEnlarged;
-    }
-
-    public boolean hit(Point point){
-        if(x < point.x && point.x < x + enlargeImage.getIconWidth()){
-            if(y < point.y && point.y < y + enlargeImage.getIconHeight() ){
-                return true;
-            }
-        }
-        return false;
-    }
-
-}
Index: /trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java	(revision 3093)
+++ /trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java	(revision 3094)
@@ -5,29 +5,12 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Toolkit;
-import java.util.Vector;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 
 import javax.swing.JPanel;
 
-import org.openstreetmap.gui.jmapviewer.Coordinate;
-import org.openstreetmap.gui.jmapviewer.JMapViewer;
-import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
-import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
-import org.openstreetmap.gui.jmapviewer.OsmFileCacheTileLoader;
-import org.openstreetmap.gui.jmapviewer.OsmMercator;
-import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
-import org.openstreetmap.gui.jmapviewer.OsmTileSource;
-import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.bbox.BBoxChooser;
+import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
 
 /**
@@ -37,24 +20,8 @@
  *
  */
-public class SlippyMapChooser extends JMapViewer implements DownloadSelection {
+public class SlippyMapChooser extends JPanel implements DownloadSelection, PropertyChangeListener{
 
     private DownloadDialog iGui;
-
-    // upper left and lower right corners of the selection rectangle (x/y on
-    // ZOOM_MAX)
-    Point iSelectionRectStart;
-    Point iSelectionRectEnd;
-
-    private SizeButton iSizeButton = new SizeButton();
-    private SourceButton iSourceButton = new SourceButton();
-
-    // standard dimension
-    private Dimension iDownloadDialogDimension;
-
-    private TileSource[] sources = { new OsmTileSource.Mapnik(), new OsmTileSource.TilesAtHome(),
-            new OsmTileSource.CycleMap() };
-    TileLoader cachedLoader;
-    TileLoader uncachedLoader;
-    JPanel slipyyMapTabPanel;
+    private SlippyMapBBoxChooser pnlSlippyMapBBoxChooser;
 
     /**
@@ -62,215 +29,25 @@
      */
     public SlippyMapChooser() {
-        super();
-        try {
-            cachedLoader = new OsmFileCacheTileLoader(this);
-        } catch(SecurityException e) {
-            // set to null if a SecurityException was thrown
-            // while creating the cachedLoader
-            //
-            cachedLoader = null;
-        }
-        uncachedLoader = new OsmTileLoader(this);
-        setZoomContolsVisible(false);
-        setMapMarkerVisible(false);
-        setMinimumSize(new Dimension(350, 350 / 2));
-        // We need to set an initial size - this prevents a wrong zoom selection for
-        // the area before the component has been displayed the first time
-        setBounds(new Rectangle(getMinimumSize()));
-        if (cachedLoader == null) {
-            setFileCacheEnabled(false);
-        } else {
-            setFileCacheEnabled(Main.pref.getBoolean("slippy_map_chooser.file_cache", true));
-        }
-        setMaxTilesInMemory(Main.pref.getInteger("slippy_map_chooser.max_tiles", 1000));
-
-        String mapStyle = Main.pref.get("slippy_map_chooser.mapstyle", "mapnik");
-        if (mapStyle.equals("osmarender")) {
-            iSourceButton.setMapStyle(SourceButton.OSMARENDER);
-            this.setTileSource(sources[1]);
-        } else if (mapStyle.equals("cyclemap")) {
-            iSourceButton.setMapStyle(SourceButton.CYCLEMAP);
-            this.setTileSource(sources[2]);
-        } else {
-            if (!mapStyle.equals("mapnik")) {
-                Main.pref.put("slippy_map_chooser.mapstyle", "mapnik");
-            }
-        }
-    }
-
-    public void setMaxTilesInMemory(int tiles) {
-        ((MemoryTileCache) getTileCache()).setCacheSize(tiles);
-    }
-
-    public void setFileCacheEnabled(boolean enabled) {
-        if (enabled) {
-            setTileLoader(cachedLoader);
-        } else {
-            setTileLoader(uncachedLoader);
-        }
+        pnlSlippyMapBBoxChooser = new SlippyMapBBoxChooser();
+        pnlSlippyMapBBoxChooser.addPropertyChangeListener(this);
     }
 
     public void addGui(final DownloadDialog gui) {
         iGui = gui;
-        slipyyMapTabPanel = new JPanel();
-        slipyyMapTabPanel.setLayout(new BorderLayout());
-        slipyyMapTabPanel.add(this, BorderLayout.CENTER);
-        iGui.addDownloadAreaSelector(slipyyMapTabPanel, tr("Slippy map"));
-        new OsmMapControl(this, slipyyMapTabPanel, iSizeButton, iSourceButton);
+        iGui.addDownloadAreaSelector(pnlSlippyMapBBoxChooser, tr("Slippy map"));
     }
 
-    protected Point getTopLeftCoordinates() {
-        return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
+
+    public void setDownloadArea(Bounds area) {
+        pnlSlippyMapBBoxChooser.setBoundingBox(area);
     }
 
-    /**
-     * Draw the map.
-     */
     @Override
-    public void paint(Graphics g) {
-        try {
-            super.paint(g);
-
-            // draw selection rectangle
-            if (iSelectionRectStart != null && iSelectionRectEnd != null) {
-
-                int zoomDiff = MAX_ZOOM - zoom;
-                Point tlc = getTopLeftCoordinates();
-                int x_min = (iSelectionRectStart.x >> zoomDiff) - tlc.x;
-                int y_min = (iSelectionRectStart.y >> zoomDiff) - tlc.y;
-                int x_max = (iSelectionRectEnd.x >> zoomDiff) - tlc.x;
-                int y_max = (iSelectionRectEnd.y >> zoomDiff) - tlc.y;
-
-                int w = x_max - x_min;
-                int h = y_max - y_min;
-                g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
-                g.fillRect(x_min, y_min, w, h);
-
-                g.setColor(Color.BLACK);
-                g.drawRect(x_min, y_min, w, h);
-
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(BBoxChooser.BBOX_PROP)) {
+            if (iGui != null) {
+                iGui.boundingBoxChanged((Bounds)evt.getNewValue(), this);
             }
-
-            iSizeButton.paint(g);
-            iSourceButton.paint(g);
-        } catch (Exception e) {
-            e.printStackTrace();
         }
     }
-
-    public void setDownloadArea(Bounds area) {
-        if (area == null)
-            return;
-
-        // test if a bounding box has been set
-        if (area.getMin().lat() == 0.0 && area.getMin().lon() == 0.0 && area.getMax().lat() == 0.0 && area.getMax().lon() == 0.0)
-            return;
-
-        int y1 = OsmMercator.LatToY(area.getMin().lat(), MAX_ZOOM);
-        int y2 = OsmMercator.LatToY(area.getMax().lat(), MAX_ZOOM);
-        int x1 = OsmMercator.LonToX(area.getMin().lon(), MAX_ZOOM);
-        int x2 = OsmMercator.LonToX(area.getMax().lon(), MAX_ZOOM);
-
-        iSelectionRectStart = new Point(Math.min(x1, x2), Math.min(y1, y2));
-        iSelectionRectEnd = new Point(Math.max(x1, x2), Math.max(y1, y2));
-
-        // calc the screen coordinates for the new selection rectangle
-        MapMarkerDot xmin_ymin = new MapMarkerDot(area.getMin().lat(), area.getMin().lon());
-        MapMarkerDot xmax_ymax = new MapMarkerDot(area.getMax().lat(), area.getMax().lon());
-
-        Vector<MapMarker> marker = new Vector<MapMarker>(2);
-        marker.add(xmin_ymin);
-        marker.add(xmax_ymax);
-        setMapMarkerList(marker);
-        setDisplayToFitMapMarkers();
-        zoomOut();
-    }
-
-    /**
-     * Callback for the OsmMapControl. (Re-)Sets the start and end point of the
-     * selection rectangle.
-     *
-     * @param aStart
-     * @param aEnd
-     */
-    public void setSelection(Point aStart, Point aEnd) {
-        if (aStart == null || aEnd == null || aStart.x == aEnd.x || aStart.y == aEnd.y)
-            return;
-
-        Point p_max = new Point(Math.max(aEnd.x, aStart.x), Math.max(aEnd.y, aStart.y));
-        Point p_min = new Point(Math.min(aEnd.x, aStart.x), Math.min(aEnd.y, aStart.y));
-
-        Point tlc = getTopLeftCoordinates();
-        int zoomDiff = MAX_ZOOM - zoom;
-        Point pEnd = new Point(p_max.x + tlc.x, p_max.y + tlc.y);
-        Point pStart = new Point(p_min.x + tlc.x, p_min.y + tlc.y);
-
-        pEnd.x <<= zoomDiff;
-        pEnd.y <<= zoomDiff;
-        pStart.x <<= zoomDiff;
-        pStart.y <<= zoomDiff;
-
-        iSelectionRectStart = pStart;
-        iSelectionRectEnd = pEnd;
-
-        Coordinate l1 = getPosition(p_max);
-        Coordinate l2 = getPosition(p_min);
-        Bounds b = new Bounds(
-                new LatLon(
-                        Math.min(l2.getLat(), l1.getLat()),
-                        Math.min(l1.getLon(), l2.getLon())
-                ),
-                new LatLon(
-                        Math.max(l2.getLat(), l1.getLat()),
-                        Math.max(l1.getLon(), l2.getLon()))
-        );
-        iGui.boundingBoxChanged(b, this);
-        repaint();
-    }
-
-    /**
-     * Performs resizing of the DownloadDialog in order to enlarge or shrink the
-     * map.
-     */
-    public void resizeSlippyMap() {
-        int w, h;
-
-        // retrieve the size of the display
-        Dimension iScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
-
-        // enlarge
-        if(iDownloadDialogDimension == null) {
-            // make the each dimension 90% of the absolute display size
-            w = iScreenSize.width * 90 / 100;
-            h = iScreenSize.height * 90 / 100;
-            iDownloadDialogDimension = iGui.getSize();
-        }
-        // shrink
-        else {
-            // set the size back to the initial dimensions
-            w = iDownloadDialogDimension.width;
-            h = iDownloadDialogDimension.height;
-            iDownloadDialogDimension = null;
-        }
-
-        // resize and center the DownloadDialog
-        iGui.setBounds((iScreenSize.width - w) / 2, (iScreenSize.height - h) / 2, w, h);
-
-        repaint();
-    }
-
-    public void toggleMapSource(int mapSource) {
-        this.tileController.setTileCache(new MemoryTileCache());
-        if (mapSource == SourceButton.MAPNIK) {
-            this.setTileSource(sources[0]);
-            Main.pref.put("slippy_map_chooser.mapstyle", "mapnik");
-        } else if (mapSource == SourceButton.CYCLEMAP) {
-            this.setTileSource(sources[2]);
-            Main.pref.put("slippy_map_chooser.mapstyle", "cyclemap");
-        } else {
-            this.setTileSource(sources[1]);
-            Main.pref.put("slippy_map_chooser.mapstyle", "osmarender");
-        }
-    }
-
 }
Index: unk/src/org/openstreetmap/josm/gui/download/SourceButton.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/download/SourceButton.java	(revision 3093)
+++ 	(revision )
@@ -1,108 +1,0 @@
-package org.openstreetmap.josm.gui.download;
-
-import java.awt.Graphics;
-import java.awt.Point;
-
-import javax.swing.ImageIcon;
-
-import org.openstreetmap.josm.tools.ImageProvider;
-
-public class SourceButton {
-
-    private int x = 0;
-    private int y = 30;
-
-    private ImageIcon enlargeImage;
-    private ImageIcon shrinkImage;
-    private ImageIcon imageMapnik;
-    private ImageIcon imageOsmarender;
-    private ImageIcon imageCycleMap;
-
-    private boolean isEnlarged = false;
-
-    private int currentMap = MAPNIK;
-
-    public static final int HIDE_OR_SHOW = 1;
-    public static final int MAPNIK = 2;
-    public static final int OSMARENDER = 3;
-    public static final int CYCLEMAP = 4;
-
-    public SourceButton() {
-        enlargeImage = ImageProvider.get("layer-switcher-maximize.png");
-        shrinkImage = ImageProvider.get("layer-switcher-minimize.png");
-        imageMapnik = ImageProvider.get("blue_Mapnik.png");
-        imageOsmarender = ImageProvider.get("blue_Osmarender.png");
-        imageCycleMap = ImageProvider.get("blue_CycleMap.png");
-    }
-
-    public void paint(Graphics g) {
-
-        if (isEnlarged) {
-            if (currentMap == MAPNIK) {
-                g.drawImage(imageMapnik.getImage(), g.getClipBounds().width
-                        - imageMapnik.getIconWidth(), y, null);
-            }else if(currentMap == CYCLEMAP){
-                 g.drawImage(imageCycleMap.getImage(), g.getClipBounds().width
-                         - imageCycleMap.getIconWidth(), y, null);
-            }
-            else {
-                g.drawImage(imageOsmarender.getImage(), g.getClipBounds().width
-                        - imageMapnik.getIconWidth(), y, null);
-            }
-
-            if (shrinkImage != null) {
-                this.x = g.getClipBounds().width - shrinkImage.getIconWidth();
-                g.drawImage(shrinkImage.getImage(), x, y, null);
-            }
-
-        } else {
-            if (enlargeImage != null) {
-                this.x = g.getClipBounds().width - enlargeImage.getIconWidth();
-                g.drawImage(enlargeImage.getImage(), x, y, null);
-            }
-        }
-    }
-
-    public void toggle() {
-        this.isEnlarged = !this.isEnlarged;
-
-    }
-
-    public int hit(Point point) {
-        if (isEnlarged) {
-            if (x < point.x && point.x < x + shrinkImage.getIconWidth()) {
-                if (y < point.y && point.y < y + shrinkImage.getIconHeight()) {
-                    return HIDE_OR_SHOW;
-                }
-            } else if (x - imageMapnik.getIconWidth() < point.x && point.x < x) {
-                if (y < point.y && point.y < y + imageMapnik.getIconHeight() / 3) {
-                    currentMap = OSMARENDER;
-                    return OSMARENDER;
-                } else if (y + imageMapnik.getIconHeight() / 3 < point.y
-                        && point.y < y + imageMapnik.getIconHeight() *2/3) {
-                    currentMap = MAPNIK;
-                    return MAPNIK;
-                } else if (y + imageMapnik.getIconHeight()* 2/3 < point.y
-                        && point.y < y + imageMapnik.getIconHeight()) {
-                    currentMap = CYCLEMAP;
-                    return CYCLEMAP;
-                }
-            }
-        } else {
-            if (x < point.x && point.x < x + enlargeImage.getIconWidth()) {
-                if (y < point.y && point.y < y + enlargeImage.getIconHeight()) {
-                    return HIDE_OR_SHOW;
-                }
-            }
-        }
-
-        return 0;
-    }
-
-    /**
-     * One of the constants OSMARENDER,MAPNIK or CYCLEMAP
-     */
-    public void setMapStyle (int style) {
-       currentMap = (style < 2 || style > 4) ? MAPNIK : style;
-    }
-}
Index: /trunk/src/org/openstreetmap/josm/gui/download/TileSelection.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/download/TileSelection.java	(revision 3093)
+++ /trunk/src/org/openstreetmap/josm/gui/download/TileSelection.java	(revision 3094)
@@ -4,19 +4,10 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.Dimension;
-import java.awt.GridBagLayout;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSpinner;
-import javax.swing.JTextField;
-import javax.swing.SpinnerNumberModel;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.gui.bbox.BBoxChooser;
+import org.openstreetmap.josm.gui.bbox.TileSelectionBBoxChooser;
 /**
  * Tile selector.
@@ -27,95 +18,33 @@
  *
  */
-public class TileSelection implements DownloadSelection {
+public class TileSelection implements DownloadSelection, PropertyChangeListener {
 
-    private JTextField tileX0 = new JTextField(7);
-    private JTextField tileY0 = new JTextField(7);
-    private JTextField tileX1 = new JTextField(7);
-    private JTextField tileY1 = new JTextField(7);
-    private JSpinner tileZ = new JSpinner(new SpinnerNumberModel(12, 10, 18, 1));
+    private TileSelectionBBoxChooser chooser;
+    private DownloadDialog parent;
+
+    protected void build() {
+        chooser = new TileSelectionBBoxChooser();
+        chooser.addPropertyChangeListener(this);
+    }
+
+    public TileSelection() {
+        build();
+    }
 
     public void addGui(final DownloadDialog gui) {
-
-        JPanel smpanel = new JPanel(new GridBagLayout());
-        smpanel.add(new JLabel(tr("zoom level")), GBC.std().insets(0,0,10,0));
-        smpanel.add(new JLabel(tr("x from")), GBC.std().insets(10,0,5,0));
-        smpanel.add(tileX0, GBC.std());
-        smpanel.add(new JLabel(tr("to")), GBC.std().insets(10,0,5,0));
-        smpanel.add(tileX1, GBC.eol());
-        smpanel.add(tileZ, GBC.std().insets(0,0,10,0));
-        smpanel.add(new JLabel(tr("y from")), GBC.std().insets(10,0,5,0));
-        smpanel.add(tileY0, GBC.std());
-        smpanel.add(new JLabel(tr("to")), GBC.std().insets(10,0,5,0));
-        smpanel.add(tileY1, GBC.eol());
-
-        final FocusListener dialogUpdater = new FocusAdapter() {
-            @Override public void focusLost(FocusEvent e) {
-                try {
-                    int zoomlvl = (Integer) tileZ.getValue();
-                    int fromx = Integer.parseInt(tileX0.getText());
-                    int tox = fromx;
-                    if (tileX1.getText().length()>0) {
-                        tox = Integer.parseInt(tileX1.getText());
-                    }
-                    if (tox<fromx) { int i = fromx; fromx=tox; tox=i; }
-
-                    int fromy = Integer.parseInt(tileY0.getText());
-                    int toy = fromy;
-                    if (tileY1.getText().length()>0) {
-                        toy = Integer.parseInt(tileY1.getText());
-                    }
-                    if (toy<fromy) { int i = fromy; fromy=toy; toy=i; }
-
-                    Bounds b = new Bounds(
-                            new LatLon(tileYToLat(zoomlvl, toy + 1), tileXToLon(zoomlvl, fromx)),
-                            new LatLon(tileYToLat(zoomlvl, fromy), tileXToLon(zoomlvl, tox + 1))
-                            );
-                    gui.boundingBoxChanged(b, TileSelection.this);
-                    //repaint();
-                } catch (NumberFormatException x) {
-                    // ignore
-                }
-            }
-        };
-
-        for (JTextField f : new JTextField[] { tileX0, tileX1, tileY0, tileY1 }) {
-            f.setMinimumSize(new Dimension(100,new JTextField().getMinimumSize().height));
-            f.addFocusListener(dialogUpdater);
-        }
-
-        gui.addDownloadAreaSelector(smpanel, tr("Tile Numbers"));
+        gui.addDownloadAreaSelector(chooser, tr("Tile Numbers"));
+        parent = gui;
     }
 
     public void setDownloadArea(Bounds area) {
-        if (area == null)
-            return;
-        int z = ((Integer) tileZ.getValue()).intValue();
-        tileX0.setText(Integer.toString(lonToTileX(z, area.getMin().lon())));
-        tileX1.setText(Integer.toString(lonToTileX(z, area.getMax().lon()-.00001)));
-        tileY0.setText(Integer.toString(latToTileY(z, area.getMax().lat()-.00001)));
-        tileY1.setText(Integer.toString(latToTileY(z, area.getMin().lat())));
+        chooser.setBoundingBox(area);
     }
 
-    public static int latToTileY(int zoom, double lat) {
-        if ((zoom < 3) || (zoom > 18)) return -1;
-        double l = lat / 180 * Math.PI;
-        double pf = Math.log(Math.tan(l) + (1/Math.cos(l)));
-        return (int) ((1<<(zoom-1)) * (Math.PI - pf) / Math.PI);
-    }
-
-    public static int lonToTileX(int zoom, double lon) {
-        if ((zoom < 3) || (zoom > 18)) return -1;
-        return (int) ((1<<(zoom-3)) * (lon + 180.0) / 45.0);
-    }
-
-    public static double tileYToLat(int zoom, int y) {
-        if ((zoom < 3) || (zoom > 18)) return Double.MIN_VALUE;
-        return Math.atan(Math.sinh(Math.PI - (Math.PI*y / (1<<(zoom-1))))) * 180 / Math.PI;
-    }
-
-    public static double tileXToLon(int zoom, int x) {
-        if ((zoom < 3) || (zoom > 18)) return Double.MIN_VALUE;
-        return x * 45.0 / (1<<(zoom-3)) - 180.0;
-
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(BBoxChooser.BBOX_PROP)) {
+            Bounds bbox = (Bounds)evt.getNewValue();
+            parent.boundingBoxChanged(bbox, this);
+        }
     }
 }
