Index: applications/editors/josm/plugins/wmsplugin/build.xml
===================================================================
--- applications/editors/josm/plugins/wmsplugin/build.xml	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/build.xml	(revision 23207)
@@ -29,6 +29,5 @@
 
 	<property name="commit.message" value="fixed josm bug 4671 - wms url for sicily has changed" />
-	<property name="plugin.main.version" value="3451" />
-
+	<property name="plugin.main.version" value="3530" />
 
 	<property name="josm" location="../../core/dist/josm-custom.jar" />
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/GeorefImage.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/GeorefImage.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/GeorefImage.java	(revision 23207)
@@ -21,203 +21,203 @@
 
 public class GeorefImage implements Serializable {
-	private static final long serialVersionUID = 1L;
-
-	public enum State { IMAGE, NOT_IN_CACHE, FAILED};
-
-	private WMSLayer layer;
-	private State state;
-
-	private BufferedImage image;
-	private SoftReference<BufferedImage> reImg;
-	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;
-			flushedResizedCachedInstance();
-		}
-	}
-
-	public boolean equalPosition(int xIndex, int yIndex) {
-		return this.xIndex == xIndex && this.yIndex == yIndex;
-	}
-
-	public void changeImage(State state, BufferedImage image) {
-		flushedResizedCachedInstance();
-		this.image = image;
-		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() {
-		return new BufferedImage(layer.getBaseImageWidth(), layer.getBaseImageHeight(), BufferedImage.TYPE_INT_RGB);
-	}
-
-	public boolean paint(Graphics g, NavigatableComponent nc, int xIndex, int yIndex, int leftEdge, int bottomEdge) {
-		if (image == null)
-			return false;
-
-		if(!(this.xIndex == xIndex && this.yIndex == yIndex)){
-			return false;
-		}
-
-		int left = layer.getImageX(xIndex);
-		int bottom = layer.getImageY(yIndex);
-		int width = layer.getImageWidth(xIndex);
-		int height = layer.getImageHeight(yIndex);
-
-		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;
-
-		BufferedImage img = reImg == null?null:reImg.get();
-		if(img != null && img.getWidth() == width && img.getHeight() == height) {
-			g.drawImage(img, x, y, null);
-			return true;
-		}
-
-		boolean alphaChannel = WMSLayer.PROP_ALPHA_CHANNEL.get() && getImage().getTransparency() != Transparency.OPAQUE;
-
-		try {
-			if(img != null) img.flush();
-			long freeMem = Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory();
-			//System.out.println("Free Memory:           "+ (freeMem/1024/1024) +" MB");
-			// Notice that this value can get negative due to integer overflows
-			//System.out.println("Img Size:              "+ (width*height*3/1024/1024) +" MB");
-
-			int multipl = alphaChannel ? 4 : 3;
-			// This happens when requesting images while zoomed out and then zooming in
-			// Storing images this large in memory will certainly hang up JOSM. Luckily
-			// traditional rendering is as fast at these zoom levels, so it's no loss.
-			// Also prevent caching if we're out of memory soon
-			if(width > 2000 || height > 2000 || width*height*multipl > freeMem) {
-				fallbackDraw(g, getImage(), x, y, width, height);
-			} else {
-				// We haven't got a saved resized copy, so resize and cache it
-				img = new BufferedImage(width, height, alphaChannel?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_3BYTE_BGR);
-				img.getGraphics().drawImage(getImage(),
-						0, 0, width, height, // dest
-						0, 0, getImage().getWidth(null), getImage().getHeight(null), // src
-						null);
-				img.getGraphics().dispose();
-				g.drawImage(img, x, y, null);
-				reImg = new SoftReference<BufferedImage>(img);
-			}
-		} catch(Exception e) {
-			fallbackDraw(g, getImage(), x, y, width, height);
-		}
-		return true;
-	}
-
-	private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height) {
-		flushedResizedCachedInstance();
-		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 {
-		state = (State) in.readObject();
-		boolean hasImage = in.readBoolean();
-		if (hasImage)
-			image = (ImageIO.read(ImageIO.createImageInputStream(in)));
-		else {
-			in.readObject(); // read null from input stream
-			image = null;
-		}
-	}
-
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.writeObject(state);
-		if(getImage() == null) {
-			out.writeBoolean(false);
-			out.writeObject(null);
-		} else {
-			out.writeBoolean(true);
-			ImageIO.write(getImage(), "png", ImageIO.createImageOutputStream(out));
-		}
-	}
-
-	public void flushedResizedCachedInstance() {
-		if (reImg != null) {
-			BufferedImage img = reImg.get();
-			if (img != null) {
-				img.flush();
-			}
-		}
-		reImg = null;
-	}
-
-
-	public BufferedImage getImage() {
-		return image;
-	}
-
-	public State getState() {
-		return state;
-	}
-
-	public int getXIndex() {
-		return xIndex;
-	}
-
-	public int getYIndex() {
-		return yIndex;
-	}
-
-	public void setLayer(WMSLayer layer) {
-		this.layer = layer;
-	}
+    private static final long serialVersionUID = 1L;
+
+    public enum State { IMAGE, NOT_IN_CACHE, FAILED};
+
+    private WMSLayer layer;
+    private State state;
+
+    private BufferedImage image;
+    private SoftReference<BufferedImage> reImg;
+    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;
+            flushedResizedCachedInstance();
+        }
+    }
+
+    public boolean equalPosition(int xIndex, int yIndex) {
+        return this.xIndex == xIndex && this.yIndex == yIndex;
+    }
+
+    public void changeImage(State state, BufferedImage image) {
+        flushedResizedCachedInstance();
+        this.image = image;
+        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() {
+        return new BufferedImage(layer.getBaseImageWidth(), layer.getBaseImageHeight(), BufferedImage.TYPE_INT_RGB);
+    }
+
+    public boolean paint(Graphics g, NavigatableComponent nc, int xIndex, int yIndex, int leftEdge, int bottomEdge) {
+        if (image == null)
+            return false;
+
+        if(!(this.xIndex == xIndex && this.yIndex == yIndex)){
+            return false;
+        }
+
+        int left = layer.getImageX(xIndex);
+        int bottom = layer.getImageY(yIndex);
+        int width = layer.getImageWidth(xIndex);
+        int height = layer.getImageHeight(yIndex);
+
+        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;
+
+        BufferedImage img = reImg == null?null:reImg.get();
+        if(img != null && img.getWidth() == width && img.getHeight() == height) {
+            g.drawImage(img, x, y, null);
+            return true;
+        }
+
+        boolean alphaChannel = WMSLayer.PROP_ALPHA_CHANNEL.get() && getImage().getTransparency() != Transparency.OPAQUE;
+
+        try {
+            if(img != null) img.flush();
+            long freeMem = Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory();
+            //System.out.println("Free Memory:           "+ (freeMem/1024/1024) +" MB");
+            // Notice that this value can get negative due to integer overflows
+            //System.out.println("Img Size:              "+ (width*height*3/1024/1024) +" MB");
+
+            int multipl = alphaChannel ? 4 : 3;
+            // This happens when requesting images while zoomed out and then zooming in
+            // Storing images this large in memory will certainly hang up JOSM. Luckily
+            // traditional rendering is as fast at these zoom levels, so it's no loss.
+            // Also prevent caching if we're out of memory soon
+            if(width > 2000 || height > 2000 || width*height*multipl > freeMem) {
+                fallbackDraw(g, getImage(), x, y, width, height);
+            } else {
+                // We haven't got a saved resized copy, so resize and cache it
+                img = new BufferedImage(width, height, alphaChannel?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_3BYTE_BGR);
+                img.getGraphics().drawImage(getImage(),
+                        0, 0, width, height, // dest
+                        0, 0, getImage().getWidth(null), getImage().getHeight(null), // src
+                        null);
+                img.getGraphics().dispose();
+                g.drawImage(img, x, y, null);
+                reImg = new SoftReference<BufferedImage>(img);
+            }
+        } catch(Exception e) {
+            fallbackDraw(g, getImage(), x, y, width, height);
+        }
+        return true;
+    }
+
+    private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height) {
+        flushedResizedCachedInstance();
+        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 {
+        state = (State) in.readObject();
+        boolean hasImage = in.readBoolean();
+        if (hasImage)
+            image = (ImageIO.read(ImageIO.createImageInputStream(in)));
+        else {
+            in.readObject(); // read null from input stream
+            image = null;
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeObject(state);
+        if(getImage() == null) {
+            out.writeBoolean(false);
+            out.writeObject(null);
+        } else {
+            out.writeBoolean(true);
+            ImageIO.write(getImage(), "png", ImageIO.createImageOutputStream(out));
+        }
+    }
+
+    public void flushedResizedCachedInstance() {
+        if (reImg != null) {
+            BufferedImage img = reImg.get();
+            if (img != null) {
+                img.flush();
+            }
+        }
+        reImg = null;
+    }
+
+
+    public BufferedImage getImage() {
+        return image;
+    }
+
+    public State getState() {
+        return state;
+    }
+
+    public int getXIndex() {
+        return xIndex;
+    }
+
+    public int getYIndex() {
+        return yIndex;
+    }
+
+    public void setLayer(WMSLayer layer) {
+        this.layer = layer;
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Grabber.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Grabber.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Grabber.java	(revision 23207)
@@ -11,105 +11,105 @@
 
 abstract public class Grabber implements Runnable {
-	protected final MapView mv;
-	protected final WMSLayer layer;
-	protected final CacheFiles cache;
+    protected final MapView mv;
+    protected final WMSLayer layer;
+    protected final CacheFiles cache;
 
-	protected ProjectionBounds b;
-	protected Projection proj;
-	protected double pixelPerDegree;
-	protected WMSRequest request;
-	protected volatile boolean canceled;
+    protected ProjectionBounds b;
+    protected Projection proj;
+    protected double pixelPerDegree;
+    protected WMSRequest request;
+    protected volatile boolean canceled;
 
-	Grabber(MapView mv, WMSLayer layer, CacheFiles cache) {
-		this.mv = mv;
-		this.layer = layer;
-		this.cache = cache;
-	}
+    Grabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+        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.PROP_OVERLAP.get()) {
-			double eastSize =  b.max.east() - b.min.east();
-			double northSize =  b.max.north() - b.min.north();
+    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.instance.PROP_OVERLAP.get()) {
+            double eastSize =  b.max.east() - b.min.east();
+            double northSize =  b.max.north() - b.min.north();
 
-			double eastCoef = WMSPlugin.PROP_OVERLAP_EAST.get() / 100.0;
-			double northCoef = WMSPlugin.PROP_OVERLAP_NORTH.get() / 100.0;
+            double eastCoef = WMSPlugin.instance.PROP_OVERLAP_EAST.get() / 100.0;
+            double northCoef = WMSPlugin.instance.PROP_OVERLAP_NORTH.get() / 100.0;
 
-			this.b = new ProjectionBounds( new EastNorth(b.min.east(),
-					b.min.north()),
-					new EastNorth(b.max.east() + eastCoef * eastSize,
-							b.max.north() + northCoef * northSize));
-		}
+            this.b = new ProjectionBounds( new EastNorth(b.min.east(),
+                    b.min.north()),
+                    new EastNorth(b.max.east() + eastCoef * eastSize,
+                            b.max.north() + northCoef * northSize));
+        }
 
-		this.proj = Main.proj;
-		this.pixelPerDegree = request.getPixelPerDegree();
-		this.request = request;
-	}
+        this.proj = Main.proj;
+        this.pixelPerDegree = request.getPixelPerDegree();
+        this.request = request;
+    }
 
-	abstract void fetch(WMSRequest request) throws Exception; // the image fetch code
+    abstract void fetch(WMSRequest request) throws Exception; // the image fetch code
 
-	int width(){
-		return layer.getBaseImageWidth();
-	}
-	int height(){
-		return layer.getBaseImageHeight();
-	}
+    int width(){
+        return layer.getBaseImageWidth();
+    }
+    int height(){
+        return layer.getBaseImageHeight();
+    }
 
-	@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 (request.getState() != null) {
-				layer.finishRequest(request);
-				mv.repaint();
-			}
-		}
-	}
+    @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 (request.getState() != null) {
+                layer.finishRequest(request);
+                mv.repaint();
+            }
+        }
+    }
 
-	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) {}
+    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) {
-					e.printStackTrace();
-					request.finish(State.FAILED, null);
-				}
-			}
-		}
-	}
+                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(WMSRequest request);
+    abstract public boolean loadFromCache(WMSRequest request);
 
-	public void cancel() {
-		canceled = true;
-	}
+    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 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/HTMLGrabber.java	(revision 23207)
@@ -15,33 +15,32 @@
 
 public class HTMLGrabber extends WMSGrabber {
-	HTMLGrabber(MapView mv, WMSLayer layer, CacheFiles cache) {
-		super(mv, layer, cache);
-		this.baseURL = layer.baseURL.replaceFirst("html:", "");
-	}
+    HTMLGrabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+        super(mv, layer, cache);
+    }
 
