Index: /applications/editors/josm/plugins/wmsplugin/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/.settings/org.eclipse.jdt.core.prefs	(revision 22712)
+++ /applications/editors/josm/plugins/wmsplugin/.settings/org.eclipse.jdt.core.prefs	(revision 22712)
@@ -0,0 +1,12 @@
+#Thu Aug 12 20:39:44 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
Index: /applications/editors/josm/plugins/wmsplugin/build.xml
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/build.xml	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/build.xml	(revision 22712)
@@ -29,5 +29,5 @@
 
 	<property name="commit.message" value="add commit message" />
-	<property name="plugin.main.version" value="3408" />
+	<property name="plugin.main.version" value="3451" />
 
 
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/GeorefImage.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/GeorefImage.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/GeorefImage.java	(revision 22712)
@@ -1,8 +1,10 @@
 package wmsplugin;
 
-import java.awt.Dimension;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Image;
-import java.awt.Point;
 import java.awt.Transparency;
 import java.awt.image.BufferedImage;
@@ -18,65 +20,116 @@
 
 public class GeorefImage implements Serializable {
-	public BufferedImage image = null;
-	private Image reImg = null;
-	private Dimension reImgHash = new Dimension(0, 0);
-	public EastNorth min, max;
-	public boolean downloadingStarted;
-	public boolean failed = false;
-	public boolean infotext = false;
-
-	public GeorefImage(boolean downloadingStarted) {
-		this.downloadingStarted = downloadingStarted;
-	}
-
-	public boolean contains(EastNorth en, double dx, double dy) {
-		return min.east()+dx <= en.east() && en.east() <= max.east()+dx
-		&& min.north()+dy <= en.north() && en.north() <= max.north()+dy;
-	}
-
-	public boolean isVisible(NavigatableComponent nc, double dx, double dy) {
-		EastNorth mi = new EastNorth(min.east()+dx, min.north()+dy);
-		EastNorth ma = new EastNorth(max.east()+dx, max.north()+dy);
-		Point minPt = nc.getPoint(mi), maxPt = nc.getPoint(ma);
-		Graphics g = nc.getGraphics();
-
-		return (g.hitClip(minPt.x, maxPt.y,
-				maxPt.x - minPt.x, minPt.y - maxPt.y));
-	}
-
-	public boolean paint(Graphics g, NavigatableComponent nc, double dx, double dy) {
-		if (image == null || min == null || max == null) return false;
-
-		EastNorth mi = new EastNorth(min.east()+dx, min.north()+dy);
-		EastNorth ma = new EastNorth(max.east()+dx, max.north()+dy);
-		Point minPt = nc.getPoint(mi), maxPt = nc.getPoint(ma);
-
-		if(!isVisible(nc, dx, dy)){
+	private static final long serialVersionUID = 1L;
+
+	public enum State { IMAGE, NOT_IN_CACHE, FAILED};
+
+	private final WMSLayer layer;
+	private State state;
+
+	private BufferedImage image;
+	private BufferedImage reImg = null;
+	private int xIndex;
+	private int yIndex;
+
+
+	public EastNorth getMin() {
+		return layer.getEastNorth(xIndex, yIndex);
+	}
+
+	public EastNorth getMax() {
+		return layer.getEastNorth(xIndex+1, yIndex+1);
+	}
+
+
+	public GeorefImage(WMSLayer layer) {
+		this.layer = layer;
+	}
+
+	public void changePosition(int xIndex, int yIndex) {
+		if (!equalPosition(xIndex, yIndex)) {
+			this.xIndex = xIndex;
+			this.yIndex = yIndex;
+			this.image = null;
+			this.reImg = null;
+		}
+	}
+
+	public boolean equalPosition(int xIndex, int yIndex) {
+		return this.xIndex == xIndex && this.yIndex == yIndex;
+	}
+
+	public void changeImage(State state, BufferedImage image) {
+		this.image = image;
+		this.reImg = null;
+		this.state = state;
+
+		switch (state) {
+		case FAILED:
+		{
+			BufferedImage img = createImage();
+			Graphics g = img.getGraphics();
+			g.setColor(Color.RED);
+			g.fillRect(0, 0, img.getWidth(), img.getHeight());
+			g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(36.0f));
+			g.setColor(Color.BLACK);
+			g.drawString(tr("Exception occurred"), 10, img.getHeight()/2);
+			this.image = img;
+			break;
+		}
+		case NOT_IN_CACHE:
+		{
+			BufferedImage img = createImage();
+			Graphics g = img.getGraphics();
+			g.setColor(Color.GRAY);
+			g.fillRect(0, 0, img.getWidth(), img.getHeight());
+			Font font = g.getFont();
+			Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f);
+			g.setFont(tempFont);
+			g.setColor(Color.BLACK);
+			g.drawString(tr("Not in cache"), 10, img.getHeight()/2);
+			g.setFont(font);
+			this.image = img;
+			break;
+		}
+		default:
+			break;
+		}
+	}
+
+	private BufferedImage createImage() {
+		int left = layer.getImageX(xIndex);
+		int bottom = layer.getImageY(yIndex);
+		int width = layer.getImageX(xIndex + 1) - left;
+		int height = layer.getImageY(yIndex + 1) - bottom;
+
+		return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+	}
+
+	public boolean paint(Graphics g, NavigatableComponent nc, int xIndex, int yIndex, int leftEdge, int bottomEdge) {
+		if (image == null)
 			return false;
-		}
-
-		// Width and height flicker about 2 pixels due to rounding errors, typically only 1
-		int width = Math.abs(maxPt.x-minPt.x);
-		int height = Math.abs(minPt.y-maxPt.y);
-		int diffx, diffy;
-
-		diffx = reImgHash.width - width;
-		diffy = reImgHash.height - height;
+
+		if(!(this.xIndex == xIndex && this.yIndex == yIndex)){
+			return false;
+		}
+
+		int left = layer.getImageX(xIndex);
+		int bottom = layer.getImageY(yIndex);
+		int width = layer.getImageX(xIndex + 1) - left;
+		int height = layer.getImageY(yIndex + 1) - bottom;
+
+		int x = left - leftEdge;
+		int y = nc.getHeight() - (bottom - bottomEdge) - height;
+
 		// This happens if you zoom outside the world
 		if(width == 0 || height == 0)
 			return false;
 
-		// We still need to re-render if the requested size is larger (otherwise we'll have black lines)
-		// If it's only up to two pixels smaller, just draw the old image, the errors are minimal
-		// but the performance improvements when moving are huge
-		// Zooming is still slow because the images need to be resized
-		if(diffx >= 0 && diffx <= 2 && diffy >= 0 && diffy <= 2 && reImg != null) {
-			/*g.setColor(Color.RED);
-              g.drawRect(minPt.x, minPt.y-height, width, height);*/
-			g.drawImage(reImg, minPt.x, maxPt.y, null);
+		if(reImg != null && reImg.getWidth() == width && reImg.getHeight() == height) {
+			g.drawImage(reImg, x, y, null);
 			return true;
 		}
 
-		boolean alphaChannel = WMSLayer.PROP_ALPHA_CHANNEL.get() && image.getTransparency() != Transparency.OPAQUE;
+		boolean alphaChannel = WMSLayer.PROP_ALPHA_CHANNEL.get() && getImage().getTransparency() != Transparency.OPAQUE;
 
 		try {
@@ -93,42 +146,36 @@
 			// Also prevent caching if we're out of memory soon
 			if(width > 2000 || height > 2000 || width*height*multipl > freeMem) {
-				fallbackDraw(g, image, minPt, maxPt);
+				fallbackDraw(g, getImage(), x, y, width, height);
 			} else {
 				// We haven't got a saved resized copy, so resize and cache it
 				reImg = new BufferedImage(width, height, alphaChannel?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_3BYTE_BGR);
-				reImg.getGraphics().drawImage(image,
+				reImg.getGraphics().drawImage(getImage(),
 						0, 0, width, height, // dest
-						0, 0, image.getWidth(null), image.getHeight(null), // src
+						0, 0, getImage().getWidth(null), getImage().getHeight(null), // src
 						null);
 				reImg.getGraphics().dispose();
-
-				reImgHash.setSize(width, height);
-				/*g.setColor(Color.RED);
-                  g.drawRect(minPt.x, minPt.y-height, width, height);*/
-				g.drawImage(reImg, minPt.x, maxPt.y, null);
+				g.drawImage(reImg, x, y, null);
 			}
 		} catch(Exception e) {
-			fallbackDraw(g, image, minPt, maxPt);
+			fallbackDraw(g, getImage(), x, y, width, height);
 		}
 		return true;
 	}
 
-	private void fallbackDraw(Graphics g, Image img, Point min, Point max) {
+	private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height) {
 		if(reImg != null) {
 			reImg.flush();
 			reImg = null;
 		}
-		g.drawImage(img,
-				min.x, max.y, max.x, min.y, // dest
-				0, 0, img.getWidth(null), img.getHeight(null), // src
+		g.drawImage(
+				img, x, y, x + width, y + height,
+				0, 0, img.getWidth(null), img.getHeight(null),
 				null);
 	}
 
 	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		max = (EastNorth) in.readObject();
-		min = (EastNorth) in.readObject();
 		boolean hasImage = in.readBoolean();
 		if (hasImage)
-			image = ImageIO.read(ImageIO.createImageInputStream(in));
+			image = (ImageIO.read(ImageIO.createImageInputStream(in)));
 		else {
 			in.readObject(); // read null from input stream
@@ -138,12 +185,10 @@
 
 	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.writeObject(max);
-		out.writeObject(min);
-		if(image == null) {
+		if(getImage() == null) {
 			out.writeBoolean(false);
 			out.writeObject(null);
 		} else {
 			out.writeBoolean(true);
-			ImageIO.write(image, "png", ImageIO.createImageOutputStream(out));
+			ImageIO.write(getImage(), "png", ImageIO.createImageOutputStream(out));
 		}
 	}
@@ -152,3 +197,20 @@
 		reImg = null;
 	}
+
+
+	public BufferedImage getImage() {
+		return image;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	public int getXIndex() {
+		return xIndex;
+	}
+
+	public int getYIndex() {
+		return yIndex;
+	}
 }
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Grabber.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Grabber.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Grabber.java	(revision 22712)
@@ -1,10 +1,3 @@
 package wmsplugin;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.Graphics;
-import java.awt.image.BufferedImage;
 
 import org.openstreetmap.josm.Main;
@@ -15,103 +8,107 @@
 import org.openstreetmap.josm.io.CacheFiles;
 
-abstract public class Grabber extends Thread {
-    protected ProjectionBounds b;
-    protected Projection proj;
-    protected double pixelPerDegree;
-    protected MapView mv;
-    protected WMSLayer layer;
-    protected GeorefImage image;
-    protected CacheFiles cache;
+import wmsplugin.GeorefImage.State;
 
-    Grabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache)
-    {
-        if (b.min != null && b.max != null && WMSPlugin.doOverlap) { 
-            double eastSize =  b.max.east() - b.min.east(); 
-            double northSize =  b.max.north() - b.min.north(); 
+abstract public class Grabber implements Runnable {
+	protected final MapView mv;
+	protected final WMSLayer layer;
+	protected final CacheFiles cache;
 
-            double eastCoef = WMSPlugin.overlapEast / 100.0; 
-            double northCoef = WMSPlugin.overlapNorth / 100.0; 
+	protected ProjectionBounds b;
+	protected Projection proj;
+	protected double pixelPerDegree;
+	protected volatile boolean canceled;
 
-            this.b = new ProjectionBounds( new EastNorth(b.min.east(),
-                                            b.min.north()), 
-                                 new EastNorth(b.max.east() + eastCoef * eastSize, 
-                                            b.max.north() + northCoef * northSize));
-        } else 
-           this.b = b;
+	Grabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+		this.mv = mv;
+		this.layer = layer;
+		this.cache = cache;
+	}
 
-        this.proj = Main.proj;
-        this.pixelPerDegree = layer.pixelPerDegree;
-        this.image = image;
-        this.mv = mv;
-        this.layer = layer;
-        this.cache = cache;
+	private void updateState(WMSRequest request) {
+		b = new ProjectionBounds(
+				layer.getEastNorth(request.getXIndex(), request.getYIndex()),
+				layer.getEastNorth(request.getXIndex() + 1, request.getYIndex() + 1));
+		if (b.min != null && b.max != null && WMSPlugin.doOverlap) {
+			double eastSize =  b.max.east() - b.min.east();
+			double northSize =  b.max.north() - b.min.north();
 
-    }
+			double eastCoef = WMSPlugin.overlapEast / 100.0;
+			double northCoef = WMSPlugin.overlapNorth / 100.0;
 
-    abstract void fetch() throws Exception; // the image fetch code
+			this.b = new ProjectionBounds( new EastNorth(b.min.east(),
+					b.min.north()),
+					new EastNorth(b.max.east() + eastCoef * eastSize,
+							b.max.north() + northCoef * northSize));
+		}
 
-    int width(){
-        return (int) ((b.max.north() - b.min.north()) * pixelPerDegree);
-    }
-    int height(){
-        return (int) ((b.max.east() - b.min.east()) * pixelPerDegree);
-    }
+		this.proj = Main.proj;
+		this.pixelPerDegree = request.getPixelPerDegree();
+	}
 
-    protected void grabError(Exception e){ // report error when grabing image
-        e.printStackTrace();
+	abstract void fetch(WMSRequest request) throws Exception; // the image fetch code
 
-        BufferedImage img = new BufferedImage(width(), height(), BufferedImage.TYPE_INT_ARGB);
-        Graphics g = img.getGraphics();
-        g.setColor(Color.RED);
-        g.fillRect(0, 0, width(), height());
-        Font font = g.getFont();
-        Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f);
-        g.setFont(tempFont);
-        g.setColor(Color.BLACK);
-        g.drawString(tr("Exception occurred"), 10, height()/2);
-        image.image = img;
-        image.flushedResizedCachedInstance();
-        image.failed = true;
-        image.downloadingStarted = false;
-        g.setFont(font);
-    }
+	int width(){
+		return (int) ((b.max.north() - b.min.north()) * pixelPerDegree);
+	}
+	int height(){
+		return (int) ((b.max.east() - b.min.east()) * pixelPerDegree);
+	}
 
-    protected void grabNotInCache(){ // report not in cache
-        BufferedImage img = new BufferedImage(width(), height(), BufferedImage.TYPE_INT_ARGB);
-        Graphics g = img.getGraphics();
-        g.setColor(Color.GRAY);
-        g.fillRect(0, 0, width(), height());
-        Font font = g.getFont();
-        Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f);
-        g.setFont(tempFont);
-        g.setColor(Color.BLACK);
-        g.drawString(tr("Not in cache"), 10, height()/2);
-        image.image = img;
-        image.flushedResizedCachedInstance();
-        image.infotext = true;
-        image.downloadingStarted = false;
-        g.setFont(font);
-    }
+	@Override
+	public void run() {
+		while (true) {
+			if (canceled) {
+				return;
+			}
+			WMSRequest request = layer.getRequest();
+			if (request == null) {
+				return;
+			}
+			updateState(request);
+			if(!loadFromCache(request)){
+				attempt(request);
+			}
+			if (canceled) {
+				return;
+			}
+			layer.finishRequest(request);
+			mv.repaint();
+		}
+	}
 
-    protected void attempt(){ // try to fetch the image
-        int maxTries = 5; // n tries for every image
-        for (int i = 1; i <= maxTries; i++) {
-            try {
-                fetch();
-                break; // break out of the retry loop
-            } catch (Exception e) {
-                try { // sleep some time and then ask the server again
-                    Thread.sleep(random(1000, 2000));
-                } catch (InterruptedException e1) {}
+	protected void attempt(WMSRequest request){ // try to fetch the image
+		int maxTries = 5; // n tries for every image
+		for (int i = 1; i <= maxTries; i++) {
+			if (canceled) {
+				return;
+			}
+			try {
+				if (!layer.requestIsValid(request)) {
+					return;
+				}
+				fetch(request);
+				break; // break out of the retry loop
+			} catch (Exception e) {
+				try { // sleep some time and then ask the server again
+					Thread.sleep(random(1000, 2000));
+				} catch (InterruptedException e1) {}
 
-                if(i == maxTries) grabError(e);
-            }
-        }
-    }
+				if(i == maxTries) {
+					e.printStackTrace();
+					request.finish(State.FAILED, null);
+				}
+			}
+		}
+	}
 
-    public static int random(int min, int max) {
-        return (int)(Math.random() * ((max+1)-min) ) + min;
-    }
+	public static int random(int min, int max) {
+		return (int)(Math.random() * ((max+1)-min) ) + min;
+	}
 
-    abstract public boolean loadFromCache(boolean real);
+	abstract public boolean loadFromCache(WMSRequest request);
+
+	public void cancel() {
+		canceled = true;
+	}
 }
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/HTMLGrabber.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/HTMLGrabber.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/HTMLGrabber.java	(revision 22712)
@@ -11,38 +11,37 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.io.CacheFiles;
 
 public class HTMLGrabber extends WMSGrabber {
-    HTMLGrabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache) {
-        super(b, image, mv, layer, cache);
-        this.baseURL = layer.baseURL.replaceFirst("html:", "");
-    }
+	HTMLGrabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+		super(mv, layer, cache);
+		this.baseURL = layer.baseURL.replaceFirst("html:", "");
+	}
 
