[DebianGIS-dev] [SCM] josm-plugins branch, upstream, updated. upstream/0.0.svn22363-2-g961fcf5

David Paleino dapal at debian.org
Tue Sep 7 09:37:17 UTC 2010


The following commit has been merged in the upstream branch:
commit 9776afb71cc8fe8bcbeb67015c4b8fe7c310d556
Author: David Paleino <dapal at debian.org>
Date:   Tue Sep 7 11:05:52 2010 +0200

    Imported Upstream version 0.0.svn23040

diff --git a/cadastre-fr/.settings/org.eclipse.jdt.core.prefs b/cadastre-fr/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0b5c6c4
--- /dev/null
+++ b/cadastre-fr/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Thu Mar 04 08:24:37 CET 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/cadastre-fr/build.xml b/cadastre-fr/build.xml
index fd5d8da..f8d743f 100644
--- a/cadastre-fr/build.xml
+++ b/cadastre-fr/build.xml
@@ -16,7 +16,7 @@
 **    > ant  install
 **
 ** To build against the core in ../../core, create a correct manifest and deploy to
-** SVN, 
+** SVN,
 **    set the properties commit.message and plugin.main.version
 ** and run
 **    > ant  publish
@@ -29,12 +29,12 @@
     <property name="plugin.build.dir"       value="build"/>
     <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
     <property name="ant.build.javac.target" value="1.5"/>
-	
-	
+
+
 
 	<property name="commit.message" value="Changed constructor for Plugin" />
-	<property name="plugin.main.version" value="3174" />
-	
+	<property name="plugin.main.version" value="3408" />
+
     <target name="init">
         <mkdir dir="${plugin.build.dir}"/>
     </target>
@@ -86,12 +86,12 @@
         </condition>
         <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
     </target>
-	
+
 	<!--
-		 ************************** Publishing the plugin *********************************** 
+		 ************************** Publishing the plugin ***********************************
 		-->
 			<!--
-			  ** extracts the JOSM release for the JOSM version in ../core and saves it in the 
+			  ** extracts the JOSM release for the JOSM version in ../core and saves it in the
 			  ** property ${coreversion.info.entry.revision}
 			  **
 			-->
@@ -103,7 +103,7 @@
 		                    <arg value="../../core"/>
 		        </exec>
 		        <xmlproperty file="core.info.xml" prefix="coreversion" keepRoot="true" collapseAttributes="true"/>
-				<echo>Building against core revision ${coreversion.info.entry.revision}.</echo>			
+				<echo>Building against core revision ${coreversion.info.entry.revision}.</echo>
 				<echo>Plugin-Mainversion is set to ${plugin.main.version}.</echo>
 				<delete file="core.info.xml" />
 			</target>
@@ -118,7 +118,7 @@
 			                    <arg value="commit"/>
 			                    <arg value="-m '${commit.message}'"/>
 			                    <arg value="."/>
-			    </exec>	    
+			    </exec>
 			</target>
 
 			<!--
@@ -130,37 +130,37 @@
 			                    <env key="LANG" value="C"/>
 			                    <arg value="up"/>
 			                    <arg value="."/>
-			    </exec>	    
+			    </exec>
 				<echo>Updating ${plugin.jar} ...</echo>
 			    <exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
 			                    <env key="LANG" value="C"/>
 			                    <arg value="up"/>
 			                    <arg value="../dist/${plugin.jar}"/>
-			    </exec>	    
+			    </exec>
 			</target>
-			
+
 			<!--
-			 ** commits the plugin.jar 
+			 ** commits the plugin.jar
 			 -->
 			<target name="commit-dist">
 					<echo>
 	***** Properties of published ${plugin.jar} *****
-	Commit message    : '${commit.message}'					
+	Commit message    : '${commit.message}'
 	Plugin-Mainversion: ${plugin.main.version}
 	JOSM build version: ${coreversion.info.entry.revision}
 	Plugin-Version    : ${version.entry.commit.revision}
-	***** / Properties of published ${plugin.jar} *****					
-						
+	***** / Properties of published ${plugin.jar} *****
+
 	Now commiting ${plugin.jar} ...
-	</echo>					
+	</echo>
 				    <exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
 				                    <env key="LANG" value="C"/>
 					    			<arg value="-m '${commit.message}'"/>
-				    				<arg value="commit"/>	                			
+				    				<arg value="commit"/>
 	            			        <arg value="${plugin.jar}"/>
-				    </exec>	    
+				    </exec>
 		   	</target>
-					
+
 			<target name="publish" depends="core-info,commit-current,update-current,clean,dist,commit-dist">
 			</target>
 </project>
diff --git a/cadastre-fr/src/cadastre_fr/Address.java b/cadastre-fr/src/cadastre_fr/Address.java
index 8d398f9..d0cff1d 100644
--- a/cadastre-fr/src/cadastre_fr/Address.java
+++ b/cadastre-fr/src/cadastre_fr/Address.java
@@ -88,8 +88,8 @@ public class Address extends MapMode implements MouseListener, MouseMotionListen
     final JTextField inputStreet = new JTextField();
     JLabel link = new JLabel();
     private Way selectedWay;
-    //private Relation selectedRelation;
     private boolean shift;
