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, 7 years 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.