Index: trunk/src/org/openstreetmap/josm/data/projection/CustomProjection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/CustomProjection.java	(revision 5202)
+++ trunk/src/org/openstreetmap/josm/data/projection/CustomProjection.java	(revision 5226)
@@ -4,9 +4,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.GridBagLayout;
-import java.awt.event.ActionListener;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -14,6 +10,4 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import javax.swing.JPanel;
-import javax.swing.JTextField;
 
 import org.openstreetmap.josm.data.Bounds;
@@ -21,6 +15,9 @@
 import org.openstreetmap.josm.data.projection.datum.CentricDatum;
 import org.openstreetmap.josm.data.projection.datum.Datum;
+import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
+import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
 import org.openstreetmap.josm.data.projection.datum.SevenParameterDatum;
 import org.openstreetmap.josm.data.projection.datum.ThreeParameterDatum;
+import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
 import org.openstreetmap.josm.data.projection.proj.Proj;
 import org.openstreetmap.josm.data.projection.proj.ProjParameters;
@@ -32,15 +29,25 @@
  * Inspired by PROJ.4 and Proj4J.
  */
-public class CustomProjection extends AbstractProjection implements ProjectionSubPrefs {
-
-    private String pref = "";
-
-    public void update(String pref) {
-        try {
+public class CustomProjection extends AbstractProjection {
+
+    /**
+     * pref String that defines the projection
+     *
+     * null means fall back mode (Mercator)
+     */
+    protected String pref = null;
+
+    public void update(String pref) throws ProjectionConfigurationException {
+        if (pref == null) {
+            this.pref = null;
+            ellps = Ellipsoid.WGS84;
+            datum = WGS84Datum.INSTANCE;
+            proj = new org.openstreetmap.josm.data.projection.proj.Mercator();
+        } else {
             Map<String, String> parameters = new HashMap<String, String>();
             String[] parts = pref.trim().split("\\s+");
             for (int i = 0; i < parts.length; i++) {
                 String part = parts[i];
-                if (part.charAt(0) != '+')
+                if (part.isEmpty() || part.charAt(0) != '+')
                     throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' sign (found ''{0}'')", part));
                 Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part);
@@ -74,8 +81,6 @@
                 this.k_0 = parseDouble(s, "k_0");
             }
-        } catch (ProjectionConfigurationException e) {
-            System.err.println(e.toString()); // FIXME
-        }
-        this.pref = pref;
+            this.pref = pref;
+        }
     }
 
@@ -121,4 +126,12 @@
 
     public Datum parseDatum(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
+        String nadgridsId = parameters.get("nadgrids");
+        if (nadgridsId != null) {
+            NTV2GridShiftFileWrapper nadgrids = Projections.getNadgrids(nadgridsId);
+            if (nadgrids == null)
+                throw new ProjectionConfigurationException(tr("Grid shift file ''{0}'' for option +nadgrids not supported.", nadgridsId));
+            return new NTV2Datum(nadgridsId, null, ellps, nadgrids);
+        }
+
         String towgs84 = parameters.get("towgs84");
         if (towgs84 != null)
@@ -298,10 +311,10 @@
     @Override
     public String toCode() {
-        return Utils.md5Hex(pref).substring(0, 10);
+        return "proj:" + (pref == null ? "ERROR" : pref);
     }
 
     @Override
     public String getCacheDirectoryName() {
-        return toCode();
+        return "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
     }
 
@@ -317,35 +330,3 @@
         return tr("Custom Projection (from PROJ.4 string)");
     }
-
-    @Override
-    public void setupPreferencePanel(JPanel p, ActionListener listener) {
-        JTextField input = new JTextField(pref, 50);
-        p.setLayout(new GridBagLayout());
-        p.add(input);
-    }
-
-    @Override
-    public Collection<String> getPreferences(JPanel p) {
-        Object prefTf = p.getComponent(0);
-        if (!(prefTf instanceof JTextField))
-            return null;
-        String pref = ((JTextField) prefTf).getText();
-        return Collections.singleton(pref);
-    }
-
-    @Override
-    public void setPreferences(Collection<String> args) {
-        update(args.iterator().next());
-    }
-
-    @Override
-    public String[] allCodes() {
-        return new String[0];
-    }
-
-    @Override
-    public Collection<String> getPreferencesFromCode(String code) {
-        return null;
-    }
-
 }
Index: trunk/src/org/openstreetmap/josm/data/projection/CustomProjectionPrefGui.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/CustomProjectionPrefGui.java	(revision 5226)
+++ trunk/src/org/openstreetmap/josm/data/projection/CustomProjectionPrefGui.java	(revision 5226)
@@ -0,0 +1,128 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.projection;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.gui.preferences.projection.SubPrefsOptions;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class CustomProjectionPrefGui extends CustomProjection implements ProjectionSubPrefs, SubPrefsOptions {
+
+    @Override
+    public void setupPreferencePanel(JPanel p, ActionListener listener) {
+        final JTextField input = new JTextField(pref == null ? "" : pref, 30);
+        final HtmlPanel errorsPanel = new HtmlPanel();
+        errorsPanel.setVisible(false);
+        final JLabel valStatus = new JLabel();
+        valStatus.setVisible(false);
+
+        final AbstractTextComponentValidator val = new AbstractTextComponentValidator(input, false, false, false) {
+
+            private String error;
+
+            @Override
+            public void validate() {
+                if (!isValid()) {
+                    feedbackInvalid(tr("Invalid projection configuration: {0}",error));
+                } else {
+                    feedbackValid(tr("Projection configuration is valid."));
+                }
+            }
+
+            @Override
+            public boolean isValid() {
+                try {
+                    CustomProjection test = new CustomProjection();
+                    test.update(input.getText());
+                } catch (ProjectionConfigurationException ex) {
+                    error = ex.getMessage();
+                    valStatus.setIcon(ImageProvider.get("data", "error.png"));
+                    valStatus.setVisible(true);
+                    errorsPanel.setText(error);
+                    errorsPanel.setVisible(true);
+                    return false;
+                }
+                errorsPanel.setVisible(false);
+                valStatus.setIcon(ImageProvider.get("misc", "green_check.png"));
+                valStatus.setVisible(true);
+                return true;
+            }
+
+        };
+
+        JButton btnCheck = new JButton(tr("Validate"));
+        btnCheck.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                val.validate();
+            }
+        });
+        btnCheck.setLayout(new BorderLayout());
+        btnCheck.setMargin(new Insets(-1,0,-1,0));
+
+        p.setLayout(new GridBagLayout());
+        p.add(input, GBC.std().fill(GBC.HORIZONTAL).insets(0, 0, 5, 5));
+        p.add(btnCheck, GBC.eol());
+        JPanel p2 = new JPanel(new GridBagLayout());
+        p2.add(valStatus, GBC.std().anchor(GBC.WEST).weight(0.0001, 0));
+        p2.add(errorsPanel, GBC.eol().fill(GBC.HORIZONTAL));
+        p.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
+    }
+
+    @Override
+    public Collection<String> getPreferences(JPanel p) {
+        Object prefTf = p.getComponent(0);
+        if (!(prefTf instanceof JTextField))
+            return null;
+        String pref = ((JTextField) prefTf).getText();
+        return Collections.singleton(pref);
+    }
+
+    @Override
+    public void setPreferences(Collection<String> args) {
+        try {
+            update(args.iterator().next());
+        } catch (ProjectionConfigurationException ex) {
+            System.err.println("Error: Parsing of custom projection failed, falling back to Mercator. Error message is: "+ex.getMessage());
+            try {
+                update(null);
+            } catch (ProjectionConfigurationException ex1) {
+                throw new RuntimeException(ex1);
+            }
+        }
+    }
+
+    @Override
+    public String[] allCodes() {
+        return new String[0];
+    }
+
+    @Override
+    public Collection<String> getPreferencesFromCode(String code) {
+        return null;
+    }
+
+    @Override
+    public boolean showProjectionCode() {
+        return false;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/projection/GaussKrueger.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/GaussKrueger.java	(revision 5202)
+++ trunk/src/org/openstreetmap/josm/data/projection/GaussKrueger.java	(revision 5226)
@@ -6,5 +6,4 @@
 import java.awt.GridBagLayout;
 import java.awt.event.ActionListener;
-import java.io.InputStream;
 import java.util.Collection;
 import java.util.Collections;
@@ -14,9 +13,8 @@
 import javax.swing.JPanel;
 
-import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
-import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFile;
+import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
 import org.openstreetmap.josm.data.projection.proj.ProjParameters;
 import org.openstreetmap.josm.data.projection.proj.TransverseMercator;
@@ -35,6 +33,4 @@
     };
 
