Index: /trunk/src/org/openstreetmap/josm/actions/AddNodeAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AddNodeAction.java	(revision 2542)
+++ /trunk/src/org/openstreetmap/josm/actions/AddNodeAction.java	(revision 2543)
@@ -5,10 +5,23 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
 import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
-
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
 import javax.swing.JDialog;
 import javax.swing.JLabel;
@@ -16,11 +29,21 @@
 import javax.swing.JPanel;
 import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.data.coor.CoordinateFormat;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.WindowGeometry;
 
 /**
@@ -29,4 +52,5 @@
  */
 public final class AddNodeAction extends JosmAction {
+    static private final Logger logger = Logger.getLogger(AddNodeAction.class.getName());
 
     public AddNodeAction() {
@@ -37,71 +61,20 @@
     }
 
-    private String normalizeUserInputForLatLonValue(String value) {
-        if (value == null) return null;
-        value = value.trim();
-        return value.replaceAll("\u00B0", ""); // the degree symbol
-    }
 
     public void actionPerformed(ActionEvent e) {
-        // we abort if we are not in the context of an OsmDataLayer
-        //
         if (!isEnabled())
             return;
 
-        JPanel p = new JPanel(new GridBagLayout());
-        p.add(new JLabel("<html>"+
-                tr("Enter the coordinates for the new node.") +
-                "<br>" + tr("Use decimal degrees.") +
-                "<br>" + tr("Negative values denote Western/Southern hemisphere.")),
-                GBC.eol());
-
-        p.add(new JLabel(tr("Latitude")), GBC.std().insets(0,10,5,0));
-        final JTextField lat = new JTextField(12);
-        p.add(lat, GBC.eol().insets(0,10,0,0));
-        p.add(new JLabel(tr("Longitude")), GBC.std().insets(0,0,5,10));
-        final JTextField lon = new JTextField(12);
-        p.add(lon, GBC.eol().insets(0,0,0,10));
-
-        Node nnew = null;
-
-        while(nnew == null) {
-            JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
-            JDialog dialog = pane.createDialog(Main.parent, tr("Add Node..."));
-            dialog.addWindowListener(new WindowAdapter() {
-                @Override
-                public void windowGainedFocus(WindowEvent e) {
-                    lat.selectAll();
-                    lat.requestFocusInWindow();
-                }
-            }
-            );
-            dialog.setVisible(true);
-
-            if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
-                return;
-            try {
-                LatLon ll = new LatLon(
-                        Double.parseDouble(
-                                normalizeUserInputForLatLonValue(lat.getText())
-                        ),
-                        Double.parseDouble(
-                                normalizeUserInputForLatLonValue(lon.getText())
-                        )
-                );
-                if (!ll.isOutSideWorld()) {
-                    nnew = new Node(ll);
-                }
-            } catch (Exception ex) {
-                ex.printStackTrace();
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        tr("Could not create a node with the entered latitude/longitude."),
-                        tr("Illegal lat/lon value"),
-                        JOptionPane.ERROR_MESSAGE
-                );
-            }
-        }
-
-        /* Now execute the commands to add the dupicated contents of the paste buffer to the map */
+        LatLonDialog dialog = new LatLonDialog(Main.parent);
+        dialog.setVisible(true);
+        if (dialog.isCanceled())
+            return;
+
+        LatLon coordinates = dialog.getCoordinates();
+        if (coordinates == null)
+            return;
+        Node nnew = new Node(coordinates);
+
+        // add the node
         Main.main.undoRedo.add(new AddCommand(nnew));
         getCurrentDataSet().setSelected(nnew);
@@ -113,3 +86,252 @@
         setEnabled(getEditLayer() != null);
     }
