source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/LatLonDialog.java @ 12735

Last change on this file since 12735 was 12735, checked in by bastiK, 3 months ago

see #15229 - move CoordinateFormat code out of LatLon class

  • Property svn:eol-style set to native
File size: 14.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.GridBagLayout;
9import java.awt.event.FocusEvent;
10import java.awt.event.FocusListener;
11import java.awt.event.WindowAdapter;
12import java.awt.event.WindowEvent;
13import java.util.Arrays;
14import java.util.Optional;
15
16import javax.swing.BorderFactory;
17import javax.swing.JLabel;
18import javax.swing.JPanel;
19import javax.swing.JSeparator;
20import javax.swing.JTabbedPane;
21import javax.swing.UIManager;
22import javax.swing.event.DocumentEvent;
23import javax.swing.event.DocumentListener;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.data.coor.EastNorth;
27import org.openstreetmap.josm.data.coor.LatLon;
28import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
29import org.openstreetmap.josm.gui.ExtendedDialog;
30import org.openstreetmap.josm.gui.util.WindowGeometry;
31import org.openstreetmap.josm.gui.widgets.HtmlPanel;
32import org.openstreetmap.josm.gui.widgets.JosmTextField;
33import org.openstreetmap.josm.tools.GBC;
34import org.openstreetmap.josm.tools.Logging;
35import org.openstreetmap.josm.tools.Utils;
36
37/**
38 * A dialog that lets the user add a node at the coordinates he enters.
39 */
40public class LatLonDialog extends ExtendedDialog {
41    private static final Color BG_COLOR_ERROR = new Color(255, 224, 224);
42
43    /**
44     * The tabs that define the coordinate mode.
45     */
46    public JTabbedPane tabs;
47    private JosmTextField tfLatLon, tfEastNorth;
48    private LatLon latLonCoordinates;
49    private EastNorth eastNorthCoordinates;
50
51    protected JPanel buildLatLon() {
52        JPanel pnl = new JPanel(new GridBagLayout());
53        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
54
55        pnl.add(new JLabel(tr("Coordinates:")), GBC.std().insets(0, 10, 5, 0));
56        tfLatLon = new JosmTextField(24);
57        pnl.add(tfLatLon, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL).weight(1.0, 0.0));
58
59        pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
60
61        pnl.add(new HtmlPanel(
62                Utils.join("<br/>", Arrays.asList(
63                        tr("Enter the coordinates for the new node."),
64                        tr("You can separate longitude and latitude with space, comma or semicolon."),
65                        tr("Use positive numbers or N, E characters to indicate North or East cardinal direction."),
66                        tr("For South and West cardinal directions you can use either negative numbers or S, W characters."),
67                        tr("Coordinate value can be in one of three formats:")
68                      )) +
69                Utils.joinAsHtmlUnorderedList(Arrays.asList(
70                        tr("<i>degrees</i><tt>&deg;</tt>"),
71                        tr("<i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt>"),
72                        tr("<i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt> <i>seconds</i><tt>&quot</tt>")
73                      )) +
74                Utils.join("<br/><br/>", Arrays.asList(
75                        tr("Symbols <tt>&deg;</tt>, <tt>&#39;</tt>, <tt>&prime;</tt>, <tt>&quot;</tt>, <tt>&Prime;</tt> are optional."),
76                        tr("You can also use the syntax <tt>lat=\"...\" lon=\"...\"</tt> or <tt>lat=''...'' lon=''...''</tt>."),
77                        tr("Some examples:")
78                      )) +
79                "<table><tr><td>" +
80                Utils.joinAsHtmlUnorderedList(Arrays.asList(
81                        "49.29918&deg; 19.24788&deg;",
82                        "N 49.29918 E 19.24788",
83                        "W 49&deg;29.918&#39; S 19&deg;24.788&#39;",
84                        "N 49&deg;29&#39;04&quot; E 19&deg;24&#39;43&quot;",
85                        "49.29918 N, 19.24788 E",
86                        "49&deg;29&#39;21&quot; N 19&deg;24&#39;38&quot; E",
87                        "49 29 51, 19 24 18",
88                        "49 29, 19 24",
89                        "E 49 29, N 19 24"
90                      )) +
91                "</td><td>" +
92                Utils.joinAsHtmlUnorderedList(Arrays.asList(
93                        "49&deg; 29; 19&deg; 24",
94                        "N 49&deg; 29, W 19&deg; 24",
95                        "49&deg; 29.5 S, 19&deg; 24.6 E",
96                        "N 49 29.918 E 19 15.88",
97                        "49 29.4 19 24.5",
98                        "-49 29.4 N -19 24.5 W",
99                        "48 deg 42&#39; 52.13\" N, 21 deg 11&#39; 47.60\" E",
100                        "lat=\"49.29918\" lon=\"19.24788\"",
101                        "lat='49.29918' lon='19.24788'"
102                    )) +
103                "</td></tr></table>"),
104                GBC.eol().fill().weight(1.0, 1.0));
105
106        // parse and verify input on the fly
107        //
108        LatLonInputVerifier inputVerifier = new LatLonInputVerifier();
109        tfLatLon.getDocument().addDocumentListener(inputVerifier);
110
111        // select the text in the field on focus
112        //
113        TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
114        tfLatLon.addFocusListener(focusHandler);
115        return pnl;
116    }
117
118    private JPanel buildEastNorth() {
119        JPanel pnl = new JPanel(new GridBagLayout());
120        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
121
122        pnl.add(new JLabel(tr("Projected coordinates:")), GBC.std().insets(0, 10, 5, 0));
123        tfEastNorth = new JosmTextField(24);
124
125        pnl.add(tfEastNorth, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL).weight(1.0, 0.0));
126
127        pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
128
129        pnl.add(new HtmlPanel(
130                tr("Enter easting and northing (x and y) separated by space, comma or semicolon.")),
131                GBC.eol().fill(GBC.HORIZONTAL));
132
133        pnl.add(GBC.glue(1, 1), GBC.eol().fill().weight(1.0, 1.0));
134
135        EastNorthInputVerifier inputVerifier = new EastNorthInputVerifier();
136        tfEastNorth.getDocument().addDocumentListener(inputVerifier);
137
138        TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
139        tfEastNorth.addFocusListener(focusHandler);
140
141        return pnl;
142    }
143
144    protected void build() {
145        tabs = new JTabbedPane();
146        tabs.addTab(tr("Lat/Lon"), buildLatLon());
147        tabs.addTab(tr("East/North"), buildEastNorth());
148        tabs.getModel().addChangeListener(e -> {
149            switch (tabs.getModel().getSelectedIndex()) {
150                case 0: parseLatLonUserInput(); break;
151                case 1: parseEastNorthUserInput(); break;
152                default: throw new AssertionError();
153            }
154        });
155        setContent(tabs, false);
156        addWindowListener(new WindowAdapter() {
157            @Override
158            public void windowOpened(WindowEvent e) {
159                tfLatLon.requestFocusInWindow();
160            }
161        });
162    }
163
164    /**
165     * Creates a new {@link LatLonDialog}
166     * @param parent The parent
167     * @param title The title of this dialog
168     * @param help The help text to use
169     */
170    public LatLonDialog(Component parent, String title, String help) {
171        super(parent, title, tr("Ok"), tr("Cancel"));
172        setButtonIcons("ok", "cancel");
173        configureContextsensitiveHelp(help, true);
174
175        build();
176        setCoordinates(null);
177    }
178
179    /**
180     * Check if lat/lon mode is active
181     * @return <code>true</code> iff the user selects lat/lon coordinates
182     */
183    public boolean isLatLon() {
184        return tabs.getModel().getSelectedIndex() == 0;
185    }
186
187    /**
188     * Sets the coordinate fields to the given coordinates
189     * @param ll The lat/lon coordinates
190     */
191    public void setCoordinates(LatLon ll) {
192        LatLon llc = Optional.ofNullable(ll).orElse(LatLon.ZERO);
193        tfLatLon.setText(CoordinateFormatManager.getDefaultFormat().latToString(llc) + ' ' +
194                         CoordinateFormatManager.getDefaultFormat().lonToString(llc));
195        EastNorth en = Main.getProjection().latlon2eastNorth(llc);
196        tfEastNorth.setText(Double.toString(en.east()) + ' ' + Double.toString(en.north()));
197        // Both latLonCoordinates and eastNorthCoordinates may have been reset to null if ll is out of the world
198        latLonCoordinates = llc;
199        eastNorthCoordinates = en;
200        setOkEnabled(true);
201    }
202
203    /**
204     * Gets the coordinates that are entered by the user.
205     * @return The coordinates
206     */
207    public LatLon getCoordinates() {
208        if (isLatLon()) {
209            return latLonCoordinates;
210        } else {
211            if (eastNorthCoordinates == null) return null;
212            return Main.getProjection().eastNorth2latlon(eastNorthCoordinates);
213        }
214    }
215
216    /**
217     * Gets the coordinates that are entered in the lat/lon field
218     * @return The lat/lon coordinates
219     */
220    public LatLon getLatLonCoordinates() {
221        return latLonCoordinates;
222    }
223
224    /**
225     * Gets the coordinates that are entered in the east/north field
226     * @return The east/north coordinates
227     */
228    public EastNorth getEastNorthCoordinates() {
229        return eastNorthCoordinates;
230    }
231
232    protected void setErrorFeedback(JosmTextField tf, String message) {
233        tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
234        tf.setToolTipText(message);
235        tf.setBackground(BG_COLOR_ERROR);
236    }
237
238    protected void clearErrorFeedback(JosmTextField tf, String message) {
239        tf.setBorder(UIManager.getBorder("TextField.border"));
240        tf.setToolTipText(message);
241        tf.setBackground(UIManager.getColor("TextField.background"));
242    }
243
244    protected void parseLatLonUserInput() {
245        LatLon latLon;
246        try {
247            latLon = LatLon.parse(tfLatLon.getText());
248            if (!LatLon.isValidLat(latLon.lat()) || !LatLon.isValidLon(latLon.lon())) {
249                latLon = null;
250            }
251        } catch (IllegalArgumentException e) {
252            Logging.trace(e);
253            latLon = null;
254        }
255        if (latLon == null) {
256            setErrorFeedback(tfLatLon, tr("Please enter a GPS coordinates"));
257            latLonCoordinates = null;
258            setOkEnabled(false);
259        } else {
260            clearErrorFeedback(tfLatLon, tr("Please enter a GPS coordinates"));
261            latLonCoordinates = latLon;
262            setOkEnabled(true);
263        }
264    }
265
266    protected void parseEastNorthUserInput() {
267        EastNorth en;
268        try {
269            en = parseEastNorth(tfEastNorth.getText());
270        } catch (IllegalArgumentException e) {
271            Logging.trace(e);
272            en = null;
273        }
274        if (en == null) {
275            setErrorFeedback(tfEastNorth, tr("Please enter a Easting and Northing"));
276            latLonCoordinates = null;
277            setOkEnabled(false);
278        } else {
279            clearErrorFeedback(tfEastNorth, tr("Please enter a Easting and Northing"));
280            eastNorthCoordinates = en;
281            setOkEnabled(true);
282        }
283    }
284
285    private void setOkEnabled(boolean b) {
286        if (buttons != null && !buttons.isEmpty()) {
287            buttons.get(0).setEnabled(b);
288        }
289    }
290
291    @Override
292    public void setVisible(boolean visible) {
293        final String preferenceKey = getClass().getName() + ".geometry";
294        if (visible) {
295            new WindowGeometry(
296                    preferenceKey,
297                    WindowGeometry.centerInWindow(getParent(), getSize())
298            ).applySafe(this);
299        } else {
300            new WindowGeometry(this).remember(preferenceKey);
301        }
302        super.setVisible(visible);
303    }
304
305    class LatLonInputVerifier implements DocumentListener {
306        @Override
307        public void changedUpdate(DocumentEvent e) {
308            parseLatLonUserInput();
309        }
310
311        @Override
312        public void insertUpdate(DocumentEvent e) {
313            parseLatLonUserInput();
314        }
315
316        @Override
317        public void removeUpdate(DocumentEvent e) {
318            parseLatLonUserInput();
319        }
320    }
321
322    class EastNorthInputVerifier implements DocumentListener {
323        @Override
324        public void changedUpdate(DocumentEvent e) {
325            parseEastNorthUserInput();
326        }
327
328        @Override
329        public void insertUpdate(DocumentEvent e) {
330            parseEastNorthUserInput();
331        }
332
333        @Override
334        public void removeUpdate(DocumentEvent e) {
335            parseEastNorthUserInput();
336        }
337    }
338
339    static class TextFieldFocusHandler implements FocusListener {
340        @Override
341        public void focusGained(FocusEvent e) {
342            Component c = e.getComponent();
343            if (c instanceof JosmTextField) {
344                JosmTextField tf = (JosmTextField) c;
345                tf.selectAll();
346            }
347        }
348
349        @Override
350        public void focusLost(FocusEvent e) {
351            // Not used
352        }
353    }
354
355    /**
356     * Parses a east/north coordinate string
357     * @param s The coordinates
358     * @return The east/north coordinates or <code>null</code> on error.
359     */
360    public static EastNorth parseEastNorth(String s) {
361        String[] en = s.split("[;, ]+");
362        if (en.length != 2) return null;
363        try {
364            double east = Double.parseDouble(en[0]);
365            double north = Double.parseDouble(en[1]);
366            return new EastNorth(east, north);
367        } catch (NumberFormatException nfe) {
368            return null;
369        }
370    }
371
372    /**
373     * Gets the text entered in the lat/lon text field.
374     * @return The text the user entered
375     */
376    public String getLatLonText() {
377        return tfLatLon.getText();
378    }
379
380    /**
381     * Set the text in the lat/lon text field.
382     * @param text The new text
383     */
384    public void setLatLonText(String text) {
385        tfLatLon.setText(text);
386    }
387
388    /**
389     * Gets the text entered in the east/north text field.
390     * @return The text the user entered
391     */
392    public String getEastNorthText() {
393        return tfEastNorth.getText();
394    }
395
396    /**
397     * Set the text in the east/north text field.
398     * @param text The new text
399     */
400    public void setEastNorthText(String text) {
401        tfEastNorth.setText(text);
402    }
403}
Note: See TracBrowser for help on using the repository browser.