-    private static NTV2GridShiftFile BETA2007 = null;
-
     private static String[] zones = { "2", "3", "4", "5" };
 
@@ -44,16 +40,4 @@
 
     public GaussKrueger(int zone) {
-        if (BETA2007 == null) {
-            try {
-                String gridFileName = "BETA2007.gsb";
-                InputStream is = Main.class.getResourceAsStream("/data/"+gridFileName);
-                if (is == null)
-                    throw new RuntimeException(tr("Error: failed to open input stream for resource ''/data/{0}''.", gridFileName));
-                BETA2007 = new NTV2GridShiftFile();
-                BETA2007.loadGridShiftFile(is, false);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
         updateParameters(zone);
     }
@@ -62,5 +46,5 @@
         this.zone = zone;
         ellps = Ellipsoid.Bessel1841;
-        datum = new NTV2Datum("BETA2007", null, ellps, BETA2007);
+        datum = new NTV2Datum("BETA2007", null, ellps, NTV2GridShiftFileWrapper.BETA2007);
         ////less acurrate datum (errors up to 3m):
         //datum = new SevenParameterDatum(
Index: trunk/src/org/openstreetmap/josm/data/projection/Lambert.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/Lambert.java	(revision 5202)
+++ trunk/src/org/openstreetmap/josm/data/projection/Lambert.java	(revision 5226)
@@ -19,4 +19,5 @@
 import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
 import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFile;
+import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
 import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
 import org.openstreetmap.josm.data.projection.proj.ProjParameters;
@@ -77,24 +78,5 @@
     private int layoutZone;
 
-    private static NTV2GridShiftFile ntf_rgf93Grid = null;
-
-    public static NTV2GridShiftFile getNtf_rgf93Grid() {
-        return ntf_rgf93Grid;
-    }
-
     public Lambert() {
-        if (ntf_rgf93Grid == null) {
-            try {
-                String gridFileName = "ntf_r93_b.gsb";
-                InputStream is = Main.class.getResourceAsStream("/data/"+gridFileName);
-                if (is == null) {
-                    throw new RuntimeException(tr("Error: failed to open input stream for resource ''/data/{0}''. Cannot load NTF<->RGF93 grid", gridFileName));
-                }
-                ntf_rgf93Grid = new NTV2GridShiftFile();
-                ntf_rgf93Grid.loadGridShiftFile(is, false);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
         updateParameters(DEFAULT_ZONE);
     }
@@ -103,5 +85,5 @@
         this.layoutZone = layoutZone;
         ellps = Ellipsoid.clarkeIGN;
-        datum = new NTV2Datum("ntf_rgf93Grid", null, ellps, ntf_rgf93Grid);
+        datum = new NTV2Datum("ntf_rgf93Grid", null, ellps, NTV2GridShiftFileWrapper.ntf_rgf93);
         x_0 = x_0s[layoutZone];
         lon_0 = 2.0 + 20.0 / 60 + 14.025 / 3600; // 0 grade Paris
Index: trunk/src/org/openstreetmap/josm/data/projection/Projections.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/Projections.java	(revision 5202)
+++ trunk/src/org/openstreetmap/josm/data/projection/Projections.java	(revision 5226)
@@ -11,4 +11,5 @@
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.projection.datum.Datum;
+import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
 import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
 import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
@@ -16,5 +17,4 @@
 import org.openstreetmap.josm.data.projection.proj.SwissObliqueMercator;
 import org.openstreetmap.josm.data.projection.proj.TransverseMercator;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
 
 /**
@@ -49,5 +49,5 @@
     static {
         if (Main.pref.getBoolean("customprojection")) {
-            addProjection(new CustomProjection());
+            addProjection(new CustomProjectionPrefGui());
         }
     }
@@ -86,4 +86,5 @@
     private static Map<String, Class<? extends Proj>> projs = new HashMap<String, Class<? extends Proj>>();
     private static Map<String, Datum> datums = new HashMap<String, Datum>();
+    private static Map<String, NTV2GridShiftFileWrapper> nadgrids = new HashMap<String, NTV2GridShiftFileWrapper>();
 
     static {
@@ -99,4 +100,7 @@
 
         datums.put("WGS84", WGS84Datum.INSTANCE);
+
+        nadgrids.put("BETA2007.gsb", NTV2GridShiftFileWrapper.BETA2007);
+        nadgrids.put("ntf_r93_b.gsb", NTV2GridShiftFileWrapper.ntf_rgf93);
     }
 
@@ -122,3 +126,7 @@
         return datums.get(id);
     }
+
+    public static NTV2GridShiftFileWrapper getNadgrids(String id) {
+        return nadgrids.get(id);
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Datum.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Datum.java	(revision 5202)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Datum.java	(revision 5226)
@@ -10,7 +10,7 @@
 public class NTV2Datum extends AbstractDatum {
 
-    protected NTV2GridShiftFile nadgrids;
+    protected NTV2GridShiftFileWrapper nadgrids;
 
-    public NTV2Datum(String name, String proj4Id, Ellipsoid ellps, NTV2GridShiftFile nadgrids) {
+    public NTV2Datum(String name, String proj4Id, Ellipsoid ellps, NTV2GridShiftFileWrapper nadgrids) {
         super(name, proj4Id, ellps);
         this.nadgrids = nadgrids;
@@ -20,5 +20,5 @@
     public LatLon toWGS84(LatLon ll) {
         NTV2GridShift gs = new NTV2GridShift(ll);
-        nadgrids.gridShiftForward(gs);
+        nadgrids.getShiftFile().gridShiftForward(gs);
         return new LatLon(ll.lat() + gs.getLatShiftDegrees(), ll.lon() + gs.getLonShiftPositiveEastDegrees());
     }
@@ -27,5 +27,5 @@
     public LatLon fromWGS84(LatLon ll) {
         NTV2GridShift gs = new NTV2GridShift(ll);
-        nadgrids.gridShiftReverse(gs);
+        nadgrids.getShiftFile().gridShiftReverse(gs);
         return new LatLon(ll.lat() + gs.getLatShiftDegrees(), ll.lon() + gs.getLonShiftPositiveEastDegrees());
     }
Index: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFileWrapper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFileWrapper.java	(revision 5226)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFileWrapper.java	(revision 5226)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.projection.datum;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.InputStream;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.io.MirroredInputStream;
+
+/**
+ * Wrapper for NTV2GridShiftFile.
+ *
+ * Loads the shift file from disk, when it is first accessed.
+ */
+public class NTV2GridShiftFileWrapper {
+
+    public final static NTV2GridShiftFileWrapper BETA2007 = new NTV2GridShiftFileWrapper("resource://data/BETA2007.gsb");
+    public final static NTV2GridShiftFileWrapper ntf_rgf93 = new NTV2GridShiftFileWrapper("resource://data/ntf_r93_b.gsb");
+    
+
+    private NTV2GridShiftFile instance = null;
+    private String gridFileName;
+
+    public NTV2GridShiftFileWrapper(String filename) {
+        this.gridFileName = filename;
+    }
+
+    public NTV2GridShiftFile getShiftFile() {
+        if (instance == null) {
+            try {
+                InputStream is = new MirroredInputStream(gridFileName);
+                if (is == null)
+                    throw new RuntimeException(tr("Error: failed to open input stream for resource ''/data/{0}''.", gridFileName));
+                instance = new NTV2GridShiftFile();
+                instance.loadGridShiftFile(is, false);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return instance;
+    }
+
+}