+
+    static private class LatLonDialog extends JDialog {
+        private static final Color BG_COLOR_ERROR = new Color(255,224,224);
+
+        private JTextField tfLat;
+        private JTextField tfLon;
+        private boolean canceled = false;
+        private LatLon coordinates;
+        private OKAction actOK;
+        private CancelAction actCancel;
+
+        protected JPanel buildInputForm() {
+            JPanel pnl = new JPanel(new GridBagLayout());
+            pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+            pnl.add(new JLabel("<html>"+
+                    tr("Enter the coordinates for the new node.") +
+                    "<br>" + tr("Use decimal degrees.") +
+                    "<br>" + tr("Negative values denote Western/Southern hemisphere.")),
+                    GBC.eol());
+
+            pnl.add(new JLabel(tr("Latitude")), GBC.std().insets(0,10,5,0));
+            tfLat = new JTextField(12);
+            pnl.add(tfLat, GBC.eol().insets(0,10,0,0));
+            pnl.add(new JLabel(tr("Longitude")), GBC.std().insets(0,0,5,10));
+            tfLon = new JTextField(12);
+            pnl.add(tfLon, GBC.eol().insets(0,0,0,10));
+
+            // parse and verify input on the fly
+            //
+            LatLonInputVerifier inputVerifier = new LatLonInputVerifier();
+            tfLat.getDocument().addDocumentListener(inputVerifier);
+            tfLon.getDocument().addDocumentListener(inputVerifier);
+
+            // select the text in the field on focus
+            //
+            TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
+            tfLat.addFocusListener(focusHandler);
+            tfLon.addFocusListener(focusHandler);
+            return pnl;
+        }
+
+        protected JPanel buildButtonRow() {
+            JPanel pnl = new JPanel(new FlowLayout());
+
+            SideButton btn;
+            pnl.add(btn = new SideButton(actOK = new OKAction()));
+            makeButtonRespondToEnter(btn);
+            pnl.add(btn = new SideButton(actCancel = new CancelAction()));
+            makeButtonRespondToEnter(btn);
+            pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Actions/AddNode"))));
+            return pnl;
+        }
+
+        protected void makeButtonRespondToEnter(SideButton btn) {
+            btn.setFocusable(true);
+            btn.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
+            btn.getActionMap().put("enter", btn.getAction());
+        }
+
+        protected void build() {
+            getContentPane().setLayout(new BorderLayout());
+            getContentPane().add(buildInputForm(), BorderLayout.CENTER);
+            getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
+            pack();
+
+            // make dialog respond to ESCAPE
+            //
+            getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "escape");
+            getRootPane().getActionMap().put("escape", actCancel);
+
+            // make dialog respond to F1
+            //
+            HelpUtil.setHelpContext(getRootPane(), ht("/Actions/AddNode"));
+        }
+
+        public LatLonDialog(Component parent) {
+            super(JOptionPane.getFrameForComponent(parent), true /* modal */);
+            setTitle(tr("Add Node..."));
+            build();
+            addWindowListener(new WindowEventHandler());
+            setCoordinates(null);
+        }
+
+        public void setCoordinates(LatLon coordinates) {
+            if (coordinates == null) {
+                coordinates = new LatLon(0,0);
+            }
+            this.coordinates = coordinates;
+            tfLat.setText(coordinates.latToString(CoordinateFormat.DECIMAL_DEGREES));
+            tfLon.setText(coordinates.lonToString(CoordinateFormat.DECIMAL_DEGREES));
+            actOK.setEnabled(true);
+        }
+
+        public LatLon getCoordinates() {
+            return coordinates;
+        }
+
+        protected void setErrorFeedback(JTextField tf, String message) {
+            tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
+            tf.setToolTipText(message);
+            tf.setBackground(BG_COLOR_ERROR);
+        }
+
+        protected void clearErrorFeedback(JTextField tf, String message) {
+            tf.setBorder(UIManager.getBorder("TextField.border"));
+            tf.setToolTipText(message);
+            tf.setBackground(UIManager.getColor("TextField.background"));
+        }
+
+        protected Double parseDoubleFromUserInput(String input) {
+            if (input == null) return null;
+            // remove white space and an optional degree symbol
+            //
+            input = input.trim();
+            input = input.replaceAll("\u00B0", ""); // the degree symbol
+
+            // try to parse using the current locale
+            //
+            NumberFormat f = NumberFormat.getNumberInstance();
+            Number n=null;
+            ParsePosition pp = new ParsePosition(0);
+            n = f.parse(input,pp);
+            if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length()) {
+                // fall back - try to parse with the english locale
+                //
+                pp = new ParsePosition(0);
+                f = NumberFormat.getNumberInstance(Locale.ENGLISH);
+                n = f.parse(input, pp);
+                if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length())
+                    return null;
+            }
+            return n== null ? null : n.doubleValue();
+        }
+
+        protected Double parseLatFromUserInput() {
+            Double d = parseDoubleFromUserInput(tfLat.getText());
+            if (d == null || ! LatLon.isValidLat(d)) {
+                setErrorFeedback(tfLat, tr("Please enter a valid latitude in the range -90..90"));
+                return null;
+            } else {
+                clearErrorFeedback(tfLat,tr("Please enter a latitude in the range -90..90"));
+            }
+            return d;
+        }
+
+        protected Double parseLonFromUserInput() {
+            Double d = parseDoubleFromUserInput(tfLon.getText());
+            if (d == null || ! LatLon.isValidLat(d)) {
+                setErrorFeedback(tfLon, tr("Please enter a valid longitude in the range -180..180"));
+                return null;
+            } else {
+                clearErrorFeedback(tfLon,tr("Please enter a longitude in the range -180..178"));
+            }
+            return d;
+        }
+
+        protected void parseUserInput() {
+            Double lat = parseLatFromUserInput();
+            Double lon = parseLonFromUserInput();
+            if (lat == null || lon == null) {
+                coordinates = null;
+                actOK.setEnabled(false);
+            } else {
+                coordinates = new LatLon(lat,lon);
+                actOK.setEnabled(true);
+            }
+        }
+
+        public boolean isCanceled() {
+            return canceled;
+        }
+
+        protected void setCanceled(boolean canceled) {
+            this.canceled = canceled;
+        }
+
+        @Override
+        public void setVisible(boolean visible) {
+            if (visible) {
+                setCanceled(false);
+                WindowGeometry.centerInWindow(Main.parent, getSize()).apply(this);
+            }
+            super.setVisible(visible);
+        }
+
+        class OKAction extends AbstractAction {
+            public OKAction() {
+                putValue(NAME, tr("OK"));
+                putValue(SHORT_DESCRIPTION, tr("Close the dialog and create a new node"));
+                putValue(SMALL_ICON, ImageProvider.get("ok"));
+            }
+
+            public void actionPerformed(ActionEvent e) {
+                setCanceled(false);
+                setVisible(false);
+            }
+        }
+
+        class CancelAction extends AbstractAction {
+            public CancelAction() {
+                putValue(NAME, tr("Cancel"));
+                putValue(SHORT_DESCRIPTION, tr("Close the dialog, don't create a new node"));
+                putValue(SMALL_ICON, ImageProvider.get("cancel"));
+            }
+
+            public void actionPerformed(ActionEvent e) {
+                setCanceled(true);
+                setVisible(false);
+            }
+        }
+
+        class LatLonInputVerifier implements DocumentListener {
+            public void changedUpdate(DocumentEvent e) {
+                parseUserInput();
+            }
+
+            public void insertUpdate(DocumentEvent e) {
+                parseUserInput();
+            }
+
+            public void removeUpdate(DocumentEvent e) {
+                parseUserInput();
+            }
+        }
+
+        class TextFieldFocusHandler implements FocusListener {
+            public void focusGained(FocusEvent e) {
+                Component c = e.getComponent();
+                if (c instanceof JTextField) {
+                    JTextField tf = (JTextField)c;
+                    tf.selectAll();
+                }
+            }
+            public void focusLost(FocusEvent e) {}
+        }
+
+        class WindowEventHandler extends WindowAdapter {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                setCanceled(true);
+                setVisible(false);
+            }
+
+            @Override
+            public void windowOpened(WindowEvent e) {
+                tfLat.requestFocusInWindow();
+            }
+        }
+    }
 }