-    @Override
-    protected BufferedImage grab(URL url) throws IOException {
-        String urlstring = url.toExternalForm();
+	@Override
+	protected BufferedImage grab(URL url) throws IOException {
+		String urlstring = url.toExternalForm();
 
-        System.out.println("Grabbing HTML " + url);
+		System.out.println("Grabbing HTML " + url);
 
-        ArrayList<String> cmdParams = new ArrayList<String>();
-        StringTokenizer st = new StringTokenizer(MessageFormat.format(
-        Main.pref.get("wmsplugin.browser", "webkit-image {0}"), urlstring));
-        while( st.hasMoreTokens() )
-            cmdParams.add(st.nextToken());
+		ArrayList<String> cmdParams = new ArrayList<String>();
+		StringTokenizer st = new StringTokenizer(MessageFormat.format(
+				Main.pref.get("wmsplugin.browser", "webkit-image {0}"), urlstring));
+		while( st.hasMoreTokens() )
+			cmdParams.add(st.nextToken());
 
-        ProcessBuilder builder = new ProcessBuilder( cmdParams);
+		ProcessBuilder builder = new ProcessBuilder( cmdParams);
 
-        Process browser;
-        try {
-            browser = builder.start();
-        } catch(IOException ioe) {
-            throw new IOException( "Could not start browser. Please check that the executable path is correct.\n" + ioe.getMessage() );
-        }
+		Process browser;
+		try {
+			browser = builder.start();
+		} catch(IOException ioe) {
+			throw new IOException( "Could not start browser. Please check that the executable path is correct.\n" + ioe.getMessage() );
+		}
 
-        BufferedImage img = ImageIO.read(browser.getInputStream());
-        cache.saveImg(urlstring, img);
-        return img;
-    }
+		BufferedImage img = ImageIO.read(browser.getInputStream());
+		cache.saveImg(urlstring, img);
+		return img;
+	}
 }
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSGrabber.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSGrabber.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSGrabber.java	(revision 22712)
@@ -23,9 +23,8 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.Version;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.projection.Mercator;
-import org.openstreetmap.josm.data.Version;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.io.CacheFiles;
@@ -33,188 +32,173 @@
 import org.openstreetmap.josm.io.ProgressInputStream;
 
