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

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

closes #15273, see #15229, see #15182 - add command line interface module for projections

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