-	@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/Map_Rectifier_WMSmenuAction.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java	(revision 23207)
@@ -29,219 +29,208 @@
 
 public class Map_Rectifier_WMSmenuAction extends JosmAction {
-	/**
-	 * Class that bundles all required information of a rectifier service
-	 */
-	public static class RectifierService {
-		private final String name;
-		private final String url;
-		private final String wmsUrl;
-		private final Pattern urlRegEx;
-		private final Pattern idValidator;
-		public JRadioButton btn;
-		/**
-		 * @param name: Name of the rectifing service
-		 * @param url: URL to the service where users can register, upload, etc.
-		 * @param wmsUrl: URL to the WMS server where JOSM will grab the images. Insert __s__ where the ID should be placed
-		 * @param urlRegEx: a regular expression that determines if a given URL is one of the service and returns the WMS id if so
-		 * @param idValidator: regular expression that checks if a given ID is syntactically valid
-		 */
-		public RectifierService(String name, String url, String wmsUrl, String urlRegEx, String idValidator) {
-			this.name = name;
-			this.url = url;
-			this.wmsUrl = wmsUrl;
-			this.urlRegEx = Pattern.compile(urlRegEx);
-			this.idValidator = Pattern.compile(idValidator);
-		}
-
-		public boolean isSelected() {
-			return btn.isSelected();
-		}
-	}
-
-	/**
-	 * List of available rectifier services. May be extended from the outside
-	 */
-	public ArrayList<RectifierService> services = new ArrayList<RectifierService>();
-
-	public Map_Rectifier_WMSmenuAction() {
-		super(tr("Rectified Image..."),
-				"OLmarker",
-				tr("Download Rectified Images From Various Services"),
-				Shortcut.registerShortcut("wms:rectimg",
-						tr("WMS: {0}", tr("Rectified Image...")),
-						KeyEvent.VK_R,
-						Shortcut.GROUP_NONE),
-						true
-		);
-
-		// Add default services
-		services.add(
-				new RectifierService("Metacarta Map Rectifier",
-						"http://labs.metacarta.com/rectifier/",
-						"http://labs.metacarta.com/rectifier/wms.cgi?id=__s__&srs=EPSG:4326"
-						+ "&Service=WMS&Version=1.1.0&Request=GetMap&format=image/png&",
-						// This matches more than the "classic" WMS link, so users can pretty much
-						// copy any link as long as it includes the ID
-						"labs\\.metacarta\\.com/(?:.*?)(?:/|=)([0-9]+)(?:\\?|/|\\.|$)",
-				"^[0-9]+$")
-		);
-		services.add(
-				// TODO: Change all links to mapwarper.net once the project has moved.
-				// The RegEx already matches the new URL and old URLs will be forwarded
-				// to make the transition as smooth as possible for the users
-				new RectifierService("Geothings Map Warper",
-						"http://warper.geothings.net/",
-						"http://warper.geothings.net/maps/wms/__s__?request=GetMap&version=1.1.1"
-						+ "&styles=&format=image/png&srs=epsg:4326&exceptions=application/vnd.ogc.se_inimage&",
-						// This matches more than the "classic" WMS link, so users can pretty much
-						// copy any link as long as it includes the ID
-						"(?:mapwarper\\.net|warper\\.geothings\\.net)/(?:.*?)/([0-9]+)(?:\\?|/|\\.|$)",
-				"^[0-9]+$")
-		);
-
-		// This service serves the purpose of "just this once" without forcing the user
-		// to commit the link to the preferences
-
-		// Clipboard content gets trimmed, so matching whitespace only ensures that this
-		// service will never be selected automatically.
-		services.add(new RectifierService(tr("Custom WMS Link"), "", "", "^\\s+$", ""));
-	}
-
-	public void actionPerformed(ActionEvent e) {
-		JPanel panel = new JPanel(new GridBagLayout());
-		panel.add(new JLabel(tr("Supported Rectifier Services:")), GBC.eol());
-
-		JTextField tfWmsUrl = new JTextField(30);
-
-		String clip = getClipboardContents();
-		ButtonGroup group = new ButtonGroup();
-
-		JRadioButton firstBtn = null;
-		for(RectifierService s : services) {
-			JRadioButton serviceBtn = new JRadioButton(s.name);
-			if(firstBtn == null)
-				firstBtn = serviceBtn;
-			// Checks clipboard contents against current service if no match has been found yet.
-			// If the contents match, they will be inserted into the text field and the corresponding
-			// service will be pre-selected.
-			if(!clip.equals("") && tfWmsUrl.getText().equals("")
-					&& (s.urlRegEx.matcher(clip).find() || s.idValidator.matcher(clip).matches())) {
-				serviceBtn.setSelected(true);
-				tfWmsUrl.setText(clip);
-			}
-			s.btn = serviceBtn;
-			group.add(serviceBtn);
-			if(!s.url.equals("")) {
-				panel.add(serviceBtn, GBC.std());
-				panel.add(new UrlLabel(s.url, tr("Visit Homepage")), GBC.eol().anchor(GridBagConstraints.EAST));
-			} else
-				panel.add(serviceBtn, GBC.eol().anchor(GridBagConstraints.WEST));
-		}
-
-		// Fallback in case no match was found
-		if(tfWmsUrl.getText().equals("") && firstBtn != null)
-			firstBtn.setSelected(true);
-
-		panel.add(new JLabel(tr("WMS URL or Image ID:")), GBC.eol());
-		panel.add(tfWmsUrl, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
-
-		ExtendedDialog diag = new ExtendedDialog(Main.parent,
-				tr("Add Rectified Image"),
-
-				new String[] {tr("Add Rectified Image"), tr("Cancel")});
-		diag.setContent(panel);
-		diag.setButtonIcons(new String[] {"OLmarker.png", "cancel.png"});
-
-		// This repeatedly shows the dialog in case there has been an error.
-		// The loop is break;-ed if the users cancels
-		outer: while(true) {
-			diag.showDialog();
-			int answer = diag.getValue();
-			// Break loop when the user cancels
-			if(answer != 1)
-				break;
-
-			String text = tfWmsUrl.getText().trim();
-			// Loop all services until we find the selected one
-			for(RectifierService s : services) {
-				if(!s.isSelected())
-					continue;
-
-				// We've reached the custom WMS URL service
-				// Just set the URL and hope everything works out
-				if(s.wmsUrl.equals("")) {
-					addWMSLayer(s.name + " (" + text + ")", text);
-					break outer;
-				}
-
-				// First try to match if the entered string as an URL
-				Matcher m = s.urlRegEx.matcher(text);
-				if(m.find()) {
-					String id = m.group(1);
-					String newURL = s.wmsUrl.replaceAll("__s__", id);
-					String title = s.name + " (" + id + ")";
-					addWMSLayer(title, newURL);
-					break outer;
-				}
-				// If not, look if it's a valid ID for the selected service
-				if(s.idValidator.matcher(text).matches()) {
-					String newURL = s.wmsUrl.replaceAll("__s__", text);
-					String title = s.name + " (" + text + ")";
-					addWMSLayer(title, newURL);
-					break outer;
-				}
-
-				// We've found the selected service, but the entered string isn't suitable for
-				// it. So quit checking the other radio buttons
-				break;
-			}
-
-			// and display an error message. The while(true) ensures that the dialog pops up again
-			JOptionPane.showMessageDialog(Main.parent,
-					tr("Couldn't match the entered link or id to the selected service. Please try again."),
-					tr("No valid WMS URL or id"),
-					JOptionPane.ERROR_MESSAGE);
-			diag.setVisible(true);
-		}
-	}
-
-	/**
-	 * Adds a WMS Layer with given title and URL
-	 * @param title: Name of the layer as it will shop up in the layer manager
-	 * @param url: URL to the WMS server
-	 * @param cookies: Cookies to send with each image request (Format: josm=is; very=cool)
-	 */
-	private void addWMSLayer(String title, String url, String cookies) {
-		WMSLayer wmsLayer = new WMSLayer(title, url, cookies);
-		Main.main.addLayer(wmsLayer);
-	}
-
-	/**
-	 * Adds a WMS Layer with given title and URL
-	 * @param title: Name of the layer as it will shop up in the layer manager
-	 * @param url: URL to the WMS server
-	 */
-	private void addWMSLayer(String title, String url) {
-		addWMSLayer(title, url, "");
-	}
-
-	/**
-	 * Helper function that extracts a String from the Clipboard if available.
-	 * Returns an empty String otherwise
-	 * @return String Clipboard contents if available
-	 */
-	private String getClipboardContents() {
-		String result = "";
-		Transferable contents = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
-
-		if(contents == null || !contents.isDataFlavorSupported(DataFlavor.stringFlavor))
-			return "";
-
-		try {
-			result = (String)contents.getTransferData(DataFlavor.stringFlavor);
-		} catch(Exception ex) {
-			return "";
-		}
-		return result.trim();
-	}
+    /**
+     * Class that bundles all required information of a rectifier service
+     */
+    public static class RectifierService {
+        private final String name;
+        private final String url;
+        private final String wmsUrl;
+        private final Pattern urlRegEx;
+        private final Pattern idValidator;
+        public JRadioButton btn;
+        /**
+         * @param name: Name of the rectifing service
+         * @param url: URL to the service where users can register, upload, etc.
+         * @param wmsUrl: URL to the WMS server where JOSM will grab the images. Insert __s__ where the ID should be placed
+         * @param urlRegEx: a regular expression that determines if a given URL is one of the service and returns the WMS id if so
+         * @param idValidator: regular expression that checks if a given ID is syntactically valid
+         */
+        public RectifierService(String name, String url, String wmsUrl, String urlRegEx, String idValidator) {
+            this.name = name;
+            this.url = url;
+            this.wmsUrl = wmsUrl;
+            this.urlRegEx = Pattern.compile(urlRegEx);
+            this.idValidator = Pattern.compile(idValidator);
+        }
+
+        public boolean isSelected() {
+            return btn.isSelected();
+        }
+    }
+
+    /**
+     * List of available rectifier services. May be extended from the outside
+     */
+    public ArrayList<RectifierService> services = new ArrayList<RectifierService>();
+
+    public Map_Rectifier_WMSmenuAction() {
+        super(tr("Rectified Image..."),
+                "OLmarker",
+                tr("Download Rectified Images From Various Services"),
+                Shortcut.registerShortcut("wms:rectimg",
+                        tr("WMS: {0}", tr("Rectified Image...")),
+                        KeyEvent.VK_R,
+                        Shortcut.GROUP_NONE),
+                        true
+        );
+
+        // Add default services
+        services.add(
+                new RectifierService("Metacarta Map Rectifier",
+                        "http://labs.metacarta.com/rectifier/",
+                        "http://labs.metacarta.com/rectifier/wms.cgi?id=__s__&srs=EPSG:4326"
+                        + "&Service=WMS&Version=1.1.0&Request=GetMap&format=image/png&",
+                        // This matches more than the "classic" WMS link, so users can pretty much
+                        // copy any link as long as it includes the ID
+                        "labs\\.metacarta\\.com/(?:.*?)(?:/|=)([0-9]+)(?:\\?|/|\\.|$)",
+                "^[0-9]+$")
+        );
+        services.add(
+                // TODO: Change all links to mapwarper.net once the project has moved.
+                // The RegEx already matches the new URL and old URLs will be forwarded
+                // to make the transition as smooth as possible for the users
+                new RectifierService("Geothings Map Warper",
+                        "http://warper.geothings.net/",
+                        "http://warper.geothings.net/maps/wms/__s__?request=GetMap&version=1.1.1"
+                        + "&styles=&format=image/png&srs=epsg:4326&exceptions=application/vnd.ogc.se_inimage&",
+                        // This matches more than the "classic" WMS link, so users can pretty much
+                        // copy any link as long as it includes the ID
+                        "(?:mapwarper\\.net|warper\\.geothings\\.net)/(?:.*?)/([0-9]+)(?:\\?|/|\\.|$)",
+                "^[0-9]+$")
+        );
+
+        // This service serves the purpose of "just this once" without forcing the user
+        // to commit the link to the preferences
+
+        // Clipboard content gets trimmed, so matching whitespace only ensures that this
+        // service will never be selected automatically.
+        services.add(new RectifierService(tr("Custom WMS Link"), "", "", "^\\s+$", ""));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        JPanel panel = new JPanel(new GridBagLayout());
+        panel.add(new JLabel(tr("Supported Rectifier Services:")), GBC.eol());
+
+        JTextField tfWmsUrl = new JTextField(30);
+
+        String clip = getClipboardContents();
+        ButtonGroup group = new ButtonGroup();
+
+        JRadioButton firstBtn = null;
+        for(RectifierService s : services) {
+            JRadioButton serviceBtn = new JRadioButton(s.name);
+            if(firstBtn == null)
+                firstBtn = serviceBtn;
+            // Checks clipboard contents against current service if no match has been found yet.
+            // If the contents match, they will be inserted into the text field and the corresponding
+            // service will be pre-selected.
+            if(!clip.equals("") && tfWmsUrl.getText().equals("")
+                    && (s.urlRegEx.matcher(clip).find() || s.idValidator.matcher(clip).matches())) {
+                serviceBtn.setSelected(true);
+                tfWmsUrl.setText(clip);
+            }
+            s.btn = serviceBtn;
+            group.add(serviceBtn);
+            if(!s.url.equals("")) {
+                panel.add(serviceBtn, GBC.std());
+                panel.add(new UrlLabel(s.url, tr("Visit Homepage")), GBC.eol().anchor(GridBagConstraints.EAST));
+            } else
+                panel.add(serviceBtn, GBC.eol().anchor(GridBagConstraints.WEST));
+        }
+
+        // Fallback in case no match was found
+        if(tfWmsUrl.getText().equals("") && firstBtn != null)
+            firstBtn.setSelected(true);
+
+        panel.add(new JLabel(tr("WMS URL or Image ID:")), GBC.eol());
+        panel.add(tfWmsUrl, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
+
+        ExtendedDialog diag = new ExtendedDialog(Main.parent,
+                tr("Add Rectified Image"),
+
+                new String[] {tr("Add Rectified Image"), tr("Cancel")});
+        diag.setContent(panel);
+        diag.setButtonIcons(new String[] {"OLmarker.png", "cancel.png"});
+
+        // This repeatedly shows the dialog in case there has been an error.
+        // The loop is break;-ed if the users cancels
+        outer: while(true) {
+            diag.showDialog();
+            int answer = diag.getValue();
+            // Break loop when the user cancels
+            if(answer != 1)
+                break;
+
+            String text = tfWmsUrl.getText().trim();
+            // Loop all services until we find the selected one
+            for(RectifierService s : services) {
+                if(!s.isSelected())
+                    continue;
+
+                // We've reached the custom WMS URL service
+                // Just set the URL and hope everything works out
+                if(s.wmsUrl.equals("")) {
+                    addWMSLayer(s.name + " (" + text + ")", text);
+                    break outer;
+                }
+
+                // First try to match if the entered string as an URL
+                Matcher m = s.urlRegEx.matcher(text);
+                if(m.find()) {
+                    String id = m.group(1);
+                    String newURL = s.wmsUrl.replaceAll("__s__", id);
+                    String title = s.name + " (" + id + ")";
+                    addWMSLayer(title, newURL);
+                    break outer;
+                }
+                // If not, look if it's a valid ID for the selected service
+                if(s.idValidator.matcher(text).matches()) {
+                    String newURL = s.wmsUrl.replaceAll("__s__", text);
+                    String title = s.name + " (" + text + ")";
+                    addWMSLayer(title, newURL);
+                    break outer;
+                }
+
+                // We've found the selected service, but the entered string isn't suitable for
+                // it. So quit checking the other radio buttons
+                break;
+            }
+
+            // and display an error message. The while(true) ensures that the dialog pops up again
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("Couldn't match the entered link or id to the selected service. Please try again."),
+                    tr("No valid WMS URL or id"),
+                    JOptionPane.ERROR_MESSAGE);
+            diag.setVisible(true);
+        }
+    }
+
+    /**
+     * Adds a WMS Layer with given title and URL
+     * @param title: Name of the layer as it will shop up in the layer manager
+     * @param url: URL to the WMS server
+     */
+    private void addWMSLayer(String title, String url) {
+        Main.main.addLayer(new WMSLayer(new WMSInfo(title, url)));
+    }
+
+    /**
+     * Helper function that extracts a String from the Clipboard if available.
+     * Returns an empty String otherwise
+     * @return String Clipboard contents if available
+     */
+    private String getClipboardContents() {
+        String result = "";
+        Transferable contents = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
+
+        if(contents == null || !contents.isDataFlavorSupported(DataFlavor.stringFlavor))
+            return "";
+
+        try {
+            result = (String)contents.getTransferData(DataFlavor.stringFlavor);
+        } catch(Exception ex) {
+            return "";
+        }
+        return result.trim();
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSAdjustAction.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSAdjustAction.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSAdjustAction.java	(revision 23207)
@@ -31,186 +31,186 @@
 
 public class WMSAdjustAction extends MapMode implements MouseListener, MouseMotionListener{
-	//static private final Logger logger = Logger.getLogger(WMSAdjustAction.class.getName());
-
-	GeorefImage selectedImage;
-	boolean mouseDown;
-	EastNorth prevEastNorth;
-	private WMSLayer adjustingLayer;
-
-	public WMSAdjustAction(MapFrame mapFrame) {
-		super(tr("Adjust WMS"), "adjustwms",
-				tr("Adjust the position of the selected WMS layer"), mapFrame,
-				ImageProvider.getCursor("normal", "move"));
-	}
-
-
-
-	@Override public void enterMode() {
-		super.enterMode();
-		if (!hasWMSLayersToAdjust()) {
-			warnNoWMSLayers();
-			return;
-		}
-		List<WMSLayer> wmsLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
-		if (wmsLayers.size() == 1) {
-			adjustingLayer = wmsLayers.get(0);
-		} else {
-			adjustingLayer = (WMSLayer)askAdjustLayer(Main.map.mapView.getLayersOfType(WMSLayer.class));
-		}
-		if (adjustingLayer == null)
-			return;
-		if (!adjustingLayer.isVisible()) {
-			adjustingLayer.setVisible(true);
-		}
-		Main.map.mapView.addMouseListener(this);
-		Main.map.mapView.addMouseMotionListener(this);
-	}
-
-	@Override public void exitMode() {
-		super.exitMode();
-		Main.map.mapView.removeMouseListener(this);
-		Main.map.mapView.removeMouseMotionListener(this);
-		adjustingLayer = null;
-	}
-
-	@Override public void mousePressed(MouseEvent e) {
-		if (e.getButton() != MouseEvent.BUTTON1)
-			return;
-
-		if (adjustingLayer.isVisible()) {
-			prevEastNorth=Main.map.mapView.getEastNorth(e.getX(),e.getY());
-			selectedImage = adjustingLayer.findImage(prevEastNorth);
-			if(selectedImage!=null) {
-				Main.map.mapView.setCursor
-				(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
-			}
-		}
-	}
-
-	@Override public void mouseDragged(MouseEvent e) {
-		if(selectedImage!=null) {
-			EastNorth eastNorth=
-				Main.map.mapView.getEastNorth(e.getX(),e.getY());
-			adjustingLayer.displace(
-					eastNorth.east()-prevEastNorth.east(),
-					eastNorth.north()-prevEastNorth.north()
-			);
-			prevEastNorth = eastNorth;
-			Main.map.mapView.repaint();
-		}
-	}
-
-	@Override public void mouseReleased(MouseEvent e) {
-		Main.map.mapView.repaint();
-		Main.map.mapView.setCursor(Cursor.getDefaultCursor());
-		selectedImage = null;
-		prevEastNorth = null;
-	}
-
-	@Override
-	public void mouseEntered(MouseEvent e) {
-	}
-
-	@Override
-	public void mouseExited(MouseEvent e) {
-	}
-
-	@Override
-	public void mouseMoved(MouseEvent e) {
-	}
-
-	@Override public void mouseClicked(MouseEvent e) {
-	}
-
-	@Override public boolean layerIsSupported(Layer l) {
-		return hasWMSLayersToAdjust();
-	}
-
-	/**
-	 * the list cell renderer used to render layer list entries
-	 *
-	 */
-	static public class LayerListCellRenderer extends DefaultListCellRenderer {
-
-		protected boolean isActiveLayer(Layer layer) {
-			if (Main.map == null)
-				return false;
-			if (Main.map.mapView == null)
-				return false;
-			return Main.map.mapView.getActiveLayer() == layer;
-		}
-
-		@Override
-		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
-				boolean cellHasFocus) {
-			Layer layer = (Layer) value;
-			JLabel label = (JLabel) super.getListCellRendererComponent(list, layer.getName(), index, isSelected,
-					cellHasFocus);
-			Icon icon = layer.getIcon();
-			label.setIcon(icon);
-			label.setToolTipText(layer.getToolTipText());
-			return label;
-		}
-	}
-
-	/**
-	 * Prompts the user with a list of WMS layers which can be adjusted
-	 *
-	 * @param adjustableLayers the list of adjustable layers
-	 * @return  the selected layer; null, if no layer was selected
-	 */
-	protected Layer askAdjustLayer(List<? extends Layer> adjustableLayers) {
-		JComboBox layerList = new JComboBox();
-		layerList.setRenderer(new LayerListCellRenderer());
-		layerList.setModel(new DefaultComboBoxModel(adjustableLayers.toArray()));
-		layerList.setSelectedIndex(0);
-
-		JPanel pnl = new JPanel();
-		pnl.setLayout(new GridBagLayout());
-		pnl.add(new JLabel(tr("Please select the WMS layer to adjust.")), GBC.eol());
-		pnl.add(layerList, GBC.eol());
-
-		ExtendedDialog diag = new ExtendedDialog(
-				Main.parent,
-				tr("Select WMS layer"),
-				new String[] { tr("Start adjusting"),tr("Cancel") }
-		);
-		diag.setContent(pnl);
-		diag.setButtonIcons(new String[] { "mapmode/adjustwms", "cancel" });
-		diag.showDialog();
-		int decision = diag.getValue();
-		if (decision != 1)
-			return null;
-		Layer adjustLayer = (Layer) layerList.getSelectedItem();
-		return adjustLayer;
-	}
-
-	/**
-	 * Displays a warning message if there are no WMS layers to adjust
-	 *
-	 */
-	protected void warnNoWMSLayers() {
-		JOptionPane.showMessageDialog(
-				Main.parent,
-				tr("There are currently no WMS layer to adjust."),
-				tr("No layers to adjust"),
-				JOptionPane.WARNING_MESSAGE
-		);
-	}
-
-	/**
-	 * Replies true if there is at least one WMS layer
-	 *
-	 * @return true if there is at least one WMS layer
-	 */
-	protected boolean hasWMSLayersToAdjust() {
-		if (Main.map == null) return false;
-		if (Main.map.mapView == null) return false;
-		return ! Main.map.mapView.getLayersOfType(WMSLayer.class).isEmpty();
-	}
-
-	@Override
-	protected void updateEnabledState() {
-		setEnabled(hasWMSLayersToAdjust());
-	}
+    //static private final Logger logger = Logger.getLogger(WMSAdjustAction.class.getName());
+
+    GeorefImage selectedImage;
+    boolean mouseDown;
+    EastNorth prevEastNorth;
+    private WMSLayer adjustingLayer;
+
+    public WMSAdjustAction(MapFrame mapFrame) {
+        super(tr("Adjust WMS"), "adjustwms",
+                tr("Adjust the position of the selected WMS layer"), mapFrame,
+                ImageProvider.getCursor("normal", "move"));
+    }
+
+
+
+    @Override public void enterMode() {
+        super.enterMode();
+        if (!hasWMSLayersToAdjust()) {
+            warnNoWMSLayers();
+            return;
+        }
+        List<WMSLayer> wmsLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
+        if (wmsLayers.size() == 1) {
+            adjustingLayer = wmsLayers.get(0);
+        } else {
+            adjustingLayer = (WMSLayer)askAdjustLayer(Main.map.mapView.getLayersOfType(WMSLayer.class));
+        }
+        if (adjustingLayer == null)
+            return;
+        if (!adjustingLayer.isVisible()) {
+            adjustingLayer.setVisible(true);
+        }
+        Main.map.mapView.addMouseListener(this);
+        Main.map.mapView.addMouseMotionListener(this);
+    }
+
+    @Override public void exitMode() {
+        super.exitMode();
+        Main.map.mapView.removeMouseListener(this);
+        Main.map.mapView.removeMouseMotionListener(this);
+        adjustingLayer = null;
+    }
+
+    @Override public void mousePressed(MouseEvent e) {
+        if (e.getButton() != MouseEvent.BUTTON1)
+            return;
+
+        if (adjustingLayer.isVisible()) {
+            prevEastNorth=Main.map.mapView.getEastNorth(e.getX(),e.getY());
+            selectedImage = adjustingLayer.findImage(prevEastNorth);
+            if(selectedImage!=null) {
+                Main.map.mapView.setCursor
+                (Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+            }
+        }
+    }
+
+    @Override public void mouseDragged(MouseEvent e) {
+        if(selectedImage!=null) {
+            EastNorth eastNorth=
+                Main.map.mapView.getEastNorth(e.getX(),e.getY());
+            adjustingLayer.displace(
+                    eastNorth.east()-prevEastNorth.east(),
+                    eastNorth.north()-prevEastNorth.north()
+            );
+            prevEastNorth = eastNorth;
+            Main.map.mapView.repaint();
+        }
+    }
+
+    @Override public void mouseReleased(MouseEvent e) {
+        Main.map.mapView.repaint();
+        Main.map.mapView.setCursor(Cursor.getDefaultCursor());
+        selectedImage = null;
+        prevEastNorth = null;
+    }
+
+    @Override
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    @Override
+    public void mouseExited(MouseEvent e) {
+    }
+
+    @Override
+    public void mouseMoved(MouseEvent e) {
+    }
+
+    @Override public void mouseClicked(MouseEvent e) {
+    }
+
+    @Override public boolean layerIsSupported(Layer l) {
+        return hasWMSLayersToAdjust();
+    }
+
+    /**
+     * the list cell renderer used to render layer list entries
+     *
+     */
+    static public class LayerListCellRenderer extends DefaultListCellRenderer {
+
+        protected boolean isActiveLayer(Layer layer) {
+            if (Main.map == null)
+                return false;
+            if (Main.map.mapView == null)
+                return false;
+            return Main.map.mapView.getActiveLayer() == layer;
+        }
+
+        @Override
+        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
+                boolean cellHasFocus) {
+            Layer layer = (Layer) value;
+            JLabel label = (JLabel) super.getListCellRendererComponent(list, layer.getName(), index, isSelected,
+                    cellHasFocus);
+            Icon icon = layer.getIcon();
+            label.setIcon(icon);
+            label.setToolTipText(layer.getToolTipText());
+            return label;
+        }
+    }
+
+    /**
+     * Prompts the user with a list of WMS layers which can be adjusted
+     *
+     * @param adjustableLayers the list of adjustable layers
+     * @return  the selected layer; null, if no layer was selected
+     */
+    protected Layer askAdjustLayer(List<? extends Layer> adjustableLayers) {
+        JComboBox layerList = new JComboBox();
+        layerList.setRenderer(new LayerListCellRenderer());
+        layerList.setModel(new DefaultComboBoxModel(adjustableLayers.toArray()));
+        layerList.setSelectedIndex(0);
+
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        pnl.add(new JLabel(tr("Please select the WMS layer to adjust.")), GBC.eol());
+        pnl.add(layerList, GBC.eol());
+
+        ExtendedDialog diag = new ExtendedDialog(
+                Main.parent,
+                tr("Select WMS layer"),
+                new String[] { tr("Start adjusting"),tr("Cancel") }
+        );
+        diag.setContent(pnl);
+        diag.setButtonIcons(new String[] { "mapmode/adjustwms", "cancel" });
+        diag.showDialog();
+        int decision = diag.getValue();
+        if (decision != 1)
+            return null;
+        Layer adjustLayer = (Layer) layerList.getSelectedItem();
+        return adjustLayer;
+    }
+
+    /**
+     * Displays a warning message if there are no WMS layers to adjust
+     *
+     */
+    protected void warnNoWMSLayers() {
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                tr("There are currently no WMS layer to adjust."),
+                tr("No layers to adjust"),
+                JOptionPane.WARNING_MESSAGE
+        );
+    }
+
+    /**
+     * Replies true if there is at least one WMS layer
+     *
+     * @return true if there is at least one WMS layer
+     */
+    protected boolean hasWMSLayersToAdjust() {
+        if (Main.map == null) return false;
+        if (Main.map.mapView == null) return false;
+        return ! Main.map.mapView.getLayersOfType(WMSLayer.class).isEmpty();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(hasWMSLayersToAdjust());
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSDownloadAction.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSDownloadAction.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSDownloadAction.java	(revision 23207)
@@ -10,24 +10,15 @@
 public class WMSDownloadAction extends JosmAction {
 
-	private final WMSInfo info;
+    private final WMSInfo info;
 
-	public WMSDownloadAction(WMSInfo info) {
-		super(info.name, "wmsmenu", tr("Download WMS tile from {0}",info.name), null, false);
-		putValue("toolbar", "wms_" + info.name);
-		this.info = info;
-	}
+    public WMSDownloadAction(WMSInfo info) {
+        super(info.getMenuName(), "wmsmenu", tr("Download WMS tile from {0}",info.name), null, false);
+        putValue("toolbar", "wms_" + info.getToolbarName());
+        this.info = info;
+    }
 
-	public void actionPerformed(ActionEvent e) {
-		//System.out.println(info.url);
-
-		WMSLayer wmsLayer = new WMSLayer(info.name, info.url, info.cookies);
-		Main.main.addLayer(wmsLayer);
-	}
-
-	public static WMSLayer getLayer(WMSInfo info) {
-		// FIXME: move this to WMSPlugin/WMSInfo/preferences.
-		WMSLayer wmsLayer = new WMSLayer(info.name, info.url, info.cookies);
-		Main.main.addLayer(wmsLayer);
-		return wmsLayer;
-	}
+    public void actionPerformed(ActionEvent e) {
+        WMSLayer wmsLayer = new WMSLayer(info);
+        Main.main.addLayer(wmsLayer);
+    }
 };
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSGrabber.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSGrabber.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSGrabber.java	(revision 23207)
@@ -36,169 +36,169 @@
 
 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(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();
-	}
+    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.info.url;
+        /* 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.info.cookies != null && !layer.info.cookies.equals(""))
+            conn.setRequestProperty("Cookie", layer.info.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/WMSInfo.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSInfo.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSInfo.java	(revision 23207)
@@ -1,3 +1,6 @@
 package wmsplugin;
+
+import java.util.ArrayList;
+import java.util.Collection;
 
 import org.openstreetmap.josm.Main;
@@ -11,22 +14,63 @@
 
     String name;
-    String url;
-    String cookies;
-    int prefid;
+    String url=null;
+    String cookies = null;
+    boolean html = false;
+    double pixelPerDegree = 0.0;
 
-    public WMSInfo(String name, String url, int prefid) {
-        this(name, url, null, prefid);
+    public WMSInfo(String name) {
+        this.name=name;
     }
 
-    public WMSInfo(String name, String url, String cookies, int prefid) {
+    public WMSInfo(String name, String url) {
         this.name=name;
-        this.url=url;
-        this.cookies=cookies;
-        this.prefid=prefid;
+        setURL(url);
     }
 
-    public void save() {
-        Main.pref.put("wmsplugin.url." + prefid + ".name", name);
-        Main.pref.put("wmsplugin.url." + prefid + ".url", url);
+    public WMSInfo(String name, String url, String cookies) {
+        this.name=name;
+        setURL(url);
+        this.cookies=cookies;
+    }
+
+    public WMSInfo(String name, String url, String cookies, double pixelPerDegree) {
+        this.name=name;
+        setURL(url);
+        this.cookies=cookies;
+        this.pixelPerDegree=pixelPerDegree;
+    }
+
+    public ArrayList<String> getInfoArray() {
+        String e2 = null;
+        String e3 = null;
+        String e4 = null;
+        if(url != null && !url.isEmpty()) e2 = getFullURL();
+        if(cookies != null && !cookies.isEmpty()) e3 = cookies;
+        if(pixelPerDegree != 0.0) e4 = String.valueOf(pixelPerDegree);
+        if(e4 != null && e3 == null) e3 = "";
+        if(e3 != null && e2 == null) e2 = "";
+
+        ArrayList<String> res = new ArrayList<String>();
+        res.add(name);
+        if(e2 != null) res.add(e2);
+        if(e3 != null) res.add(e3);
+        if(e4 != null) res.add(e4);
+        return res;
+    }
+
+    public WMSInfo(Collection<String> list) {
+        ArrayList<String> array = new ArrayList<String>(list);
+        this.name=array.get(0);
+        if(array.size() >= 2) setURL(array.get(1));
+        if(array.size() >= 3) this.cookies=array.get(2);
+        if(array.size() >= 4) this.pixelPerDegree=Double.valueOf(array.get(3));
+    }
+
+    public WMSInfo(WMSInfo i) {
+        this.name=i.name;
+        this.url=i.url;
+        this.cookies=i.cookies;
+        this.html=i.html;
+        this.pixelPerDegree=i.pixelPerDegree;
     }
 
@@ -37,6 +81,44 @@
             i = url.compareTo(in.url);
         if(i == 0)
-            i = prefid-in.prefid;
+            i = Double.compare(pixelPerDegree, in.pixelPerDegree);
         return i;
     }
+
+    public boolean equalsBaseValues(WMSInfo in)
+    {
+        return url.equals(in.url);
+    }
+
+    public void setPixelPerDegree(double ppd) {
+        this.pixelPerDegree = ppd;
+    }
+
+    public void setURL(String url) {
+        if(url.startsWith("html:")) {
+            this.url = url.substring(5);
+            html = true;
+        } else {
+            this.url = url;
+        }
+    }
+
+    public String getFullURL() {
+        return html ? "html:" + url : url;
+    }
+
+    public String getToolbarName()
+    {
+        String res = name;
+        if(pixelPerDegree != 0.0)
+            res += "#PPD="+pixelPerDegree;
+        return res;
+    }
+
+    public String getMenuName()
+    {
+        String res = name;
+        if(pixelPerDegree != 0.0)
+            res += " ("+pixelPerDegree+")";
+        return res;
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayer.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayer.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayer.java	(revision 23207)
@@ -54,764 +54,741 @@
 public class WMSLayer extends Layer implements PreferenceChangedListener {
 
-	protected static final Icon icon =
-		new ImageIcon(Toolkit.getDefaultToolkit().createImage(WMSPlugin.class.getResource("/images/wms_small.png")));
-
-	public static final BooleanProperty PROP_ALPHA_CHANNEL = new BooleanProperty("wmsplugin.alpha_channel", true);
-
-	public int messageNum = 5; //limit for messages per layer
-	protected MapView mv;
-	protected String resolution;
-	protected int imageSize = 500;
-	protected int dax = 10;
-	protected int day = 10;
-	protected int daStep = 5;
-	protected int minZoom = 3;
-
-	protected double dx = 0.0;
-	protected double dy = 0.0;
-
-	protected double pixelPerDegree;
-	protected GeorefImage[][] images;
-	protected String baseURL;
-	protected String cookies;
-	protected final int serializeFormatVersion = 5;
-	protected boolean autoDownloadEnabled = true;
-	protected boolean settingsChanged;
-
-	// 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;
-
-	// Request queue
-	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 */
-	private boolean usesInvalidUrl = false;
-	/** set to true if the user confirmed to use an potentially invalid WMS base url */
-	private boolean isInvalidUrlConfirmed = false;
-
-	public WMSLayer() {
-		this(tr("Blank Layer"), null, null);
-		initializeImages();
-		mv = Main.map.mapView;
-	}
-
-	public WMSLayer(String name, String baseURL, String cookies) {
-		super(name);
-		setBackgroundLayer(true); /* set global background variable */
-		initializeImages();
-		this.baseURL = baseURL;
-		this.cookies = cookies;
-		WMSGrabber.getProjection(baseURL, true);
-		mv = Main.map.mapView;
-
-		// quick hack to predefine the PixelDensity to reuse the cache
-		int codeIndex = getName().indexOf("#PPD=");
-		if (codeIndex != -1) {
-			pixelPerDegree = Double.valueOf(getName().substring(codeIndex+5));
-		} else {
-			pixelPerDegree = getPPD();
-		}
-		resolution = mv.getDist100PixelText();
-
-		if(baseURL != null)
-		{
-			startGrabberThreads();
-		}
-		if (baseURL != null && !baseURL.startsWith("html:") && !WMSGrabber.isUrlWithPatterns(baseURL)) {
-			if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
-				if (!confirmMalformedUrl(baseURL)) {
-					System.out.println(tr("Warning: WMS layer deactivated because of malformed base url ''{0}''", baseURL));
-					usesInvalidUrl = true;
-					setName(getName() + tr("(deactivated)"));
-					return;
-				} else {
-					isInvalidUrlConfirmed = true;
-				}
-			}
-		}
-
-		Main.pref.addPreferenceChangeListener(this);
-	}
-
-	public boolean hasAutoDownload(){
-		return autoDownloadEnabled;
-	}
-
-
-	@Override
-	public void destroy() {
-		cancelGrabberThreads(false);
-		Main.pref.removePreferenceChangeListener(this);
-	}
-
-	public void initializeImages() {
-		GeorefImage[][] old = images;
-		images = new GeorefImage[dax][day];
-		if (old != null) {
-			for (int i=0; i<old.length; i++) {
-				for (int k=0; k<old[i].length; k++) {
-					GeorefImage o = old[i][k];
-					images[modulo(o.getXIndex(),dax)][modulo(o.getYIndex(),day)] = old[i][k];
-				}
-			}
-		}
-		for(int x = 0; x<dax; ++x) {
-			for(int y = 0; y<day; ++y) {
-				if (images[x][y] == null) {
-					images[x][y]= new GeorefImage(this);
-				}
-			}
-		}
-	}
-
-	@Override public Icon getIcon() {
-		return icon;
-	}
-
-	@Override public String getToolTipText() {
-		if(autoDownloadEnabled)
-			return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolution);
-		else
-			return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolution);
-	}
-
-	@Override public boolean isMergable(Layer other) {
-		return false;
-	}
-
-	@Override public void mergeFrom(Layer from) {
-	}
-
-	private int modulo (int a, int b) {
-		return a % b >= 0 ? a%b : a%b+b;
-	}
-
-	private boolean zoomIsTooBig() {
-		//don't download when it's too outzoomed
-		return pixelPerDegree / getPPD() > minZoom;
-	}
-
-	@Override public void paint(Graphics2D g, final MapView mv, Bounds b) {
-		if(baseURL == null) return;
-		if (usesInvalidUrl && !isInvalidUrlConfirmed) return;
-
-		settingsChanged = false;
-
-		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 = 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);
-				}
-			}
-		} else {
-			downloadAndPaintVisible(g, mv, false);
-		}
-	}
-
-	protected boolean confirmMalformedUrl(String url) {
-		if (isInvalidUrlConfirmed)
-			return true;
-		String msg  = tr("<html>The base URL<br>"
-				+ "''{0}''<br>"
-				+ "for this WMS layer does neither end with a ''&'' nor with a ''?''.<br>"
-				+ "This is likely to lead to invalid WMS request. You should check your<br>"
-				+ "preference settings.<br>"
-				+ "Do you want to fetch WMS tiles anyway?",
-				url);
-		String [] options = new String[] {
-				tr("Yes, fetch images"),
-				tr("No, abort")
-		};
-		int ret = JOptionPane.showOptionDialog(
-				Main.parent,
-				msg,
-				tr("Invalid URL?"),
-				JOptionPane.YES_NO_OPTION,
-				JOptionPane.WARNING_MESSAGE,
-				null,
-				options, options[1]
-		);
-		switch(ret) {
-		case JOptionPane.YES_OPTION: return true;
-		default: return false;
-		}
-	}
-
-	public double getPPD(){
-		ProjectionBounds bounds = mv.getProjectionBounds();
-		return mv.getWidth() / (bounds.max.east() - bounds.min.east());
-	}
-
-	public void displace(double dx, double dy) {
-		settingsChanged = true;
-		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());
-	}
-
-	public int getImageWidth(int xIndex) {
-		int overlap = (int)(WMSPlugin.PROP_OVERLAP.get()?WMSPlugin.PROP_OVERLAP_EAST.get() * imageSize * getPPD() / pixelPerDegree / 100:0);
-		return getImageX(xIndex + 1) - getImageX(xIndex) + overlap;
-	}
-
-	public int getImageHeight(int yIndex) {
-		int overlap = (int)(WMSPlugin.PROP_OVERLAP.get()?WMSPlugin.PROP_OVERLAP_NORTH.get() * imageSize * getPPD() / pixelPerDegree / 100:0);
-		return getImageY(yIndex + 1) - getImageY(yIndex) + overlap;
-	}
-
-	/**
-	 *
-	 * @return Size of image in original zoom
-	 */
-	public int getBaseImageWidth() {
-		int overlap = (WMSPlugin.PROP_OVERLAP.get()?WMSPlugin.PROP_OVERLAP_EAST.get() * imageSize / 100:0);
-		return imageSize + overlap;
-	}
-
-	/**
-	 *
-	 * @return Size of image in original zoom
-	 */
-	public int getBaseImageHeight() {
-		int overlap = (WMSPlugin.PROP_OVERLAP.get()?WMSPlugin.PROP_OVERLAP_NORTH.get() * imageSize / 100:0);
-		return imageSize + overlap;
-	}
-
-
-	/**
-	 *
-	 * @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){
-
-		int newDax = dax;
-		int newDay = day;
-
-		if (bmaxx - bminx >= dax || bmaxx - bminx < dax - 2 * daStep) {
-			newDax = ((bmaxx - bminx) / daStep + 1) * daStep;
-		}
-
-		if (bmaxy - bminy >= day || bmaxy - bminx < day - 2 * daStep) {
-			newDay = ((bmaxy - bminy) / daStep + 1) * daStep;
-		}
-
-		if (newDax != dax || newDay != day) {
-			dax = newDax;
-			day = newDay;
-			initializeImages();
-		}
-
-		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, x, y, leftEdge, bottomEdge)) {
-					WMSRequest request = new WMSRequest(x, y, pixelPerDegree, real);
-					addRequest(request);
-				}
-			}
-		}
-	}
-
-	@Override public void visitBoundingBox(BoundingXYVisitor v) {
-		for(int x = 0; x<dax; ++x) {
-			for(int y = 0; y<day; ++y)
-				if(images[x][y].getImage() != null){
-					v.visit(images[x][y].getMin());
-					v.visit(images[x][y].getMax());
-				}
-		}
-	}
-
-	@Override public Object getInfoComponent() {
-		return getToolTipText();
-	}
-
-	@Override public Action[] getMenuEntries() {
-		return new Action[]{
-				LayerListDialog.getInstance().createActivateLayerAction(this),
-				LayerListDialog.getInstance().createShowHideLayerAction(),
-				LayerListDialog.getInstance().createDeleteLayerAction(),
-				SeparatorLayerAction.INSTANCE,
-				new LoadWmsAction(),
-				new SaveWmsAction(),
-				new BookmarkWmsAction(),
-				SeparatorLayerAction.INSTANCE,
-				new StartStopAction(),
-				new ToggleAlphaAction(),
-				new ChangeResolutionAction(),
-				new ReloadErrorTilesAction(),
-				new DownloadAction(),
-				SeparatorLayerAction.INSTANCE,
-				new LayerListPopup.InfoAction(this)
-		};
-	}
-
-	public GeorefImage findImage(EastNorth eastNorth) {
-		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) {
-		if (request.getState() == null) {
-			throw new IllegalArgumentException("Finished request without state");
-		}
-		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());
-				}
-			}
-		} finally {
-			finishedRequests.clear();
-			requestQueueLock.unlock();
-		}
-	}
-
-	public class DownloadAction extends AbstractAction {
-		private static final long serialVersionUID = -7183852461015284020L;
-		public DownloadAction() {
-			super(tr("Download visible tiles"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			if (zoomIsTooBig()) {
-				JOptionPane.showMessageDialog(
-						Main.parent,
-						tr("The requested area is too big. Please zoom in a little, or change resolution"),
-						tr("Error"),
-						JOptionPane.ERROR_MESSAGE
-				);
-			} else {
-				downloadAndPaintVisible(mv.getGraphics(), mv, true);
-			}
-		}
-	}
-
-	public class ChangeResolutionAction extends AbstractAction {
-		public ChangeResolutionAction() {
-			super(tr("Change resolution"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			initializeImages();
-			resolution = mv.getDist100PixelText();
-			pixelPerDegree = getPPD();
-			settingsChanged = true;
-			mv.repaint();
-		}
-	}
-
-	public class ReloadErrorTilesAction extends AbstractAction {
-		public ReloadErrorTilesAction() {
-			super(tr("Reload erroneous tiles"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			// Delete small files, because they're probably blank tiles.
-			// See https://josm.openstreetmap.de/ticket/2307
-			WMSPlugin.cache.customCleanUp(CacheFiles.CLEAN_SMALL_FILES, 4096);
-
-			for (int x = 0; x < dax; ++x) {
-				for (int y = 0; y < day; ++y) {
-					GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
-					if(img.getState() == State.FAILED){
-						addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), pixelPerDegree, true));
-						mv.repaint();
-					}
-				}
-			}
-		}
-	}
-
-	public class ToggleAlphaAction extends AbstractAction implements LayerAction {
-		public ToggleAlphaAction() {
-			super(tr("Alpha channel"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource();
-			boolean alphaChannel = checkbox.isSelected();
-			PROP_ALPHA_CHANNEL.put(alphaChannel);
-
-			// clear all resized cached instances and repaint the layer
-			for (int x = 0; x < dax; ++x) {
-				for (int y = 0; y < day; ++y) {
-					GeorefImage img = images[modulo(x, dax)][modulo(y, day)];
-					img.flushedResizedCachedInstance();
-				}
-			}
-			mv.repaint();
-		}
-		public Component createMenuComponent() {
-			JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
-			item.setSelected(PROP_ALPHA_CHANNEL.get());
-			return item;
-		}
-		public boolean supportLayers(List<Layer> layers) {
-			return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
-		}
-	}
-
-	public class SaveWmsAction extends AbstractAction {
-		public SaveWmsAction() {
-			super(tr("Save WMS layer to file"), ImageProvider.get("save"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			File f = SaveActionBase.createAndOpenSaveFileChooser(
-					tr("Save WMS layer"), ".wms");
-			try {
-				if (f != null) {
-					ObjectOutputStream oos = new ObjectOutputStream(
-							new FileOutputStream(f)
-					);
-					oos.writeInt(serializeFormatVersion);
-					oos.writeInt(dax);
-					oos.writeInt(day);
-					oos.writeInt(imageSize);
-					oos.writeDouble(pixelPerDegree);
-					oos.writeObject(getName());
-					oos.writeObject(baseURL);
-					oos.writeObject(images);
-					oos.close();
-				}
-			} catch (Exception ex) {
-				ex.printStackTrace(System.out);
-			}
-		}
-	}
-
-	public class LoadWmsAction extends AbstractAction {
-		public LoadWmsAction() {
-			super(tr("Load WMS layer from file"), ImageProvider.get("load"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			JFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true,
-					false, tr("Load WMS layer"), "wms");
-			if(fc == null) return;
-			File f = fc.getSelectedFile();
-			if (f == null) return;
-			try
-			{
-				FileInputStream fis = new FileInputStream(f);
-				ObjectInputStream ois = new ObjectInputStream(fis);
-				int sfv = ois.readInt();
-				if (sfv != serializeFormatVersion) {
-					JOptionPane.showMessageDialog(Main.parent,
-							tr("Unsupported WMS file version; found {0}, expected {1}", sfv, serializeFormatVersion),
-							tr("File Format Error"),
-							JOptionPane.ERROR_MESSAGE);
-					return;
-				}
-				autoDownloadEnabled = false;
-				dax = ois.readInt();
-				day = ois.readInt();
-				imageSize = ois.readInt();
-				pixelPerDegree = ois.readDouble();
-				setName((String)ois.readObject());
-				baseURL = (String) ois.readObject();
-				images = (GeorefImage[][])ois.readObject();
-				ois.close();
-				fis.close();
-				for (GeorefImage[] imgs : images) {
-					for (GeorefImage img : imgs) {
-						if (img != null) {
-							img.setLayer(WMSLayer.this);
-						}
-					}
-				}
-				settingsChanged = true;
-				mv.repaint();
-				if(baseURL != null)
-				{
-					startGrabberThreads();
-				}
-			}
-			catch (Exception ex) {
-				// FIXME be more specific
-				ex.printStackTrace(System.out);
-				JOptionPane.showMessageDialog(Main.parent,
-						tr("Error loading file"),
-						tr("Error"),
-						JOptionPane.ERROR_MESSAGE);
-				return;
-			}
-		}
-	}
-	/**
-	 * This action will add a WMS layer menu entry with the current WMS layer
-	 * URL and name extended by the current resolution.
-	 * When using the menu entry again, the WMS cache will be used properly.
-	 */
-	public class BookmarkWmsAction extends AbstractAction {
-		public BookmarkWmsAction() {
-			super(tr("Set WMS Bookmark"));
-		}
-		public void actionPerformed(ActionEvent ev) {
-			int i = 0;
-			while (Main.pref.hasKey("wmsplugin.url."+i+".url")) {
-				i++;
-			}
-			String baseName;
-			// cut old parameter
-			int parameterIndex = getName().indexOf("#PPD=");
-			if (parameterIndex != -1) {
-				baseName = getName().substring(0,parameterIndex);
-			}
-			else {
-				baseName = getName();
-			}
-			Main.pref.put("wmsplugin.url."+ i +".url",baseURL );
-			Main.pref.put("wmsplugin.url."+String.valueOf(i)+".name",
-					baseName + "#PPD=" + pixelPerDegree );
-			WMSPlugin.refreshMenu();
-		}
-	}
-
-	private class StartStopAction extends AbstractAction implements LayerAction {
-
-		public StartStopAction() {
-			super(tr("Automatic downloading"));
-		}
-
-		public Component createMenuComponent() {
-			JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
-			item.setSelected(autoDownloadEnabled);
-			return item;
-		}
-
-		public boolean supportLayers(List<Layer> layers) {
-			return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
-		}
-
-		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();
-		}
-	}
-
-	@Override
-	public boolean isChanged() {
-		requestQueueLock.lock();
-		try {
-			return !finishedRequests.isEmpty() || settingsChanged;
-		} finally {
-			requestQueueLock.unlock();
-		}
-	}
-
-	public void preferenceChanged(PreferenceChangeEvent event) {
-		if (event.getKey().equals(WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.getKey())) {
-			cancelGrabberThreads(true);
-			startGrabberThreads();
-		} else if (
-				event.getKey().equals(WMSPlugin.PROP_OVERLAP.getKey())
-				|| event.getKey().equals(WMSPlugin.PROP_OVERLAP_EAST.getKey())
-				|| event.getKey().equals(WMSPlugin.PROP_OVERLAP_NORTH.getKey())) {
-			for (int i=0; i<images.length; i++) {
-				for (int k=0; k<images[i].length; k++) {
-					images[i][k] = new GeorefImage(this);
-				}
-			}
-
-			settingsChanged = true;
-		}
-	}
+    protected static final Icon icon =
+        new ImageIcon(Toolkit.getDefaultToolkit().createImage(WMSPlugin.class.getResource("/images/wms_small.png")));
+
+    public static final BooleanProperty PROP_ALPHA_CHANNEL = new BooleanProperty("wmsplugin.alpha_channel", true);
+    WMSPlugin plugin = WMSPlugin.instance;
+
+    public int messageNum = 5; //limit for messages per layer
+    protected MapView mv;
+    protected String resolution;
+    protected int imageSize = 500;
+    protected int dax = 10;
+    protected int day = 10;
+    protected int daStep = 5;
+    protected int minZoom = 3;
+
+    protected double dx = 0.0;
+    protected double dy = 0.0;
+
+    protected GeorefImage[][] images;
+    protected final int serializeFormatVersion = 5;
+    protected boolean autoDownloadEnabled = true;
+    protected boolean settingsChanged;
+    protected WMSInfo info;
+
+    // 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;
+
+    // Request queue
+    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 */
+    private boolean usesInvalidUrl = false;
+    /** set to true if the user confirmed to use an potentially invalid WMS base url */
+    private boolean isInvalidUrlConfirmed = false;
+
+    public WMSLayer() {
+        this(new WMSInfo(tr("Blank Layer")));
+    }
+
+    public WMSLayer(WMSInfo info) {
+        super(info.name);
+        setBackgroundLayer(true); /* set global background variable */
+        initializeImages();
+        this.info = new WMSInfo(info);
+        mv = Main.map.mapView;
+        if(this.info.pixelPerDegree == 0.0)
+            this.info.setPixelPerDegree(getPPD());
+        resolution = mv.getDist100PixelText();
+
+        if(info.url != null) {
+            WMSGrabber.getProjection(info.url, true);
+            startGrabberThreads();
+            if(!info.url.startsWith("html:") && !WMSGrabber.isUrlWithPatterns(info.url)) {
+                if (!(info.url.endsWith("&") || info.url.endsWith("?"))) {
+                    if (!confirmMalformedUrl(info.url)) {
+                        System.out.println(tr("Warning: WMS layer deactivated because of malformed base url ''{0}''", info.url));
+                        usesInvalidUrl = true;
+                        setName(getName() + tr("(deactivated)"));
+                        return;
+                    } else {
+                        isInvalidUrlConfirmed = true;
+                    }
+                }
+            }
+        }
+
+        Main.pref.addPreferenceChangeListener(this);
+    }
+
+    public void doSetName(String name) {
+        setName(name);
+        info.name = name;
+    }
+
+    public boolean hasAutoDownload(){
+        return autoDownloadEnabled;
+    }
+
+
+    @Override
+    public void destroy() {
+        cancelGrabberThreads(false);
+        Main.pref.removePreferenceChangeListener(this);
+    }
+
+    public void initializeImages() {
+        GeorefImage[][] old = images;
+        images = new GeorefImage[dax][day];
+        if (old != null) {
+            for (int i=0; i<old.length; i++) {
+                for (int k=0; k<old[i].length; k++) {
+                    GeorefImage o = old[i][k];
+                    images[modulo(o.getXIndex(),dax)][modulo(o.getYIndex(),day)] = old[i][k];
+                }
+            }
+        }
+        for(int x = 0; x<dax; ++x) {
+            for(int y = 0; y<day; ++y) {
+                if (images[x][y] == null) {
+                    images[x][y]= new GeorefImage(this);
+                }
+            }
+        }
+    }
+
+    @Override public Icon getIcon() {
+        return icon;
+    }
+
+    @Override public String getToolTipText() {
+        if(autoDownloadEnabled)
+            return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolution);
+        else
+            return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolution);
+    }
+
+    @Override public boolean isMergable(Layer other) {
+        return false;
+    }
+
+    @Override public void mergeFrom(Layer from) {
+    }
+
+    private int modulo (int a, int b) {
+        return a % b >= 0 ? a%b : a%b+b;
+    }
+
+    private boolean zoomIsTooBig() {
+        //don't download when it's too outzoomed
+        return info.pixelPerDegree / getPPD() > minZoom;
+    }
+
+    @Override public void paint(Graphics2D g, final MapView mv, Bounds b) {
+        if(info.url == null || (usesInvalidUrl && !isInvalidUrlConfirmed)) return;
+
+        settingsChanged = false;
+
+        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 = 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);
+                }
+            }
+        } else {
+            downloadAndPaintVisible(g, mv, false);
+        }
+    }
+
+    protected boolean confirmMalformedUrl(String url) {
+        if (isInvalidUrlConfirmed)
+            return true;
+        String msg  = tr("<html>The base URL<br>"
+                + "''{0}''<br>"
+                + "for this WMS layer does neither end with a ''&'' nor with a ''?''.<br>"
+                + "This is likely to lead to invalid WMS request. You should check your<br>"
+                + "preference settings.<br>"
+                + "Do you want to fetch WMS tiles anyway?",
+                url);
+        String [] options = new String[] {
+                tr("Yes, fetch images"),
+                tr("No, abort")
+        };
+        int ret = JOptionPane.showOptionDialog(
+                Main.parent,
+                msg,
+                tr("Invalid URL?"),
+                JOptionPane.YES_NO_OPTION,
+                JOptionPane.WARNING_MESSAGE,
+                null,
+                options, options[1]
+        );
+        switch(ret) {
+        case JOptionPane.YES_OPTION: return true;
+        default: return false;
+        }
+    }
+
+    public double getPPD(){
+        ProjectionBounds bounds = mv.getProjectionBounds();
+        return mv.getWidth() / (bounds.max.east() - bounds.min.east());
+    }
+
+    public void displace(double dx, double dy) {
+        settingsChanged = true;
+        this.dx += dx;
+        this.dy += dy;
+    }
+
+    public int getImageXIndex(double coord) {
+        return (int)Math.floor( ((coord - dx) * info.pixelPerDegree) / imageSize);
+    }
+
+    public int getImageYIndex(double coord) {
+        return (int)Math.floor( ((coord - dy) * info.pixelPerDegree) / imageSize);
+    }
+
+    public int getImageX(int imageIndex) {
+        return (int)(imageIndex * imageSize * (getPPD() / info.pixelPerDegree) + dx * getPPD());
+    }
+
+    public int getImageY(int imageIndex) {
+        return (int)(imageIndex * imageSize * (getPPD() / info.pixelPerDegree) + dy * getPPD());
+    }
+
+    public int getImageWidth(int xIndex) {
+        int overlap = (int)(plugin.PROP_OVERLAP.get()?plugin.PROP_OVERLAP_EAST.get() * imageSize * getPPD() / info.pixelPerDegree / 100:0);
+        return getImageX(xIndex + 1) - getImageX(xIndex) + overlap;
+    }
+
+    public int getImageHeight(int yIndex) {
+        int overlap = (int)(plugin.PROP_OVERLAP.get()?plugin.PROP_OVERLAP_NORTH.get() * imageSize * getPPD() / info.pixelPerDegree / 100:0);
+        return getImageY(yIndex + 1) - getImageY(yIndex) + overlap;
+    }
+
+    /**
+     *
+     * @return Size of image in original zoom
+     */
+    public int getBaseImageWidth() {
+        int overlap = (plugin.PROP_OVERLAP.get()?plugin.PROP_OVERLAP_EAST.get() * imageSize / 100:0);
+        return imageSize + overlap;
+    }
+
+    /**
+     *
+     * @return Size of image in original zoom
+     */
+    public int getBaseImageHeight() {
+        int overlap = (plugin.PROP_OVERLAP.get()?plugin.PROP_OVERLAP_NORTH.get() * imageSize / 100:0);
+        return imageSize + overlap;
+    }
+
+
+    /**
+     *
+     * @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) / info.pixelPerDegree, (yIndex * imageSize) / info.pixelPerDegree);
+    }
+
+
+    protected void downloadAndPaintVisible(Graphics g, final MapView mv, boolean real){
+
+        int newDax = dax;
+        int newDay = day;
+
+        if (bmaxx - bminx >= dax || bmaxx - bminx < dax - 2 * daStep) {
+            newDax = ((bmaxx - bminx) / daStep + 1) * daStep;
+        }
+
+        if (bmaxy - bminy >= day || bmaxy - bminx < day - 2 * daStep) {
+            newDay = ((bmaxy - bminy) / daStep + 1) * daStep;
+        }
+
+        if (newDax != dax || newDay != day) {
+            dax = newDax;
+            day = newDay;
+            initializeImages();
+        }
+
+        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, x, y, leftEdge, bottomEdge)) {
+                    WMSRequest request = new WMSRequest(x, y, info.pixelPerDegree, real);
+                    addRequest(request);
+                }
+            }
+        }
+    }
+
+    @Override public void visitBoundingBox(BoundingXYVisitor v) {
+        for(int x = 0; x<dax; ++x) {
+            for(int y = 0; y<day; ++y)
+                if(images[x][y].getImage() != null){
+                    v.visit(images[x][y].getMin());
+                    v.visit(images[x][y].getMax());
+                }
+        }
+    }
+
+    @Override public Object getInfoComponent() {
+        return getToolTipText();
+    }
+
+    @Override public Action[] getMenuEntries() {
+        return new Action[]{
+                LayerListDialog.getInstance().createActivateLayerAction(this),
+                LayerListDialog.getInstance().createShowHideLayerAction(),
+                LayerListDialog.getInstance().createDeleteLayerAction(),
+                SeparatorLayerAction.INSTANCE,
+                new LoadWmsAction(),
+                new SaveWmsAction(),
+                new BookmarkWmsAction(),
+                SeparatorLayerAction.INSTANCE,
+                new StartStopAction(),
+                new ToggleAlphaAction(),
+                new ChangeResolutionAction(),
+                new ReloadErrorTilesAction(),
+                new DownloadAction(),
+                SeparatorLayerAction.INSTANCE,
+                new LayerListPopup.InfoAction(this)
+        };
+    }
+
+    public GeorefImage findImage(EastNorth eastNorth) {
+        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() != info.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) {
+        if (request.getState() == null) {
+            throw new IllegalArgumentException("Finished request without state");
+        }
+        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());
+                }
+            }
+        } finally {
+            finishedRequests.clear();
+            requestQueueLock.unlock();
+        }
+    }
+
+    public class DownloadAction extends AbstractAction {
+        private static final long serialVersionUID = -7183852461015284020L;
+        public DownloadAction() {
+            super(tr("Download visible tiles"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            if (zoomIsTooBig()) {
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        tr("The requested area is too big. Please zoom in a little, or change resolution"),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE
+                );
+            } else {
+                downloadAndPaintVisible(mv.getGraphics(), mv, true);
+            }
+        }
+    }
+
+    public class ChangeResolutionAction extends AbstractAction {
+        public ChangeResolutionAction() {
+            super(tr("Change resolution"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            initializeImages();
+            resolution = mv.getDist100PixelText();
+            info.setPixelPerDegree(getPPD());
+            settingsChanged = true;
+            mv.repaint();
+        }
+    }
+
+    public class ReloadErrorTilesAction extends AbstractAction {
+        public ReloadErrorTilesAction() {
+            super(tr("Reload erroneous tiles"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            // Delete small files, because they're probably blank tiles.
+            // See https://josm.openstreetmap.de/ticket/2307
+            plugin.cache.customCleanUp(CacheFiles.CLEAN_SMALL_FILES, 4096);
+
+            for (int x = 0; x < dax; ++x) {
+                for (int y = 0; y < day; ++y) {
+                    GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
+                    if(img.getState() == State.FAILED){
+                        addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.pixelPerDegree, true));
+                        mv.repaint();
+                    }
+                }
+            }
+        }
+    }
+
+    public class ToggleAlphaAction extends AbstractAction implements LayerAction {
+        public ToggleAlphaAction() {
+            super(tr("Alpha channel"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource();
+            boolean alphaChannel = checkbox.isSelected();
+            PROP_ALPHA_CHANNEL.put(alphaChannel);
+
+            // clear all resized cached instances and repaint the layer
+            for (int x = 0; x < dax; ++x) {
+                for (int y = 0; y < day; ++y) {
+                    GeorefImage img = images[modulo(x, dax)][modulo(y, day)];
+                    img.flushedResizedCachedInstance();
+                }
+            }
+            mv.repaint();
+        }
+        public Component createMenuComponent() {
+            JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
+            item.setSelected(PROP_ALPHA_CHANNEL.get());
+            return item;
+        }
+        public boolean supportLayers(List<Layer> layers) {
+            return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
+        }
+    }
+
+    public class SaveWmsAction extends AbstractAction {
+        public SaveWmsAction() {
+            super(tr("Save WMS layer to file"), ImageProvider.get("save"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            File f = SaveActionBase.createAndOpenSaveFileChooser(
+                    tr("Save WMS layer"), ".wms");
+            try {
+                if (f != null) {
+                    ObjectOutputStream oos = new ObjectOutputStream(
+                            new FileOutputStream(f)
+                    );
+                    oos.writeInt(serializeFormatVersion);
+                    oos.writeInt(dax);
+                    oos.writeInt(day);
+                    oos.writeInt(imageSize);
+                    oos.writeDouble(info.pixelPerDegree);
+                    oos.writeObject(info.name);
+                    oos.writeObject(info.getFullURL());
+                    oos.writeObject(images);
+                    oos.close();
+                }
+            } catch (Exception ex) {
+                ex.printStackTrace(System.out);
+            }
+        }
+    }
+
+    public class LoadWmsAction extends AbstractAction {
+        public LoadWmsAction() {
+            super(tr("Load WMS layer from file"), ImageProvider.get("load"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            JFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true,
+                    false, tr("Load WMS layer"), "wms");
+            if(fc == null) return;
+            File f = fc.getSelectedFile();
+            if (f == null) return;
+            try
+            {
+                FileInputStream fis = new FileInputStream(f);
+                ObjectInputStream ois = new ObjectInputStream(fis);
+                int sfv = ois.readInt();
+                if (sfv != serializeFormatVersion) {
+                    JOptionPane.showMessageDialog(Main.parent,
+                            tr("Unsupported WMS file version; found {0}, expected {1}", sfv, serializeFormatVersion),
+                            tr("File Format Error"),
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                autoDownloadEnabled = false;
+                dax = ois.readInt();
+                day = ois.readInt();
+                imageSize = ois.readInt();
+                info.setPixelPerDegree(ois.readDouble());
+                doSetName((String)ois.readObject());
+                info.setURL((String) ois.readObject());
+                images = (GeorefImage[][])ois.readObject();
+                ois.close();
+                fis.close();
+                for (GeorefImage[] imgs : images) {
+                    for (GeorefImage img : imgs) {
+                        if (img != null) {
+                            img.setLayer(WMSLayer.this);
+                        }
+                    }
+                }
+                settingsChanged = true;
+                mv.repaint();
+                if(info.url != null)
+                {
+                    startGrabberThreads();
+                }
+            }
+            catch (Exception ex) {
+                // FIXME be more specific
+                ex.printStackTrace(System.out);
+                JOptionPane.showMessageDialog(Main.parent,
+                        tr("Error loading file"),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+        }
+    }
+    /**
+     * This action will add a WMS layer menu entry with the current WMS layer
+     * URL and name extended by the current resolution.
+     * When using the menu entry again, the WMS cache will be used properly.
+     */
+    public class BookmarkWmsAction extends AbstractAction {
+        public BookmarkWmsAction() {
+            super(tr("Set WMS Bookmark"));
+        }
+        public void actionPerformed(ActionEvent ev) {
+            plugin.addLayer(new WMSInfo(info));
+        }
+    }
+
+    private class StartStopAction extends AbstractAction implements LayerAction {
+
+        public StartStopAction() {
+            super(tr("Automatic downloading"));
+        }
+
+        public Component createMenuComponent() {
+            JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
+            item.setSelected(autoDownloadEnabled);
+            return item;
+        }
+
+        public boolean supportLayers(List<Layer> layers) {
+            return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
+        }
+
+        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 = plugin.PROP_SIMULTANEOUS_CONNECTIONS.get();
+        requestQueueLock.lock();
+        try {
+            canceled = false;
+            grabbers.clear();
+            grabberThreads.clear();
+            for (int i=0; i<threadCount; i++) {
+                Grabber grabber = plugin.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();
+        }
+    }
+
+    @Override
+    public boolean isChanged() {
+        requestQueueLock.lock();
+        try {
+            return !finishedRequests.isEmpty() || settingsChanged;
+        } finally {
+            requestQueueLock.unlock();
+        }
+    }
+
+    public void preferenceChanged(PreferenceChangeEvent event) {
+        if (event.getKey().equals(plugin.PROP_SIMULTANEOUS_CONNECTIONS.getKey())) {
+            cancelGrabberThreads(true);
+            startGrabberThreads();
+        } else if (
+                event.getKey().equals(plugin.PROP_OVERLAP.getKey())
+                || event.getKey().equals(plugin.PROP_OVERLAP_EAST.getKey())
+                || event.getKey().equals(plugin.PROP_OVERLAP_NORTH.getKey())) {
+            for (int i=0; i<images.length; i++) {
+                for (int k=0; k<images[i].length; k++) {
+                    images[i][k] = new GeorefImage(this);
+                }
+            }
+
+            settingsChanged = true;
+        }
+    }
 
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayerInfo.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayerInfo.java	(revision 23207)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSLayerInfo.java	(revision 23207)
@@ -0,0 +1,153 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.io.MirroredInputStream;
+
+public class WMSLayerInfo {
+    ArrayList<WMSInfo> layers = new ArrayList<WMSInfo>();
+    ArrayList<WMSInfo> defaultLayers = new ArrayList<WMSInfo>();
+    private final static String[] DEFAULT_LAYER_SITES = {
+    "http://svn.openstreetmap.org/applications/editors/josm/plugins/wmsplugin/sources.cfg"};
+
+    public void load() {
+        layers.clear();
+        Collection<String> defaults = Main.pref.getCollection(
+            "wmslayers.default", Collections.<String>emptySet());
+        for(Collection<String> c : Main.pref.getArray("wmslayers",
+        Collections.<Collection<String>>emptySet())) {
+            layers.add(new WMSInfo(c));
+        }
+
+        { /* REMOVE following old block in spring 2011 */
+            defaults = new LinkedList<String>(defaults);
+            Map<String,String> prefs = Main.pref.getAllPrefix("wmsplugin.default.");
+            for(String s : prefs.keySet()) {
+                Main.pref.put(s, null);
+                defaults.add(s.substring(18));
+            }
+            prefs = Main.pref.getAllPrefix("wmsplugin.url.");
+            for(String s : prefs.keySet()) {
+                Main.pref.put(s, null);
+            }
+            TreeSet<String> keys = new TreeSet<String>(prefs.keySet());
+
+            // And then the names+urls of WMS servers
+            int prefid = 0;
+            String name = null;
+            String url = null;
+            String cookies = null;
+            double pixelPerDegree = 0.0;
+            int lastid = -1;
+            for (String key : keys) {
+                String[] elements = key.split("\\.");
+                if (elements.length != 4) continue;
+                try {
+                    prefid = Integer.parseInt(elements[2]);
+                } catch(NumberFormatException e) {
+                    continue;
+                }
+                if (prefid != lastid) {
+                    name = url = cookies = null; pixelPerDegree = 0.0; lastid = prefid;
+                }
+                if (elements[3].equals("name")) {
+                    name = prefs.get(key);
+                    int codeIndex = name.indexOf("#PPD=");
+                    if (codeIndex != -1) {
+                        pixelPerDegree = Double.valueOf(name.substring(codeIndex+5));
+                        name = name.substring(0, codeIndex);
+                    }
+                }
+                else if (elements[3].equals("url"))
+                {
+                    url = prefs.get(key);
+                }
+                else if (elements[3].equals("cookies"))
+                    cookies = prefs.get(key);
+                if (name != null && url != null)
+                    layers.add(new WMSInfo(name, url, cookies, pixelPerDegree));
+            }
+        }
+        ArrayList<String> defaultsSave = new ArrayList<String>();
+        for(String source : Main.pref.getCollection("wmslayers.sites", Arrays.asList(DEFAULT_LAYER_SITES)))
+        {
+            try
+            {
+                MirroredInputStream s = new MirroredInputStream(source, WMSPlugin.instance.getPluginDir(), -1);
+                InputStreamReader r;
+                try
+                {
+                    r = new InputStreamReader(s, "UTF-8");
+                }
+                catch (UnsupportedEncodingException e)
+                {
+                    r = new InputStreamReader(s);
+                }
+                BufferedReader reader = new BufferedReader(r);
+                String line;
+                while((line = reader.readLine()) != null)
+                {
+                    String val[] = line.split(";");
+                    if(!line.startsWith("#") && val.length == 3) {
+                        boolean force = "true".equals(val[0]);
+                        String name = tr(val[1]);
+                        String url = val[2];
+
+                        defaultLayers.add(new WMSInfo(name, url));
+
+                        if(force) {
+                            defaultsSave.add(url);
+                            if(!defaults.contains(url)) {
+                                int id = -1;
+                                for(WMSInfo i : layers) {
+                                    if(url.equals(i.url))
+                                        force = false;
+                                }
+                                if(force)
+                                    layers.add(new WMSInfo(name, url));
+                            }
+                        }
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+            }
+        }
+
+        Main.pref.putCollection("wmslayers.default", defaultsSave.size() > 0
+            ? defaultsSave : defaults);
+        Collections.sort(layers);
+        save();
+    }
+
+    public void add(WMSInfo info) {
+        layers.add(info);
+    }
+
+    public void remove(WMSInfo info) {
+        layers.remove(info);
+    }
+
+    public void save() {
+        LinkedList<Collection<String>> coll = new LinkedList<Collection<String>>();
+        for (WMSInfo info : layers) {
+            coll.add(info.getInfoArray());
+        }
+        Main.pref.putArray("wmslayers", coll);
+    }
+}
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPlugin.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPlugin.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPlugin.java	(revision 23207)
@@ -7,19 +7,7 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.TreeSet;
 
 import javax.swing.JMenu;
@@ -38,5 +26,4 @@
 import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.io.CacheFiles;
-import org.openstreetmap.josm.io.MirroredInputStream;
 import org.openstreetmap.josm.plugins.Plugin;
 import org.openstreetmap.josm.plugins.PluginHandler;
@@ -48,357 +35,254 @@
 
 public class WMSPlugin extends Plugin {
-	static CacheFiles cache = new CacheFiles("wmsplugin");
-
-	public static final IntegerProperty PROP_SIMULTANEOUS_CONNECTIONS = new IntegerProperty("wmsplugin.simultaneousConnections", 3);
-	public static final BooleanProperty PROP_OVERLAP = new BooleanProperty("wmsplugin.url.overlap", false);
-	public static final IntegerProperty PROP_OVERLAP_EAST = new IntegerProperty("wmsplugin.url.overlapEast", 14);
-	public static final IntegerProperty PROP_OVERLAP_NORTH = new IntegerProperty("wmsplugin.url.overlapNorth", 4);
-
-	WMSLayer wmsLayer;
-	static JMenu wmsJMenu;
-
-	static ArrayList<WMSInfo> wmsList = new ArrayList<WMSInfo>();
-	static TreeMap<String,String> wmsListDefault = new TreeMap<String,String>();
-
-	// remember state of menu item to restore on changed preferences
-	static private boolean menuEnabled = false;
-
-	/***************************************************************
-	 * Remote control initialization:
-	 * If you need remote control in some other plug-in
-	 * copy this stuff and the call to initRemoteControl below
-	 * and replace the RequestHandler subclass in initRemoteControl
-	 ***************************************************************/
-
-	/** name of remote control plugin */
-	private static final String REMOTECONTROL_NAME = "remotecontrol";
-
-	/* if necessary change these version numbers to ensure compatibility */
-
-	/** RemoteControlPlugin older than this SVN revision is not compatible */
-	static final int REMOTECONTROL_MIN_REVISION = 22734;
-	/** WMSPlugin needs this specific API major version of RemoteControlPlugin */
-	static final int REMOTECONTROL_NEED_API_MAJOR = 1;
-	/** All API minor versions starting from this should be compatible */
-	static final int REMOTECONTROL_MIN_API_MINOR = 0;
-
-	/* these fields will contain state and version of remote control plug-in */
-	boolean remoteControlAvailable = false;
-	boolean remoteControlCompatible = true;
-	boolean remoteControlInitialized = false;
-	int remoteControlRevision = 0;
-	int remoteControlApiMajor = 0;
-	int remoteControlApiMinor = 0;
-	int remoteControlProtocolMajor = 0;
-	int remoteControlProtocolMinor = 0;
-
-	/**
-	 * Check if remote control plug-in is available and if its version is
-	 * high enough and register remote control command for this plug-in.
-	 */
-	private void initRemoteControl() {
-		for(PluginProxy pp: PluginHandler.pluginList)
-		{
-			PluginInformation info = pp.getPluginInformation();
-			if(REMOTECONTROL_NAME.equals(info.name))
-			{
-				remoteControlAvailable = true;
-				remoteControlRevision = Integer.parseInt(info.version);
-				if(REMOTECONTROL_MIN_REVISION > remoteControlRevision)
-				{
-					remoteControlCompatible = false;
-				}
-			}
-		}
-
-		if(remoteControlAvailable && remoteControlCompatible)
-		{
-			Plugin plugin =
-				(Plugin) PluginHandler.getPlugin(REMOTECONTROL_NAME);
-			try {
-				Method method;
-				method = plugin.getClass().getMethod("getVersion");
-				Object obj = method.invoke(plugin);
-				if((obj != null ) && (obj instanceof int[]))
-				{
-					int[] versions = (int[]) obj;
-					if(versions.length >= 4)
-					{
-						remoteControlApiMajor = versions[0];
-						remoteControlApiMinor = versions[1];
-						remoteControlProtocolMajor = versions[2];
-						remoteControlProtocolMinor = versions[3];
-					}
-				}
-
-				if((remoteControlApiMajor != REMOTECONTROL_NEED_API_MAJOR) ||
-						(remoteControlApiMinor < REMOTECONTROL_MIN_API_MINOR))
-				{
-					remoteControlCompatible = false;
-				}
-				if(remoteControlCompatible)
-				{
-					System.out.println(this.getClass().getSimpleName() + ": initializing remote control");
-					method = plugin.getClass().getMethod("addRequestHandler", String.class, Class.class);
-					// replace command and class when you copy this to some other plug-in
-					// for compatibility with old remotecontrol add leading "/"
-					method.invoke(plugin, "/" + WMSRemoteHandler.command, WMSRemoteHandler.class);
-					remoteControlInitialized = true;
-				}
-			} catch (SecurityException e) {
-				e.printStackTrace();
-			} catch (NoSuchMethodException e) {
-				e.printStackTrace();
-			} catch (IllegalArgumentException e) {
-				e.printStackTrace();
-			} catch (IllegalAccessException e) {
-				e.printStackTrace();
-			} catch (InvocationTargetException e) {
-				e.printStackTrace();
-			}
-		}
-		if(remoteControlAvailable)
-		{
-			String msg = null;
-
-			if(remoteControlCompatible)
-			{
-				if(!remoteControlInitialized)
-				{
-					msg  = tr("Could not initialize remote control.");
-				}
-			}
-			else
-			{
-				msg  = tr("Remote control plugin is not compatible with {0}.",
-						this.getClass().getSimpleName());
-			}
-
-			if(msg != null)
-			{
-				String additionalMessage = tr("{0} will work but remote control for this plugin is disabled.\n"
-						+ "You should update the plugins.",
-						this.getClass().getSimpleName());
-				String versionMessage = tr("Current version of \"{1}\": {2}, internal version {3}. "
-						+ "Need version {4}, internal version {5}.\n"
-						+ "If updating the plugins does not help report a bug for \"{0}\".",
-						this.getClass().getSimpleName(),
-						REMOTECONTROL_NAME,
-						""+remoteControlRevision,
-						(remoteControlApiMajor != 0) ?
-								""+remoteControlApiMajor+"."+remoteControlApiMinor :
-									tr("unknown"),
-									""+REMOTECONTROL_MIN_REVISION,
-									""+REMOTECONTROL_NEED_API_MAJOR+"."+REMOTECONTROL_MIN_API_MINOR );
-
-				String title = tr("{0}: Problem with remote control",
-						this.getClass().getSimpleName());
-
-				System.out.println(this.getClass().getSimpleName() + ": " +
-						msg + "\n" + versionMessage);
-
-				JOptionPane.showMessageDialog(
-						Main.parent,
-						msg + "\n" + additionalMessage,
-						title,
-						JOptionPane.WARNING_MESSAGE
-				);
-			}
-		}
-
-		if(!remoteControlAvailable) {
-			System.out.println(this.getClass().getSimpleName() + ": remote control not available");
-		}
-	}
-
-	/***************************************
-	 * end of remote control initialization
-	 ***************************************/
-
-	protected void initExporterAndImporter() {
-		ExtensionFileFilter.exporters.add(new WMSLayerExporter());
-		ExtensionFileFilter.importers.add(new WMSLayerImporter());
-	}
-
-	public WMSPlugin(PluginInformation info) {
-		super(info);
-		/*
-		System.out.println("constructor " + this.getClass().getName() + " (" + info.name +
-				" v " + info.version + " stage " + info.stage + ")");
-		 */
-		refreshMenu();
-		cache.setExpire(CacheFiles.EXPIRE_MONTHLY, false);
-		cache.setMaxSize(70, false);
-		initExporterAndImporter();
-		initRemoteControl();
-	}
-
-	// this parses the preferences settings. preferences for the wms plugin have to
-	// look like this:
-	// wmsplugin.1.name=Landsat
-	// wmsplugin.1.url=http://and.so.on/
-
-	@Override
-	public void copy(String from, String to) throws FileNotFoundException, IOException
-	{
-		File pluginDir = new File(getPrefsPath());
-		if (!pluginDir.exists())
-			pluginDir.mkdirs();
-		FileOutputStream out = new FileOutputStream(getPrefsPath() + to);
-		InputStream in = WMSPlugin.class.getResourceAsStream(from);
-		byte[] buffer = new byte[8192];
-		for(int len = in.read(buffer); len > 0; len = in.read(buffer))
-			out.write(buffer, 0, len);
-		in.close();
-		out.close();
-	}
-
-
-	public static void refreshMenu() {
-		wmsList.clear();
-		Map<String,String> prefs = Main.pref.getAllPrefix("wmsplugin.url.");
-
-		TreeSet<String> keys = new TreeSet<String>(prefs.keySet());
-
-		// And then the names+urls of WMS servers
-		int prefid = 0;
-		String name = null;
-		String url = null;
-		String cookies = "";
-		int lastid = -1;
-		for (String key : keys) {
-			String[] elements = key.split("\\.");
-			if (elements.length != 4) continue;
-			try {
-				prefid = Integer.parseInt(elements[2]);
-			} catch(NumberFormatException e) {
-				continue;
-			}
-			if (prefid != lastid) {
-				name = url = null; lastid = prefid;
-			}
-			if (elements[3].equals("name"))
-				name = prefs.get(key);
-			else if (elements[3].equals("url"))
-			{
-				/* FIXME: Remove the if clause after some time */
-				if(!prefs.get(key).startsWith("yahoo:")) /* legacy stuff */
-					url = prefs.get(key);
-			}
-			else if (elements[3].equals("cookies"))
-				cookies = prefs.get(key);
-			if (name != null && url != null)
-				wmsList.add(new WMSInfo(name, url, cookies, prefid));
-		}
-		String source = "http://svn.openstreetmap.org/applications/editors/josm/plugins/wmsplugin/sources.cfg";
-		try
-		{
-			MirroredInputStream s = new MirroredInputStream(source,
-					Main.pref.getPreferencesDir() + "plugins/wmsplugin/", -1);
-			InputStreamReader r;
-			try
-			{
-				r = new InputStreamReader(s, "UTF-8");
-			}
-			catch (UnsupportedEncodingException e)
-			{
-				r = new InputStreamReader(s);
-			}
-			BufferedReader reader = new BufferedReader(r);
-			String line;
-			while((line = reader.readLine()) != null)
-			{
-				String val[] = line.split(";");
-				if(!line.startsWith("#") && val.length == 3)
-					setDefault("true".equals(val[0]), tr(val[1]), val[2]);
-			}
-		}
-		catch (IOException e)
-		{
-		}
-
-		Collections.sort(wmsList);
-		MainMenu menu = Main.main.menu;
-
-		if (wmsJMenu == null)
-			wmsJMenu = menu.addMenu(marktr("WMS"), KeyEvent.VK_W, menu.defaultMenuPos, ht("/Plugin/WMS"));
-		else
-			wmsJMenu.removeAll();
-
-		// for each configured WMSInfo, add a menu entry.
-		for (final WMSInfo u : wmsList) {
-			wmsJMenu.add(new JMenuItem(new WMSDownloadAction(u)));
-		}
-		wmsJMenu.addSeparator();
-		wmsJMenu.add(new JMenuItem(new Map_Rectifier_WMSmenuAction()));
-
-		wmsJMenu.addSeparator();
-		wmsJMenu.add(new JMenuItem(new
-				JosmAction(tr("Blank Layer"), "blankmenu", tr("Open a blank WMS layer to load data from a file"), null, false) {
-			public void actionPerformed(ActionEvent ev) {
-				Main.main.addLayer(new WMSLayer());
-			}
-		}));
-		setEnabledAll(menuEnabled);
-	}
-
-	/* add a default entry in case the URL does not yet exist */
-	private static void setDefault(Boolean force, String name, String url)
-	{
-		String testurl = url.replaceAll("=", "_");
-		wmsListDefault.put(name, url);
-
-		if(force && !Main.pref.getBoolean("wmsplugin.default."+testurl))
-		{
-			Main.pref.put("wmsplugin.default."+testurl, true);
-			int id = -1;
-			for(WMSInfo i : wmsList)
-			{
-				if(url.equals(i.url))
-					return;
-				if(i.prefid > id)
-					id = i.prefid;
-			}
-			WMSInfo newinfo = new WMSInfo(name, url, id+1);
-			newinfo.save();
-			wmsList.add(newinfo);
-		}
-	}
-
-	public static Grabber getGrabber(MapView mv, WMSLayer layer){
-		if(layer.baseURL.startsWith("html:"))
-			return new HTMLGrabber(mv, layer, cache);
-		else
-			return new WMSGrabber(mv, layer, cache);
-	}
-
-	private static void setEnabledAll(boolean isEnabled) {
-		for(int i=0; i < wmsJMenu.getItemCount(); i++) {
-			JMenuItem item = wmsJMenu.getItem(i);
-
-			if(item != null) item.setEnabled(isEnabled);
-		}
-		menuEnabled = isEnabled;
-	}
-
-	@Override
-	public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
-		if (oldFrame==null && newFrame!=null) {
-			setEnabledAll(true);
-			Main.map.addMapMode(new IconToggleButton
-					(new WMSAdjustAction(Main.map)));
-		} else if (oldFrame!=null && newFrame==null ) {
-			setEnabledAll(false);
-		}
-	}
-
-	@Override
-	public PreferenceSetting getPreferenceSetting() {
-		return new WMSPreferenceEditor();
-	}
-
-	static public String getPrefsPath()
-	{
-		return Main.pref.getPluginsDirectory().getPath() + "/wmsplugin/";
-	}
+    CacheFiles cache = new CacheFiles("wmsplugin");
+
+    public final IntegerProperty PROP_SIMULTANEOUS_CONNECTIONS = new IntegerProperty("wmsplugin.simultaneousConnections", 3);
+    public final BooleanProperty PROP_OVERLAP = new BooleanProperty("wmsplugin.url.overlap", false);
+    public final IntegerProperty PROP_OVERLAP_EAST = new IntegerProperty("wmsplugin.url.overlapEast", 14);
+    public final IntegerProperty PROP_OVERLAP_NORTH = new IntegerProperty("wmsplugin.url.overlapNorth", 4);
+
+    JMenu wmsJMenu;
+    static WMSPlugin instance;
+
+    public WMSLayerInfo info = new WMSLayerInfo();
+
+    // remember state of menu item to restore on changed preferences
+    private boolean menuEnabled = false;
+
+    /***************************************************************
+     * Remote control initialization:
+     * If you need remote control in some other plug-in
+     * copy this stuff and the call to initRemoteControl below
+     * and replace the RequestHandler subclass in initRemoteControl
+     ***************************************************************/
+
+    /** name of remote control plugin */
+    private final String REMOTECONTROL_NAME = "remotecontrol";
+
+    /* if necessary change these version numbers to ensure compatibility */
+
+    /** RemoteControlPlugin older than this SVN revision is not compatible */
+    final int REMOTECONTROL_MIN_REVISION = 22734;
+    /** WMSPlugin needs this specific API major version of RemoteControlPlugin */
+    final int REMOTECONTROL_NEED_API_MAJOR = 1;
+    /** All API minor versions starting from this should be compatible */
+    final int REMOTECONTROL_MIN_API_MINOR = 0;
+
+    /* these fields will contain state and version of remote control plug-in */
+    boolean remoteControlAvailable = false;
+    boolean remoteControlCompatible = true;
+    boolean remoteControlInitialized = false;
+    int remoteControlRevision = 0;
+    int remoteControlApiMajor = 0;
+    int remoteControlApiMinor = 0;
+    int remoteControlProtocolMajor = 0;
+    int remoteControlProtocolMinor = 0;
+
+    /**
+     * Check if remote control plug-in is available and if its version is
+     * high enough and register remote control command for this plug-in.
+     */
+    private void initRemoteControl() {
+        for(PluginProxy pp: PluginHandler.pluginList)
+        {
+            PluginInformation info = pp.getPluginInformation();
+            if(REMOTECONTROL_NAME.equals(info.name))
+            {
+                remoteControlAvailable = true;
+                remoteControlRevision = Integer.parseInt(info.version);
+                if(REMOTECONTROL_MIN_REVISION > remoteControlRevision)
+                {
+                    remoteControlCompatible = false;
+                }
+            }
+        }
+
+        if(remoteControlAvailable && remoteControlCompatible)
+        {
+            Plugin plugin =
+                (Plugin) PluginHandler.getPlugin(REMOTECONTROL_NAME);
+            try {
+                Method method;
+                method = plugin.getClass().getMethod("getVersion");
+                Object obj = method.invoke(plugin);
+                if((obj != null ) && (obj instanceof int[]))
+                {
+                    int[] versions = (int[]) obj;
+                    if(versions.length >= 4)
+                    {
+                        remoteControlApiMajor = versions[0];
+                        remoteControlApiMinor = versions[1];
+                        remoteControlProtocolMajor = versions[2];
+                        remoteControlProtocolMinor = versions[3];
+                    }
+                }
+
+                if((remoteControlApiMajor != REMOTECONTROL_NEED_API_MAJOR) ||
+                        (remoteControlApiMinor < REMOTECONTROL_MIN_API_MINOR))
+                {
+                    remoteControlCompatible = false;
+                }
+                if(remoteControlCompatible)
+                {
+                    System.out.println(this.getClass().getSimpleName() + ": initializing remote control");
+                    method = plugin.getClass().getMethod("addRequestHandler", String.class, Class.class);
+                    // replace command and class when you copy this to some other plug-in
+                    // for compatibility with old remotecontrol add leading "/"
+                    method.invoke(plugin, "/" + WMSRemoteHandler.command, WMSRemoteHandler.class);
+                    remoteControlInitialized = true;
+                }
+            } catch (SecurityException e) {
+                e.printStackTrace();
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        }
+        if(remoteControlAvailable)
+        {
+            String msg = null;
+
+            if(remoteControlCompatible)
+            {
+                if(!remoteControlInitialized)
+                {
+                    msg  = tr("Could not initialize remote control.");
+                }
+            }
+            else
+            {
+                msg  = tr("Remote control plugin is not compatible with {0}.",
+                        this.getClass().getSimpleName());
+            }
+
+            if(msg != null)
+            {
+                String additionalMessage = tr("{0} will work but remote control for this plugin is disabled.\n"
+                        + "You should update the plugins.",
+                        this.getClass().getSimpleName());
+                String versionMessage = tr("Current version of \"{1}\": {2}, internal version {3}. "
+                        + "Need version {4}, internal version {5}.\n"
+                        + "If updating the plugins does not help report a bug for \"{0}\".",
+                        this.getClass().getSimpleName(),
+                        REMOTECONTROL_NAME,
+                        ""+remoteControlRevision,
+                        (remoteControlApiMajor != 0) ?
+                                ""+remoteControlApiMajor+"."+remoteControlApiMinor :
+                                    tr("unknown"),
+                                    ""+REMOTECONTROL_MIN_REVISION,
+                                    ""+REMOTECONTROL_NEED_API_MAJOR+"."+REMOTECONTROL_MIN_API_MINOR );
+
+                String title = tr("{0}: Problem with remote control",
+                        this.getClass().getSimpleName());
+
+                System.out.println(this.getClass().getSimpleName() + ": " +
+                        msg + "\n" + versionMessage);
+
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        msg + "\n" + additionalMessage,
+                        title,
+                        JOptionPane.WARNING_MESSAGE
+                );
+            }
+        }
+
+        if(!remoteControlAvailable) {
+            System.out.println(this.getClass().getSimpleName() + ": remote control not available");
+        }
+    }
+
+    /***************************************
+     * end of remote control initialization
+     ***************************************/
+
+    protected void initExporterAndImporter() {
+        ExtensionFileFilter.exporters.add(new WMSLayerExporter());
+        ExtensionFileFilter.importers.add(new WMSLayerImporter());
+    }
+
+    public WMSPlugin(PluginInformation info) {
+        super(info);
+        instance = this;
+        this.info.load();
+        refreshMenu();
+        cache.setExpire(CacheFiles.EXPIRE_MONTHLY, false);
+        cache.setMaxSize(70, false);
+        initExporterAndImporter();
+        initRemoteControl();
+    }
+
+    public void addLayer(WMSInfo info) {
+        this.info.add(info);
+        this.info.save();
+        refreshMenu();
+    }
+
+    public void refreshMenu() {
+        MainMenu menu = Main.main.menu;
+
+        if (wmsJMenu == null)
+            wmsJMenu = menu.addMenu(marktr("WMS"), KeyEvent.VK_W, menu.defaultMenuPos, ht("/Plugin/WMS"));
+        else
+            wmsJMenu.removeAll();
+
+        // for each configured WMSInfo, add a menu entry.
+        for (final WMSInfo u : info.layers) {
+            wmsJMenu.add(new JMenuItem(new WMSDownloadAction(u)));
+        }
+        wmsJMenu.addSeparator();
+        wmsJMenu.add(new JMenuItem(new Map_Rectifier_WMSmenuAction()));
+
+        wmsJMenu.addSeparator();
+        wmsJMenu.add(new JMenuItem(new
+                JosmAction(tr("Blank Layer"), "blankmenu", tr("Open a blank WMS layer to load data from a file"), null, false) {
+            public void actionPerformed(ActionEvent ev) {
+                Main.main.addLayer(new WMSLayer());
+            }
+        }));
+        setEnabledAll(menuEnabled);
+    }
+
+    public Grabber getGrabber(MapView mv, WMSLayer layer){
+        if(layer.info.html)
+            return new HTMLGrabber(mv, layer, cache);
+        else
+            return new WMSGrabber(mv, layer, cache);
+    }
+
+    private void setEnabledAll(boolean isEnabled) {
+        for(int i=0; i < wmsJMenu.getItemCount(); i++) {
+            JMenuItem item = wmsJMenu.getItem(i);
+
+            if(item != null) item.setEnabled(isEnabled);
+        }
+        menuEnabled = isEnabled;
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (oldFrame==null && newFrame!=null) {
+            setEnabledAll(true);
+            Main.map.addMapMode(new IconToggleButton
+                    (new WMSAdjustAction(Main.map)));
+        } else if (oldFrame!=null && newFrame==null ) {
+            setEnabledAll(false);
+        }
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new WMSPreferenceEditor();
+    }
+
+    @Override
+    public String getPluginDir()
+    {
+        return new File(Main.pref.getPluginsDirectory(), "wmsplugin").getPath();
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java	(revision 23207)
@@ -2,4 +2,5 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
 
 import java.awt.Dimension;
@@ -8,4 +9,6 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -23,4 +26,5 @@
 import javax.swing.SpinnerNumberModel;
 import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumnModel;
 
 import org.openstreetmap.josm.Main;
@@ -30,7 +34,6 @@
 
 public class WMSPreferenceEditor implements PreferenceSetting {
-    private DefaultTableModel model;
+    private WMSLayerTableModel model;
     private JComboBox browser;
-    private HashMap<Integer, WMSInfo> oldValues = new HashMap<Integer, WMSInfo>();
 
     JCheckBox overlapCheckBox;
@@ -40,24 +43,28 @@
     JCheckBox remoteCheckBox;
     boolean allowRemoteControl = true;
+    WMSPlugin plugin = WMSPlugin.instance;
 
     public void addGui(final PreferenceTabbedPane gui) {
         JPanel p = gui.createPreferenceTab("wms", tr("WMS Plugin Preferences"), tr("Modify list of WMS servers displayed in the WMS plugin menu"));
 
-        model = new DefaultTableModel(new String[]{tr("Menu Name"), tr("WMS URL")}, 0);
-        final JTable list = new JTable(model);
+        model = new WMSLayerTableModel();
+        final JTable list = new JTable(model) {
+            @Override
+            public String getToolTipText(MouseEvent e) {
+                java.awt.Point p = e.getPoint();
+                return (String) model.getValueAt(rowAtPoint(p), columnAtPoint(p));
+            }
+        };
         JScrollPane scroll = new JScrollPane(list);
         p.add(scroll, GBC.eol().fill(GridBagConstraints.BOTH));
         scroll.setPreferredSize(new Dimension(200,200));
 
-        for (WMSInfo i : WMSPlugin.wmsList) {
-            oldValues.put(i.prefid, i);
-            model.addRow(new String[]{i.name, i.url});
-        }
-
-        final DefaultTableModel modeldef = new DefaultTableModel(
-                new String[]{tr("Menu Name (Default)"), tr("WMS URL (Default)")}, 0);
-        final JTable listdef = new JTable(modeldef){
+        final WMSDefaultLayerTableModel modeldef = new WMSDefaultLayerTableModel();
+        final JTable listdef = new JTable(modeldef) {
             @Override
-            public boolean isCellEditable(int row,int column){return false;}
+            public String getToolTipText(MouseEvent e) {
+                java.awt.Point p = e.getPoint();
+                return (String) modeldef.getValueAt(rowAtPoint(p), columnAtPoint(p));
+            }
         };
         JScrollPane scrolldef = new JScrollPane(listdef);
@@ -66,7 +73,11 @@
         scrolldef.setPreferredSize(new Dimension(200,200));
 
-        for (Map.Entry<String,String> i : WMSPlugin.wmsListDefault.entrySet()) {
-            modeldef.addRow(new String[]{i.getKey(), i.getValue()});
-        }
+        TableColumnModel mod = listdef.getColumnModel();
+        mod.getColumn(1).setPreferredWidth(800);
+        mod.getColumn(0).setPreferredWidth(200);
+        mod = list.getColumnModel();
+        mod.getColumn(2).setPreferredWidth(50);
+        mod.getColumn(1).setPreferredWidth(800);
+        mod.getColumn(0).setPreferredWidth(200);
 
         JPanel buttonPanel = new JPanel(new FlowLayout());
@@ -82,5 +93,5 @@
                         JOptionPane.OK_CANCEL_OPTION);
                 if (answer == JOptionPane.OK_OPTION) {
-                    model.addRow(new String[]{p.getUrlName(), p.getUrl()});
+                    model.addRow(new WMSInfo(p.getUrlName(), p.getUrl()));
                 }
             }
@@ -118,12 +129,10 @@
 
                 outer: for(int i = 0; i < lines.length; i++) {
-                    String c1 = modeldef.getValueAt(lines[i], 0).toString();
-                    String c2 = modeldef.getValueAt(lines[i], 1).toString();
+                    WMSInfo info = modeldef.getRow(lines[i]);
 
                     // Check if an entry with exactly the same values already
                     // exists
                     for(int j = 0; j < model.getRowCount(); j++) {
-                        if(c1.equals(model.getValueAt(j, 0).toString())
-                                && c2.equals(model.getValueAt(j, 1).toString())) {
+                        if(info.equalsBaseValues(model.getRow(j))) {
                             // Select the already existing row so the user has
                             // some feedback in case an entry exists
@@ -134,5 +143,5 @@
                     }
 
-                    model.addRow(new String[] {c1, c2});
+                    model.addRow(new WMSInfo(info));
                     int lastLine = model.getRowCount() - 1;
                     list.getSelectionModel().setSelectionInterval(lastLine, lastLine);
@@ -151,5 +160,5 @@
                 "gnome-web-photo --mode=photo --format=png {0} /dev/stdout",
                 "gnome-web-photo-fixed {0}",
-        "webkit-image-gtk {0}"});
+                "webkit-image-gtk {0}"});
         browser.setEditable(true);
         browser.setSelectedItem(Main.pref.get("wmsplugin.browser", "webkit-image {0}"));
@@ -160,9 +169,9 @@
         p.add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
 
-        overlapCheckBox = new JCheckBox(tr("Overlap tiles"), WMSPlugin.PROP_OVERLAP.get() );
+        overlapCheckBox = new JCheckBox(tr("Overlap tiles"), plugin.PROP_OVERLAP.get() );
         JLabel labelEast = new JLabel(tr("% of east:"));
         JLabel labelNorth = new JLabel(tr("% of north:"));
-        spinEast = new JSpinner(new SpinnerNumberModel(WMSPlugin.PROP_OVERLAP_EAST.get(), 1, 50, 1));
-        spinNorth = new JSpinner(new SpinnerNumberModel(WMSPlugin.PROP_OVERLAP_NORTH.get(), 1, 50, 1));
+        spinEast = new JSpinner(new SpinnerNumberModel(plugin.PROP_OVERLAP_EAST.get(), 1, 50, 1));
+        spinNorth = new JSpinner(new SpinnerNumberModel(plugin.PROP_OVERLAP_NORTH.get(), 1, 50, 1));
 
         JPanel overlapPanel = new JPanel(new FlowLayout());
@@ -178,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.PROP_SIMULTANEOUS_CONNECTIONS.get(), 1, 30, 1));
+        spinSimConn = new JSpinner(new SpinnerNumberModel(plugin.PROP_SIMULTANEOUS_CONNECTIONS.get(), 1, 30, 1));
         JPanel overlapPanelSimConn = new JPanel(new FlowLayout());
         overlapPanelSimConn.add(labelSimConn);
@@ -191,48 +200,14 @@
 
         p.add(remotePanel);
-
     }
 
     public boolean ok() {
-        boolean change = false;
-        for (int i = 0; i < model.getRowCount(); ++i) {
-            String name = model.getValueAt(i,0).toString();
-            String url = model.getValueAt(i,1).toString();
-
-            WMSInfo origValue = oldValues.get(i);
-            if (origValue == null)
-            {
-                new WMSInfo(name, url, i).save();
-                change = true;
-            }
-            else
-            {
-                if (!origValue.name.equals(name) || !origValue.url.equals(url))
-                {
-                    origValue.name = name;
-                    origValue.url = url;
-                    origValue.save();
-                    change = true;
-                }
-                oldValues.remove(i);
-            }
-        }
-
-        // using null values instead of empty string really deletes
-        // the preferences entry
-        for (WMSInfo i : oldValues.values())
-        {
-            i.url = null;
-            i.name = null;
-            i.save();
-            change = true;
-        }
-
-        if (change) WMSPlugin.refreshMenu();
-
-        WMSPlugin.PROP_OVERLAP.put(overlapCheckBox.getModel().isSelected());
-        WMSPlugin.PROP_OVERLAP_EAST.put((Integer) spinEast.getModel().getValue());
-        WMSPlugin.PROP_OVERLAP_NORTH.put((Integer) spinNorth.getModel().getValue());
-        WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.put((Integer) spinSimConn.getModel().getValue());
+        plugin.info.save();
+        plugin.refreshMenu();
+
+        plugin.PROP_OVERLAP.put(overlapCheckBox.getModel().isSelected());
+        plugin.PROP_OVERLAP_EAST.put((Integer) spinEast.getModel().getValue());
+        plugin.PROP_OVERLAP_NORTH.put((Integer) spinNorth.getModel().getValue());
+        plugin.PROP_SIMULTANEOUS_CONNECTIONS.put((Integer) spinSimConn.getModel().getValue());
         allowRemoteControl = remoteCheckBox.getModel().isSelected();
 
@@ -279,4 +254,83 @@
         return null;
     }
+
+    /**
+     * The table model for the WMS layer
+     *
+     */
+    class WMSLayerTableModel extends DefaultTableModel {
+        public WMSLayerTableModel() {
+            setColumnIdentifiers(new String[]{tr("Menu Name"), tr("WMS URL"), trc("layer", "Zoom")});
+        }
+
+        public WMSInfo getRow(int row) {
+            return plugin.info.layers.get(row);
+        }
+
+        public void addRow(WMSInfo i) {
+            plugin.info.add(i);
+            int p = getRowCount()-1;
+            fireTableRowsInserted(p,p);
+        }
+
+        public void removeRow(int i) {
+            plugin.info.remove(getRow(i));
+            fireTableRowsDeleted(i,i);
+        }
+
+        @Override
+        public int getRowCount() {
+            return plugin.info.layers.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            WMSInfo info = plugin.info.layers.get(row);
+            switch(column) {
+            case 0: return info.name;
+            case 1: return info.getFullURL();
+            case 2: return info.pixelPerDegree == 0.0 ? "" : info.pixelPerDegree;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return (column != 2);
+        }
+    }
+
+    /**
+     * The table model for the WMS layer
+     *
+     */
+    class WMSDefaultLayerTableModel extends DefaultTableModel {
+        public WMSDefaultLayerTableModel() {
+            setColumnIdentifiers(new String[]{tr("Menu Name (Default)"), tr("WMS URL (Default)")});
+        }
+
+        public WMSInfo getRow(int row) {
+            return plugin.info.defaultLayers.get(row);
+        }
+
+        @Override
+        public int getRowCount() {
+            return plugin.info.defaultLayers.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            WMSInfo info = plugin.info.defaultLayers.get(row);
+            switch(column) {
+            case 0: return info.name;
+            case 1: return info.getFullURL();
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+    }
 }
-
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRemoteHandler.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRemoteHandler.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRemoteHandler.java	(revision 23207)
@@ -15,96 +15,92 @@
 public class WMSRemoteHandler extends RequestHandler {
 
-	public static final String command = "wms";
+    public static final String command = "wms";
 
-	@Override
-	public String getPermissionMessage() {
-		return tr("Remote Control has been asked to load a WMS layer from the following URL:") +
-		"<br>" + args.get("url");
-	}
+    @Override
+    public String getPermissionMessage() {
+        return tr("Remote Control has been asked to load a WMS layer from the following URL:") +
+        "<br>" + args.get("url");
+    }
 
-	@Override
-	public PermissionPrefWithDefault getPermissionPref()
-	{
-		return new PermissionPrefWithDefault(
-				"wmsplugin.remotecontrol",
-				true,
-		"RemoteControl: WMS forbidden by preferences");
-	}
+    @Override
+    public PermissionPrefWithDefault getPermissionPref()
+    {
+        return new PermissionPrefWithDefault(
+                "wmsplugin.remotecontrol",
+                true,
+        "RemoteControl: WMS forbidden by preferences");
+    }
 
-	@Override
-	protected String[] getMandatoryParams()
-	{
-		return new String[] { "url" };
-	}
+    @Override
+    protected String[] getMandatoryParams()
+    {
+        return new String[] { "url" };
+    }
 
-	@Override
-	protected void handleRequest() throws RequestHandlerErrorException {
-		String url = args.get("url");
-		String title = args.get("title");
-		if((title == null) || (title.length() == 0))
-		{
-			title = "remote WMS";
-		}
-		String cookies = args.get("cookies");
-		if(cookies == null)
-		{
-			cookies = "";
-		}
-		WMSLayer wmsLayer = new WMSLayer(title, url, cookies);
-		Main.main.addLayer(wmsLayer);
+    @Override
+    protected void handleRequest() throws RequestHandlerErrorException {
+        String url = args.get("url");
+        String title = args.get("title");
+        if((title == null) || (title.length() == 0))
+        {
+            title = tr("Remote WMS");
+        }
+        String cookies = args.get("cookies");
+        WMSLayer wmsLayer = new WMSLayer(new WMSInfo(title, url, cookies));
+        Main.main.addLayer(wmsLayer);
 
-	}
+    }
 
-	@Override
-	public void parseArgs() {
-		StringTokenizer st = new StringTokenizer(request, "&?");
-		HashMap<String, String> args = new HashMap<String, String>();
-		// skip first element which is the command
-		if(st.hasMoreTokens()) st.nextToken();
-		while (st.hasMoreTokens()) {
-			String param = st.nextToken();
-			int eq = param.indexOf("=");
-			if (eq > -1)
-			{
-				String key = param.substring(0, eq);
-				/* "url=" terminates normal parameters
-				 * and will be handled separately
-				 */
-				if("url".equals(key)) break;
+    @Override
+    public void parseArgs() {
+        StringTokenizer st = new StringTokenizer(request, "&?");
+        HashMap<String, String> args = new HashMap<String, String>();
+        // skip first element which is the command
+        if(st.hasMoreTokens()) st.nextToken();
+        while (st.hasMoreTokens()) {
+            String param = st.nextToken();
+            int eq = param.indexOf("=");
+            if (eq > -1)
+            {
+                String key = param.substring(0, eq);
+                /* "url=" terminates normal parameters
+                 * and will be handled separately
+                 */
+                if("url".equals(key)) break;
 
-				String value = param.substring(eq + 1);
-				// urldecode all normal values
-				try {
-					value = URLDecoder.decode(value, "UTF-8");
-				} catch (UnsupportedEncodingException e) {
-					// TODO Auto-generated catch block
-					e.printStackTrace();
-				}
-				args.put(key,
-						value);
-			}
-		}
-		// url as second or later parameter
-		int urlpos = request.indexOf("&url=");
-		// url as first (and only) parameter
-		if(urlpos < 0) urlpos = request.indexOf("?url=");
-		// url found?
-		if(urlpos >= 0) {
-			// URL value
-			String value = request.substring(urlpos + 5);
-			// allow skipping URL decoding with urldecode=false
-			String urldecode = args.get("urldecode");
-			if((urldecode == null) || (Boolean.valueOf(urldecode) == true))
-			{
-				try {
-					value = URLDecoder.decode(value, "UTF-8");
-				} catch (UnsupportedEncodingException e) {
-					// TODO Auto-generated catch block
-					e.printStackTrace();
-				}
-			}
-			args.put("url", value);
-		}
-		this.args = args;
-	}
+                String value = param.substring(eq + 1);
+                // urldecode all normal values
+                try {
+                    value = URLDecoder.decode(value, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+                args.put(key,
+                        value);
+            }
+        }
+        // url as second or later parameter
+        int urlpos = request.indexOf("&url=");
+        // url as first (and only) parameter
+        if(urlpos < 0) urlpos = request.indexOf("?url=");
+        // url found?
+        if(urlpos >= 0) {
+            // URL value
+            String value = request.substring(urlpos + 5);
+            // allow skipping URL decoding with urldecode=false
+            String urldecode = args.get("urldecode");
+            if((urldecode == null) || (Boolean.valueOf(urldecode) == true))
+            {
+                try {
+                    value = URLDecoder.decode(value, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+            args.put("url", value);
+        }
+        this.args = args;
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRequest.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRequest.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/WMSRequest.java	(revision 23207)
@@ -6,96 +6,96 @@
 
 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;
+    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 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 void finish(State state, BufferedImage image) {
+        this.state = state;
+        this.image = image;
+    }
 
-	public int getXIndex() {
-		return xIndex;
-	}
+    public int getXIndex() {
+        return xIndex;
+    }
 
-	public int getYIndex() {
-		return yIndex;
-	}
+    public int getYIndex() {
+        return yIndex;
+    }
 
-	public double getPixelPerDegree() {
-		return pixelPerDegree;
-	}
+    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 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;
-	}
+    @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 void setPriority(int priority) {
+        this.priority = priority;
+    }
 
-	public int getPriority() {
-		return priority;
-	}
+    public int getPriority() {
+        return priority;
+    }
 
-	public int compareTo(WMSRequest o) {
-		return priority - o.priority;
-	}
+    public int compareTo(WMSRequest o) {
+        return priority - o.priority;
+    }
 
-	public State getState() {
-		return state;
-	}
+    public State getState() {
+        return state;
+    }
 
-	public BufferedImage getImage() {
-		return image;
-	}
+    public BufferedImage getImage() {
+        return image;
+    }
 
-	@Override
-	public String toString() {
-		return "WMSRequest [xIndex=" + xIndex + ", yIndex=" + yIndex
-		+ ", pixelPerDegree=" + pixelPerDegree + "]";
-	}
+    @Override
+    public String toString() {
+        return "WMSRequest [xIndex=" + xIndex + ", yIndex=" + yIndex
+        + ", pixelPerDegree=" + pixelPerDegree + "]";
+    }
 
-	public boolean isReal() {
-		return real;
-	}
+    public boolean isReal() {
+        return real;
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/io/WMSLayerExporter.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/io/WMSLayerExporter.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/io/WMSLayerExporter.java	(revision 23207)
@@ -7,7 +7,7 @@
 
 public class WMSLayerExporter extends FileExporter{
-	
-	public WMSLayerExporter() {
-		super(new ExtensionFileFilter("wms", "wms", tr("WMS Files (*.wms)")));
-	}
+    
+    public WMSLayerExporter() {
+        super(new ExtensionFileFilter("wms", "wms", tr("WMS Files (*.wms)")));
+    }
 }
Index: applications/editors/josm/plugins/wmsplugin/src/wmsplugin/io/WMSLayerImporter.java
===================================================================
--- applications/editors/josm/plugins/wmsplugin/src/wmsplugin/io/WMSLayerImporter.java	(revision 23206)
+++ applications/editors/josm/plugins/wmsplugin/src/wmsplugin/io/WMSLayerImporter.java	(revision 23207)
@@ -7,7 +7,7 @@
 public class WMSLayerImporter extends FileImporter{
 
-	public WMSLayerImporter() {
-		super(new ExtensionFileFilter("wms", "wms", tr("WMS Files (*.wms)")));
-	}
-	
+    public WMSLayerImporter() {
+        super(new ExtensionFileFilter("wms", "wms", tr("WMS Files (*.wms)")));
+    }
+    
 }