+import wmsplugin.GeorefImage.State;
+
 
 public class WMSGrabber extends Grabber {
-    public static boolean isUrlWithPatterns(String url) {
-        return url != null && url.contains("{") && url.contains("}");
-    }
-
-    protected String baseURL;
-    private final boolean urlWithPatterns;
-
-    WMSGrabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache) {
-        super(b, image, mv, layer, cache);
-        this.baseURL = layer.baseURL;
-        /* URL containing placeholders? */
-        urlWithPatterns = isUrlWithPatterns(baseURL);
-    }
-
-    @Override
-	public void run() {
-        attempt();
-        mv.repaint();
-    }
-
-    @Override
-    void fetch() throws Exception{
-        URL url = null;
-        try {
-            url = getURL(
-                b.min.east(), b.min.north(),
-                b.max.east(), b.max.north(),
-                width(), height());
-
-            image.min = b.min;
-            image.max = b.max;
-
-            if(image.isVisible(mv, layer.getDx(), layer.getDy())) { //don't download, if the image isn't visible already
-                image.image = grab(url);
-                image.flushedResizedCachedInstance();
-            }
-            image.downloadingStarted = false;
-        } catch(Exception e) {
-            e.printStackTrace();
-            throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""));
-        }
-    }
-
-    public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000",
-            new DecimalFormatSymbols(Locale.US));
-
-    protected URL getURL(double w, double s,double e,double n,
-            int wi, int ht) throws MalformedURLException {
-        String myProj = Main.proj.toCode();
-        if(Main.proj instanceof Mercator) // don't use mercator code directly
-        {
-            LatLon sw = Main.proj.eastNorth2latlon(new EastNorth(w, s));
-            LatLon ne = Main.proj.eastNorth2latlon(new EastNorth(e, n));
-            myProj = "EPSG:4326";
-            s = sw.lat();
-            w = sw.lon();
-            n = ne.lat();
-            e = ne.lon();
-        }
-
-        String str = baseURL;
-        String bbox = latLonFormat.format(w) + ","
-                           + latLonFormat.format(s) + ","
-                           + latLonFormat.format(e) + ","
-                           + latLonFormat.format(n);
-
-        if (urlWithPatterns) {
-            str = str.replaceAll("\\{proj\\}", myProj)
-            .replaceAll("\\{bbox\\}", bbox)
-            .replaceAll("\\{w\\}", latLonFormat.format(w))
-            .replaceAll("\\{s\\}", latLonFormat.format(s))
-            .replaceAll("\\{e\\}", latLonFormat.format(e))
-            .replaceAll("\\{n\\}", latLonFormat.format(n))
-            .replaceAll("\\{width\\}", String.valueOf(wi))
-            .replaceAll("\\{height\\}", String.valueOf(ht));
-        } else {
-            str += "bbox=" + bbox
-                + getProjection(baseURL, false)
-                + "&width=" + wi + "&height=" + ht;
-            if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
-                System.out.println(tr("Warning: The base URL ''{0}'' for a WMS service doesn't have a trailing '&' or a trailing '?'.", baseURL));
-                System.out.println(tr("Warning: Fetching WMS tiles is likely to fail. Please check you preference settings."));
-                System.out.println(tr("Warning: The complete URL is ''{0}''.", str));
-            }
-        }
-        return new URL(str.replace(" ", "%20"));
-    }
-
-    static public String getProjection(String baseURL, Boolean warn)
-    {
-        String projname = Main.proj.toCode();
-        if(Main.proj instanceof Mercator) // don't use mercator code
-            projname = "EPSG:4326";
-        String res = "";
-        try
-        {
-            Matcher m = Pattern.compile(".*srs=([a-z0-9:]+).*").matcher(baseURL.toLowerCase());
-            if(m.matches())
-            {
-                projname = projname.toLowerCase();
-                if(!projname.equals(m.group(1)) && warn)
-                {
-                    JOptionPane.showMessageDialog(Main.parent,
-                    tr("The projection ''{0}'' in URL and current projection ''{1}'' mismatch.\n"
-                    + "This may lead to wrong coordinates.",
-                    m.group(1), projname),
-                    tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE);
-                }
-            }
-            else
-                res ="&srs="+projname;
-        }
-        catch(Exception e)
-        {
-        }
-        return res;
-    }
-
-    @Override
-	public boolean loadFromCache(boolean real){
-        URL url = null;
-        try{
-           url = getURL(
-              b.min.east(), b.min.north(),
-              b.max.east(), b.max.north(),
-              width(), height());
-        } catch(Exception e) {
-           return false;
-        }
-        BufferedImage cached = cache.getImg(url.toString());
-        if((!real && !layer.hasAutoDownload()) || cached != null){
-           image.min = b.min;
-           image.max = b.max;
-           if(cached == null){
-              grabNotInCache();
-              return true;
-           }
-           image.image = cached;
-           image.flushedResizedCachedInstance();
-           image.downloadingStarted = false;
-           return true;
-        }
-        return false;
-    }
-
-    protected BufferedImage grab(URL url) throws IOException, OsmTransferException {
-        System.out.println("Grabbing WMS " + url);
-
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        if(layer.cookies != null && !layer.cookies.equals(""))
-            conn.setRequestProperty("Cookie", layer.cookies);
-        conn.setRequestProperty("User-Agent", Main.pref.get("wmsplugin.user_agent", Version.getInstance().getAgentString()));
-        conn.setConnectTimeout(Main.pref.getInteger("wmsplugin.timeout.connect", 30) * 1000);
-        conn.setReadTimeout(Main.pref.getInteger("wmsplugin.timeout.read", 30) * 1000);
-
-        String contentType = conn.getHeaderField("Content-Type");
-        if( conn.getResponseCode() != 200
-                || contentType != null && !contentType.startsWith("image") ) {
-            throw new IOException(readException(conn));
-        }
-
-        InputStream is = new ProgressInputStream(conn, null);
-        BufferedImage img = ImageIO.read(is);
-        is.close();
-
-        cache.saveImg(url.toString(), img);
-        return img;
-    }
-
-    protected String readException(URLConnection conn) throws IOException {
-        StringBuilder exception = new StringBuilder();
-        InputStream in = conn.getInputStream();
-        BufferedReader br = new BufferedReader(new InputStreamReader(in));
-
-        String line = null;
-        while( (line = br.readLine()) != null) {
-            // filter non-ASCII characters and control characters
-            exception.append(line.replaceAll("[^\\p{Print}]", ""));
-            exception.append('\n');
-        }
-        return exception.toString();
-    }
+	public static boolean isUrlWithPatterns(String url) {
+		return url != null && url.contains("{") && url.contains("}");
+	}
+
+	protected String baseURL;
+	private final boolean urlWithPatterns;
+
+	WMSGrabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+		super(mv, layer, cache);
+		this.baseURL = layer.baseURL;
+		/* URL containing placeholders? */
+		urlWithPatterns = isUrlWithPatterns(baseURL);
+	}
+
+	@Override
+	void fetch(WMSRequest request) throws Exception{
+		URL url = null;
+		try {
+			url = getURL(
+					b.min.east(), b.min.north(),
+					b.max.east(), b.max.north(),
+					width(), height());
+			request.finish(State.IMAGE, grab(url));
+
+		} catch(Exception e) {
+			e.printStackTrace();
+			throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""));
+		}
+	}
+
+	public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000",
+			new DecimalFormatSymbols(Locale.US));
+
+	protected URL getURL(double w, double s,double e,double n,
+			int wi, int ht) throws MalformedURLException {
+		String myProj = Main.proj.toCode();
+		if(Main.proj instanceof Mercator) // don't use mercator code directly
+		{
+			LatLon sw = Main.proj.eastNorth2latlon(new EastNorth(w, s));
+			LatLon ne = Main.proj.eastNorth2latlon(new EastNorth(e, n));
+			myProj = "EPSG:4326";
+			s = sw.lat();
+			w = sw.lon();
+			n = ne.lat();
+			e = ne.lon();
+		}
+
+		String str = baseURL;
+		String bbox = latLonFormat.format(w) + ","
+		+ latLonFormat.format(s) + ","
+		+ latLonFormat.format(e) + ","
+		+ latLonFormat.format(n);
+
+		if (urlWithPatterns) {
+			str = str.replaceAll("\\{proj\\}", myProj)
+			.replaceAll("\\{bbox\\}", bbox)
+			.replaceAll("\\{w\\}", latLonFormat.format(w))
+			.replaceAll("\\{s\\}", latLonFormat.format(s))
+			.replaceAll("\\{e\\}", latLonFormat.format(e))
+			.replaceAll("\\{n\\}", latLonFormat.format(n))
+			.replaceAll("\\{width\\}", String.valueOf(wi))
+			.replaceAll("\\{height\\}", String.valueOf(ht));
+		} else {
+			str += "bbox=" + bbox
+			+ getProjection(baseURL, false)
+			+ "&width=" + wi + "&height=" + ht;
+			if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
+				System.out.println(tr("Warning: The base URL ''{0}'' for a WMS service doesn't have a trailing '&' or a trailing '?'.", baseURL));
+				System.out.println(tr("Warning: Fetching WMS tiles is likely to fail. Please check you preference settings."));
+				System.out.println(tr("Warning: The complete URL is ''{0}''.", str));
+			}
+		}
+		return new URL(str.replace(" ", "%20"));
+	}
+
+	static public String getProjection(String baseURL, Boolean warn)
+	{
+		String projname = Main.proj.toCode();
+		if(Main.proj instanceof Mercator) // don't use mercator code
+			projname = "EPSG:4326";
+		String res = "";
+		try
+		{
+			Matcher m = Pattern.compile(".*srs=([a-z0-9:]+).*").matcher(baseURL.toLowerCase());
+			if(m.matches())
+			{
+				projname = projname.toLowerCase();
+				if(!projname.equals(m.group(1)) && warn)
+				{
+					JOptionPane.showMessageDialog(Main.parent,
+							tr("The projection ''{0}'' in URL and current projection ''{1}'' mismatch.\n"
+									+ "This may lead to wrong coordinates.",
+									m.group(1), projname),
+									tr("Warning"),
+									JOptionPane.WARNING_MESSAGE);
+				}
+			}
+			else
+				res ="&srs="+projname;
+		}
+		catch(Exception e)
+		{
+		}
+		return res;
+	}
+
+	@Override
+	public boolean loadFromCache(WMSRequest request) {
+		URL url = null;
+		try{
+			url = getURL(
+					b.min.east(), b.min.north(),
+					b.max.east(), b.max.north(),
+					width(), height());
+		} catch(Exception e) {
+			return false;
+		}
+		BufferedImage cached = cache.getImg(url.toString());
+		if((!request.isReal() && !layer.hasAutoDownload()) || cached != null){
+			if(cached == null){
+				request.finish(State.NOT_IN_CACHE, null);
+				return true;
+			}
+			request.finish(State.IMAGE, cached);
+			return true;
+		}
+		return false;
+	}
+
+	protected BufferedImage grab(URL url) throws IOException, OsmTransferException {
+		System.out.println("Grabbing WMS " + url);
+
+		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+		if(layer.cookies != null && !layer.cookies.equals(""))
+			conn.setRequestProperty("Cookie", layer.cookies);
+		conn.setRequestProperty("User-Agent", Main.pref.get("wmsplugin.user_agent", Version.getInstance().getAgentString()));
+		conn.setConnectTimeout(Main.pref.getInteger("wmsplugin.timeout.connect", 30) * 1000);
+		conn.setReadTimeout(Main.pref.getInteger("wmsplugin.timeout.read", 30) * 1000);
+
+		String contentType = conn.getHeaderField("Content-Type");
+		if( conn.getResponseCode() != 200
+				|| contentType != null && !contentType.startsWith("image") ) {
+			throw new IOException(readException(conn));
+		}
+
+		InputStream is = new ProgressInputStream(conn, null);
+		BufferedImage img = ImageIO.read(is);
+		is.close();
+
+		cache.saveImg(url.toString(), img);
+		return img;
+	}
+
+	protected String readException(URLConnection conn) throws IOException {
+		StringBuilder exception = new StringBuilder();
+		InputStream in = conn.getInputStream();
+		BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+		String line = null;
+		while( (line = br.readLine()) != null) {
+			// filter non-ASCII characters and control characters
+			exception.append(line.replaceAll("[^\\p{Print}]", ""));
+			exception.append('\n');
+		}
+		return exception.toString();
+	}
 }
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayer.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayer.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayer.java	(revision 22712)
@@ -13,7 +13,11 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.swing.AbstractAction;
@@ -29,7 +33,7 @@
 import org.openstreetmap.josm.actions.SaveActionBase;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
