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

Last change on this file since 11653 was 11653, checked in by Don-vip, 7 years ago

fix #14437 - NPE at AbstractProjection.latlon2eastNorth with Tools => Add Node...

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