+    private boolean ctrl;
 
     public Address(MapFrame mapFrame) {
         super(tr("Add address"), "buildings", 
@@ -120,6 +120,7 @@ public class Address extends MapMode implements MouseListener, MouseMotionListen
         if (e.getButton() != MouseEvent.BUTTON1)
             return;
         shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
+        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
         MapView mv = Main.map.mapView;
         Point mousePos = e.getPoint();
         List<Way> mouseOnExistingWays = new ArrayList<Way>();
@@ -142,12 +143,21 @@ public class Address extends MapMode implements MouseListener, MouseMotionListen
             }
             if (currentMouseNode.get(tagHouseStreet) != null) {
                 inputStreet.setText(currentMouseNode.get(tagHouseStreet));
+                if (ctrl) {
+                    Collection<Command> cmds = new LinkedList<Command>();
+                    addAddrToPrimitive(currentMouseNode, cmds);
+                    if (num == null)
+                        applyInputNumberChange();
+                }
                 setSelectedWay((Way)null);
             } else {
                 // check if the node belongs to an associatedStreet relation
                 Way wayInRelationAddr = findWayInRelationAddr(currentMouseNode);
                 if (wayInRelationAddr == null) {
                     // node exists but doesn't carry address information : add tags like a new node
+                    if (ctrl) {
+                        applyInputNumberChange();
+                    }
                     Collection<Command> cmds = new LinkedList<Command>();
                     addAddrToPrimitive(currentMouseNode, cmds);
                 } else {
@@ -175,6 +185,9 @@ public class Address extends MapMode implements MouseListener, MouseMotionListen
                     Toolkit.getDefaultToolkit().beep();
                 } else {
                     Collection<Command> cmds = new LinkedList<Command>();
+                    if (ctrl) {
+                        applyInputNumberChange();
+                    }
                     if (tagPolygon.isSelected()) {
                         addAddrToPolygon(mouseOnExistingBuildingWays, cmds);
                     } else {
@@ -211,7 +224,7 @@ public class Address extends MapMode implements MouseListener, MouseMotionListen
     }
     
     private void addAddrToPrimitive(OsmPrimitive osm, Collection<Command> cmds) {
-        // add the current tag addr:housenumber in node and member in relation
+        // add the current tag addr:housenumber in node and member in relation (if so configured)
         if (shift) {
             try {
                 revertInputNumberChange();
diff --git a/cadastre-fr/src/cadastre_fr/CadastreInterface.java b/cadastre-fr/src/cadastre_fr/CadastreInterface.java
index 0169279..be0d168 100644
--- a/cadastre-fr/src/cadastre_fr/CadastreInterface.java
+++ b/cadastre-fr/src/cadastre_fr/CadastreInterface.java
@@ -58,7 +58,9 @@ public class CadastreInterface {
     final String cInterfaceRasterTA = "afficherCarteTa.do";
     final String cInterfaceRasterFeuille = "afficherCarteFeuille.do";
     final String cImageLinkStart = "title=\"image\"><a href=\"#\" onClick=\"popup('afficherCarteFeuille.do?f=";
+    final String cTAImageLinkStart = "title=\"image\"><a href=\"#\" onClick=\"popup('afficherCarteTa.do?f=";
     final String cImageNameStart = ">Feuille ";
+    final String cTAImageNameStart = "Tableau d'assemblage <strong>";
     
     final static long cCookieExpiration = 30 * 60 * 1000; // 30 minutes expressed in milliseconds
 
@@ -379,6 +381,17 @@ public class CadastreInterface {
     
     private void parseFeuillesList(String input) {
         listOfFeuilles.clear();
+        // get "Tableau d'assemblage"
+        if (Main.pref.getBoolean("cadastrewms.useTA", false)) {
+            while (input.indexOf(cTAImageLinkStart) != -1) {
+                input = input.substring(input.indexOf(cTAImageLinkStart) + cTAImageLinkStart.length());
+                String refTA = input.substring(0, input.indexOf("'"));
+                String nameTA = input.substring(input.indexOf(cTAImageNameStart) + cTAImageNameStart.length());
+                nameTA = nameTA.substring(0, nameTA.indexOf("<"));
+                listOfFeuilles.add(new PlanImage(nameTA, refTA));
+            }
+        }
+        // get "Feuilles"
         while (input.indexOf(cImageLinkStart) != -1) {
             input = input.substring(input.indexOf(cImageLinkStart)+cImageLinkStart.length());
             String refFeuille = input.substring(0, input.indexOf("'"));
diff --git a/cadastre-fr/src/cadastre_fr/CadastrePlugin.java b/cadastre-fr/src/cadastre_fr/CadastrePlugin.java
index 3443cf3..7f993e0 100644
--- a/cadastre-fr/src/cadastre_fr/CadastrePlugin.java
+++ b/cadastre-fr/src/cadastre_fr/CadastrePlugin.java
@@ -104,7 +104,7 @@ import org.openstreetmap.josm.data.projection.*;
  *                 - improved download cancellation
  *                 - from Erik Amzallag:
  *                 -     possibility to modify the auto-sourcing text just before upload 
- *                 - from Clément Ménier:
+ *                 - from Clément Ménier:
  *                 -     new option allowing an auto-selection of the first cadastre layer for grab
  *                 -     non-modal JDialog in MenuActionGrabPlanImage
  *                 -     new options in the image filter (bilinear, bicubic)
@@ -114,6 +114,9 @@ import org.openstreetmap.josm.data.projection.*;
  * 2.0 xx-xxx-xxxx - update projection for "La Reunion" departement to RGR92, UTM40S. 
  *                 - add 'departement' as option in the municipality selection
  *                 - fixed bug in cache directory size control (and disabled by default)
+ *                 - add map mode for addressing
+ *                 - from Nicolas Dumoulin:
+ *                 -     add "tableau d'assemblage" in raster images for georeferencing (as option)
  */
 public class CadastrePlugin extends Plugin {
     static String VERSION = "2.0";
diff --git a/cadastre-fr/src/cadastre_fr/CadastrePreferenceSetting.java b/cadastre-fr/src/cadastre_fr/CadastrePreferenceSetting.java
index b4ab592..d641c79 100644
--- a/cadastre-fr/src/cadastre_fr/CadastrePreferenceSetting.java
+++ b/cadastre-fr/src/cadastre_fr/CadastrePreferenceSetting.java
@@ -40,6 +40,8 @@ public class CadastrePreferenceSetting implements PreferenceSetting {
 
     private JCheckBox disableImageCropping = new JCheckBox(tr("Disable image cropping during georeferencing."));
     
+    private JCheckBox enableTableauAssemblage = new JCheckBox(tr("Use \"Tableau d''assemblage\""));
+    
     private JCheckBox autoFirstLayer = new JCheckBox(tr("Select first WMS layer in list."));
     
     private JCheckBox dontUseRelation = new JCheckBox(tr("Don't use relation for addresses (but \"addr:street\" on elements)."));
@@ -286,7 +288,11 @@ public class CadastrePreferenceSetting implements PreferenceSetting {
         // option to disable image cropping during raster image georeferencing
         disableImageCropping.setSelected(Main.pref.getBoolean("cadastrewms.noImageCropping", false));
         disableImageCropping.setToolTipText(tr("Disable image cropping during georeferencing."));
-        cadastrewms.add(disableImageCropping, GBC.eop().insets(0, 0, 0, 0));
+        cadastrewms.add(disableImageCropping, GBC.std().insets(0, 0, 10, 0));
+        // option to add the "Tableau d'assemblage" in list of sheets to grab
+        enableTableauAssemblage.setSelected(Main.pref.getBoolean("cadastrewms.useTA", false));
+        enableTableauAssemblage.setToolTipText(tr("Add the \"Tableau(x) d'assemblage\" in the list of cadastre sheets to grab."));
+        cadastrewms.add(enableTableauAssemblage, GBC.eop().insets(0, 0, 0, 0));
         // the crosspiece display
         JLabel jLabelCrosspieces = new JLabel(tr("Display crosspieces:"));
         cadastrewms.add(jLabelCrosspieces, GBC.std().insets(0, 0, 10, 0));
@@ -400,6 +406,7 @@ public class CadastrePreferenceSetting implements PreferenceSetting {
         } catch (NumberFormatException e) { // ignore the last input
         }
         Main.pref.put("cadastrewms.noImageCropping", disableImageCropping.isSelected());
+        Main.pref.put("cadastrewms.useTA", enableTableauAssemblage.isSelected());
         if (crosspiece1.isSelected()) Main.pref.put("cadastrewms.crosspieces", "0");
         else if (crosspiece2.isSelected()) Main.pref.put("cadastrewms.crosspieces", "1");
         else if (crosspiece3.isSelected()) Main.pref.put("cadastrewms.crosspieces", "2");
diff --git a/cadastre-fr/src/cadastre_fr/WMSLayer.java b/cadastre-fr/src/cadastre_fr/WMSLayer.java
index dd2f804..bfc4650 100644
--- a/cadastre-fr/src/cadastre_fr/WMSLayer.java
+++ b/cadastre-fr/src/cadastre_fr/WMSLayer.java
@@ -4,7 +4,6 @@ package cadastre_fr;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
@@ -21,9 +20,9 @@ import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Vector;
 
+import javax.swing.Action;
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
-import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
@@ -43,8 +42,6 @@ import org.openstreetmap.josm.io.OsmTransferException;
  */
 public class WMSLayer extends Layer implements ImageObserver {
 
-    Component[] component = null;
-
     private int lambertZone = -1;
 
     protected static final Icon icon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(
@@ -58,9 +55,9 @@ public class WMSLayer extends Layer implements ImageObserver {
      * v3 to v4 = add original raster image width and height
      */
     protected final int serializeFormatVersion = 4;
-    
+
     public static int currentFormat;
-    
+
     private static final int cBBoxForBuildings = 50; // hard coded size of grabbed boxes for building layers
 
     private ArrayList<EastNorthBound> dividedBbox = new ArrayList<EastNorthBound>();
@@ -68,7 +65,7 @@ public class WMSLayer extends Layer implements ImageObserver {
     private CacheControl cacheControl = null;
 
     private String location = "";
-    
+
     private String departement = "";
 
     private String codeCommune = "";
@@ -87,8 +84,8 @@ public class WMSLayer extends Layer implements ImageObserver {
     private EastNorth rasterMax;
     private double rasterRatio;
 
-    private JMenuItem saveAsPng;
-    
+    private Action saveAsPng;
+
     public boolean adjustModeEnabled;
 
 
@@ -105,7 +102,8 @@ public class WMSLayer extends Layer implements ImageObserver {
         CadastrePlugin.pluginUsed = true;
     }
 
-    public void destroy() {
+    @Override
+	public void destroy() {
         // if the layer is currently saving the images in the cache, wait until it's finished
         if (cacheControl != null) {
             while (!cacheControl.isCachePipeEmpty()) {
@@ -118,9 +116,9 @@ public class WMSLayer extends Layer implements ImageObserver {
         dividedBbox = null;
         System.out.println("Layer "+location+" destroyed");
     }
-    
+
     private static String buildName(String location, String codeCommune, boolean buildingOnly) {
-        String ret = new String(location.toUpperCase());
+        String ret = location.toUpperCase();
         if (codeCommune != null && !codeCommune.equals(""))
             ret += "(" + codeCommune + ")";
         if (buildingOnly)
@@ -135,7 +133,7 @@ public class WMSLayer extends Layer implements ImageObserver {
     public void grab(CadastreGrabber grabber, Bounds b) throws IOException {
         grab(grabber, b, true);
     }
-    
+
     public void grab(CadastreGrabber grabber, Bounds b, boolean useFactor) throws IOException {
         cancelled = false;
         if (useFactor) {
@@ -224,7 +222,7 @@ public class WMSLayer extends Layer implements ImageObserver {
             }
         } else {
             // divide to fixed size squares
-            int cSquare = factor == 4 ? Integer.parseInt(Main.pref.get("cadastrewms.squareSize", "100")) : optionalSize;   
+            int cSquare = factor == 4 ? Integer.parseInt(Main.pref.get("cadastrewms.squareSize", "100")) : optionalSize;
             minEast = minEast - minEast % cSquare;
             minNorth = minNorth - minNorth % cSquare;
             for (int xEast = (int)minEast; xEast < lambertMax.east(); xEast+=cSquare)
@@ -285,7 +283,7 @@ public class WMSLayer extends Layer implements ImageObserver {
         }
         if (this.adjustModeEnabled) {
             WMSAdjustAction.paintAdjustFrames(g, mv);
-        }            
+        }
     }
 
     @Override
@@ -302,17 +300,17 @@ public class WMSLayer extends Layer implements ImageObserver {
     }
 
     @Override
-    public Component[] getMenuEntries() {
-        saveAsPng = new JMenuItem(new MenuActionSaveRasterAs(this));
+    public Action[] getMenuEntries() {
+        saveAsPng = new MenuActionSaveRasterAs(this);
         saveAsPng.setEnabled(isRaster);
-        component = new Component[] { new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
-                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
-                new JMenuItem(new MenuActionLoadFromCache()),
+        return new Action[] {
+        		LayerListDialog.getInstance().createShowHideLayerAction(),
+                LayerListDialog.getInstance().createDeleteLayerAction(),
+                new MenuActionLoadFromCache(),
                 saveAsPng,
-                new JMenuItem(new LayerListPopup.InfoAction(this)),
+                new LayerListPopup.InfoAction(this),
 
         };
-        return component;
     }
 
     public GeorefImage findImage(EastNorth eastNorth) {
@@ -442,8 +440,8 @@ public class WMSLayer extends Layer implements ImageObserver {
         double rasterSizeY = communeBBox.max.getY() - communeBBox.min.getY();
         double ratio = rasterSizeY/rasterSizeX;
         // keep same ratio on screen as WMS bbox (stored in communeBBox)
-        rasterMin = new EastNorth(eaMin.getX(), rasterCenter.getY()-(eaMax.getX()-eaMin.getX())*ratio/2); 
-        rasterMax = new EastNorth(eaMax.getX(), rasterCenter.getY()+(eaMax.getX()-eaMin.getX())*ratio/2); 
+        rasterMin = new EastNorth(eaMin.getX(), rasterCenter.getY()-(eaMax.getX()-eaMin.getX())*ratio/2);
+        rasterMax = new EastNorth(eaMax.getX(), rasterCenter.getY()+(eaMax.getX()-eaMin.getX())*ratio/2);
         rasterRatio = (rasterMax.getX()-rasterMin.getX())/rasterSizeX;
     }
 
@@ -597,7 +595,7 @@ public class WMSLayer extends Layer implements ImageObserver {
         setCommuneBBox(new EastNorthBound(new EastNorth(0,0), new EastNorth(images.get(0).image.getWidth()-1,images.get(0).image.getHeight()-1)));
         rasterRatio = (rasterMax.getX()-rasterMin.getX())/(communeBBox.max.getX() - communeBBox.min.getX());
     }
-    
+
     public EastNorthBound getCommuneBBox() {
         return communeBBox;
     }
@@ -639,7 +637,7 @@ public class WMSLayer extends Layer implements ImageObserver {
         this.rasterMax = rasterMax.rotate(rasterCenter, angle);
 //        double proportion = dst1.distance(dst2)/org1.distance(org2);
         images.get(0).rotate(rasterCenter, angle);
-        this.angle += angle; 
+        this.angle += angle;
     }
 
     private void paintCrosspieces(Graphics g, MapView mv) {
diff --git a/editgpx/build.xml b/editgpx/build.xml
index 598213d..326bed0 100644
--- a/editgpx/build.xml
+++ b/editgpx/build.xml
@@ -17,7 +17,7 @@
 **    > ant  install
 **
 ** To build against the core in ../../core, create a correct manifest and deploy to
-** SVN, 
+** SVN,
 **    set the properties commit.message and plugin.main.version
 ** and run
 **    > ant  publish
@@ -28,7 +28,7 @@
 
 
 	<property name="commit.message" value="Changed constructor signature of plugin main class" />
-	<property name="plugin.main.version" value="2907" />
+	<property name="plugin.main.version" value="3408" />
 
 
 	<property name="josm"                   location="../../core/dist/josm-custom.jar"/>
@@ -88,10 +88,10 @@
 	</target>
 
 	<!--
-	 ************************** Publishing the plugin *********************************** 
+	 ************************** Publishing the plugin ***********************************
 	-->
 	<!--
-	** extracts the JOSM release for the JOSM version in ../core and saves it in the 
+	** extracts the JOSM release for the JOSM version in ../core and saves it in the
 	** property ${coreversion.info.entry.revision}
 	**
 	-->
@@ -140,17 +140,17 @@
 	</target>
 
 	<!--
-	** commits the plugin.jar 
+	** commits the plugin.jar
 	-->
 	<target name="commit-dist">
 		<echo>
 ***** Properties of published ${plugin.jar} *****
-Commit message    : '${commit.message}'					
+Commit message    : '${commit.message}'
 Plugin-Mainversion: ${plugin.main.version}
 JOSM build version: ${coreversion.info.entry.revision}
 Plugin-Version    : ${version.entry.commit.revision}
-***** / Properties of published ${plugin.jar} *****					
-					
+***** / Properties of published ${plugin.jar} *****
+
 Now commiting ${plugin.jar} ...
 </echo>
 		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
diff --git a/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxLayer.java b/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxLayer.java
index 6f49c7c..12d1fd3 100644
--- a/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxLayer.java
+++ b/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxLayer.java
@@ -6,17 +6,15 @@ package org.openstreetmap.josm.plugins.editgpx;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Graphics2D;
 import java.awt.Point;
 import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 
 import javax.swing.AbstractAction;
+import javax.swing.Action;
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
-import javax.swing.JMenuItem;
-import javax.swing.JSeparator;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
@@ -72,16 +70,16 @@ public class EditGpxLayer extends Layer {
 	}
 
 	@Override
-	public Component[] getMenuEntries() {
-		return new Component[] {
-				new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
-				new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
-				new JSeparator(),
-				new JMenuItem(layerImport),
-				new JMenuItem(new ConvertToGpxLayerAction()),
-				new JMenuItem(new ConvertToAnonTimeGpxLayerAction()),
-				new JSeparator(),
-				new JMenuItem(new LayerListPopup.InfoAction(this))};
+	public Action[] getMenuEntries() {
+		return new Action[] {
+				LayerListDialog.getInstance().createShowHideLayerAction(),
+				LayerListDialog.getInstance().createDeleteLayerAction(),
+				SeparatorLayerAction.INSTANCE,
+				layerImport,
+				new ConvertToGpxLayerAction(),
+				new ConvertToAnonTimeGpxLayerAction(),
+				SeparatorLayerAction.INSTANCE,
+				new LayerListPopup.InfoAction(this)};
 	}
 
 	@Override
diff --git a/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxPlugin.java b/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxPlugin.java
index 6699953..b33f5f5 100644
--- a/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxPlugin.java
+++ b/editgpx/src/org/openstreetmap/josm/plugins/editgpx/EditGpxPlugin.java
@@ -24,7 +24,6 @@ import org.openstreetmap.josm.plugins.PluginInformation;
  *
  * TODO:
  * - BUG: when importing eGpxLayer is shown as RawGpxLayer??
- * - BUG: after deletion of layer not all data is deleted (eg dataset)
  * - implement reset if user made mistake while marking
  *
  *
diff --git a/measurement/build.xml b/measurement/build.xml
index 087f340..47ef8c2 100644
--- a/measurement/build.xml
+++ b/measurement/build.xml
@@ -17,7 +17,7 @@
 **    > ant  install
 **
 ** To build against the core in ../../core, create a correct manifest and deploy to
-** SVN, 
+** SVN,
 **    set the properties commit.message and plugin.main.version
 ** and run
 **    > ant  publish
@@ -27,7 +27,7 @@
 <project name="measurement" default="dist" basedir=".">
 
 	<property name="commit.message" value="Changed the constructor signature of the plugin main class" />
-	<property name="plugin.main.version" value="2907" />
+	<property name="plugin.main.version" value="3408" />
 
 
 	<property name="josm"                   location="../../core/dist/josm-custom.jar"/>
@@ -86,10 +86,10 @@
 	</target>
 
 	<!--
-		 ************************** Publishing the plugin *********************************** 
+		 ************************** Publishing the plugin ***********************************
 		-->
 	<!--
-		** extracts the JOSM release for the JOSM version in ../core and saves it in the 
+		** extracts the JOSM release for the JOSM version in ../core and saves it in the
 		** property ${coreversion.info.entry.revision}
 		**
 		-->
@@ -138,17 +138,17 @@
 	</target>
 
 	<!--
-		** commits the plugin.jar 
+		** commits the plugin.jar
 		-->
 	<target name="commit-dist">
 		<echo>
 	***** Properties of published ${plugin.jar} *****
-	Commit message    : '${commit.message}'					
+	Commit message    : '${commit.message}'
 	Plugin-Mainversion: ${plugin.main.version}
 	JOSM build version: ${coreversion.info.entry.revision}
 	Plugin-Version    : ${version.entry.commit.revision}
-	***** / Properties of published ${plugin.jar} *****					
-						
+	***** / Properties of published ${plugin.jar} *****
+
 	Now commiting ${plugin.jar} ...
 	</echo>
 		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
diff --git a/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java
index a34fd33..087a52a 100644
--- a/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java
+++ b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java
@@ -14,6 +14,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 
 import javax.swing.AbstractAction;
+import javax.swing.Action;
 import javax.swing.Box;
 import javax.swing.DefaultListCellRenderer;
 import javax.swing.DefaultListModel;
@@ -23,9 +24,7 @@ import javax.swing.JCheckBox;
 import javax.swing.JDialog;
 import javax.swing.JLabel;
 import javax.swing.JList;
-import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
-import javax.swing.JSeparator;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
@@ -93,14 +92,14 @@ public class MeasurementLayer extends Layer {
         return getToolTipText();
     }
 
-    @Override public Component[] getMenuEntries() {
-        return new Component[]{
-            new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+    @Override public Action[] getMenuEntries() {
+        return new Action[]{
+            LayerListDialog.getInstance().createShowHideLayerAction(),
             // TODO: implement new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
-            new JSeparator(),
-            new JMenuItem(new GPXLayerImportAction(this)),
-            new JSeparator(),
-            new JMenuItem(new LayerListPopup.InfoAction(this))};
+            SeparatorLayerAction.INSTANCE,
+            new GPXLayerImportAction(this),
+            SeparatorLayerAction.INSTANCE,
+            new LayerListPopup.InfoAction(this)};
     }
 
     public void removeLastPoint(){
@@ -221,7 +220,7 @@ public class MeasurementLayer extends Layer {
                            Math.sin(lat1) * coslat2 * Math.cos(dlon)))) / Math.PI;
     }
 
-    public static double OldangleBetween(LatLon p1, LatLon p2){
+    public static double oldAngleBetween(LatLon p1, LatLon p2){
         double lat1, lon1, lat2, lon2;
         double dlon, dlat;
         double heading;
diff --git a/DirectUpload/.classpath b/remotecontrol/.classpath
similarity index 97%
copy from DirectUpload/.classpath
copy to remotecontrol/.classpath
index b5edc13..11208d5 100644
--- a/DirectUpload/.classpath
+++ b/remotecontrol/.classpath
@@ -1,7 +1,7 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/colorscheme/.project b/remotecontrol/.project
similarity index 82%
copy from colorscheme/.project
copy to remotecontrol/.project
index f9fda49..9b0c9ee 100644
--- a/colorscheme/.project
+++ b/remotecontrol/.project
@@ -1,17 +1,18 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>JOSM-colorscheme</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>remotecontrol</name>
+	<comment></comment>
+	<projects>
+		<project>JOSM</project>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/remotecontrol/CONTRIBUTION b/remotecontrol/CONTRIBUTION
new file mode 100644
index 0000000..06f8465
--- /dev/null
+++ b/remotecontrol/CONTRIBUTION
@@ -0,0 +1,6 @@
+Originally developed by Frederik Ramm <frederik at remote.org>. Add yourself
+if you contribute.
+
+Stephan Knauss <osm at stephans-server.de>:
+   added /version command to return protocol version
+   added CORS headers for modern browsers, legacy browsers can use jsonp
diff --git a/remotecontrol/README b/remotecontrol/README
new file mode 100644
index 0000000..26828d4
--- /dev/null
+++ b/remotecontrol/README
@@ -0,0 +1,9 @@
+A plugin for JOSM that listens for commands on a TCP port (8111).
+It can be used to tell JOSM to zoom to an area and download data.
+Details are found on the Wiki page,
+
+http://wiki.openstreetmap.org/index.php/JOSM/Plugins/RemoteControl
+
+Initially written by Frederik Ramm <frederik at remote.org>. License:
+GPL V2 or later. Incorporates code taken from YWMS plugin by frsantos.
+
diff --git a/cadastre-fr/build.xml b/remotecontrol/build.xml
similarity index 84%
copy from cadastre-fr/build.xml
copy to remotecontrol/build.xml
index fd5d8da..f9fe76a 100644
--- a/cadastre-fr/build.xml
+++ b/remotecontrol/build.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
-** This is a template build file for the cadastre-fr plugin.
+** This is the build file for the remotecontrol plugin
 **
 ** Maintaining versions
 ** ====================
@@ -23,25 +24,24 @@
 **
 **
 -->
-<project name="cadastre-fr" default="dist" basedir=".">
+<project name="remotecontrol" default="dist" basedir=".">
+	
+	<!-- set before publising -->
+	<property name="commit.message" value="permissen checkbox for new object creation" />		
+	<property name="plugin.main.version" value="2830" />
+	
     <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
     <property name="plugin.dist.dir"        value="../../dist"/>
     <property name="plugin.build.dir"       value="build"/>
     <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
-    <property name="ant.build.javac.target" value="1.5"/>
-	
-	
-
-	<property name="commit.message" value="Changed constructor for Plugin" />
-	<property name="plugin.main.version" value="3174" />
-	
     <target name="init">
         <mkdir dir="${plugin.build.dir}"/>
     </target>
     <target name="compile" depends="init">
         <echo message="creating ${plugin.jar}"/>
-        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
-            <compilerarg value="-Xlint:deprecation"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}"
+		       target="1.5" source="1.5" encoding="UTF-8">
+		<compilerarg value="-Xlint:deprecation"/>
             <compilerarg value="-Xlint:unchecked"/>
         </javac>
     </target>
@@ -51,15 +51,17 @@
         </copy>
         <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
             <manifest>
-                <attribute name="Author" value="Pieren"/>
-                <attribute name="Plugin-Class" value="cadastre_fr.CadastrePlugin"/>
+                <attribute name="Author" value="Frederik Ramm, Bodo Meissner"/>
+                <attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.remotecontrol.RemoteControlPlugin"/>
                 <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
-                <attribute name="Plugin-Description" value="A special handler for the French land registry WMS server."/>
-                <attribute name="Plugin-Icon" value="images/preferences/cadastrewms.gif"/>
-                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/wiki/FR:JOSM/Fr:Plugin/Cadastre"/>
+                <attribute name="Plugin-Description" value="Let other applications send commands to JOSM."/>
+                <attribute name="Plugin-Icon" value="images/preferences/remotecontrol.gif"/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/RemoteControl"/>
                 <attribute name="Plugin-Mainversion" value="${plugin.main.version}"/>
-                <attribute name="Plugin-Stage" value="60"/>
                 <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            	<!-- This plugin must be loaded before any plugin that may want to register 
+            		 handlers for additional remote commands.-->
+            	<attribute name="Plugin-Stage" value="20"/>
             </manifest>
         </jar>
     </target>
diff --git a/remotecontrol/images/preferences/remotecontrol.gif b/remotecontrol/images/preferences/remotecontrol.gif
new file mode 100644
index 0000000..f82493b
Binary files /dev/null and b/remotecontrol/images/preferences/remotecontrol.gif differ
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/HttpServer.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/HttpServer.java
new file mode 100644
index 0000000..d2ea82c
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/HttpServer.java
@@ -0,0 +1,75 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.InetAddress;
+
+/**
+ * Simple HTTP server that spawns a {@link RequestProcessor} for every
+ * connection.
+ *
+ * Taken from YWMS plugin by frsantos.
+ */
+
+public class HttpServer extends Thread {
+
+    /** Default port for the HTTP server */
+    public static final int DEFAULT_PORT = 8111;
+
+    /** The server socket */
+    private ServerSocket server;
+
+    /**
+     * Constructor
+     * @param port The port this server will listen on
+     * @throws IOException when connection errors
+     */
+    public HttpServer(int port)
+        throws IOException
+    {
+        super("RemoteControl HTTP Server");
+        this.setDaemon(true);
+        // Start the server socket with only 1 connection.
+        // Also make sure we only listen
+        // on the local interface so nobody from the outside can connect!
+        this.server = new ServerSocket(port, 1,
+            InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
+    }
+
+    /**
+     * The main loop, spawns a {@link RequestProcessor} for each connection
+     */
+    public void run()
+    {
+        System.out.println("RemoteControl::Accepting connections on port " + server.getLocalPort());
+        while (true)
+        {
+            try
+            {
+                Socket request = server.accept();
+                RequestProcessor.processRequest(request);
+            }
+            catch( SocketException se)
+            {
+                if( !server.isClosed() )
+                    se.printStackTrace();
+            }
+            catch (IOException ioe)
+            {
+                ioe.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Stops the HTTP server
+     *
+     * @throws IOException
+     */
+    public void stopServer() throws IOException
+    {
+        server.close();
+    }
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/PermissionPref.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/PermissionPref.java
new file mode 100644
index 0000000..b1d8eb0
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/PermissionPref.java
@@ -0,0 +1,24 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+/**
+ * Contains a preference name to control permission for the operation
+ * implemented by the RequestHandler, and an error message to be displayed
+ * if not permitted.
+ *
+ * Use @see PermissionPrefWithDefault instead of this class.
+ * 
+ * @author Bodo Meissner
+ */
+ @Deprecated
+public class PermissionPref {
+	/** name of the preference setting to permit the remote operation */
+	String pref;
+	/** message to be displayed if operation is not permitted */
+	String message;
+	
+	public PermissionPref(String pref, String message)
+	{
+		this.pref = pref;
+		this.message = message;
+	}
+}
\ No newline at end of file
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/PermissionPrefWithDefault.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/PermissionPrefWithDefault.java
new file mode 100644
index 0000000..83a2f3d
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/PermissionPrefWithDefault.java
@@ -0,0 +1,22 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+/**
+ * This class should replace PermissionPref because it allows explicit 
+ * specification of the permission's default value.
+ * 
+ * @author Bodo Meissner
+ */
+ at SuppressWarnings("deprecation")
+public class PermissionPrefWithDefault extends PermissionPref {
+
+	boolean defaultVal = true;
+
+	public PermissionPrefWithDefault(String pref, boolean defaultVal, String message) {
+		super(pref, message);
+		this.defaultVal = defaultVal;
+	}
+
+	public PermissionPrefWithDefault(PermissionPref prefWithoutDefault) {
+		super(prefWithoutDefault.pref, prefWithoutDefault.message);
+	}
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RemoteControlPlugin.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RemoteControlPlugin.java
new file mode 100644
index 0000000..fe90e82
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RemoteControlPlugin.java
@@ -0,0 +1,106 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+import java.io.IOException;
+
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+/**
+ * Base plugin for remote control operations. 
+ * This plugin contains operations that use JOSM core only.
+ * 
+ * Other plugins can register additional operations by calling 
+ * @see addRequestHandler().
+ * To allow API changes this plugin contains a @see getVersion() method.
+ * 
+ * IMPORTANT! increment the minor version on compatible API extensions
+ * and increment the major version and set minor to 0 on incompatible changes.
+ */
+public class RemoteControlPlugin extends Plugin
+{
+	/** API version
+	 * IMPORTANT! update the version number on API changes.
+	 */
+	static final int apiMajorVersion = 1;
+	static final int apiMinorVersion = 0;
+	
+	/**
+	 * RemoteControl HTTP protocol version. Change minor number for compatible
+	 * interface extensions. Change major number in case of incompatible
+	 * changes.
+	 */
+	static final int protocolMajorVersion = 1;
+	static final int protocolMinorVersion = 2;
+	
+    /** The HTTP server this plugin launches */
+    static HttpServer server;
+
+    /**
+     * Returns an array of int values with major and minor API version 
+     * and major and minor HTTP protocol version.
+     *  
+     * The function returns an int[4] instead of an object with fields
+     * to avoid ClassNotFound errors with old versions of remotecontrol.
+     *  
+     * @return array of integer version numbers: 
+     *    apiMajorVersion, apiMinorVersion, protocolMajorVersion, protocolMajorVersion
+     */
+    public int[] getVersion()
+    {
+    	int versions[] = {apiMajorVersion, apiMinorVersion, protocolMajorVersion, protocolMajorVersion};
+    	return versions;
+    }
+    
+    /**
+     * Creates the plugin, and starts the HTTP server
+     */
+    public RemoteControlPlugin(PluginInformation info)
+    {
+    	super(info);
+    	/*
+		System.out.println("constructor " + this.getClass().getName() + " (" + info.name +
+				" v " + info.version + " stage " + info.stage + ")");
+		*/
+        restartServer();
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting()
+    {
+        return new RemoteControlPreferences();
+    }
+
+    /**
+     * Starts or restarts the HTTP server
+     *
+     */
+    public void restartServer()
+    {
+        try
+        {
+            if (server != null)
+                server.stopServer();
+
+            int port = HttpServer.DEFAULT_PORT;
+            server = new HttpServer(port);
+            server.start();
+        }
+        catch(IOException ioe)
+        {
+            ioe.printStackTrace();
+        }
+    }
+
+    /**
+     * Add external external request handler.
+     * Can be used by other plug-ins that want to use remote control.
+     *
+     * @param handler The additional request handler.
+     */
+    public void addRequestHandler(String command, Class<? extends RequestHandler> handlerClass)
+    {
+        RequestProcessor.addRequestHandlerClass(command, handlerClass);
+    }
+    
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RemoteControlPreferences.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RemoteControlPreferences.java
new file mode 100644
index 0000000..40d7424
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RemoteControlPreferences.java
@@ -0,0 +1,80 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.GridBagLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.AddNodeHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.ImportHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.LoadAndZoomHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.VersionHandler;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Preference settings for the Remote Control plugin
+ *
+ * @author Frederik Ramm
+ */
+public class RemoteControlPreferences implements PreferenceSetting
+{
+    private JCheckBox permissionLoadData = new JCheckBox(tr("load data from API"));
+    private JCheckBox permissionImportData = new JCheckBox(tr("import data from URL"));
+    private JCheckBox permissionCreateObjects = new JCheckBox(tr("create new objects"));
+    private JCheckBox permissionChangeSelection = new JCheckBox(tr("change the selection"));
+    private JCheckBox permissionChangeViewport = new JCheckBox(tr("change the viewport"));
+    private JCheckBox permissionReadProtocolversion = new JCheckBox(tr("read protocol version"));
+    private JCheckBox alwaysAskUserConfirm = new JCheckBox(tr("confirm all Remote Control actions manually"));
+
+    public void addGui(final PreferenceTabbedPane gui)
+    {
+        String description = tr("A plugin that allows JOSM to be controlled from other applications.");
+        JPanel remote = gui.createPreferenceTab("remotecontrol.gif", tr("Remote Control"), tr("Settings for the Remote Control plugin."));
+        remote.add(new JLabel("<html>"+tr("The Remote Control plugin will always listen on port 8111 on localhost." +
+                "The port is not variable because it is referenced by external applications talking to the plugin.") + "</html>"), GBC.eol().insets(0,5,0,10).fill(GBC.HORIZONTAL));
+
+        JPanel perms = new JPanel();
+        perms.setLayout(new GridBagLayout());
+        perms.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.gray), tr("Permitted actions")));
+        perms.add(permissionLoadData, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        perms.add(permissionImportData, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        perms.add(permissionChangeSelection, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        perms.add(permissionChangeViewport, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        perms.add(permissionCreateObjects, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        perms.add(permissionReadProtocolversion, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        remote.add(perms, GBC.eol().fill(GBC.HORIZONTAL));
+
+        remote.add(alwaysAskUserConfirm, GBC.eol().insets(0,5,0,0).fill(GBC.HORIZONTAL));
+        remote.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
+
+        permissionLoadData.setSelected(Main.pref.getBoolean(LoadAndZoomHandler.loadDataPermissionKey, LoadAndZoomHandler.loadDataPermissionDefault));
+        permissionImportData.setSelected(Main.pref.getBoolean(ImportHandler.permissionKey, ImportHandler.permissionDefault));
+        permissionChangeSelection.setSelected(Main.pref.getBoolean(LoadAndZoomHandler.changeSelectionPermissionKey, LoadAndZoomHandler.changeSelectionPermissionDefault));
+        permissionChangeViewport.setSelected(Main.pref.getBoolean(LoadAndZoomHandler.changeViewportPermissionKey, LoadAndZoomHandler.changeViewportPermissionDefault));
+        permissionCreateObjects.setSelected(Main.pref.getBoolean(AddNodeHandler.permissionKey, AddNodeHandler.permissionDefault));
+        permissionReadProtocolversion.setSelected(Main.pref.getBoolean(VersionHandler.permissionKey, VersionHandler.permissionDefault));
+        alwaysAskUserConfirm.setSelected(Main.pref.getBoolean(RequestHandler.globalConfirmationKey, RequestHandler.globalConfirmationDefault));
+
+    }
+
+    public boolean ok() {
+        Main.pref.put(LoadAndZoomHandler.loadDataPermissionKey, permissionLoadData.isSelected());
+        Main.pref.put(ImportHandler.permissionKey, permissionImportData.isSelected());
+        Main.pref.put(LoadAndZoomHandler.changeSelectionPermissionKey, permissionChangeSelection.isSelected());
+        Main.pref.put(LoadAndZoomHandler.changeViewportPermissionKey, permissionChangeViewport.isSelected());
+        Main.pref.put(AddNodeHandler.permissionKey, permissionCreateObjects.isSelected());
+        Main.pref.put(VersionHandler.permissionKey, permissionReadProtocolversion.isSelected());
+        Main.pref.put(RequestHandler.globalConfirmationKey, alwaysAskUserConfirm.isSelected());
+        // FIXME confirm return value - really no restart needed?
+        return false /* no restart needed */;
+    }
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandler.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandler.java
new file mode 100644
index 0000000..2a2175d
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandler.java
@@ -0,0 +1,207 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * This is the parent of all classes that handle a specific command 
+ * in remote control plug-in.
+ *   
+ * @author Bodo Meissner
+ */
+public abstract class RequestHandler
+{
+	public static final String globalConfirmationKey = "remotecontrol.always-confirm";
+	public static final boolean globalConfirmationDefault = false;
+	
+	/** The GET request arguments */
+	protected HashMap<String,String> args;
+	
+	/** The request URL without "GET". */
+    protected String request;
+
+    /** default response */
+    protected String content = "OK\r\n";
+    /** default content type */
+    protected String contentType = "text/plain";
+
+    /** will be filled with the command assigned to the subclass */
+    protected String myCommand;
+    
+    /**
+     * Check permission and parameters and handle request.
+     * 
+     * @throws RequestHandlerForbiddenException
+     * @throws RequestHandlerBadRequestException
+     * @throws RequestHandlerErrorException
+     */
+    final void handle() throws RequestHandlerForbiddenException, RequestHandlerBadRequestException, RequestHandlerErrorException
+    {
+    	checkPermission();
+    	checkMandatoryParams();
+    	handleRequest();
+    }
+    
+    /**
+     * Handle a specific command sent as remote control.
+     * 
+     * This method of the subclass will do the real work.  
+     * 
+     * @throws RequestHandlerErrorException
+     * @throws RequestHandlerBadRequestException 
+     */
+    protected abstract void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException;
+
+    /**
+     * Get a specific message to ask the user for permission for the operation
+     * requested via remote control.
+     * 
+     * This message will be displayed to the user if the preference
+     * remotecontrol.always-confirm is true.
+     * 
+     * @return the message
+     */
+    abstract public String getPermissionMessage();
+
+    /**
+     * Get a PermissionPref object containing the name of a special permission
+     * preference to individually allow the requested operation and an error
+     * message to be displayed when a disabled operation is requested.
+     * 
+     * Default is not to check any special preference. Override this in a 
+     * subclass to define permission preference and error message.
+     * 
+     * @return the preference name and error message or null
+     */
+	@SuppressWarnings("deprecation")
+	public PermissionPref getPermissionPref()
+    {
+        /* Example:
+        return new PermissionPrefWithDefault("fooobar.remotecontrol",
+        true
+        "RemoteControl: foobar forbidden by preferences");
+        */
+        return null;
+    }
+
+    protected String[] getMandatoryParams()
+    {
+    	return null;
+    }
+    
+    /**
+     * Check permissions in preferences and display error message 
+     * or ask for permission.
+     * 
+     * @throws RequestHandlerForbiddenException
+     */
+    @SuppressWarnings("deprecation")
+	final public void checkPermission() throws RequestHandlerForbiddenException
+    {
+        /* 
+         * If the subclass defines a specific preference and if this is set
+         * to false, abort with an error message.
+         * 
+         * Note: we use the deprecated class here for compatibility with
+         * older versions of WMSPlugin.
+         */
+        PermissionPref permissionPref = getPermissionPref();
+        if((permissionPref != null) && (permissionPref.pref != null))
+        {
+            PermissionPrefWithDefault permissionPrefWithDefault;
+            if(permissionPref instanceof PermissionPrefWithDefault)
+            {
+            	permissionPrefWithDefault = (PermissionPrefWithDefault) permissionPref;
+            }
+            else 
+            {
+            	permissionPrefWithDefault = new PermissionPrefWithDefault(permissionPref);
+            }
+            if (!Main.pref.getBoolean(permissionPrefWithDefault.pref,
+            		permissionPrefWithDefault.defaultVal)) {
+                System.out.println(permissionPrefWithDefault.message);
+                throw new RequestHandlerForbiddenException();
+            }
+        }
+
+        /* Does the user want to confirm everything?
+         * If yes, display specific confirmation message.
+         */
+        if (Main.pref.getBoolean(globalConfirmationKey, globalConfirmationDefault)) {
+            if (JOptionPane.showConfirmDialog(Main.parent,
+                "<html>" + getPermissionMessage() +
+                "<br>" + tr("Do you want to allow this?"),
+                tr("Confirm Remote Control action"),
+                JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
+                    throw new RequestHandlerForbiddenException();
+            }
+        }
+    }
+
+    /**
+     * Set request URL and parse args.
+     * 
+     * @param url The request URL.
+     */
+	public void setUrl(String url) {
+		this.request = url;
+		parseArgs();
+	}
+
+	/**
+	 * Parse the request parameters as key=value pairs.
+	 * The result will be stored in this.args.
+	 * 
+	 * Can be overridden by subclass.
+	 */
+	protected void parseArgs() {
+		StringTokenizer st = new StringTokenizer(this.request, "&?");
+		HashMap<String, String> args = new HashMap<String, String>();
+		// ignore first token which is the command
+		if(st.hasMoreTokens()) st.nextToken();
+		while (st.hasMoreTokens()) {
+			String param = st.nextToken();
+			int eq = param.indexOf("=");
+			if (eq > -1)
+				args.put(param.substring(0, eq),
+                         param.substring(eq + 1));
+		}
+		this.args = args;
+	}
+	
+	void checkMandatoryParams() throws RequestHandlerBadRequestException
+	{
+		String[] mandatory = getMandatoryParams();
+		if(mandatory == null) return;
+		
+		boolean error = false;
+		for(int i = 0; i < mandatory.length; ++i)
+		{
+			String key = mandatory[i];
+			String value = args.get(key);
+			if((value == null) || (value.length() == 0))
+			{
+				error = true;
+				System.out.println("'" + myCommand + "' remote control request must have '" + key + "' parameter");
+			}
+		}
+		if(error) throw new RequestHandlerBadRequestException();
+	}
+	
+	/**
+	 * Save command associated with this handler.
+	 * 
+	 * @param command The command.
+	 */
+	public void setCommand(String command)
+	{
+		if(command.charAt(0) == '/') command = command.substring(1);
+		myCommand = command;
+	}
+}
\ No newline at end of file
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerBadRequestException.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerBadRequestException.java
new file mode 100644
index 0000000..55d30e3
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerBadRequestException.java
@@ -0,0 +1,4 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+public class RequestHandlerBadRequestException extends RequestHandlerException {
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerErrorException.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerErrorException.java
new file mode 100644
index 0000000..5f6753d
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerErrorException.java
@@ -0,0 +1,4 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+public class RequestHandlerErrorException extends RequestHandlerException {
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerException.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerException.java
new file mode 100644
index 0000000..9ff10a3
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerException.java
@@ -0,0 +1,4 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+public class RequestHandlerException extends Exception {
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerForbiddenException.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerForbiddenException.java
new file mode 100644
index 0000000..38d9fe4
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestHandlerForbiddenException.java
@@ -0,0 +1,5 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+public class RequestHandlerForbiddenException extends RequestHandlerException {
+	private static final long serialVersionUID = 2263904699747115423L;
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestProcessor.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestProcessor.java
new file mode 100644
index 0000000..5fcc818
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/RequestProcessor.java
@@ -0,0 +1,308 @@
+package org.openstreetmap.josm.plugins.remotecontrol;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.Socket;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import org.openstreetmap.josm.plugins.remotecontrol.handler.AddNodeHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.ImportHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.LoadAndZoomHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.handler.VersionHandler;
+
+/**
+ * Processes HTTP "remote control" requests.
+ */
+public class RequestProcessor extends Thread {
+	/**
+	 * RemoteControl protocol version. Change minor number for compatible
+	 * interface extensions. Change major number in case of incompatible
+	 * changes.
+	 */
+	public static final String PROTOCOLVERSION = "{\"protocolversion\": {\"major\": " +
+		RemoteControlPlugin.protocolMajorVersion + ", \"minor\": " + 
+		RemoteControlPlugin.protocolMinorVersion +
+		"}, \"application\": \"JOSM RemoteControl\"}";
+
+	/** The socket this processor listens on */
+	private Socket request;
+
+	/**
+	 * Collection of request handlers.
+	 * Will be initialized with default handlers here. Other plug-ins
+	 * can extend this list by using @see addRequestHandler 
+	 */
+	private static HashMap<String, Class<? extends RequestHandler>> handlers = new HashMap<String, Class<? extends RequestHandler>>();
+
+	/**
+	 * Constructor
+	 * 
+	 * @param request A socket to read the request.
+	 */
+	public RequestProcessor(Socket request) {
+		super("RemoteControl request processor");
+		this.setDaemon(true);
+		this.request = request;
+	}
+
+	/**
+	 * Spawns a new thread for the request
+	 * 
+	 * @param request
+	 *            The WMS request
+	 */
+	public static void processRequest(Socket request) {
+		RequestProcessor processor = new RequestProcessor(request);
+		processor.start();
+	}
+
+	/**
+	 * Add external request handler. Can be used by other plug-ins that
+	 * want to use remote control.
+	 * @param command The command to handle.
+	 * @param handler The additional request handler.
+	 */
+	static void addRequestHandlerClass(String command,
+			Class<? extends RequestHandler> handler) {
+		addRequestHandlerClass(command, handler, false);
+	}
+
+	/**
+	 * Add external request handler. Message can be suppressed.
+	 * (for internal use)
+	 * @param command The command to handle.
+	 * @param handler The additional request handler.
+	 * @param silent Don't show message if true.
+	 */
+	private static void addRequestHandlerClass(String command,
+				Class<? extends RequestHandler> handler, boolean silent) {
+		if(command.charAt(0) == '/')
+		{
+			command = command.substring(1);
+		}
+		String commandWithSlash = "/" + command;
+		if (handlers.get(commandWithSlash) != null) {
+			System.out.println("RemoteControl: ignoring duplicate command " + command
+					+ " with handler " + handler.getName());
+		} else {
+			if(!silent) System.out.println("RemoteControl: adding command \"" + 
+					command + "\" (handled by " + handler.getSimpleName() + ")");
+			handlers.put(commandWithSlash, handler);
+		}
+	}
+
+	/** Add default request handlers */
+	static {
+		addRequestHandlerClass(LoadAndZoomHandler.command,
+				LoadAndZoomHandler.class, true);
+		addRequestHandlerClass(LoadAndZoomHandler.command2,
+				LoadAndZoomHandler.class, true);
+		addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true);
+		addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true);
+		addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
+	}
+
+	/**
+	 * The work is done here.
+	 */
+	public void run() {
+		Writer out = null;
+		try {
+			OutputStream raw = new BufferedOutputStream(
+					request.getOutputStream());
+			out = new OutputStreamWriter(raw);
+			Reader in = new InputStreamReader(new BufferedInputStream(
+					request.getInputStream()), "ASCII");
+
+			StringBuffer requestLine = new StringBuffer();
+			while (requestLine.length() < 1024) {
+				int c = in.read();
+				if (c == '\r' || c == '\n')
+					break;
+				requestLine.append((char) c);
+			}
+
+			System.out.println("RemoteControl received: " + requestLine);
+			String get = requestLine.toString();
+			StringTokenizer st = new StringTokenizer(get);
+			if (!st.hasMoreTokens()) {
+				sendError(out);
+				return;
+			}
+			String method = st.nextToken();
+			if (!st.hasMoreTokens()) {
+				sendError(out);
+				return;
+			}
+			String url = st.nextToken();
+
+			if (!method.equals("GET")) {
+				sendNotImplemented(out);
+				return;
+			}
+
+			String command = null;
+			int questionPos = url.indexOf('?');
+			if(questionPos < 0)
+			{
+				command = url;
+			}
+			else
+			{
+				command = url.substring(0, questionPos);
+			}
+
+			// find a handler for this command
+			Class<? extends RequestHandler> handlerClass = handlers
+					.get(command);
+			if (handlerClass == null) {
+				// no handler found
+				sendBadRequest(out);
+			} else {
+				// create handler object
+				RequestHandler handler = handlerClass.newInstance();
+				try {
+					handler.setCommand(command);
+					handler.setUrl(url);
+					handler.checkPermission();
+					handler.handle();
+					sendHeader(out, "200 OK", handler.contentType, false);
+					out.write("Content-length: " + handler.content.length()
+							+ "\r\n");
+					out.write("\r\n");
+					out.write(handler.content);
+					out.flush();
+				} catch (RequestHandlerErrorException ex) {
+					sendError(out);
+				} catch (RequestHandlerBadRequestException ex) {
+					sendBadRequest(out);
+				} catch (RequestHandlerForbiddenException ex) {
+					sendForbidden(out);
+				}
+			}
+
+		} catch (IOException ioe) {
+		} catch (Exception e) {
+			e.printStackTrace();
+			try {
+				sendError(out);
+			} catch (IOException e1) {
+			}
+		} finally {
+			try {
+				request.close();
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	/**
+	 * Sends a 500 error: server error
+	 * 
+	 * @param out
+	 *            The writer where the error is written
+	 * @throws IOException
+	 *             If the error can not be written
+	 */
+	private void sendError(Writer out) throws IOException {
+		sendHeader(out, "500 Internal Server Error", "text/html", true);
+		out.write("<HTML>\r\n");
+		out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n");
+		out.write("</HEAD>\r\n");
+		out.write("<BODY>");
+		out.write("<H1>HTTP Error 500: Internal Server Error</h2>\r\n");
+		out.write("</BODY></HTML>\r\n");
+		out.flush();
+	}
+
+	/**
+	 * Sends a 501 error: not implemented
+	 * 
+	 * @param out
+	 *            The writer where the error is written
+	 * @throws IOException
+	 *             If the error can not be written
+	 */
+	private void sendNotImplemented(Writer out) throws IOException {
+		sendHeader(out, "501 Not Implemented", "text/html", true);
+		out.write("<HTML>\r\n");
+		out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
+		out.write("</HEAD>\r\n");
+		out.write("<BODY>");
+		out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n");
+		out.write("</BODY></HTML>\r\n");
+		out.flush();
+	}
+
+	/**
+	 * Sends a 403 error: forbidden
+	 * 
+	 * @param out
+	 *            The writer where the error is written
+	 * @throws IOException
+	 *             If the error can not be written
+	 */
+	private void sendForbidden(Writer out) throws IOException {
+		sendHeader(out, "403 Forbidden", "text/html", true);
+		out.write("<HTML>\r\n");
+		out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n");
+		out.write("</HEAD>\r\n");
+		out.write("<BODY>");
+		out.write("<H1>HTTP Error 403: Forbidden</h2>\r\n");
+		out.write("</BODY></HTML>\r\n");
+		out.flush();
+	}
+
+	/**
+	 * Sends a 403 error: forbidden
+	 * 
+	 * @param out
+	 *            The writer where the error is written
+	 * @throws IOException
+	 *             If the error can not be written
+	 */
+	private void sendBadRequest(Writer out) throws IOException {
+		sendHeader(out, "400 Bad Request", "text/html", true);
+		out.write("<HTML>\r\n");
+		out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n");
+		out.write("</HEAD>\r\n");
+		out.write("<BODY>");
+		out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n");
+		out.write("</BODY></HTML>\r\n");
+		out.flush();
+	}
+
+	/**
+	 * Send common HTTP headers to the client.
+	 * 
+	 * @param out
+	 *            The Writer
+	 * @param status
+	 *            The status string ("200 OK", "500", etc)
+	 * @param contentType
+	 *            The content type of the data sent
+	 * @param endHeaders
+	 *            If true, adds a new line, ending the headers.
+	 * @throws IOException
+	 *             When error
+	 */
+	private void sendHeader(Writer out, String status, String contentType,
+			boolean endHeaders) throws IOException {
+		out.write("HTTP/1.1 " + status + "\r\n");
+		Date now = new Date();
+		out.write("Date: " + now + "\r\n");
+		out.write("Server: JOSM RemoteControl\r\n");
+		out.write("Content-type: " + contentType + "\r\n");
+		out.write("Access-Control-Allow-Origin: *\r\n");
+		if (endHeaders)
+			out.write("\r\n");
+	}
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/AddNodeHandler.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/AddNodeHandler.java
new file mode 100644
index 0000000..2d5b9bd
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/AddNodeHandler.java
@@ -0,0 +1,68 @@
+package org.openstreetmap.josm.plugins.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashMap;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.plugins.remotecontrol.PermissionPrefWithDefault;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandler;
+
+/**
+ * Handler for add_node request.
+ */
+public class AddNodeHandler extends RequestHandler {
+
+	public static final String command = "add_node";
+	public static final String permissionKey = "remotecontrol.permission.create-objects";
+	public static final boolean permissionDefault = false;
+
+	@Override
+	protected void handleRequest() {
+        addNode(args);
+	}
+
+	@Override
+	protected String[] getMandatoryParams()
+	{
+		return new String[] { "lat", "lon" };
+	}
+	
+	@Override
+	public String getPermissionMessage() {
+		return tr("Remote Control has been asked to create a new node.");
+	}
+
+	@Override
+	public PermissionPrefWithDefault getPermissionPref()
+	{
+		return new PermissionPrefWithDefault(permissionKey, permissionDefault,
+				"RemoteControl: creating objects forbidden by preferences");
+	}
+	
+    /**
+     * Adds a node, implements the GET /add_node?lon=...&amp;lat=... request.
+     * @param args
+     */
+    private void addNode(HashMap<String, String> args){
+
+        // Parse the arguments
+        double lat = Double.parseDouble(args.get("lat"));
+        double lon = Double.parseDouble(args.get("lon"));
+        System.out.println("Adding node at (" + lat + ", " + lon + ")");
+
+        // Create a new node
+        LatLon ll = new LatLon(lat, lon);
+        Node nnew = new Node(ll);
+
+        // Now execute the commands to add this node.
+        Main.main.undoRedo.add(new AddCommand(nnew));
+        Main.main.getCurrentDataSet().setSelected(nnew);
+        Main.map.mapView.repaint();
+
+    }
+
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/ImportHandler.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/ImportHandler.java
new file mode 100644
index 0000000..398dd92
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/ImportHandler.java
@@ -0,0 +1,52 @@
+package org.openstreetmap.josm.plugins.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.net.URLDecoder;
+
+import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
+import org.openstreetmap.josm.plugins.remotecontrol.PermissionPrefWithDefault;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandlerErrorException;
+
+/**
+ * Handler for import request
+ */
+public class ImportHandler extends RequestHandler {
+
+	public static final String command = "import";
+	public static final String permissionKey = "remotecontrol.permission.import";
+	public static final boolean permissionDefault = true;
+
+	@Override
+	protected void handleRequest() throws RequestHandlerErrorException {
+        try {
+            DownloadTask osmTask = new DownloadOsmTask();
+            osmTask.loadUrl(false, URLDecoder.decode(args.get("url"), "UTF-8"), null);
+        } catch (Exception ex) {
+            System.out.println("RemoteControl: Error parsing import remote control request:");
+            ex.printStackTrace();
+            throw new RequestHandlerErrorException();
+        }
+	}
+
+	@Override
+	protected String[] getMandatoryParams()
+	{
+		return new String[] { "url" };
+	}
+	
+	@Override
+	public String getPermissionMessage() {
+		return tr("Remote Control has been asked to import data from the following URL:") +
+        "<br>" + request;
+	}
+
+	@Override
+	public PermissionPrefWithDefault getPermissionPref()
+	{
+		return new PermissionPrefWithDefault(permissionKey, permissionDefault,
+				"RemoteControl: import forbidden by preferences");
+	}
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/LoadAndZoomHandler.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/LoadAndZoomHandler.java
new file mode 100644
index 0000000..0b7b8a9
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/LoadAndZoomHandler.java
@@ -0,0 +1,167 @@
+package org.openstreetmap.josm.plugins.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+import java.util.HashSet;
+import java.util.concurrent.Future;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandlerErrorException;
+
+/**
+ * Handler for load_and_zoom request.
+ */
+public class LoadAndZoomHandler extends RequestHandler
+{
+	public static final String command = "load_and_zoom";
+	public static final String command2 = "zoom";
+	
+	public static final String loadDataPermissionKey = "remotecontrol.permission.load-data";
+	public static final boolean loadDataPermissionDefault = true;
+	public static final String changeSelectionPermissionKey = "remotecontrol.permission.change-selection";
+	public static final boolean changeSelectionPermissionDefault = true;
+	public static final String changeViewportPermissionKey = "remotecontrol.permission.change-viewport";
+	public static final boolean changeViewportPermissionDefault = true;
+	
+    @Override
+    public String getPermissionMessage()
+    {
+    	return tr("Remote Control has been asked to load data from the API.") +
+        "<br>" + tr("Request details: {0}", request);
+    }
+
+	@Override
+	protected String[] getMandatoryParams()
+	{
+		return new String[] { "bottom", "top", "left", "right" };
+	}
+	
+	@Override
+	protected void handleRequest() throws RequestHandlerErrorException
+	{
+		DownloadTask osmTask = new DownloadOsmTask();
+		double minlat = 0;
+		double maxlat = 0;
+		double minlon = 0;
+		double maxlon = 0;
+		try {
+			minlat = Double.parseDouble(args.get("bottom"));
+			maxlat = Double.parseDouble(args.get("top"));
+			minlon = Double.parseDouble(args.get("left"));
+			maxlon = Double.parseDouble(args.get("right"));
+
+			if(command.equals(myCommand))
+			{
+				if (!Main.pref.getBoolean(loadDataPermissionKey, loadDataPermissionDefault))
+				{
+					System.out.println("RemoteControl: download forbidden by preferences");
+				}
+				else
+				{
+	
+					// find out whether some data has already been downloaded
+					Area present = null;
+					Area toDownload = null;
+					DataSet ds = Main.main.getCurrentDataSet();
+					if (ds != null)
+						present = ds.getDataSourceArea();
+					if (present != null && !present.isEmpty()) {
+						toDownload = new Area(new Rectangle2D.Double(minlon,minlat,maxlon-minlon,maxlat-minlat));
+						toDownload.subtract(present);
+						if (!toDownload.isEmpty())
+						{
+							// the result might not be a rectangle (L shaped etc)
+							Rectangle2D downloadBounds = toDownload.getBounds2D();
+							minlat = downloadBounds.getMinY();
+							minlon = downloadBounds.getMinX();
+							maxlat = downloadBounds.getMaxY();
+							maxlon = downloadBounds.getMaxX();
+						}
+					}
+					if((toDownload != null) && toDownload.isEmpty())
+					{
+						System.out.println("RemoteControl: no download necessary");
+					}
+					else
+					{
+	                    Future<?> future = osmTask.download(false /*no new layer*/, new Bounds(minlat,minlon,maxlat,maxlon), null /* let the task manage the progress monitor */);
+	                    Main.worker.submit(new PostDownloadHandler(osmTask, future));
+					}
+				}
+			}
+		} catch (Exception ex) {
+			System.out.println("RemoteControl: Error parsing load_and_zoom remote control request:");
+			ex.printStackTrace();
+			throw new RequestHandlerErrorException();
+		}
+		if (args.containsKey("select") && Main.pref.getBoolean(changeSelectionPermissionKey, changeSelectionPermissionDefault)) {
+			// select objects after downloading, zoom to selection.
+			final String selection = args.get("select");
+			Main.worker.execute(new Runnable() {
+				public void run() {
+					HashSet<Long> ways = new HashSet<Long>();
+					HashSet<Long> nodes = new HashSet<Long>();
+					HashSet<Long> relations = new HashSet<Long>();
+					HashSet<OsmPrimitive> newSel = new HashSet<OsmPrimitive>();
+					for (String item : selection.split(",")) {
+						if (item.startsWith("way")) {
+							ways.add(Long.parseLong(item.substring(3)));
+						} else if (item.startsWith("node")) {
+							nodes.add(Long.parseLong(item.substring(4)));
+						} else if (item.startsWith("relation")) {
+							relations.add(Long.parseLong(item.substring(8)));
+						} else if (item.startsWith("rel")) {
+							relations.add(Long.parseLong(item.substring(3)));
+						} else {
+							System.out.println("RemoteControl: invalid selection '"+item+"' ignored");
+						}
+					}
+					DataSet ds = Main.main.getCurrentDataSet();
+					if(ds == null) // e.g. download failed
+						return;
+					for (Way w : ds.getWays()) if (ways.contains(w.getId())) newSel.add(w);
+					for (Node n : ds.getNodes()) if (nodes.contains(n.getId())) newSel.add(n);
+					for (Relation r : ds.getRelations()) if (relations.contains(r.getId())) newSel.add(r);
+					ds.setSelected(newSel);
+					if (Main.pref.getBoolean(changeViewportPermissionKey, changeViewportPermissionDefault))
+						new AutoScaleAction("selection").actionPerformed(null);
+				}
+			});
+		} else if (Main.pref.getBoolean(changeViewportPermissionKey, changeViewportPermissionDefault)) {
+			// after downloading, zoom to downloaded area.
+			zoom(minlat, maxlat, minlon, maxlon);
+		}
+	}
+
+	protected void zoom(double minlat, double maxlat, double minlon, double maxlon) {
+		final Bounds bounds = new Bounds(new LatLon(minlat, minlon),
+				new LatLon(maxlat, maxlon));
+
+		// make sure this isn't called unless there *is* a MapView
+		//
+		if (Main.map != null && Main.map.mapView != null) {
+			Main.worker.execute(new Runnable() {
+				public void run() {
+					BoundingXYVisitor bbox = new BoundingXYVisitor();
+					bbox.visit(bounds);
+					Main.map.mapView.recalculateCenterScale(bbox);
+				}
+			});
+		}
+	}
+}
diff --git a/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/VersionHandler.java b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/VersionHandler.java
new file mode 100644
index 0000000..e96690c
--- /dev/null
+++ b/remotecontrol/src/org/openstreetmap/josm/plugins/remotecontrol/handler/VersionHandler.java
@@ -0,0 +1,41 @@
+package org.openstreetmap.josm.plugins.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.plugins.remotecontrol.PermissionPrefWithDefault;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandlerBadRequestException;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandlerErrorException;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestProcessor;
+
+/**
+ * Handler for version request.
+ */
+public class VersionHandler extends RequestHandler {
+
+	public static final String command = "version";
+	public static final String permissionKey = "remotecontrol.permission.read-protocolversion";
+	public static final boolean permissionDefault = true;
+
+	@Override
+	protected void handleRequest() throws RequestHandlerErrorException,
+			RequestHandlerBadRequestException {
+		content = RequestProcessor.PROTOCOLVERSION;
+		contentType = "application/json";
+		if (args.containsKey("jsonp")) {
+			content = args.get("jsonp")+ " && " + args.get("jsonp") + "(" + content + ")";
+		}
+	}
+
+	@Override
+	public String getPermissionMessage() {
+		return tr("Remote Control has been asked to report its protocol version. This enables web sites to detect a running JOSM.");
+	}
+
+	@Override
+	public PermissionPrefWithDefault getPermissionPref()
+	{
+		return new PermissionPrefWithDefault(permissionKey, permissionDefault,
+				"RemoteControl: /version forbidden by preferences");
+	}
+}
diff --git a/routing/build.xml b/routing/build.xml
index c21112c..ecc98b4 100644
--- a/routing/build.xml
+++ b/routing/build.xml
@@ -17,7 +17,7 @@
 **    > ant  install
 **
 ** To build against the core in ../../core, create a correct manifest and deploy to
-** SVN, 
+** SVN,
 **    set the properties commit.message and plugin.main.version
 ** and run
 **    > ant  publish
@@ -27,7 +27,7 @@
 <project name="routing" default="dist" basedir=".">
 
 	<property name="commit.message" value="Changed the constructor signature of the plugin main class" />
-	<property name="plugin.main.version" value="2830" />
+	<property name="plugin.main.version" value="3408" />
 
 	<!-- Define some properties -->
 	<property name="josm"                   location="../../core/dist/josm-custom.jar"/>
@@ -105,10 +105,10 @@
 	</target>
 
 	<!--
-		 ************************** Publishing the plugin *********************************** 
+		 ************************** Publishing the plugin ***********************************
 		-->
 	<!--
-		** extracts the JOSM release for the JOSM version in ../core and saves it in the 
+		** extracts the JOSM release for the JOSM version in ../core and saves it in the
 		** property ${coreversion.info.entry.revision}
 		**
 		-->
@@ -157,17 +157,17 @@
 	</target>
 
 	<!--
-		** commits the plugin.jar 
+		** commits the plugin.jar
 		-->
 	<target name="commit-dist">
 		<echo>
 	***** Properties of published ${plugin.jar} *****
-	Commit message    : '${commit.message}'					
+	Commit message    : '${commit.message}'
 	Plugin-Mainversion: ${plugin.main.version}
 	JOSM build version: ${coreversion.info.entry.revision}
 	Plugin-Version    : ${version.entry.commit.revision}
-	***** / Properties of published ${plugin.jar} *****					
-						
+	***** / Properties of published ${plugin.jar} *****
+
 	Now commiting ${plugin.jar} ...
 	</echo>
 		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
diff --git a/routing/src/com/innovant/josm/plugin/routing/RoutingLayer.java b/routing/src/com/innovant/josm/plugin/routing/RoutingLayer.java
index 7f42c37..6cd0eab 100644
--- a/routing/src/com/innovant/josm/plugin/routing/RoutingLayer.java
+++ b/routing/src/com/innovant/josm/plugin/routing/RoutingLayer.java
@@ -29,7 +29,6 @@ package com.innovant.josm.plugin.routing;
 
 import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Point;
@@ -38,9 +37,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.swing.Action;
 import javax.swing.Icon;
-import javax.swing.JMenuItem;
-import javax.swing.JSeparator;
 
 import org.apache.log4j.Logger;
 import org.openstreetmap.josm.Main;
@@ -181,16 +179,16 @@ public class RoutingLayer extends Layer {
      * @see org.openstreetmap.josm.gui.layer.Layer#getMenuEntries()
      */
     @Override
-    public Component[] getMenuEntries() {
-        Collection<Component> components = new ArrayList<Component>();
-        components.add(new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)));
+    public Action[] getMenuEntries() {
+        Collection<Action> components = new ArrayList<Action>();
+        components.add(LayerListDialog.getInstance().createShowHideLayerAction());
 //        components.add(new JMenuItem(new LayerListDialog.ShowHideMarkerText(this)));
-        components.add(new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)));
-        components.add(new JSeparator());
-        components.add(new JMenuItem(new RenameLayerAction(getAssociatedFile(), this)));
-        components.add(new JSeparator());
-        components.add(new JMenuItem(new LayerListPopup.InfoAction(this)));
-        return components.toArray(new Component[0]);
+        components.add(LayerListDialog.getInstance().createDeleteLayerAction());
+        components.add(SeparatorLayerAction.INSTANCE);
+        components.add(new RenameLayerAction(getAssociatedFile(), this));
+        components.add(SeparatorLayerAction.INSTANCE);
+        components.add(new LayerListPopup.InfoAction(this));
+        return components.toArray(new Action[0]);
     }
 
     /*
diff --git a/slippymap/build.xml b/slippymap/build.xml
index 4440051..3151356 100644
--- a/slippymap/build.xml
+++ b/slippymap/build.xml
@@ -17,7 +17,7 @@
 **    > ant  install
 **
 ** To build against the core in ../../core, create a correct manifest and deploy to
-** SVN, 
+** SVN,
 **    set the properties commit.message and plugin.main.version
 ** and run
 **    > ant  publish
@@ -27,7 +27,7 @@
 <project name="slippymap" default="dist" basedir=".">
 
 	<property name="commit.message" value="Added haiti imagery tile source" />
-	<property name="plugin.main.version" value="2830" />
+	<property name="plugin.main.version" value="3408" />
 
 	<property name="josm"                   location="../../core/dist/josm-custom.jar"/>
 	<property name="plugin.dist.dir"        value="../../dist"/>
@@ -86,10 +86,10 @@
 	</target>
 
 	<!--
-			 ************************** Publishing the plugin *********************************** 
+			 ************************** Publishing the plugin ***********************************
 			-->
 	<!--
-			** extracts the JOSM release for the JOSM version in ../core and saves it in the 
+			** extracts the JOSM release for the JOSM version in ../core and saves it in the
 			** property ${coreversion.info.entry.revision}
 			**
 			-->
@@ -138,17 +138,17 @@
 	</target>
 
 	<!--
-			** commits the plugin.jar 
+			** commits the plugin.jar
 			-->
 	<target name="commit-dist">
 		<echo>
 		***** Properties of published ${plugin.jar} *****
-		Commit message    : '${commit.message}'					
+		Commit message    : '${commit.message}'
 		Plugin-Mainversion: ${plugin.main.version}
 		JOSM build version: ${coreversion.info.entry.revision}
 		Plugin-Version    : ${version.entry.commit.revision}
-		***** / Properties of published ${plugin.jar} *****					
-							
+		***** / Properties of published ${plugin.jar} *****
+
 		Now commiting ${plugin.jar} ...
 		</echo>
 		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
@@ -183,7 +183,7 @@
 
 	<target name="publish" depends="ensure-svn-present,core-info,commit-current,update-current,clean,dist,commit-dist">
 	</target>
-	
+
 	<target name="dev-install">
 		<copy file="${plugin.jar}" todir="c:/data/projekte/osm/josm-dev/plugins" />
 	</target>
diff --git a/editgpx/josm-editgpx.launch b/slippymap/josm-slippymap.launch
similarity index 97%
copy from editgpx/josm-editgpx.launch
copy to slippymap/josm-slippymap.launch
index 068fa0c..4dc2cd4 100644
--- a/editgpx/josm-editgpx.launch
+++ b/slippymap/josm-slippymap.launch
@@ -8,5 +8,5 @@
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 6"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.openstreetmap.josm.gui.MainApplication"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="editgpx"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="slippymap"/>
 </launchConfiguration>
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java
index fc59189..2874f65 100644
--- a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java
@@ -3,7 +3,6 @@ package org.openstreetmap.josm.plugins.slippymap;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
@@ -20,11 +19,11 @@ import java.util.LinkedList;
 import java.util.List;
 
 import javax.swing.AbstractAction;
+import javax.swing.Action;
 import javax.swing.Icon;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JMenuItem;
 import javax.swing.JPopupMenu;
-import javax.swing.JSeparator;
 import javax.swing.SwingUtilities;
 
 import org.openstreetmap.gui.jmapviewer.JobDispatcher;
@@ -38,16 +37,14 @@ import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.RenameLayerAction;
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
 
 /**
  * Class that displays a slippy map layer.
@@ -57,7 +54,7 @@ import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
  * @author Dave Hansen <dave at sr71.net>
  *
  */
-public class SlippyMapLayer extends Layer implements PreferenceChangedListener, ImageObserver,
+public class SlippyMapLayer extends Layer implements ImageObserver,
     TileLoaderListener {
     boolean debug = false;
     void out(String s)
@@ -263,13 +260,10 @@ public class SlippyMapLayer extends Layer implements PreferenceChangedListener,
 
                     public void layerRemoved(Layer oldLayer) {
                     	MapView.removeLayerChangeListener(this);
-                        Main.pref.removePreferenceChangeListener(SlippyMapLayer.this);
                     }
                 });
             }
         });
-
-        Main.pref.addPreferenceChangeListener(this);
     }
 
     void zoomChanged()
@@ -879,8 +873,6 @@ public class SlippyMapLayer extends Layer implements PreferenceChangedListener,
             ts.loadAllTiles(false);
         }
 
-        int fontHeight = g.getFontMetrics().getHeight();
-
         g.setColor(Color.DARK_GRAY);
 
         List<Tile> missedTiles = this.paintTileImages(g, ts, currentZoomLevel, null);
@@ -962,7 +954,6 @@ public class SlippyMapLayer extends Layer implements PreferenceChangedListener,
         if (!ts.tooLarge())
             ts.loadAllTiles(false); // make sure there are tile objects for all tiles
         Tile clickedTile = null;
-        Point p1 = null, p2 = null;
         for (Tile t1 : ts.allTiles()) {
             Tile t2 = tempCornerTile(t1);
             Rectangle r = new Rectangle(pixelPos(t1));
@@ -992,15 +983,15 @@ public class SlippyMapLayer extends Layer implements PreferenceChangedListener,
     }
 
     @Override
-    public Component[] getMenuEntries() {
-        return new Component[] {
-                new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
-                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
-                new JSeparator(),
+    public Action[] getMenuEntries() {
+        return new Action[] {
+                LayerListDialog.getInstance().createShowHideLayerAction(),
+                LayerListDialog.getInstance().createDeleteLayerAction(),
+                SeparatorLayerAction.INSTANCE,
                 // color,
-                new JMenuItem(new RenameLayerAction(this.getAssociatedFile(), this)),
-                new JSeparator(),
-                new JMenuItem(new LayerListPopup.InfoAction(this)) };
+                new RenameLayerAction(this.getAssociatedFile(), this),
+                SeparatorLayerAction.INSTANCE,
+                new LayerListPopup.InfoAction(this) };
     }
 
     @Override
@@ -1040,31 +1031,4 @@ public class SlippyMapLayer extends Layer implements PreferenceChangedListener,
     private double tileXToLon(int x, int zoom) {
         return x * 45.0 / Math.pow(2.0, zoom - 3) - 180.0;
     }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @seeorg.openstreetmap.josm.data.Preferences.PreferenceChangedListener#
-     * preferenceChanged(java.lang.String, java.lang.String)
-     */
-    public void preferenceChanged(PreferenceChangeEvent event) {
-        if (event.getKey().startsWith(SlippyMapPreferences.PREFERENCE_PREFIX)) {
-            // System.err.println(this + ".preferenceChanged('" + key + "', '"
-            // + newValue + "') called");
-            // when fade background changed, no need to clear tile storage
-            // TODO move this code to SlippyMapPreferences class.
-            if (!event.getKey().equals(SlippyMapPreferences.PREFERENCE_FADE_BACKGROUND)) {
-                autoZoomPopup.setSelected(SlippyMapPreferences.getAutozoom());
-            }
-            if (event.getKey().equals(SlippyMapPreferences.PREFERENCE_TILE_SOURCE)) {
-                newTileStorage();
-            }
-            redraw();
-        }
-    }
-
-    @Override
-    public void destroy() {
-        Main.pref.removePreferenceChangeListener(SlippyMapLayer.this);
-    }
 }
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java
index 3b84be4..1f1cb7e 100644
--- a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java
@@ -1,6 +1,10 @@
 package org.openstreetmap.josm.plugins.slippymap;
 
+import java.util.List;
+
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.plugins.Plugin;
@@ -12,16 +16,18 @@ import org.openstreetmap.josm.plugins.PluginInformation;
  * @author Frederik Ramm <frederik at remote.org>
  *
  */
-public class SlippyMapPlugin extends Plugin
+public class SlippyMapPlugin extends Plugin implements PreferenceChangedListener
 {
     public SlippyMapPlugin(PluginInformation info)
     {
     	super(info);
+    	Main.pref.addPreferenceChangeListener(this);
     }
 
-    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame)
+    @Override
+	public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame)
     {
-        if (newFrame != null){
+        if (newFrame != null && SlippyMapPreferences.getMapSource() != SlippyMapPreferences.NO_DEFAULT_TILE_SOURCE) {
             SlippyMapLayer smlayer;
             smlayer = new SlippyMapLayer();
             Main.main.addLayer(smlayer);
@@ -39,4 +45,40 @@ public class SlippyMapPlugin extends Plugin
         return new SlippyMapPreferenceSetting();
     }
 
+    /*
+     * (non-Javadoc)
+     *
+     * @seeorg.openstreetmap.josm.data.Preferences.PreferenceChangedListener#
+     * preferenceChanged(java.lang.String, java.lang.String)
+     */
+    public void preferenceChanged(PreferenceChangeEvent event) {
+    	if (!Main.isDisplayingMapView()) {
+    		return;
+    	}
+    	List<SlippyMapLayer> layes = Main.map.mapView.getLayersOfType(SlippyMapLayer.class);
+    	assert layes.size() <= 1;
+    	SlippyMapLayer layer = layes.isEmpty()?null:layes.get(0);
+
+        if (event.getKey().equals(SlippyMapPreferences.PREFERENCE_TILE_SOURCE)) {
+        	if (layer == null && SlippyMapPreferences.getMapSource() != SlippyMapPreferences.NO_DEFAULT_TILE_SOURCE) {
+        		Main.map.mapView.addLayer(new SlippyMapLayer());
+        	} else if (layer != null && SlippyMapPreferences.getMapSource() == SlippyMapPreferences.NO_DEFAULT_TILE_SOURCE) {
+        		Main.map.mapView.removeLayer(layer);
+        	} else if (layer == null && SlippyMapPreferences.getMapSource() == SlippyMapPreferences.NO_DEFAULT_TILE_SOURCE) {
+        		// Do nothing
+        	} else {
+        		layer.newTileStorage();
+        	}
+        } else  if (event.getKey().startsWith(SlippyMapPreferences.PREFERENCE_PREFIX) && layer != null) {
+            // System.err.println(this + ".preferenceChanged('" + key + "', '"
+            // + newValue + "') called");
+            // when fade background changed, no need to clear tile storage
+            // TODO move this code to SlippyMapPreferences class.
+            if (!event.getKey().equals(SlippyMapPreferences.PREFERENCE_FADE_BACKGROUND)) {
+                layer.autoZoomPopup.setSelected(SlippyMapPreferences.getAutozoom());
+            }
+            layer.redraw();
+        }
+    }
+
 }
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java
index 3b13dd4..4cfc0a4 100644
--- a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java
@@ -2,6 +2,11 @@ package org.openstreetmap.josm.plugins.slippymap;
 
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
 import javax.swing.Box;
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
@@ -10,14 +15,8 @@ import javax.swing.JPanel;
 import javax.swing.JSlider;
 import javax.swing.JSpinner;
 import javax.swing.SpinnerNumberModel;
-import java.awt.event.ActionListener;
-import java.awt.event.ActionEvent;
-import java.util.Collection;
-
-import org.openstreetmap.gui.jmapviewer.*;
-import org.openstreetmap.gui.jmapviewer.interfaces.*;
 
-import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
 import org.openstreetmap.josm.tools.GBC;
@@ -33,20 +32,21 @@ public class SlippyMapPreferenceSetting implements PreferenceSetting {
      * ComboBox with all known tile sources.
      */
     private JComboBox tileSourceCombo;
-    
+
     private JCheckBox autozoomActive = new JCheckBox(tr("autozoom"));
     private JCheckBox autoloadTiles = new JCheckBox(tr("autoload tiles"));
     private JSpinner maxZoomLvl;
     private JSpinner minZoomLvl = new JSpinner();
     private JSlider fadeBackground = new JSlider(0, 100);
-    
+
     public void addGui(PreferenceTabbedPane gui)
     {
         minZoomLvl = new JSpinner(new SpinnerNumberModel(SlippyMapPreferences.DEFAULT_MIN_ZOOM, SlippyMapPreferences.MIN_ZOOM, SlippyMapPreferences.MAX_ZOOM, 1));
         maxZoomLvl = new JSpinner(new SpinnerNumberModel(SlippyMapPreferences.DEFAULT_MAX_ZOOM, SlippyMapPreferences.MIN_ZOOM, SlippyMapPreferences.MAX_ZOOM, 1));
         //String description = tr("A plugin that adds to JOSM new layer. This layer could render external tiles.");
         JPanel slippymapTab = gui.createPreferenceTab("slippymap.png", tr("SlippyMap"), tr("Settings for the SlippyMap plugin."));
-        Collection<TileSource> allSources = SlippyMapPreferences.getAllMapSources();
+        List<TileSource> allSources = new ArrayList<TileSource>(SlippyMapPreferences.getAllMapSources());
+        allSources.add(0, SlippyMapPreferences.NO_DEFAULT_TILE_SOURCE);
         //Collection<String> allSources = SlippyMapPreferences.getAllMapNames();
         tileSourceCombo = new JComboBox(allSources.toArray());
         //tileSourceCombo.setEditable(true);
@@ -54,19 +54,19 @@ public class SlippyMapPreferenceSetting implements PreferenceSetting {
         slippymapTab.add(new JLabel(tr("Tile Sources")), GBC.std());
         slippymapTab.add(GBC.glue(5, 0), GBC.std());
         slippymapTab.add(tileSourceCombo, GBC.eol().fill(GBC.HORIZONTAL));
-        
+
         slippymapTab.add(new JLabel(tr("Auto zoom: ")), GBC.std());
         slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
         slippymapTab.add(autozoomActive, GBC.eol().fill(GBC.HORIZONTAL));
-        
+
         slippymapTab.add(new JLabel(tr("Autoload Tiles: ")), GBC.std());
         slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
         slippymapTab.add(autoloadTiles, GBC.eol().fill(GBC.HORIZONTAL));
-        
+
         slippymapTab.add(new JLabel(tr("Min zoom lvl: ")), GBC.std());
         slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
         slippymapTab.add(this.minZoomLvl, GBC.eol().fill(GBC.HORIZONTAL));
-        
+
         slippymapTab.add(new JLabel(tr("Max zoom lvl: ")), GBC.std());
         slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
         slippymapTab.add(this.maxZoomLvl, GBC.eol().fill(GBC.HORIZONTAL));
@@ -74,7 +74,7 @@ public class SlippyMapPreferenceSetting implements PreferenceSetting {
         slippymapTab.add(new JLabel(tr("Fade background: ")), GBC.std());
         slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
         slippymapTab.add(this.fadeBackground, GBC.eol().fill(GBC.HORIZONTAL));
-        
+
         slippymapTab.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
 
         tileSourceCombo.addActionListener(new ActionListener() {
@@ -127,7 +127,7 @@ public class SlippyMapPreferenceSetting implements PreferenceSetting {
         this.minZoomLvl.setValue(SlippyMapPreferences.getMinZoomLvl());
         this.fadeBackground.setValue(Math.round(SlippyMapPreferences.getFadeBackground()*100f));
     }
-    
+
     /**
      * <p>
      * Someone pressed the "ok" button
@@ -138,7 +138,7 @@ public class SlippyMapPreferenceSetting implements PreferenceSetting {
      */
     public boolean ok()
     {
-        SlippyMapPreferences.setMapSource((TileSource)this.tileSourceCombo.getSelectedItem());
+    	SlippyMapPreferences.setMapSource((TileSource)this.tileSourceCombo.getSelectedItem());
         SlippyMapPreferences.setAutozoom(this.autozoomActive.isSelected());
         SlippyMapPreferences.setAutoloadTiles(this.autoloadTiles.isSelected());
         SlippyMapPreferences.setMaxZoomLvl((Integer)this.maxZoomLvl.getValue());
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java
index 90aad6a..680726f 100644
--- a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java
@@ -1,12 +1,15 @@
 package org.openstreetmap.josm.plugins.slippymap;
 
-import org.openstreetmap.josm.Main;
-import java.util.List;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.List;
 import java.util.Map;
-import org.openstreetmap.gui.jmapviewer.*;
-import org.openstreetmap.gui.jmapviewer.interfaces.*;
+
+import org.openstreetmap.gui.jmapviewer.OsmTileSource;
+import org.openstreetmap.gui.jmapviewer.OsmTileSource.AbstractOsmTileSource;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+import org.openstreetmap.josm.Main;
 
 /**
  * Preferences for Slippy Map Tiles
@@ -17,6 +20,7 @@ import org.openstreetmap.gui.jmapviewer.interfaces.*;
  */
 public class SlippyMapPreferences
 {
+	public static final String NO_DEFAULT_TILE_SOURCE_NAME = "{%no_default%}";
     public static final String PREFERENCE_PREFIX   = "slippymap";
 
     public static final String PREFERENCE_TILE_CUSTOM_SOURCE = PREFERENCE_PREFIX + ".custom_tile_source_";
@@ -34,6 +38,7 @@ public class SlippyMapPreferences
     public static final int DEFAULT_MAX_ZOOM = 20;
     public static final int DEFAULT_MIN_ZOOM = 2;
 
+
     public static TileSource getMapSource()
     {
         String name = Main.pref.get(PREFERENCE_TILE_SOURCE);
@@ -41,23 +46,27 @@ public class SlippyMapPreferences
     }
     public static TileSource getMapSource(String name)
     {
+    	if (NO_DEFAULT_TILE_SOURCE_NAME.equals(name)) {
+    		return NO_DEFAULT_TILE_SOURCE; // User don't want to load slippy layer on startup
+    	}
+
         List<TileSource> sources = SlippyMapPreferences.getAllMapSources();
-        TileSource source = sources.get(0);
+
         if (name == null || "".equals(name)) {
-            name = source.getName();
-            Main.pref.put(PREFERENCE_TILE_SOURCE, name);
+            Main.pref.put(PREFERENCE_TILE_SOURCE, sources.get(0).getName());
+            return sources.get(0);
         }
+
         for (TileSource s : sources) {
-            if (!name.equals(s.getName()))
-                continue;
-            source = s;
-            break;
+            if (name.equals(s.getName()))
+            	return s;
         }
-        return source;
+
+        return sources.get(0);
     }
 
     public static void setMapSource(TileSource source) {
-    	Main.pref.put(SlippyMapPreferences.PREFERENCE_TILE_SOURCE, source.getName());
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_TILE_SOURCE, source == NO_DEFAULT_TILE_SOURCE?NO_DEFAULT_TILE_SOURCE_NAME:source.getName());
     }
 
     public static boolean getAutozoom()
@@ -235,6 +244,12 @@ public class SlippyMapPreferences
     	Main.pref.put(SlippyMapPreferences.PREFERENCE_MIN_ZOOM_LVL, "" + minZoomLvl);
     }
 
+    public static TileSource NO_DEFAULT_TILE_SOURCE = new AbstractOsmTileSource(tr("(none)"), "") {
+		public TileUpdate getTileUpdate() {
+			return null;
+		}
+    };
+
     public static class Coastline extends OsmTileSource.AbstractOsmTileSource {
         public Coastline() {
             super("Coastline", "http://hypercube.telascience.org/tiles/1.0.0/coastline");
@@ -265,11 +280,13 @@ public class SlippyMapPreferences
             super("NearMap Australia", "http://www.nearmap.com/maps/hl=en&nml=Vert&");
         }
 
-        public int getMaxZoom() {
+        @Override
+		public int getMaxZoom() {
             return 21;
         }
 
-        public String getTilePath(int zoom, int tilex, int tiley) {
+        @Override
+		public String getTilePath(int zoom, int tilex, int tiley) {
             return "z=" + zoom + "&x=" + tilex + "&y=" + tiley;
         }
 
@@ -277,18 +294,20 @@ public class SlippyMapPreferences
             return TileUpdate.IfNoneMatch;
         }
     }
-    
+
 
     public static class HaitiImagery extends OsmTileSource.AbstractOsmTileSource {
         public HaitiImagery() {
             super("HaitiImagery", "http://gravitystorm.dev.openstreetmap.org/imagery/haiti");
         }
 
-        public int getMaxZoom() {
+        @Override
+		public int getMaxZoom() {
             return 21;
         }
 
-        public String getTilePath(int zoom, int tilex, int tiley) {
+        @Override
+		public String getTilePath(int zoom, int tilex, int tiley) {
         	return "/" + zoom + "/" + tilex + "/" + tiley + ".png";
         }
 
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java
deleted file mode 100644
index e69de29..0000000
diff --git a/svn-info.xml b/svn-info.xml
index 5016e5a..40df43d 100644
--- a/svn-info.xml
+++ b/svn-info.xml
@@ -3,16 +3,16 @@
 <entry
    kind="dir"
    path="plugins"
-   revision="22363">
+   revision="23040">
 <url>http://svn.openstreetmap.org/applications/editors/josm/plugins</url>
 <repository>
 <root>http://svn.openstreetmap.org</root>
 <uuid>b9d5c4c9-76e1-0310-9c85-f3177eceb1e4</uuid>
 </repository>
 <commit
-   revision="22295">
-<author>nakor</author>
-<date>2010-07-13T00:34:51.532356Z</date>
+   revision="23039">
+<author>postfix</author>
+<date>2010-09-06T21:06:40.259986Z</date>
 </commit>
 </entry>
 </info>
diff --git a/wmsplugin/.classpath b/wmsplugin/.classpath
index 663bc1b..1ae9f2f 100644
--- a/wmsplugin/.classpath
+++ b/wmsplugin/.classpath
@@ -1,7 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry including="images/" kind="src" path=""/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 6"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/remotecontrol"/>
 	<classpathentry kind="output" path="build"/>
 </classpath>
diff --git a/wmsplugin/.settings/org.eclipse.jdt.core.prefs b/wmsplugin/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..d8aed90
--- /dev/null
+++ b/wmsplugin/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Thu Aug 12 20:39:44 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/wmsplugin/build.xml b/wmsplugin/build.xml
index 7b811ba..c36ce4b 100644
--- a/wmsplugin/build.xml
+++ b/wmsplugin/build.xml
@@ -27,11 +27,12 @@
 <project name="wmsplugin" default="dist" basedir=".">
 
 
-	<property name="commit.message" value="add commit message" />
-	<property name="plugin.main.version" value="2830" />
+	<property name="commit.message" value="fixed josm bug 4671 - wms url for sicily has changed" />
+	<property name="plugin.main.version" value="3451" />
 
 
 	<property name="josm" location="../../core/dist/josm-custom.jar" />
+	<property name="remotecontrol" location="../../dist/remotecontrol.jar" />
 	<property name="plugin.dist.dir" value="../../dist" />
 	<property name="plugin.build.dir" value="build" />
 	<property name="plugin.jar" value="${plugin.dist.dir}/${ant.project.name}.jar" />
@@ -42,7 +43,7 @@
 	</target>
 	<target name="compile" depends="init">
 		<echo message="creating ${plugin.jar}" />
-		<javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+		<javac srcdir="src" classpath="${josm};${remotecontrol}" debug="true" destdir="${plugin.build.dir}">
 			<compilerarg value="-Xlint:deprecation" />
 			<compilerarg value="-Xlint:unchecked" />
 		</javac>
@@ -92,10 +93,10 @@
 	</target>
 
 	<!--
-	************************** Publishing the plugin *********************************** 
+	************************** Publishing the plugin ***********************************
 	-->
 	<!--
-	** extracts the JOSM release for the JOSM version in ../core and saves it in the 
+	** extracts the JOSM release for the JOSM version in ../core and saves it in the
 	** property ${coreversion.info.entry.revision}
 	**
 	-->
@@ -144,17 +145,17 @@
 	</target>
 
 	<!--
-	** commits the plugin.jar 
+	** commits the plugin.jar
 	-->
 	<target name="commit-dist">
 		<echo>
 	***** Properties of published ${plugin.jar} *****
-	Commit message    : '${commit.message}'					
+	Commit message    : '${commit.message}'
 	Plugin-Mainversion: ${plugin.main.version}
 	JOSM build version: ${coreversion.info.entry.revision}
 	Plugin-Version    : ${version.entry.commit.revision}
-	***** / Properties of published ${plugin.jar} *****					
-						
+	***** / Properties of published ${plugin.jar} *****
+
 	Now commiting ${plugin.jar} ...
 	</echo>
 		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="true" failonerror="true">
diff --git a/wmsplugin/josm-wmsplugin.launch b/wmsplugin/josm-wmsplugin.launch
index 0b4de4c..4d10a6f 100644
--- a/wmsplugin/josm-wmsplugin.launch
+++ b/wmsplugin/josm-wmsplugin.launch
@@ -6,7 +6,7 @@
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
 </listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 5"/>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 6"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.openstreetmap.josm.gui.MainApplication"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="wmsplugin"/>
 </launchConfiguration>
diff --git a/wmsplugin/sources.cfg b/wmsplugin/sources.cfg
index 89837de..c64ff0a 100644
--- a/wmsplugin/sources.cfg
+++ b/wmsplugin/sources.cfg
@@ -3,6 +3,7 @@
 # NOTE: default items should be common and worldwide
 #
 true;Landsat;http://onearth.jpl.nasa.gov/wms.cgi?request=GetMap&layers=global_mosaic&styles=&format=image/jpeg&
+true;Landsat (mirror);http://irs.gis-lab.info/?layers=landsat&
 false;Open Aerial Map;http://openaerialmap.org/wms/?VERSION=1.0&request=GetMap&layers=world&styles=&format=image/jpeg&
 #
 # different forms for web access
@@ -24,7 +25,7 @@ false;Terraserver Urban;http://terraservice.net/ogcmap.ashx?version=1.1.1&reques
 #
 #
 # only for Czech Republic
-false;Czech CUZK:KM;http://wms.cuzk.cz/wms.asp?service=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&LAYERS=kn,def_budovy,prehledky&FORMAT=image/png&TRANSPARENT=TRUE&
+false;Czech CUZK:KM;http://wms.cuzk.cz/wms.asp?service=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&LAYERS=parcelni_cisla_i,obrazy_parcel_i,RST_KMD_I,hranice_parcel_i,DEF_BUDOVY,RST_KN_I,dalsi_p_mapy_i,prehledka_kat_prac,prehledka_kat_uz,prehledka_kraju-linie&FORMAT=image/png&transparent=TRUE&
 false;Czech UHUL:ORTOFOTO;http://geoportal2.uhul.cz/cgi-bin/oprl.asp?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&LAYERS=Ortofoto_cb&STYLES=default&FORMAT=image/jpeg&TRANSPARENT=TRUE&
 #
 #
@@ -44,7 +45,8 @@ false;MLIT Japan (ORTHO03);http://orthophoto.mlit.go.jp:8888/wms/service/wmsRast
 #
 # only for Italy
 false;Lodi - Italy;http://sit.provincia.lodi.it/mapserver/mapserv.exe?map=ortofoto_wgs84.map&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&LAYERS=Terraitaly%20Ortofoto%202007&STYLES=%2C%2C&FORMAT=image/png&TRANSPARENT=TRUE&
-false;Sicily - Italy;http://88.53.214.52/arcgis/services/WGS84_F33/OrtofotoATA_20072008_f33/MapServer/WMSServer?LAYERS=0&REQUEST=GetMap&VERSION=1.1.1&FORMAT=image%2Fpng&STYLES=&
+false;Sicily - Italy;http://88.53.214.52/sitr/services/WGS84_F33/Ortofoto_ATA20072008_f33/MapServer/WMSServer?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&CRS=CRS:84&LAYERS=0&STYLES=default&FORMAT=image/jpeg&
+false;PCN 2006 - Italy;http://wms.pcn.minambiente.it/cgi-bin/mapserv.exe?map=/ms_ogc/service/ortofoto_colore_06.map&LAYERS=ortofoto_colore_06_32,ortofoto_colore_06_33&REQUEST=GetMap&VERSION=1.1.1&FORMAT=image%2Fjpeg&
 #
 #
 # URLS must be designed to append arguments directly behind. So the URLS should either end with '?' or '&'
diff --git a/wmsplugin/src/wmsplugin/AddWMSLayerPanel.java b/wmsplugin/src/wmsplugin/AddWMSLayerPanel.java
new file mode 100644
index 0000000..9355b10
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/AddWMSLayerPanel.java
@@ -0,0 +1,507 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.HeadlessException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JTree;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreePath;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.data.projection.ProjectionSubPrefs;
+import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
+import org.openstreetmap.josm.tools.GBC;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+public class AddWMSLayerPanel extends JPanel {
+    private List<LayerDetails> selectedLayers;
+    private URL serviceUrl;
+    private LayerDetails selectedLayer;
+
+    private JTextField menuName;
+    private JTextArea resultingLayerField;
+    private MutableTreeNode treeRootNode;
+    private DefaultTreeModel treeData;
+    private JTree layerTree;
+    private JButton showBoundsButton;
+
+    private boolean previouslyShownUnsupportedCrsError = false;
+
+    public AddWMSLayerPanel() {
+        JPanel wmsFetchPanel = new JPanel(new GridBagLayout());
+        menuName = new JTextField(40);
+        menuName.setText(tr("Unnamed WMS Layer"));
+        final JTextArea serviceUrl = new JTextArea(3, 40);
+        serviceUrl.setLineWrap(true);
+        serviceUrl.setText("http://sample.com/wms?");
+        wmsFetchPanel.add(new JLabel(tr("Menu Name")), GBC.std().insets(0,0,5,0));
+        wmsFetchPanel.add(menuName, GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
+        wmsFetchPanel.add(new JLabel(tr("Service URL")), GBC.std().insets(0,0,5,0));
+        JScrollPane scrollPane = new JScrollPane(serviceUrl,
+                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        wmsFetchPanel.add(scrollPane, GBC.eop().insets(5,0,0,0));
+        JButton getLayersButton = new JButton(tr("Get Layers"));
+        getLayersButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                Cursor beforeCursor = getCursor();
+                try {
+                    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+                    attemptGetCapabilities(serviceUrl.getText());
+                } finally {
+                    setCursor(beforeCursor);
+                }
+            }
+        });
+        wmsFetchPanel.add(getLayersButton, GBC.eop().anchor(GridBagConstraints.EAST));
+
+        treeRootNode = new DefaultMutableTreeNode();
+        treeData = new DefaultTreeModel(treeRootNode);
+        layerTree = new JTree(treeData);
+        layerTree.setCellRenderer(new LayerTreeCellRenderer());
+        layerTree.addTreeSelectionListener(new TreeSelectionListener() {
+
+            public void valueChanged(TreeSelectionEvent e) {
+                TreePath[] selectionRows = layerTree.getSelectionPaths();
+                if(selectionRows == null) {
+                    showBoundsButton.setEnabled(false);
+                    selectedLayer = null;
+                    return;
+                }
+
+                selectedLayers = new LinkedList<LayerDetails>();
+                for (TreePath i : selectionRows) {
+                    Object userObject = ((DefaultMutableTreeNode) i.getLastPathComponent()).getUserObject();
+                    if(userObject instanceof LayerDetails) {
+                        LayerDetails detail = (LayerDetails) userObject;
+                        if(!detail.isSupported()) {
+                            layerTree.removeSelectionPath(i);
+                            if(!previouslyShownUnsupportedCrsError) {
+                                JOptionPane.showMessageDialog(null, tr("That layer does not support any of JOSM's projections,\n" +
+                                "so you can not use it. This message will not show again."),
+                                tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+                                previouslyShownUnsupportedCrsError = true;
+                            }
+                        } else if(detail.ident != null) {
+                            selectedLayers.add(detail);
+                        }
+                    }
+                }
+
+                if (!selectedLayers.isEmpty()) {
+                    resultingLayerField.setText(buildGetMapUrl());
+
+                    if(selectedLayers.size() == 1) {
+                        showBoundsButton.setEnabled(true);
+                        selectedLayer = selectedLayers.get(0);
+                    }
+                } else {
+                    showBoundsButton.setEnabled(false);
+                    selectedLayer = null;
+                }
+            }
+        });
+        wmsFetchPanel.add(new JScrollPane(layerTree), GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
+
+        JPanel layerManipulationButtons = new JPanel();
+        showBoundsButton = new JButton(tr("Show Bounds"));
+        showBoundsButton.setEnabled(false);
+        showBoundsButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if(selectedLayer.bounds != null) {
+                    SlippyMapBBoxChooser mapPanel = new SlippyMapBBoxChooser();
+                    mapPanel.setBoundingBox(selectedLayer.bounds);
+                    JOptionPane.showMessageDialog(null, mapPanel, tr("Show Bounds"), JOptionPane.PLAIN_MESSAGE);
+                } else {
+                    JOptionPane.showMessageDialog(null, tr("No bounding box was found for this layer."),
+                            tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+                }
+            }
+        });
+        layerManipulationButtons.add(showBoundsButton);
+
+        wmsFetchPanel.add(layerManipulationButtons, GBC.eol().insets(0,0,5,0));
+        wmsFetchPanel.add(new JLabel(tr("WMS URL")), GBC.std().insets(0,0,5,0));
+        resultingLayerField = new JTextArea(3, 40);
+        resultingLayerField.setLineWrap(true);
+        wmsFetchPanel.add(new JScrollPane(resultingLayerField, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
+
+        add(wmsFetchPanel);
+    }
+
+    private String buildRootUrl() {
+        StringBuilder a = new StringBuilder(serviceUrl.getProtocol());
+        a.append("://");
+        a.append(serviceUrl.getHost());
+        if(serviceUrl.getPort() != -1) {
+            a.append(":");
+            a.append(serviceUrl.getPort());
+        }
+        a.append(serviceUrl.getPath());
+        a.append("?");
+        if(serviceUrl.getQuery() != null) {
+            a.append(serviceUrl.getQuery());
+            if (!serviceUrl.getQuery().isEmpty() && !serviceUrl.getQuery().endsWith("&")) {
+                a.append("&");
+            }
+        }
+        return a.toString();
+    }
+
+    private String buildGetMapUrl() {
+        StringBuilder a = new StringBuilder();
+        a.append(buildRootUrl());
+        a.append("FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&Layers=");
+        a.append(commaSepLayerList());
+        a.append("&");
+
+        return a.toString();
+    }
+
+    private String commaSepLayerList() {
+        StringBuilder b = new StringBuilder();
+
+        Iterator<LayerDetails> iterator = selectedLayers.iterator();
+        while (iterator.hasNext()) {
+            LayerDetails layerDetails = iterator.next();
+            b.append(layerDetails.ident);
+            if(iterator.hasNext()) {
+                b.append(",");
+            }
+        }
+
+        return b.toString();
+    }
+
+    private void attemptGetCapabilities(String serviceUrlStr) {
+        URL getCapabilitiesUrl = null;
+        try {
+            if (!serviceUrlStr.trim().contains("capabilities")) {
+                // If the url doesn't already have GetCapabilities, add it in
+                getCapabilitiesUrl = new URL(serviceUrlStr + "VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities");
+            } else {
+                // Otherwise assume it's a good URL and let the subsequent error
+                // handling systems deal with problems
+                getCapabilitiesUrl = new URL(serviceUrlStr);
+            }
+            serviceUrl = new URL(serviceUrlStr);
+        } catch (HeadlessException e) {
+            return;
+        } catch (MalformedURLException e) {
+            JOptionPane.showMessageDialog(this, tr("Invalid service URL."),
+                    tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        System.out.println("Connecting to: " + getCapabilitiesUrl);
+
+        String incomingData;
+        try {
+            URLConnection openConnection = getCapabilitiesUrl.openConnection();
+            InputStream inputStream = openConnection.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
+            String line;
+            StringBuilder ba = new StringBuilder();
+            while((line = br.readLine()) != null) {
+                ba.append(line);
+                ba.append("\n");
+            }
+            incomingData = ba.toString();
+        } catch (IOException e) {
+            JOptionPane.showMessageDialog(this, tr("Could not retrieve WMS layer list."),
+                    tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        System.out.println(incomingData);
+
+        Document document;
+        try {
+            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+            builderFactory.setValidating(false);
+            builderFactory.setNamespaceAware(true);
+            DocumentBuilder builder = builderFactory.newDocumentBuilder();
+            builder.setEntityResolver(new EntityResolver() {
+                public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+                    System.out.println("Ignoring DTD " + publicId + ", " + systemId);
+                    return new InputSource(new StringReader(""));
+                }
+            });
+            document = builder.parse(new InputSource(new StringReader(incomingData)));
+        } catch (ParserConfigurationException e) {
+            JOptionPane.showMessageDialog(this, tr("Could not parse WMS layer list."),
+                    tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        } catch (SAXException e) {
+            JOptionPane.showMessageDialog(this, tr("Could not parse WMS layer list."),
+                    tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        } catch (IOException e) {
+            JOptionPane.showMessageDialog(this, tr("Could not parse WMS layer list."),
+                    tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        try {
+            treeRootNode.setUserObject(getCapabilitiesUrl.getHost());
+            Element capabilityElem = getChild(document.getDocumentElement(), "Capability");
+            List<Element> children = getChildren(capabilityElem, "Layer");
+            List<LayerDetails> layers = parseLayers(children, new HashSet<String>());
+            updateTreeList(layers);
+        } catch(Exception e) {
+            JOptionPane.showMessageDialog(this, tr("Could not parse WMS layer list."),
+                    tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
+            e.printStackTrace();
+            return;
+        }
+    }
+
+    private void updateTreeList(List<LayerDetails> layers) {
+        addLayersToTreeData(treeRootNode, layers);
+        layerTree.expandRow(0);
+    }
+
+    private void addLayersToTreeData(MutableTreeNode parent, List<LayerDetails> layers) {
+        for (LayerDetails layerDetails : layers) {
+            DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(layerDetails);
+            addLayersToTreeData(treeNode, layerDetails.children);
+            treeData.insertNodeInto(treeNode, parent, 0);
+        }
+    }
+
+    private List<LayerDetails> parseLayers(List<Element> children, Set<String> parentCrs) {
+        List<LayerDetails> details = new LinkedList<LayerDetails>();
+        for (Element element : children) {
+            details.add(parseLayer(element, parentCrs));
+        }
+        return details;
+    }
+
+    private LayerDetails parseLayer(Element element, Set<String> parentCrs) {
+        String name = getChildContent(element, "Title", null, null);
+        String ident = getChildContent(element, "Name", null, null);
+
+        // The set of supported CRS/SRS for this layer
+        Set<String> crsList = new HashSet<String>();
+        // ...including this layer's already-parsed parent projections
+        crsList.addAll(parentCrs);
+
+        // Parse the CRS/SRS pulled out of this layer's XML element
+        // I think CRS and SRS are the same at this point
+        List<Element> crsChildren = getChildren(element, "CRS");
+        crsChildren.addAll(getChildren(element, "SRS"));
+        for (Element child : crsChildren) {
+            String crs = (String) getContent(child);
+            if(crs != null) {
+                String upperCase = crs.trim().toUpperCase();
+                crsList.add(upperCase);
+            }
+        }
+
+        // Check to see if any of the specified projections are supported by JOSM
+        boolean josmSupportsThisLayer = false;
+        for (String crs : crsList) {
+            josmSupportsThisLayer |= isProjSupported(crs);
+        }
+
+        Bounds bounds = null;
+        Element bboxElem = getChild(element, "EX_GeographicBoundingBox");
+        if(bboxElem != null) {
+            // Attempt to use EX_GeographicBoundingBox for bounding box
+            double left = Double.parseDouble(getChildContent(bboxElem, "westBoundLongitude", null, null));
+            double top = Double.parseDouble(getChildContent(bboxElem, "northBoundLatitude", null, null));
+            double right = Double.parseDouble(getChildContent(bboxElem, "eastBoundLongitude", null, null));
+            double bot = Double.parseDouble(getChildContent(bboxElem, "southBoundLatitude", null, null));
+            bounds = new Bounds(bot, left, top, right);
+        } else {
+            // If that's not available, try LatLonBoundingBox
+            bboxElem = getChild(element, "LatLonBoundingBox");
+            if(bboxElem != null) {
+                double left = Double.parseDouble(bboxElem.getAttribute("minx"));
+                double top = Double.parseDouble(bboxElem.getAttribute("maxy"));
+                double right = Double.parseDouble(bboxElem.getAttribute("maxx"));
+                double bot = Double.parseDouble(bboxElem.getAttribute("miny"));
+                bounds = new Bounds(bot, left, top, right);
+            }
+        }
+
+        List<Element> layerChildren = getChildren(element, "Layer");
+        List<LayerDetails> childLayers = parseLayers(layerChildren, crsList);
+
+        return new LayerDetails(name, ident, crsList, josmSupportsThisLayer, bounds, childLayers);
+    }
+
+    private boolean isProjSupported(String crs) {
+        for (Projection proj : Projection.allProjections) {
+            if (proj instanceof ProjectionSubPrefs) {
+                if (((ProjectionSubPrefs) proj).getPreferencesFromCode(crs) == null) {
+                    return true;
+                }
+            } else {
+                if (proj.toCode().equals(crs)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public String getUrlName() {
+        return menuName.getText();
+    }
+
+    public String getUrl() {
+        return resultingLayerField.getText();
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame("Test");
+        f.setContentPane(new AddWMSLayerPanel());
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.pack();
+        f.setVisible(true);
+    }
+
+    private static String getChildContent(Element parent, String name, String missing, String empty) {
+        Element child = getChild(parent, name);
+        if (child == null) {
+            return missing;
+        } else {
+            String content = (String) getContent(child);
+            return (content != null) ? content : empty;
+        }
+    }
+
+    private static Object getContent(Element element) {
+        NodeList nl = element.getChildNodes();
+        StringBuffer content = new StringBuffer();
+        for (int i = 0; i < nl.getLength(); i++) {
+            Node node = nl.item(i);
+            switch (node.getNodeType()) {
+            case Node.ELEMENT_NODE:
+                return node;
+            case Node.CDATA_SECTION_NODE:
+            case Node.TEXT_NODE:
+                content.append(node.getNodeValue());
+                break;
+            }
+        }
+        return content.toString().trim();
+    }
+
+    private static List<Element> getChildren(Element parent, String name) {
+        List<Element> retVal = new LinkedList<Element>();
+        for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
+            if (child instanceof Element && name.equals(child.getNodeName())) {
+                retVal.add((Element) child);
+            }
+        }
+        return retVal;
+    }
+
+    private static Element getChild(Element parent, String name) {
+        for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
+            if (child instanceof Element && name.equals(child.getNodeName())) {
+                return (Element) child;
+            }
+        }
+        return null;
+    }
+
+    class LayerDetails {
+
+        private String name;
+        private String ident;
+        private Set<String> crsList;
+        private List<LayerDetails> children;
+        private Bounds bounds;
+        private boolean supported;
+
+        public LayerDetails(String name, String ident, Set<String> crsList,
+                boolean supportedLayer, Bounds bounds,
+                List<LayerDetails> childLayers) {
+            this.name = name;
+            this.ident = ident;
+            this.crsList = crsList;
+            this.supported = supportedLayer;
+            this.children = childLayers;
+            this.bounds = bounds;
+        }
+
+        public boolean isSupported() {
+            return this.supported;
+        }
+
+        @Override
+        public String toString() {
+            if(this.name == null || this.name.isEmpty()) {
+                return this.ident;
+            } else {
+                return this.name;
+            }
+        }
+
+    }
+
+    class LayerTreeCellRenderer extends DefaultTreeCellRenderer {
+        @Override
+        public Component getTreeCellRendererComponent(JTree tree, Object value,
+                boolean sel, boolean expanded, boolean leaf, int row,
+                boolean hasFocus) {
+            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
+                    row, hasFocus);
+            DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
+            Object userObject = treeNode.getUserObject();
+            if (userObject instanceof LayerDetails) {
+                LayerDetails layer = (LayerDetails) userObject;
+                setEnabled(layer.isSupported());
+            }
+            return this;
+        }
+    }
+
+}
diff --git a/wmsplugin/src/wmsplugin/GeorefImage.java b/wmsplugin/src/wmsplugin/GeorefImage.java
index 7a41243..1245ff0 100644
--- a/wmsplugin/src/wmsplugin/GeorefImage.java
+++ b/wmsplugin/src/wmsplugin/GeorefImage.java
@@ -1,87 +1,136 @@
 package wmsplugin;
 
-import java.awt.Dimension;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Image;
-import java.awt.Point;
 import java.awt.Transparency;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.lang.ref.SoftReference;
 
 import javax.imageio.ImageIO;
 
-import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.gui.NavigatableComponent;
 
 public class GeorefImage implements Serializable {
-	public BufferedImage image = null;
-	private Image reImg = null;
-	private Dimension reImgHash = new Dimension(0, 0);
-	public EastNorth min, max;
-	public boolean downloadingStarted;
-	public boolean failed = false;
-	public boolean infotext = false;
+	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 GeorefImage(boolean downloadingStarted) {
-		this.downloadingStarted = downloadingStarted;
+	public EastNorth getMin() {
+		return layer.getEastNorth(xIndex, yIndex);
 	}
 
-	public boolean contains(EastNorth en, double dx, double dy) {
-		return min.east()+dx <= en.east() && en.east() <= max.east()+dx
-		&& min.north()+dy <= en.north() && en.north() <= max.north()+dy;
+	public EastNorth getMax() {
+		return layer.getEastNorth(xIndex+1, yIndex+1);
 	}
 
-	public boolean isVisible(NavigatableComponent nc, double dx, double dy) {
-		EastNorth mi = new EastNorth(min.east()+dx, min.north()+dy);
-		EastNorth ma = new EastNorth(max.east()+dx, max.north()+dy);
-		Point minPt = nc.getPoint(mi), maxPt = nc.getPoint(ma);
-		Graphics g = nc.getGraphics();
 
-		return (g.hitClip(minPt.x, maxPt.y,
-				maxPt.x - minPt.x, minPt.y - maxPt.y));
+	public GeorefImage(WMSLayer layer) {
+		this.layer = layer;
 	}
 
-	public boolean paint(Graphics g, NavigatableComponent nc, double dx, double dy) {
-		if (image == null || min == null || max == null) return false;
+	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;
+	}
 
-		EastNorth mi = new EastNorth(min.east()+dx, min.north()+dy);
-		EastNorth ma = new EastNorth(max.east()+dx, max.north()+dy);
-		Point minPt = nc.getPoint(mi), maxPt = nc.getPoint(ma);
+	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(!isVisible(nc, dx, dy)){
+		if(!(this.xIndex == xIndex && this.yIndex == yIndex)){
 			return false;
 		}
 
-		// Width and height flicker about 2 pixels due to rounding errors, typically only 1
-		int width = Math.abs(maxPt.x-minPt.x);
-		int height = Math.abs(minPt.y-maxPt.y);
-		int diffx, diffy;
+		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;
 
-		diffx = reImgHash.width - width;
-		diffy = reImgHash.height - height;
 		// This happens if you zoom outside the world
 		if(width == 0 || height == 0)
 			return false;
 
-		// We still need to re-render if the requested size is larger (otherwise we'll have black lines)
-		// If it's only up to two pixels smaller, just draw the old image, the errors are minimal
-		// but the performance improvements when moving are huge
-		// Zooming is still slow because the images need to be resized
-		if(diffx >= 0 && diffx <= 2 && diffy >= 0 && diffy <= 2 && reImg != null) {
-			/*g.setColor(Color.RED);
-              g.drawRect(minPt.x, minPt.y-height, width, height);*/
-			g.drawImage(reImg, minPt.x, maxPt.y, null);
+		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 = Main.pref.getBoolean("wmsplugin.alpha_channel") && image.getTransparency() != Transparency.OPAQUE;
+		boolean alphaChannel = WMSLayer.PROP_ALPHA_CHANNEL.get() && getImage().getTransparency() != Transparency.OPAQUE;
 
 		try {
-			if(reImg != null) reImg.flush();
+			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
@@ -93,44 +142,37 @@ public class GeorefImage implements Serializable {
 			// 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, image, minPt, maxPt);
+				fallbackDraw(g, getImage(), x, y, width, height);
 			} else {
 				// We haven't got a saved resized copy, so resize and cache it
-				reImg = new BufferedImage(width, height, alphaChannel?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_3BYTE_BGR);
-				reImg.getGraphics().drawImage(image,
+				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, image.getWidth(null), image.getHeight(null), // src
+						0, 0, getImage().getWidth(null), getImage().getHeight(null), // src
 						null);
-				reImg.getGraphics().dispose();
-
-				reImgHash.setSize(width, height);
-				/*g.setColor(Color.RED);
-                  g.drawRect(minPt.x, minPt.y-height, width, height);*/
-				g.drawImage(reImg, minPt.x, maxPt.y, null);
+				img.getGraphics().dispose();
+				g.drawImage(img, x, y, null);
+				reImg = new SoftReference<BufferedImage>(img);
 			}
 		} catch(Exception e) {
-			fallbackDraw(g, image, minPt, maxPt);
+			fallbackDraw(g, getImage(), x, y, width, height);
 		}
 		return true;
 	}
 
-	private void fallbackDraw(Graphics g, Image img, Point min, Point max) {
-		if(reImg != null) {
-			reImg.flush();
-			reImg = null;
-		}
-		g.drawImage(img,
-				min.x, max.y, max.x, min.y, // dest
-				0, 0, img.getWidth(null), img.getHeight(null), // src
+	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 {
-		max = (EastNorth) in.readObject();
-		min = (EastNorth) in.readObject();
+		state = (State) in.readObject();
 		boolean hasImage = in.readBoolean();
 		if (hasImage)
-			image = ImageIO.read(ImageIO.createImageInputStream(in));
+			image = (ImageIO.read(ImageIO.createImageInputStream(in)));
 		else {
 			in.readObject(); // read null from input stream
 			image = null;
@@ -138,18 +180,44 @@ public class GeorefImage implements Serializable {
 	}
 
 	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.writeObject(max);
-		out.writeObject(min);
-		if(image == null) {
+		out.writeObject(state);
+		if(getImage() == null) {
 			out.writeBoolean(false);
 			out.writeObject(null);
 		} else {
 			out.writeBoolean(true);
-			ImageIO.write(image, "png", ImageIO.createImageOutputStream(out));
+			ImageIO.write(getImage(), "png", ImageIO.createImageOutputStream(out));
 		}
 	}
 
 	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;
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/Grabber.java b/wmsplugin/src/wmsplugin/Grabber.java
index 54a30c8..c225370 100644
--- a/wmsplugin/src/wmsplugin/Grabber.java
+++ b/wmsplugin/src/wmsplugin/Grabber.java
@@ -1,12 +1,5 @@
 package wmsplugin;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.Graphics;
-import java.awt.image.BufferedImage;
-
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
@@ -14,104 +7,109 @@ import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.io.CacheFiles;
 
-abstract public class Grabber extends Thread {
-    protected ProjectionBounds b;
-    protected Projection proj;
-    protected double pixelPerDegree;
-    protected MapView mv;
-    protected WMSLayer layer;
-    protected GeorefImage image;
-    protected CacheFiles cache;
-
-    Grabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache)
-    {
-        if (b.min != null && b.max != null && WMSPlugin.doOverlap) { 
-            double eastSize =  b.max.east() - b.min.east(); 
-            double northSize =  b.max.north() - b.min.north(); 
-
-            double eastCoef = WMSPlugin.overlapEast / 100.0; 
-            double northCoef = WMSPlugin.overlapNorth / 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));
-        } else 
-           this.b = b;
-
-        this.proj = Main.proj;
-        this.pixelPerDegree = layer.pixelPerDegree;
-        this.image = image;
-        this.mv = mv;
-        this.layer = layer;
-        this.cache = cache;
-
-    }
-
-    abstract void fetch() throws Exception; // the image fetch code
-
-    int width(){
-        return (int) ((b.max.north() - b.min.north()) * pixelPerDegree);
-    }
-    int height(){
-        return (int) ((b.max.east() - b.min.east()) * pixelPerDegree);
-    }
-
-    protected void grabError(Exception e){ // report error when grabing image
-        e.printStackTrace();
-
-        BufferedImage img = new BufferedImage(width(), height(), BufferedImage.TYPE_INT_ARGB);
-        Graphics g = img.getGraphics();
-        g.setColor(Color.RED);
-        g.fillRect(0, 0, width(), height());
-        Font font = g.getFont();
-        Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f);
-        g.setFont(tempFont);
-        g.setColor(Color.BLACK);
-        g.drawString(tr("Exception occurred"), 10, height()/2);
-        image.image = img;
-        image.flushedResizedCachedInstance();
-        image.failed = true;
-        image.downloadingStarted = false;
-        g.setFont(font);
-    }
-
-    protected void grabNotInCache(){ // report not in cache
-        BufferedImage img = new BufferedImage(width(), height(), BufferedImage.TYPE_INT_ARGB);
-        Graphics g = img.getGraphics();
-        g.setColor(Color.GRAY);
-        g.fillRect(0, 0, width(), height());
-        Font font = g.getFont();
-        Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f);
-        g.setFont(tempFont);
-        g.setColor(Color.BLACK);
-        g.drawString(tr("Not in cache"), 10, height()/2);
-        image.image = img;
-        image.flushedResizedCachedInstance();
-        image.infotext = true;
-        image.downloadingStarted = false;
-        g.setFont(font);
-    }
-
-    protected void attempt(){ // try to fetch the image
-        int maxTries = 5; // n tries for every image
-        for (int i = 1; i <= maxTries; i++) {
-            try {
-                fetch();
-                break; // break out of the retry loop
-            } catch (Exception e) {
-                try { // sleep some time and then ask the server again
-                    Thread.sleep(random(1000, 2000));
-                } catch (InterruptedException e1) {}
-
-                if(i == maxTries) grabError(e);
-            }
-        }
-    }
-
-    public static int random(int min, int max) {
-        return (int)(Math.random() * ((max+1)-min) ) + min;
-    }
-
-    abstract public boolean loadFromCache(boolean real);
+import wmsplugin.GeorefImage.State;
+
+abstract public class Grabber implements Runnable {
+	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;
+
+	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();
+
+			double eastCoef = WMSPlugin.PROP_OVERLAP_EAST.get() / 100.0;
+			double northCoef = WMSPlugin.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.proj = Main.proj;
+		this.pixelPerDegree = request.getPixelPerDegree();
+		this.request = request;
+	}
+
+	abstract void fetch(WMSRequest request) throws Exception; // the image fetch code
+
+	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();
+			}
+		}
+	}
+
+	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);
+				}
+			}
+		}
+	}
+
+	public static int random(int min, int max) {
+		return (int)(Math.random() * ((max+1)-min) ) + min;
+	}
+
+	abstract public boolean loadFromCache(WMSRequest request);
+
+	public void cancel() {
+		canceled = true;
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/HTMLGrabber.java b/wmsplugin/src/wmsplugin/HTMLGrabber.java
index 5cc5789..ec5e8d5 100644
--- a/wmsplugin/src/wmsplugin/HTMLGrabber.java
+++ b/wmsplugin/src/wmsplugin/HTMLGrabber.java
@@ -10,39 +10,38 @@ import java.util.StringTokenizer;
 import javax.imageio.ImageIO;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.io.CacheFiles;
 
 public class HTMLGrabber extends WMSGrabber {
-    HTMLGrabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache) {
-        super(b, image, mv, layer, cache);
-        this.baseURL = layer.baseURL.replaceFirst("html:", "");
-    }
-
-    @Override
-    protected BufferedImage grab(URL url) throws IOException {
-        String urlstring = url.toExternalForm();
-
-        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());
-
-        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() );
-        }
-
-        BufferedImage img = ImageIO.read(browser.getInputStream());
-        cache.saveImg(urlstring, img);
-        return img;
-    }
+	HTMLGrabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+		super(mv, layer, cache);
+		this.baseURL = layer.baseURL.replaceFirst("html:", "");
+	}
+
+	@Override
+	protected BufferedImage grab(URL url) throws IOException {
+		String urlstring = url.toExternalForm();
+
+		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());
+
+		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() );
+		}
+
+		BufferedImage img = ImageIO.read(browser.getInputStream());
+		cache.saveImg(urlstring, img);
+		return img;
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java b/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java
index a034a90..b3cf66d 100644
--- a/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java
+++ b/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java
@@ -28,220 +28,220 @@ import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.UrlLabel;
 
 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
+	 * @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();
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/WMSAdjustAction.java b/wmsplugin/src/wmsplugin/WMSAdjustAction.java
index 7a6576c..4ed12d9 100644
--- a/wmsplugin/src/wmsplugin/WMSAdjustAction.java
+++ b/wmsplugin/src/wmsplugin/WMSAdjustAction.java
@@ -30,189 +30,187 @@ import org.openstreetmap.josm.tools.ImageProvider;
 
 
 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) {
-    }
-
-    // This only makes the buttons look disabled, but since no keyboard shortcut is
-    // provided there aren't any other means to activate this tool
-    @Override public boolean layerIsSupported(Layer l) {
-        return (l instanceof WMSLayer) && l.isVisible();
-    }
-
-    /**
-    * 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());
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/WMSDownloadAction.java b/wmsplugin/src/wmsplugin/WMSDownloadAction.java
index f7eff50..e1f807d 100644
--- a/wmsplugin/src/wmsplugin/WMSDownloadAction.java
+++ b/wmsplugin/src/wmsplugin/WMSDownloadAction.java
@@ -9,25 +9,25 @@ import org.openstreetmap.josm.actions.JosmAction;
 
 public class WMSDownloadAction extends JosmAction {
 
-    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 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;
-    }
+	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 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;
+	}
 };
diff --git a/wmsplugin/src/wmsplugin/WMSGrabber.java b/wmsplugin/src/wmsplugin/WMSGrabber.java
index 098f494..4ef6f40 100644
--- a/wmsplugin/src/wmsplugin/WMSGrabber.java
+++ b/wmsplugin/src/wmsplugin/WMSGrabber.java
@@ -22,199 +22,183 @@ import javax.imageio.ImageIO;
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.Version;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.projection.Mercator;
-import org.openstreetmap.josm.data.Version;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.io.CacheFiles;
 import org.openstreetmap.josm.io.OsmTransferException;
 import org.openstreetmap.josm.io.ProgressInputStream;
 
+import wmsplugin.GeorefImage.State;
+
 
 public class WMSGrabber extends Grabber {
-    public static boolean isUrlWithPatterns(String url) {
-        return url != null && url.contains("{") && url.contains("}");
-    }
-
-    protected String baseURL;
-    private final boolean urlWithPatterns;
-
-    WMSGrabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache) {
-        super(b, image, mv, layer, cache);
-        this.baseURL = layer.baseURL;
-        /* URL containing placeholders? */
-        urlWithPatterns = isUrlWithPatterns(baseURL);
-    }
-
-    @Override
-	public void run() {
-        attempt();
-        mv.repaint();
-    }
-
-    @Override
-    void fetch() throws Exception{
-        URL url = null;
-        try {
-            url = getURL(
-                b.min.east(), b.min.north(),
-                b.max.east(), b.max.north(),
-                width(), height());
-
-            image.min = b.min;
-            image.max = b.max;
-
-            if(image.isVisible(mv, layer.getDx(), layer.getDy())) { //don't download, if the image isn't visible already
-                image.image = grab(url);
-                image.flushedResizedCachedInstance();
-            }
-            image.downloadingStarted = false;
-        } catch(Exception e) {
-            e.printStackTrace();
-            throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""));
-        }
-    }
-
-    public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000",
-            new DecimalFormatSymbols(Locale.US));
-
-    protected URL getURL(double w, double s,double e,double n,
-            int wi, int ht) throws MalformedURLException {
-        String myProj = Main.proj.toCode();
-        if(Main.proj instanceof Mercator) // don't use mercator code directly
-        {
-            LatLon sw = Main.proj.eastNorth2latlon(new EastNorth(w, s));
-            LatLon ne = Main.proj.eastNorth2latlon(new EastNorth(e, n));
-            myProj = "EPSG:4326";
-            s = sw.lat();
-            w = sw.lon();
-            n = ne.lat();
-            e = ne.lon();
-        }
-
-        String str = baseURL;
-        String bbox = latLonFormat.format(w) + ","
-                           + latLonFormat.format(s) + ","
-                           + latLonFormat.format(e) + ","
-                           + latLonFormat.format(n);
-
-        if (urlWithPatterns) {
-            str = str.replaceAll("\\{proj\\}", myProj)
-            .replaceAll("\\{bbox\\}", bbox)
-            .replaceAll("\\{w\\}", latLonFormat.format(w))
-            .replaceAll("\\{s\\}", latLonFormat.format(s))
-            .replaceAll("\\{e\\}", latLonFormat.format(e))
-            .replaceAll("\\{n\\}", latLonFormat.format(n))
-            .replaceAll("\\{width\\}", String.valueOf(wi))
-            .replaceAll("\\{height\\}", String.valueOf(ht));
-        } else {
-            str += "bbox=" + bbox
-                + getProjection(baseURL, false)
-                + "&width=" + wi + "&height=" + ht;
-            if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
-                System.out.println(tr("Warning: The base URL ''{0}'' for a WMS service doesn't have a trailing '&' or a trailing '?'.", baseURL));
-                System.out.println(tr("Warning: Fetching WMS tiles is likely to fail. Please check you preference settings."));
-                System.out.println(tr("Warning: The complete URL is ''{0}''.", str));
-            }
-        }
-        return new URL(str.replace(" ", "%20"));
-    }
-
-    static public String getProjection(String baseURL, Boolean warn)
-    {
-        String projname = Main.proj.toCode();
-        if(Main.proj instanceof Mercator) // don't use mercator code
-            projname = "EPSG:4326";
-        String res = "";
-        try
-        {
-            Matcher m = Pattern.compile(".*srs=([a-z0-9:]+).*").matcher(baseURL.toLowerCase());
-            if(m.matches())
-            {
-                projname = projname.toLowerCase();
-                if(!projname.equals(m.group(1)) && warn)
-                {
-                    JOptionPane.showMessageDialog(Main.parent,
-                    tr("The projection ''{0}'' in URL and current projection ''{1}'' mismatch.\n"
-                    + "This may lead to wrong coordinates.",
-                    m.group(1), projname),
-                    tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE);
-                }
-            }
-            else
-                res ="&srs="+projname;
-        }
-        catch(Exception e)
-        {
-        }
-        return res;
-    }
-
-    @Override
-	public boolean loadFromCache(boolean real){
-        URL url = null;
-        try{
-           url = getURL(
-              b.min.east(), b.min.north(),
-              b.max.east(), b.max.north(),
-              width(), height());
-        } catch(Exception e) {
-           return false;
-        }
-        BufferedImage cached = cache.getImg(url.toString());
-        if((!real && !layer.hasAutoDownload()) || cached != null){
-           image.min = b.min;
-           image.max = b.max;
-           if(cached == null){
-              grabNotInCache();
-              return true;
-           }
-           image.image = cached;
-           image.flushedResizedCachedInstance();
-           image.downloadingStarted = false;
-           return true;
-        }
-        return false;
-    }
-
-    protected BufferedImage grab(URL url) throws IOException, OsmTransferException {
-        System.out.println("Grabbing WMS " + url);
-
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        if(layer.cookies != null && !layer.cookies.equals(""))
-            conn.setRequestProperty("Cookie", layer.cookies);
-        conn.setRequestProperty("User-Agent", Main.pref.get("wmsplugin.user_agent", Version.getInstance().getAgentString()));
-        conn.setConnectTimeout(Main.pref.getInteger("wmsplugin.timeout.connect", 30) * 1000);
-        conn.setReadTimeout(Main.pref.getInteger("wmsplugin.timeout.read", 30) * 1000);
-
-        String contentType = conn.getHeaderField("Content-Type");
-        if( conn.getResponseCode() != 200
-                || contentType != null && !contentType.startsWith("image") ) {
-            throw new IOException(readException(conn));
-        }
-
-        InputStream is = new ProgressInputStream(conn, null);
-        BufferedImage img = ImageIO.read(is);
-        is.close();
-
-        cache.saveImg(url.toString(), img);
-        return img;
-    }
-
-    protected String readException(URLConnection conn) throws IOException {
-        StringBuilder exception = new StringBuilder();
-        InputStream in = conn.getInputStream();
-        BufferedReader br = new BufferedReader(new InputStreamReader(in));
-
-        String line = null;
-        while( (line = br.readLine()) != null) {
-            // filter non-ASCII characters and control characters
-            exception.append(line.replaceAll("[^\\p{Print}]", ""));
-            exception.append('\n');
-        }
-        return exception.toString();
-    }
+	public static boolean isUrlWithPatterns(String url) {
+		return url != null && url.contains("{") && url.contains("}");
+	}
+
+	protected String baseURL;
+	private final boolean urlWithPatterns;
+
+	WMSGrabber(MapView mv, WMSLayer layer, CacheFiles cache) {
+		super(mv, layer, cache);
+		this.baseURL = layer.baseURL;
+		/* URL containing placeholders? */
+		urlWithPatterns = isUrlWithPatterns(baseURL);
+	}
+
+	@Override
+	void fetch(WMSRequest request) throws Exception{
+		URL url = null;
+		try {
+			url = getURL(
+					b.min.east(), b.min.north(),
+					b.max.east(), b.max.north(),
+					width(), height());
+			request.finish(State.IMAGE, grab(url));
+
+		} catch(Exception e) {
+			e.printStackTrace();
+			throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""));
+		}
+	}
+
+	public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000",
+			new DecimalFormatSymbols(Locale.US));
+
+	protected URL getURL(double w, double s,double e,double n,
+			int wi, int ht) throws MalformedURLException {
+		String myProj = Main.proj.toCode();
+		if(Main.proj instanceof Mercator) // don't use mercator code directly
+		{
+			LatLon sw = Main.proj.eastNorth2latlon(new EastNorth(w, s));
+			LatLon ne = Main.proj.eastNorth2latlon(new EastNorth(e, n));
+			myProj = "EPSG:4326";
+			s = sw.lat();
+			w = sw.lon();
+			n = ne.lat();
+			e = ne.lon();
+		}
+
+		String str = baseURL;
+		String bbox = latLonFormat.format(w) + ","
+		+ latLonFormat.format(s) + ","
+		+ latLonFormat.format(e) + ","
+		+ latLonFormat.format(n);
+
+		if (urlWithPatterns) {
+			str = str.replaceAll("\\{proj\\}", myProj)
+			.replaceAll("\\{bbox\\}", bbox)
+			.replaceAll("\\{w\\}", latLonFormat.format(w))
+			.replaceAll("\\{s\\}", latLonFormat.format(s))
+			.replaceAll("\\{e\\}", latLonFormat.format(e))
+			.replaceAll("\\{n\\}", latLonFormat.format(n))
+			.replaceAll("\\{width\\}", String.valueOf(wi))
+			.replaceAll("\\{height\\}", String.valueOf(ht));
+		} else {
+			str += "bbox=" + bbox
+			+ getProjection(baseURL, false)
+			+ "&width=" + wi + "&height=" + ht;
+			if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
+				System.out.println(tr("Warning: The base URL ''{0}'' for a WMS service doesn't have a trailing '&' or a trailing '?'.", baseURL));
+				System.out.println(tr("Warning: Fetching WMS tiles is likely to fail. Please check you preference settings."));
+				System.out.println(tr("Warning: The complete URL is ''{0}''.", str));
+			}
+		}
+		return new URL(str.replace(" ", "%20"));
+	}
+
+	static public String getProjection(String baseURL, Boolean warn)
+	{
+		String projname = Main.proj.toCode();
+		if(Main.proj instanceof Mercator) // don't use mercator code
+			projname = "EPSG:4326";
+		String res = "";
+		try
+		{
+			Matcher m = Pattern.compile(".*srs=([a-z0-9:]+).*").matcher(baseURL.toLowerCase());
+			if(m.matches())
+			{
+				projname = projname.toLowerCase();
+				if(!projname.equals(m.group(1)) && warn)
+				{
+					JOptionPane.showMessageDialog(Main.parent,
+							tr("The projection ''{0}'' in URL and current projection ''{1}'' mismatch.\n"
+									+ "This may lead to wrong coordinates.",
+									m.group(1), projname),
+									tr("Warning"),
+									JOptionPane.WARNING_MESSAGE);
+				}
+			}
+			else
+				res ="&srs="+projname;
+		}
+		catch(Exception e)
+		{
+		}
+		return res;
+	}
+
+	@Override
+	public boolean loadFromCache(WMSRequest request) {
+		URL url = null;
+		try{
+			url = getURL(
+					b.min.east(), b.min.north(),
+					b.max.east(), b.max.north(),
+					width(), height());
+		} catch(Exception e) {
+			return false;
+		}
+		BufferedImage cached = cache.getImg(url.toString());
+		if((!request.isReal() && !layer.hasAutoDownload()) || cached != null){
+			if(cached == null){
+				request.finish(State.NOT_IN_CACHE, null);
+				return true;
+			}
+			request.finish(State.IMAGE, cached);
+			return true;
+		}
+		return false;
+	}
+
+	protected BufferedImage grab(URL url) throws IOException, OsmTransferException {
+		System.out.println("Grabbing WMS " + url);
+
+		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+		if(layer.cookies != null && !layer.cookies.equals(""))
+			conn.setRequestProperty("Cookie", layer.cookies);
+		conn.setRequestProperty("User-Agent", Main.pref.get("wmsplugin.user_agent", Version.getInstance().getAgentString()));
+		conn.setConnectTimeout(Main.pref.getInteger("wmsplugin.timeout.connect", 30) * 1000);
+		conn.setReadTimeout(Main.pref.getInteger("wmsplugin.timeout.read", 30) * 1000);
+
+		String contentType = conn.getHeaderField("Content-Type");
+		if( conn.getResponseCode() != 200
+				|| contentType != null && !contentType.startsWith("image") ) {
+			throw new IOException(readException(conn));
+		}
+
+		InputStream is = new ProgressInputStream(conn, null);
+		BufferedImage img = ImageIO.read(is);
+		is.close();
+
+		cache.saveImg(url.toString(), img);
+		return img;
+	}
+
+	protected String readException(URLConnection conn) throws IOException {
+		StringBuilder exception = new StringBuilder();
+		InputStream in = conn.getInputStream();
+		BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+		String line = null;
+		while( (line = br.readLine()) != null) {
+			// filter non-ASCII characters and control characters
+			exception.append(line.replaceAll("[^\\p{Print}]", ""));
+			exception.append('\n');
+		}
+		return exception.toString();
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/WMSLayer.java b/wmsplugin/src/wmsplugin/WMSLayer.java
index 1e67263..2d1cf9e 100644
--- a/wmsplugin/src/wmsplugin/WMSLayer.java
+++ b/wmsplugin/src/wmsplugin/WMSLayer.java
@@ -12,484 +12,806 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.swing.AbstractAction;
+import javax.swing.Action;
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JFileChooser;
-import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
-import javax.swing.JSeparator;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.DiskAccessAction;
 import org.openstreetmap.josm.actions.SaveActionBase;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.io.CacheFiles;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+
+import wmsplugin.GeorefImage.State;
 
 /**
  * This is a layer that grabs the current screen from an WMS server. The data
  * fetched this way is tiled and managed to the disc to reduce server load.
  */
 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 int messageNum = 5; //limit for messages per layer
-    protected MapView mv;
-    protected String resolution;
-    protected boolean stopAfterPaint = false;
-    protected int imageSize = 500;
-    protected int dax = 10;
-    protected int day = 10;
-    protected int minZoom = 3;
-    protected double dx = 0.0;
-    protected double dy = 0.0;
-    protected double pixelPerDegree;
-    protected GeorefImage[][] images = new GeorefImage[dax][day];
-    JCheckBoxMenuItem startstop = new JCheckBoxMenuItem(tr("Automatic downloading"), true);
-    protected JCheckBoxMenuItem alphaChannel = new JCheckBoxMenuItem(new ToggleAlphaAction());
-    protected String baseURL;
-    protected String cookies;
-    protected final int serializeFormatVersion = 5;
-
-    private ExecutorService executor = null;
-
-    /** 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);
-        alphaChannel.setSelected(Main.pref.getBoolean("wmsplugin.alpha_channel"));
-        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();
-
-        executor = Executors.newFixedThreadPool(
-            Main.pref.getInteger("wmsplugin.numThreads",
-            WMSPlugin.simultaneousConnections));
-        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 startstop.isSelected();
-    }
-
-    public double getDx(){
-        return dx;
-    }
-
-    public double getDy(){
-        return dy;
-    }
-
-    @Override
-    public void destroy() {
-        try {
-            executor.shutdownNow();
-            // Might not be initialized, so catch NullPointer as well
-        } catch(Exception x) {
-            x.printStackTrace();
-        }
-    }
-
-    public double getPPD(){
-        ProjectionBounds bounds = mv.getProjectionBounds();
-        return mv.getWidth() / (bounds.max.east() - bounds.min.east());
-    }
-
-    public void initializeImages() {
-        images = new GeorefImage[dax][day];
-        for(int x = 0; x<dax; ++x) {
-            for(int y = 0; y<day; ++y) {
-                images[x][y]= new GeorefImage(false);
-            }
-        }
-    }
-
-    @Override public Icon getIcon() {
-        return icon;
-    }
-
-    @Override public String getToolTipText() {
-        if(startstop.isSelected())
-            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 ProjectionBounds XYtoBounds (int x, int y) {
-        return new ProjectionBounds(
-            new EastNorth(      x * imageSize / pixelPerDegree,       y * imageSize / pixelPerDegree),
-            new EastNorth((x + 1) * imageSize / pixelPerDegree, (y + 1) * imageSize / pixelPerDegree));
-    }
-
-    private int modulo (int a, int b) {
-        return a % b >= 0 ? a%b : a%b+b;
-    }
-
-    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 bounds) {
-        if(baseURL == null) return;
-        if (usesInvalidUrl && !isInvalidUrlConfirmed) return;
-
-        if (zoomIsTooBig()) {
-            for(int x = 0; x<dax; ++x) {
-                for(int y = 0; y<day; ++y) {
-                    images[modulo(x,dax)][modulo(y,day)].paint(g, mv, dx, dy);
-                }
-            }
-        } else {
-            downloadAndPaintVisible(g, mv, false);
-        }
-    }
-
-    public void displace(double dx, double dy) {
-        this.dx += dx;
-        this.dy += dy;
-    }
-
-    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;
-        }
-    }
-
-    protected void downloadAndPaintVisible(Graphics g, final MapView mv,
-    boolean real){
-        if (usesInvalidUrl)
-            return;
-
-        ProjectionBounds bounds = mv.getProjectionBounds();
-        int bminx= (int)Math.floor (((bounds.min.east() - dx) * pixelPerDegree) / imageSize );
-        int bminy= (int)Math.floor (((bounds.min.north() - dy) * pixelPerDegree) / imageSize );
-        int bmaxx= (int)Math.ceil  (((bounds.max.east() - dx) * pixelPerDegree) / imageSize );
-        int bmaxy= (int)Math.ceil  (((bounds.max.north() - dy) * pixelPerDegree) / imageSize );
-
-        for(int x = bminx; x<bmaxx; ++x) {
-            for(int y = bminy; y<bmaxy; ++y){
-                GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
-                if((!img.paint(g, mv, dx, dy) || img.infotext) && !img.downloadingStarted){
-                    img.downloadingStarted = true;
-                    img.image = null;
-                    img.flushedResizedCachedInstance();
-                    Grabber gr = WMSPlugin.getGrabber(XYtoBounds(x,y), img, mv, this);
-                    if(!gr.loadFromCache(real)){
-                        gr.setPriority(1);
-                        executor.submit(gr);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override public void visitBoundingBox(BoundingXYVisitor v) {
-        for(int x = 0; x<dax; ++x) {
-            for(int y = 0; y<day; ++y)
-                if(images[x][y].image!=null){
-                    v.visit(images[x][y].min);
-                    v.visit(images[x][y].max);
-                }
-        }
-    }
-
-    @Override public Object getInfoComponent() {
-        return getToolTipText();
-    }
-
-    @Override public Component[] getMenuEntries() {
-        return new Component[]{
-            new JMenuItem(LayerListDialog.getInstance().createActivateLayerAction(this)),
-            new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
-            new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
-            new JSeparator(),
-            new JMenuItem(new LoadWmsAction()),
-            new JMenuItem(new SaveWmsAction()),
-            new JMenuItem(new BookmarkWmsAction()),
-            new JSeparator(),
-            startstop,
-            alphaChannel,
-            new JMenuItem(new ChangeResolutionAction()),
-            new JMenuItem(new ReloadErrorTilesAction()),
-            new JMenuItem(new DownloadAction()),
-            new JSeparator(),
-            new JMenuItem(new LayerListPopup.InfoAction(this))
-        };
-    }
-
-    public GeorefImage findImage(EastNorth eastNorth) {
-        for(int x = 0; x<dax; ++x) {
-            for(int y = 0; y<day; ++y)
-                if(images[x][y].image!=null && images[x][y].min!=null && images[x][y].max!=null)
-                    if(images[x][y].contains(eastNorth, dx, dy))
-                        return images[x][y];
-        }
-        return null;
-    }
-
-    public class DownloadAction extends AbstractAction {
-        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();
-            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.failed){
-                        img.image = null;
-                        img.flushedResizedCachedInstance();
-                        img.downloadingStarted = false;
-                        img.failed = false;
-                        mv.repaint();
-                    }
-                }
-            }
-        }
-    }
-
-    public class ToggleAlphaAction extends AbstractAction {
-        public ToggleAlphaAction() {
-            super(tr("Alpha channel"));
-        }
-        public void actionPerformed(ActionEvent ev) {
-            JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource();
-            boolean alphaChannel = checkbox.isSelected();
-            Main.pref.put("wmsplugin.alpha_channel", 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 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;
-                }
-                startstop.setSelected(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();
-                mv.repaint();
-            }
-            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();
-        }
-    }
-
-    public void preferenceChanged(PreferenceChangeEvent event) {
-        if (event.getKey().equals("wmsplugin.simultaneousConnections")) {
-            executor.shutdownNow();
-            executor = Executors.newFixedThreadPool(
-                Main.pref.getInteger("wmsplugin.numThreads",
-                WMSPlugin.simultaneousConnections));
-        }
-    }
+
+	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;
+		}
+	}
+
 }
diff --git a/wmsplugin/src/wmsplugin/WMSPlugin.java b/wmsplugin/src/wmsplugin/WMSPlugin.java
index 9672bad..7e62f42 100644
--- a/wmsplugin/src/wmsplugin/WMSPlugin.java
+++ b/wmsplugin/src/wmsplugin/WMSPlugin.java
@@ -14,6 +14,8 @@ 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;
@@ -22,11 +24,13 @@ import java.util.TreeSet;
 
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.ExtensionFileFilter;
 import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.gui.IconToggleButton;
 import org.openstreetmap.josm.gui.MainMenu;
 import org.openstreetmap.josm.gui.MapFrame;
@@ -35,226 +39,366 @@ 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;
 import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.PluginProxy;
 
 import wmsplugin.io.WMSLayerExporter;
 import wmsplugin.io.WMSLayerImporter;
 
 public class WMSPlugin extends Plugin {
-    static CacheFiles cache = new CacheFiles("wmsplugin");
-
-    WMSLayer wmsLayer;
-    static JMenu wmsJMenu;
-
-    static ArrayList<WMSInfo> wmsList = new ArrayList<WMSInfo>();
-    static TreeMap<String,String> wmsListDefault = new TreeMap<String,String>();
-
-    static boolean doOverlap = false;
-    static int overlapEast = 14;
-    static int overlapNorth = 4;
-    static int simultaneousConnections = 3;
-
-    // remember state of menu item to restore on changed preferences
-    static private boolean menuEnabled = false;
-
-    protected void initExporterAndImporter() {
-        ExtensionFileFilter.exporters.add(new WMSLayerExporter());
-        ExtensionFileFilter.importers.add(new WMSLayerImporter());
-    }
-
-    public WMSPlugin(PluginInformation info) {
-    	super(info);
-        refreshMenu();
-        cache.setExpire(CacheFiles.EXPIRE_MONTHLY, false);
-        cache.setMaxSize(70, false);
-        initExporterAndImporter();
-    }
-
-    // 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());
-
-        // Here we load the settings for "overlap" checkbox and spinboxes.
-
-        try {
-            doOverlap = Boolean.valueOf(prefs.get("wmsplugin.url.overlap"));
-        } catch (Exception e) {} // If sth fails, we drop to default settings.
-
-        try {
-            overlapEast = Integer.valueOf(prefs.get("wmsplugin.url.overlapEast"));
-        } catch (Exception e) {} // If sth fails, we drop to default settings.
-
-        try {
-            overlapNorth = Integer.valueOf(prefs.get("wmsplugin.url.overlapNorth"));
-        } catch (Exception e) {} // If sth fails, we drop to default settings.
-               
-        // Load the settings for number of simultaneous connections
-        try {
-            simultaneousConnections = Integer.valueOf(prefs.get("wmsplugin.simultanousConnections"));
-        } catch (Exception e) {} // If sth fails, we drop to default settings.
-
-        // And then the names+urls of WMS servers
-        int prefid = 0;
-        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(ProjectionBounds bounds, GeorefImage img, MapView mv, WMSLayer layer){
-        if(layer.baseURL.startsWith("html:"))
-            return new HTMLGrabber(bounds, img, mv, layer, cache);
-        else
-            return new WMSGrabber(bounds, img, 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/";
-    }
+	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/";
+	}
 }
diff --git a/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java b/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
index 2541c17..aa73c29 100644
--- a/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
+++ b/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
@@ -5,7 +5,6 @@ import static org.openstreetmap.josm.tools.I18n.tr;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.HashMap;
@@ -21,7 +20,6 @@ import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JSpinner;
 import javax.swing.JTable;
-import javax.swing.JTextField;
 import javax.swing.SpinnerNumberModel;
 import javax.swing.table.DefaultTableModel;
 
@@ -39,6 +37,8 @@ public class WMSPreferenceEditor implements PreferenceSetting {
     JSpinner spinEast;
     JSpinner spinNorth;
     JSpinner spinSimConn;
+    JCheckBox remoteCheckBox;
+    boolean allowRemoteControl = true;
 
     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"));
@@ -55,7 +55,7 @@ public class WMSPreferenceEditor implements PreferenceSetting {
         }
 
         final DefaultTableModel modeldef = new DefaultTableModel(
-        new String[]{tr("Menu Name (Default)"), tr("WMS URL (Default)")}, 0);
+                new String[]{tr("Menu Name (Default)"), tr("WMS URL (Default)")}, 0);
         final JTable listdef = new JTable(modeldef){
             @Override
             public boolean isCellEditable(int row,int column){return false;}
@@ -75,20 +75,13 @@ public class WMSPreferenceEditor implements PreferenceSetting {
         buttonPanel.add(add, GBC.std().insets(0,5,0,0));
         add.addActionListener(new ActionListener(){
             public void actionPerformed(ActionEvent e) {
-                JPanel p = new JPanel(new GridBagLayout());
-                p.add(new JLabel(tr("Menu Name")), GBC.std().insets(0,0,5,0));
-                JTextField key = new JTextField(40);
-                JTextField value = new JTextField(40);
-                p.add(key, GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
-                p.add(new JLabel(tr("WMS URL")), GBC.std().insets(0,0,5,0));
-                p.add(value, GBC.eol().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
+                AddWMSLayerPanel p = new AddWMSLayerPanel();
                 int answer = JOptionPane.showConfirmDialog(
                         gui, p,
-                        tr("Enter a menu name and WMS URL"),
-                        JOptionPane.OK_CANCEL_OPTION,
-                        JOptionPane.QUESTION_MESSAGE);
+                        tr("Add WMS URL"),
+                        JOptionPane.OK_CANCEL_OPTION);
                 if (answer == JOptionPane.OK_OPTION) {
-                    model.addRow(new String[]{key.getText(), value.getText()});
+                    model.addRow(new String[]{p.getUrlName(), p.getUrl()});
                 }
             }
         });
@@ -119,7 +112,7 @@ public class WMSPreferenceEditor implements PreferenceSetting {
                             tr("Please select at least one row to copy."),
                             tr("Information"),
                             JOptionPane.INFORMATION_MESSAGE
-                            );
+                    );
                     return;
                 }
 
@@ -154,9 +147,9 @@ public class WMSPreferenceEditor implements PreferenceSetting {
         p.add(scrolldef, GBC.eol().insets(0,5,0,0).fill(GridBagConstraints.BOTH));
 
         browser = new JComboBox(new String[]{
-        "webkit-image {0}",
-        "gnome-web-photo --mode=photo --format=png {0} /dev/stdout",
-        "gnome-web-photo-fixed {0}",
+                "webkit-image {0}",
+                "gnome-web-photo --mode=photo --format=png {0} /dev/stdout",
+                "gnome-web-photo-fixed {0}",
         "webkit-image-gtk {0}"});
         browser.setEditable(true);
         browser.setSelectedItem(Main.pref.get("wmsplugin.browser", "webkit-image {0}"));
@@ -166,11 +159,11 @@ public class WMSPreferenceEditor implements PreferenceSetting {
         //Overlap
         p.add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
 
-        overlapCheckBox = new JCheckBox(tr("Overlap tiles"), WMSPlugin.doOverlap );
+        overlapCheckBox = new JCheckBox(tr("Overlap tiles"), WMSPlugin.PROP_OVERLAP.get() );
         JLabel labelEast = new JLabel(tr("% of east:"));
         JLabel labelNorth = new JLabel(tr("% of north:"));
-        spinEast = new JSpinner(new SpinnerNumberModel(WMSPlugin.overlapEast, 1, 50, 1));
-        spinNorth = new JSpinner(new SpinnerNumberModel(WMSPlugin.overlapNorth, 1, 50, 1));
+        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));
 
         JPanel overlapPanel = new JPanel(new FlowLayout());
         overlapPanel.add(overlapCheckBox);
@@ -180,15 +173,24 @@ public class WMSPreferenceEditor implements PreferenceSetting {
         overlapPanel.add(spinNorth);
 
         p.add(overlapPanel);
-               
+
         // Simultaneous connections
         p.add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
         JLabel labelSimConn = new JLabel(tr("Simultaneous connections"));
-        spinSimConn = new JSpinner(new SpinnerNumberModel(WMSPlugin.simultaneousConnections, 1, 30, 1));
+        spinSimConn = new JSpinner(new SpinnerNumberModel(WMSPlugin.PROP_SIMULTANEOUS_CONNECTIONS.get(), 1, 30, 1));
         JPanel overlapPanelSimConn = new JPanel(new FlowLayout());
         overlapPanelSimConn.add(labelSimConn);
         overlapPanelSimConn.add(spinSimConn);
-        p.add(overlapPanelSimConn);
+        p.add(overlapPanelSimConn, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
+
+
+        allowRemoteControl = Main.pref.getBoolean("wmsplugin.remotecontrol", true);
+        remoteCheckBox = new JCheckBox(tr("Allow remote control (reqires remotecontrol plugin)"), allowRemoteControl );
+        JPanel remotePanel = new JPanel(new FlowLayout());
+        remotePanel.add(remoteCheckBox);
+
+        p.add(remotePanel);
+
     }
 
     public boolean ok() {
@@ -228,17 +230,15 @@ public class WMSPreferenceEditor implements PreferenceSetting {
 
         if (change) WMSPlugin.refreshMenu();
 
-        WMSPlugin.doOverlap = overlapCheckBox.getModel().isSelected();
-        WMSPlugin.overlapEast = (Integer) spinEast.getModel().getValue();
-        WMSPlugin.overlapNorth = (Integer) spinNorth.getModel().getValue();
-        WMSPlugin.simultaneousConnections = (Integer) spinSimConn.getModel().getValue();
+        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());
+        allowRemoteControl = remoteCheckBox.getModel().isSelected();
 
-        Main.pref.put("wmsplugin.url.overlap",    String.valueOf(WMSPlugin.doOverlap));
-        Main.pref.put("wmsplugin.url.overlapEast", String.valueOf(WMSPlugin.overlapEast));
-        Main.pref.put("wmsplugin.url.overlapNorth", String.valueOf(WMSPlugin.overlapNorth));
+        Main.pref.put("wmsplugin.browser", browser.getEditor().getItem().toString());
 
-        Main.pref.put("wmsplugin.browser", browser.getEditor().getItem().toString()); 
-        Main.pref.put("wmsplugin.simultaneousConnections", String.valueOf(WMSPlugin.simultaneousConnections));
+        Main.pref.put("wmsplugin.remotecontrol",    String.valueOf(allowRemoteControl));
         return false;
     }
 
diff --git a/wmsplugin/src/wmsplugin/WMSRemoteHandler.java b/wmsplugin/src/wmsplugin/WMSRemoteHandler.java
new file mode 100644
index 0000000..772eca5
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSRemoteHandler.java
@@ -0,0 +1,110 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.remotecontrol.PermissionPrefWithDefault;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandler;
+import org.openstreetmap.josm.plugins.remotecontrol.RequestHandlerErrorException;
+
+public class WMSRemoteHandler extends RequestHandler {
+
+	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 PermissionPrefWithDefault getPermissionPref()
+	{
+		return new PermissionPrefWithDefault(
+				"wmsplugin.remotecontrol",
+				true,
+		"RemoteControl: WMS forbidden by preferences");
+	}
+
+	@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
+	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;
+	}
+}
diff --git a/wmsplugin/src/wmsplugin/WMSRequest.java b/wmsplugin/src/wmsplugin/WMSRequest.java
new file mode 100644
index 0000000..8a5bcda
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSRequest.java
@@ -0,0 +1,101 @@
+package wmsplugin;
+
+import java.awt.image.BufferedImage;
+
+import wmsplugin.GeorefImage.State;
+
+public class WMSRequest implements Comparable<WMSRequest> {
+	private final int xIndex;
+	private final int yIndex;
+	private final double pixelPerDegree;
+	private final boolean real; // Download even if autodownloading is disabled
+	private int priority;
+	// Result
+	private State state;
+	private BufferedImage image;
+
+	public WMSRequest(int xIndex, int yIndex, double pixelPerDegree, boolean real) {
+		this.xIndex = xIndex;
+		this.yIndex = yIndex;
+		this.pixelPerDegree = pixelPerDegree;
+		this.real = real;
+	}
+
+	public void finish(State state, BufferedImage image) {
+		this.state = state;
+		this.image = image;
+	}
+
+	public int getXIndex() {
+		return xIndex;
+	}
+
+	public int getYIndex() {
+		return yIndex;
+	}
+
+	public double getPixelPerDegree() {
+		return pixelPerDegree;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		long temp;
+		temp = Double.doubleToLongBits(pixelPerDegree);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		result = prime * result + xIndex;
+		result = prime * result + yIndex;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		WMSRequest other = (WMSRequest) obj;
+		if (Double.doubleToLongBits(pixelPerDegree) != Double
+				.doubleToLongBits(other.pixelPerDegree))
+			return false;
+		if (xIndex != other.xIndex)
+			return false;
+		if (yIndex != other.yIndex)
+			return false;
+		return true;
+	}
+
+	public void setPriority(int priority) {
+		this.priority = priority;
+	}
+
+	public int getPriority() {
+		return priority;
+	}
+
+	public int compareTo(WMSRequest o) {
+		return priority - o.priority;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	public BufferedImage getImage() {
+		return image;
+	}
+
+	@Override
+	public String toString() {
+		return "WMSRequest [xIndex=" + xIndex + ", yIndex=" + yIndex
+		+ ", pixelPerDegree=" + pixelPerDegree + "]";
+	}
+
+	public boolean isReal() {
+		return real;
+	}
+}

-- 
Plugins for josm



More information about the Pkg-grass-devel mailing list