-import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
@@ -41,4 +45,6 @@
 import org.openstreetmap.josm.io.CacheFiles;
 import org.openstreetmap.josm.tools.ImageProvider;
+
+import wmsplugin.GeorefImage.State;
 
 /**
@@ -69,5 +75,23 @@
 	protected boolean autoDownloadEnabled = true;
 
-	private ExecutorService executor = null;
+	// Image index boundary for current view
+	private volatile int bminx;
+	private volatile int bminy;
+	private volatile int bmaxx;
+	private volatile int bmaxy;
+	private volatile int leftEdge;
+	private volatile int bottomEdge;
+
+
+	private final List<WMSRequest> requestQueue = new ArrayList<WMSRequest>();
+	private final List<WMSRequest> finishedRequests = new ArrayList<WMSRequest>();
+	private final Lock requestQueueLock = new ReentrantLock();
+	private final Condition queueEmpty = requestQueueLock.newCondition();
+	private final List<Grabber> grabbers = new ArrayList<Grabber>();
+	private final List<Thread> grabberThreads = new ArrayList<Thread>();
+	private int threadCount;
+	private int workingThreadCount;
+	private boolean canceled;
+
 
 	/** set to true if this layer uses an invalid base url */
@@ -100,7 +124,5 @@
 		resolution = mv.getDist100PixelText();
 
-		executor = Executors.newFixedThreadPool(
-				Main.pref.getInteger("wmsplugin.numThreads",
-						WMSPlugin.simultaneousConnections));
+		startGrabberThreads();
 		if (baseURL != null && !baseURL.startsWith("html:") && !WMSGrabber.isUrlWithPatterns(baseURL)) {
 			if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
@@ -123,25 +145,8 @@
 	}
 
-	public double getDx(){
-		return dx;
-	}
-
-	public double getDy(){
-		return dy;
-	}
 
 	@Override
 	public void destroy() {
-		try {
-			executor.shutdownNow();
-			// Might not be initialized, so catch NullPointer as well
-		} catch(Exception x) {
-			x.printStackTrace();
-		}
-	}
-
-	public double getPPD(){
-		ProjectionBounds bounds = mv.getProjectionBounds();
-		return mv.getWidth() / (bounds.max.east() - bounds.min.east());
+		cancelGrabberThreads(false);
 	}
 
@@ -150,5 +155,5 @@
 		for(int x = 0; x<dax; ++x) {
 			for(int y = 0; y<day; ++y) {
-				images[x][y]= new GeorefImage(false);
+				images[x][y]= new GeorefImage(this);
 			}
 		}
@@ -173,10 +178,4 @@
 	}
 
-	private ProjectionBounds XYtoBounds (int x, int y) {
-		return new ProjectionBounds(
-				new EastNorth(      x * imageSize / pixelPerDegree,       y * imageSize / pixelPerDegree),
-				new EastNorth((x + 1) * imageSize / pixelPerDegree, (y + 1) * imageSize / pixelPerDegree));
-	}
-
 	private int modulo (int a, int b) {
 		return a % b >= 0 ? a%b : a%b+b;
@@ -188,12 +187,21 @@
 	}
 
-	@Override public void paint(Graphics2D g, final MapView mv, Bounds bounds) {
+	@Override public void paint(Graphics2D g, final MapView mv, Bounds b) {
 		if(baseURL == null) return;
 		if (usesInvalidUrl && !isInvalidUrlConfirmed) return;
 
+		ProjectionBounds bounds = mv.getProjectionBounds();
+		bminx= getImageXIndex(bounds.min.east());
+		bminy= getImageYIndex(bounds.min.north());
+		bmaxx= getImageXIndex(bounds.max.east());
+		bmaxy= getImageYIndex(bounds.max.north());
+
+		leftEdge = (int)(bounds.min.east() * getPPD());
+		bottomEdge = (int)(bounds.min.north() * getPPD());
+
 		if (zoomIsTooBig()) {
-			for(int x = 0; x<dax; ++x) {
-				for(int y = 0; y<day; ++y) {
-					images[modulo(x,dax)][modulo(y,day)].paint(g, mv, dx, dy);
+			for(int x = bminx; x<=bmaxx; ++x) {
+				for(int y = bminy; y<=bmaxy; ++y) {
+					images[modulo(x,dax)][modulo(y,day)].paint(g, mv, x, y, leftEdge, bottomEdge);
 				}
 			}
@@ -201,9 +209,4 @@
 			downloadAndPaintVisible(g, mv, false);
 		}
-	}
-
-	public void displace(double dx, double dy) {
-		this.dx += dx;
-		this.dy += dy;
 	}
 
@@ -237,27 +240,57 @@
 	}
 
-	protected void downloadAndPaintVisible(Graphics g, final MapView mv,
-			boolean real){
-		if (usesInvalidUrl)
-			return;
-
+	public double getPPD(){
 		ProjectionBounds bounds = mv.getProjectionBounds();
-		int bminx= (int)Math.floor (((bounds.min.east() - dx) * pixelPerDegree) / imageSize );
-		int bminy= (int)Math.floor (((bounds.min.north() - dy) * pixelPerDegree) / imageSize );
-		int bmaxx= (int)Math.ceil  (((bounds.max.east() - dx) * pixelPerDegree) / imageSize );
-		int bmaxy= (int)Math.ceil  (((bounds.max.north() - dy) * pixelPerDegree) / imageSize );
-
-		for(int x = bminx; x<bmaxx; ++x) {
-			for(int y = bminy; y<bmaxy; ++y){
+		return mv.getWidth() / (bounds.max.east() - bounds.min.east());
+	}
+
+	public void displace(double dx, double dy) {
+		this.dx += dx;
+		this.dy += dy;
+	}
+
+	public int getImageXIndex(double coord) {
+		return (int)Math.floor( ((coord - dx) * pixelPerDegree) / imageSize);
+	}
+
+	public int getImageYIndex(double coord) {
+		return (int)Math.floor( ((coord - dy) * pixelPerDegree) / imageSize);
+	}
+
+	public int getImageX(int imageIndex) {
+		return (int)(imageIndex * imageSize * (getPPD() / pixelPerDegree) + dx * getPPD());
+	}
+
+	public int getImageY(int imageIndex) {
+		return (int)(imageIndex * imageSize * (getPPD() / pixelPerDegree) + dy * getPPD());
+	}
+
+	/**
+	 *
+	 * @param xIndex
+	 * @param yIndex
+	 * @return Real EastNorth of given tile. dx/dy is not counted in
+	 */
+	public EastNorth getEastNorth(int xIndex, int yIndex) {
+		return new EastNorth((xIndex * imageSize) / pixelPerDegree, (yIndex * imageSize) / pixelPerDegree);
+	}
+
+
+	protected void downloadAndPaintVisible(Graphics g, final MapView mv, boolean real){
+
+		for(int x = bminx; x<=bmaxx; ++x) {
+			for(int y = bminy; y<=bmaxy; ++y){
+				images[modulo(x,dax)][modulo(y,day)].changePosition(x, y);
+			}
+		}
+
+		gatherFinishedRequests();
+
+		for(int x = bminx; x<=bmaxx; ++x) {
+			for(int y = bminy; y<=bmaxy; ++y){
 				GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
-				if((!img.paint(g, mv, dx, dy) || img.infotext) && !img.downloadingStarted){
-					img.downloadingStarted = true;
-					img.image = null;
-					img.flushedResizedCachedInstance();
-					Grabber gr = WMSPlugin.getGrabber(XYtoBounds(x,y), img, mv, this);
-					if(!gr.loadFromCache(real)){
-						gr.setPriority(1);
-						executor.submit(gr);
-					}
+				if (!img.paint(g, mv, x, y, leftEdge, bottomEdge)) {
+					WMSRequest request = new WMSRequest(x, y, pixelPerDegree, real);
+					addRequest(request);
 				}
 			}
@@ -268,7 +301,7 @@
 		for(int x = 0; x<dax; ++x) {
 			for(int y = 0; y<day; ++y)
-				if(images[x][y].image!=null){
-					v.visit(images[x][y].min);
-					v.visit(images[x][y].max);
+				if(images[x][y].getImage() != null){
+					v.visit(images[x][y].getMin());
+					v.visit(images[x][y].getMax());
 				}
 		}
@@ -300,11 +333,120 @@
 
 	public GeorefImage findImage(EastNorth eastNorth) {
-		for(int x = 0; x<dax; ++x) {
-			for(int y = 0; y<day; ++y)
-				if(images[x][y].image!=null && images[x][y].min!=null && images[x][y].max!=null)
-					if(images[x][y].contains(eastNorth, dx, dy))
-						return images[x][y];
-		}
-		return null;
+		int xIndex = getImageXIndex(eastNorth.east());
+		int yIndex = getImageYIndex(eastNorth.north());
+		GeorefImage result = images[modulo(xIndex, dax)][modulo(yIndex, day)];
+		if (result.getXIndex() == xIndex && result.getYIndex() == yIndex) {
+			return result;
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 *
+	 * @param request
+	 * @return -1 if request is no longer needed, otherwise priority of request (lower number <=> more important request)
+	 */
+	private int getRequestPriority(WMSRequest request) {
+		if (request.getPixelPerDegree() != pixelPerDegree) {
+			return -1;
+		}
+		if (bminx > request.getXIndex()
+				|| bmaxx < request.getXIndex()
+				|| bminy > request.getYIndex()
+				|| bmaxy < request.getYIndex()) {
+			return -1;
+		}
+
+		EastNorth cursorEastNorth = mv.getEastNorth(mv.lastMEvent.getX(), mv.lastMEvent.getY());
+		int mouseX = getImageXIndex(cursorEastNorth.east());
+		int mouseY = getImageYIndex(cursorEastNorth.north());
+		int dx = request.getXIndex() - mouseX;
+		int dy = request.getYIndex() - mouseY;
+
+		return dx * dx + dy * dy;
+	}
+
+	public WMSRequest getRequest() {
+		requestQueueLock.lock();
+		try {
+			workingThreadCount--;
+			Iterator<WMSRequest> it = requestQueue.iterator();
+			while (it.hasNext()) {
+				WMSRequest item = it.next();
+				int priority = getRequestPriority(item);
+				if (priority == -1) {
+					it.remove();
+				} else {
+					item.setPriority(priority);
+				}
+			}
+			Collections.sort(requestQueue);
+
+			EastNorth cursorEastNorth = mv.getEastNorth(mv.lastMEvent.getX(), mv.lastMEvent.getY());
+			int mouseX = getImageXIndex(cursorEastNorth.east());
+			int mouseY = getImageYIndex(cursorEastNorth.north());
+			boolean isOnMouse = requestQueue.size() > 0 && requestQueue.get(0).getXIndex() == mouseX && requestQueue.get(0).getYIndex() == mouseY;
+
+			// If there is only one thread left then keep it in case we need to download other tile urgently
+			while (!canceled &&
+					(requestQueue.isEmpty() || (!isOnMouse && threadCount - workingThreadCount == 0 && threadCount > 1))) {
+				try {
+					queueEmpty.await();
+				} catch (InterruptedException e) {
+					// Shouldn't happen
+				}
+			}
+
+			workingThreadCount++;
+			if (canceled) {
+				return null;
+			} else {
+				return requestQueue.remove(0);
+			}
+
+		} finally {
+			requestQueueLock.unlock();
+		}
+	}
+
+	public void finishRequest(WMSRequest request) {
+		requestQueueLock.lock();
+		try {
+			finishedRequests.add(request);
+		} finally {
+			requestQueueLock.unlock();
+		}
+	}
+
+	public void addRequest(WMSRequest request) {
+		requestQueueLock.lock();
+		try {
+			if (!requestQueue.contains(request)) {
+				requestQueue.add(request);
+				queueEmpty.signalAll();
+			}
+		} finally {
+			requestQueueLock.unlock();
+		}
+	}
+
+	public boolean requestIsValid(WMSRequest request) {
+		return bminx <= request.getXIndex() && bmaxx >= request.getXIndex() && bminy <= request.getYIndex() && bmaxy >= request.getYIndex();
+	}
+
+	private void gatherFinishedRequests() {
+		requestQueueLock.lock();
+		try {
+			for (WMSRequest request: finishedRequests) {
+				GeorefImage img = images[modulo(request.getXIndex(),dax)][modulo(request.getYIndex(),day)];
+				if (img.equalPosition(request.getXIndex(), request.getYIndex())) {
+					img.changeImage(request.getState(), request.getImage());
+				}
+			}
+			finishedRequests.clear();
+		} finally {
+			requestQueueLock.unlock();
+		}
 	}
 
@@ -352,9 +494,6 @@
 				for (int y = 0; y < day; ++y) {
 					GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
-					if(img.failed){
-						img.image = null;
-						img.flushedResizedCachedInstance();
-						img.downloadingStarted = false;
-						img.failed = false;
+					if(img.getState() == State.FAILED){
+						addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), pixelPerDegree, true));
 						mv.repaint();
 					}
@@ -513,14 +652,59 @@
 		public void actionPerformed(ActionEvent e) {
 			autoDownloadEnabled = !autoDownloadEnabled;
-		}
-
+			if (autoDownloadEnabled) {
+				mv.repaint();
+			}
+		}
+	}
+
+	private void cancelGrabberThreads(boolean wait) {
+		requestQueueLock.lock();
+		try {
+			canceled = true;
+			for (Grabber grabber: grabbers) {
+				grabber.cancel();
+			}
+			queueEmpty.signalAll();
+		} finally {
+			requestQueueLock.unlock();
+		}
+		if (wait) {
+			for (Thread t: grabberThreads) {
+				try {
+					t.join();
+				} catch (InterruptedException e) {
+					// Shouldn't happen
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private void startGrabberThreads() {
+		int threadCount = WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.get();
+		requestQueueLock.lock();
+		try {
+			canceled = false;
+			grabbers.clear();
+			grabberThreads.clear();
+			for (int i=0; i<threadCount; i++) {
+				Grabber grabber = WMSPlugin.getGrabber(mv, this);
+				grabbers.add(grabber);
+				Thread t = new Thread(grabber, "WMS " + getName() + " " + i);
+				t.setDaemon(true);
+				t.start();
+				grabberThreads.add(t);
+			}
+			this.workingThreadCount = grabbers.size();
+			this.threadCount = grabbers.size();
+		} finally {
+			requestQueueLock.unlock();
+		}
 	}
 
 	public void preferenceChanged(PreferenceChangeEvent event) {
-		if (event.getKey().equals("wmsplugin.simultaneousConnections")) {
-			executor.shutdownNow();
-			executor = Executors.newFixedThreadPool(
-					Main.pref.getInteger("wmsplugin.numThreads",
-							WMSPlugin.simultaneousConnections));
+		if (event.getKey().equals(WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.getKey())) {
+			cancelGrabberThreads(true);
+			startGrabberThreads();
 		}
 	}
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPlugin.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPlugin.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPlugin.java	(revision 22712)
@@ -29,5 +29,5 @@
 import org.openstreetmap.josm.actions.ExtensionFileFilter;
 import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.gui.IconToggleButton;
 import org.openstreetmap.josm.gui.MainMenu;
@@ -48,4 +48,6 @@
 	static CacheFiles cache = new CacheFiles("wmsplugin");
 
+	public static final IntegerProperty PROP_SIMULTANEOUS_CONNECTIONS = new IntegerProperty("wmsplugin.simultaneousConnections", 3);
+
 	WMSLayer wmsLayer;
 	static JMenu wmsJMenu;
@@ -57,5 +59,4 @@
 	static int overlapEast = 14;
 	static int overlapNorth = 4;
-	static int simultaneousConnections = 3;
 	// remember state of menu item to restore on changed preferences
 	static private boolean menuEnabled = false;
@@ -177,9 +178,4 @@
 		} catch (Exception e) {} // If sth fails, we drop to default settings.
 
-		// Load the settings for number of simultaneous connections
-		try {
-			simultaneousConnections = Integer.valueOf(Main.pref.get("wmsplugin.simultanousConnections"));
-		} catch (Exception e) {} // If sth fails, we drop to default settings.
-
 		// And then the names+urls of WMS servers
 		int prefid = 0;
@@ -287,9 +283,9 @@
 	}
 
-	public static Grabber getGrabber(ProjectionBounds bounds, GeorefImage img, MapView mv, WMSLayer layer){
+	public static Grabber getGrabber(MapView mv, WMSLayer layer){
 		if(layer.baseURL.startsWith("html:"))
-			return new HTMLGrabber(bounds, img, mv, layer, cache);
+			return new HTMLGrabber(mv, layer, cache);
 		else
-			return new WMSGrabber(bounds, img, mv, layer, cache);
+			return new WMSGrabber(mv, layer, cache);
 	}
 
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java	(revision 22711)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java	(revision 22712)
@@ -187,5 +187,5 @@
 		p.add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
 		JLabel labelSimConn = new JLabel(tr("Simultaneous connections"));
-		spinSimConn = new JSpinner(new SpinnerNumberModel(WMSPlugin.simultaneousConnections, 1, 30, 1));
+		spinSimConn = new JSpinner(new SpinnerNumberModel(WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.get(), 1, 30, 1));
 		JPanel overlapPanelSimConn = new JPanel(new FlowLayout());
 		overlapPanelSimConn.add(labelSimConn);
@@ -243,5 +243,5 @@
 		WMSPlugin.overlapEast = (Integer) spinEast.getModel().getValue();
 		WMSPlugin.overlapNorth = (Integer) spinNorth.getModel().getValue();
-		WMSPlugin.simultaneousConnections = (Integer) spinSimConn.getModel().getValue();
+		WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.put((Integer) spinSimConn.getModel().getValue());
 		allowRemoteControl = remoteCheckBox.getModel().isSelected();
 
@@ -251,5 +251,4 @@
 
 		Main.pref.put("wmsplugin.browser", browser.getEditor().getItem().toString());
-		Main.pref.put("wmsplugin.simultaneousConnections", String.valueOf(WMSPlugin.simultaneousConnections));
 
 		Main.pref.put("wmsplugin.remotecontrol",    String.valueOf(allowRemoteControl));
Index: /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRequest.java
===================================================================
--- /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRequest.java	(revision 22712)
+++ /applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRequest.java	(revision 22712)
@@ -0,0 +1,101 @@
+package wmsplugin;
+
+import java.awt.image.BufferedImage;
+
+import wmsplugin.GeorefImage.State;
+
+public class WMSRequest implements Comparable<WMSRequest> {
+	private final int xIndex;
+	private final int yIndex;
+	private final double pixelPerDegree;
+	private final boolean real; // Download even if autodownloading is disabled
+	private int priority;
+	// Result
+	private State state;
+	private BufferedImage image;
+
+	public WMSRequest(int xIndex, int yIndex, double pixelPerDegree, boolean real) {
+		this.xIndex = xIndex;
+		this.yIndex = yIndex;
+		this.pixelPerDegree = pixelPerDegree;
+		this.real = real;
+	}
+
+	public void finish(State state, BufferedImage image) {
+		this.state = state;
+		this.image = image;
+	}
+
+	public int getXIndex() {
+		return xIndex;
+	}
+
+	public int getYIndex() {
+		return yIndex;
+	}
+
+	public double getPixelPerDegree() {
+		return pixelPerDegree;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		long temp;
+		temp = Double.doubleToLongBits(pixelPerDegree);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		result = prime * result + xIndex;
+		result = prime * result + yIndex;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		WMSRequest other = (WMSRequest) obj;
+		if (Double.doubleToLongBits(pixelPerDegree) != Double
+				.doubleToLongBits(other.pixelPerDegree))
+			return false;
+		if (xIndex != other.xIndex)
+			return false;
+		if (yIndex != other.yIndex)
+			return false;
+		return true;
+	}
+
+	public void setPriority(int priority) {
+		this.priority = priority;
+	}
+
+	public int getPriority() {
+		return priority;
+	}
+
+	public int compareTo(WMSRequest o) {
+		return priority - o.priority;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	public BufferedImage getImage() {
+		return image;
+	}
+
+	@Override
+	public String toString() {
+		return "WMSRequest [xIndex=" + xIndex + ", yIndex=" + yIndex
+		+ ", pixelPerDegree=" + pixelPerDegree + "]";
+	}
+
+	public boolean isReal() {
+		return real;
+	}
+}
