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
RevLine 
[3720]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;
[11072]11import java.awt.event.WindowAdapter;
12import java.awt.event.WindowEvent;
[8678]13import java.util.Arrays;
[11553]14import java.util.Optional;
[3720]15
16import javax.swing.BorderFactory;
17import javax.swing.JLabel;
18import javax.swing.JPanel;
19import javax.swing.JSeparator;
[4314]20import javax.swing.JTabbedPane;
[3720]21import javax.swing.UIManager;
22import javax.swing.event.DocumentEvent;
23import javax.swing.event.DocumentListener;
24
25import org.openstreetmap.josm.Main;
[4314]26import org.openstreetmap.josm.data.coor.EastNorth;
[3720]27import org.openstreetmap.josm.data.coor.LatLon;
[12735]28import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
[12792]29import org.openstreetmap.josm.data.coor.conversion.LatLonParser;
[4314]30import org.openstreetmap.josm.gui.ExtendedDialog;
[12678]31import org.openstreetmap.josm.gui.util.WindowGeometry;
[3720]32import org.openstreetmap.josm.gui.widgets.HtmlPanel;
[5886]33import org.openstreetmap.josm.gui.widgets.JosmTextField;
[3720]34import org.openstreetmap.josm.tools.GBC;
[12620]35import org.openstreetmap.josm.tools.Logging;
[8678]36import org.openstreetmap.josm.tools.Utils;
[3720]37
[12301]38/**
39 * A dialog that lets the user add a node at the coordinates he enters.
40 */
[4314]41public class LatLonDialog extends ExtendedDialog {
[8510]42 private static final Color BG_COLOR_ERROR = new Color(255, 224, 224);
[3720]43
[12301]44 /**
45 * The tabs that define the coordinate mode.
46 */
[4314]47 public JTabbedPane tabs;
[5886]48 private JosmTextField tfLatLon, tfEastNorth;
[4314]49 private LatLon latLonCoordinates;
50 private EastNorth eastNorthCoordinates;
[3720]51
[4314]52 protected JPanel buildLatLon() {
[3720]53 JPanel pnl = new JPanel(new GridBagLayout());
[8510]54 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
[3720]55
[8510]56 pnl.add(new JLabel(tr("Coordinates:")), GBC.std().insets(0, 10, 5, 0));
[5886]57 tfLatLon = new JosmTextField(24);
[8510]58 pnl.add(tfLatLon, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL).weight(1.0, 0.0));
[3720]59
[8510]60 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
[3720]61
62 pnl.add(new HtmlPanel(
[8678]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>"),
[3720]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
[4314]119 private JPanel buildEastNorth() {
120 JPanel pnl = new JPanel(new GridBagLayout());
[8510]121 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
[3720]122
[8510]123 pnl.add(new JLabel(tr("Projected coordinates:")), GBC.std().insets(0, 10, 5, 0));
[5886]124 tfEastNorth = new JosmTextField(24);
[3720]125
[8510]126 pnl.add(tfEastNorth, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL).weight(1.0, 0.0));
[3720]127
[8510]128 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
[3720]129
[4314]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));
[3720]133
[4314]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;
[3720]143 }
144
[4314]145 protected void build() {
146 tabs = new JTabbedPane();
147 tabs.addTab(tr("Lat/Lon"), buildLatLon());
148 tabs.addTab(tr("East/North"), buildEastNorth());
[10611]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();
[4314]154 }
155 });
156 setContent(tabs, false);
[11072]157 addWindowListener(new WindowAdapter() {
158 @Override
159 public void windowOpened(WindowEvent e) {
160 tfLatLon.requestFocusInWindow();
161 }
162 });
[4314]163 }
164
[12301]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 */
[3720]171 public LatLonDialog(Component parent, String title, String help) {
[12279]172 super(parent, title, tr("Ok"), tr("Cancel"));
173 setButtonIcons("ok", "cancel");
[6361]174 configureContextsensitiveHelp(help, true);
[4314]175
[3720]176 build();
177 setCoordinates(null);
178 }
179
[12301]180 /**
181 * Check if lat/lon mode is active
182 * @return <code>true</code> iff the user selects lat/lon coordinates
183 */
[4314]184 public boolean isLatLon() {
185 return tabs.getModel().getSelectedIndex() == 0;
186 }
187
[12301]188 /**
189 * Sets the coordinate fields to the given coordinates
190 * @param ll The lat/lon coordinates
191 */
[4314]192 public void setCoordinates(LatLon ll) {
[11653]193 LatLon llc = Optional.ofNullable(ll).orElse(LatLon.ZERO);
[12735]194 tfLatLon.setText(CoordinateFormatManager.getDefaultFormat().latToString(llc) + ' ' +
195 CoordinateFormatManager.getDefaultFormat().lonToString(llc));
[11653]196 EastNorth en = Main.getProjection().latlon2eastNorth(llc);
[10300]197 tfEastNorth.setText(Double.toString(en.east()) + ' ' + Double.toString(en.north()));
[11653]198 // Both latLonCoordinates and eastNorthCoordinates may have been reset to null if ll is out of the world
199 latLonCoordinates = llc;
200 eastNorthCoordinates = en;
[4314]201 setOkEnabled(true);
[3720]202 }
203
[12301]204 /**
205 * Gets the coordinates that are entered by the user.
206 * @return The coordinates
207 */
[3720]208 public LatLon getCoordinates() {
[4314]209 if (isLatLon()) {
210 return latLonCoordinates;
211 } else {
212 if (eastNorthCoordinates == null) return null;
213 return Main.getProjection().eastNorth2latlon(eastNorthCoordinates);
214 }
[3720]215 }
216
[12301]217 /**
218 * Gets the coordinates that are entered in the lat/lon field
219 * @return The lat/lon coordinates
220 */
[4314]221 public LatLon getLatLonCoordinates() {
222 return latLonCoordinates;
223 }
224
[12301]225 /**
226 * Gets the coordinates that are entered in the east/north field
227 * @return The east/north coordinates
228 */
[4314]229 public EastNorth getEastNorthCoordinates() {
230 return eastNorthCoordinates;
231 }
232
[5886]233 protected void setErrorFeedback(JosmTextField tf, String message) {
[3720]234 tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
235 tf.setToolTipText(message);
236 tf.setBackground(BG_COLOR_ERROR);
237 }
238
[5886]239 protected void clearErrorFeedback(JosmTextField tf, String message) {
[3720]240 tf.setBorder(UIManager.getBorder("TextField.border"));
241 tf.setToolTipText(message);
242 tf.setBackground(UIManager.getColor("TextField.background"));
243 }
244
[4314]245 protected void parseLatLonUserInput() {
[3720]246 LatLon latLon;
247 try {
[12792]248 latLon = LatLonParser.parse(tfLatLon.getText());
[3720]249 if (!LatLon.isValidLat(latLon.lat()) || !LatLon.isValidLon(latLon.lon())) {
250 latLon = null;
251 }
252 } catch (IllegalArgumentException e) {
[12620]253 Logging.trace(e);
[3720]254 latLon = null;
255 }
256 if (latLon == null) {
257 setErrorFeedback(tfLatLon, tr("Please enter a GPS coordinates"));
[4314]258 latLonCoordinates = null;
259 setOkEnabled(false);
[3720]260 } else {
[8510]261 clearErrorFeedback(tfLatLon, tr("Please enter a GPS coordinates"));
[4314]262 latLonCoordinates = latLon;
263 setOkEnabled(true);
[3720]264 }
265 }
266
[4314]267 protected void parseEastNorthUserInput() {
268 EastNorth en;
269 try {
270 en = parseEastNorth(tfEastNorth.getText());
271 } catch (IllegalArgumentException e) {
[12620]272 Logging.trace(e);
[4314]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 {
[8510]280 clearErrorFeedback(tfEastNorth, tr("Please enter a Easting and Northing"));
[4314]281 eastNorthCoordinates = en;
282 setOkEnabled(true);
283 }
[3720]284 }
285
[4314]286 private void setOkEnabled(boolean b) {
[6093]287 if (buttons != null && !buttons.isEmpty()) {
[4314]288 buttons.get(0).setEnabled(b);
289 }
[3720]290 }
291
292 @Override
293 public void setVisible(boolean visible) {
[8909]294 final String preferenceKey = getClass().getName() + ".geometry";
[3720]295 if (visible) {
[8909]296 new WindowGeometry(
297 preferenceKey,
298 WindowGeometry.centerInWindow(getParent(), getSize())
299 ).applySafe(this);
300 } else {
301 new WindowGeometry(this).remember(preferenceKey);
[3720]302 }
303 super.setVisible(visible);
304 }
305
[4314]306 class LatLonInputVerifier implements DocumentListener {
[6084]307 @Override
[4314]308 public void changedUpdate(DocumentEvent e) {
309 parseLatLonUserInput();
[3720]310 }
311
[6084]312 @Override
[4314]313 public void insertUpdate(DocumentEvent e) {
314 parseLatLonUserInput();
[3720]315 }
316
[6084]317 @Override
[4314]318 public void removeUpdate(DocumentEvent e) {
319 parseLatLonUserInput();
[3720]320 }
321 }
322
[4314]323 class EastNorthInputVerifier implements DocumentListener {
[6084]324 @Override
[3720]325 public void changedUpdate(DocumentEvent e) {
[4314]326 parseEastNorthUserInput();
[3720]327 }
328
[6084]329 @Override
[3720]330 public void insertUpdate(DocumentEvent e) {
[4314]331 parseEastNorthUserInput();
[3720]332 }
333
[6084]334 @Override
[3720]335 public void removeUpdate(DocumentEvent e) {
[4314]336 parseEastNorthUserInput();
[3720]337 }
338 }
339
340 static class TextFieldFocusHandler implements FocusListener {
[6084]341 @Override
[3720]342 public void focusGained(FocusEvent e) {
343 Component c = e.getComponent();
[5886]344 if (c instanceof JosmTextField) {
[8510]345 JosmTextField tf = (JosmTextField) c;
[3720]346 tf.selectAll();
347 }
348 }
[8510]349
[6084]350 @Override
[8366]351 public void focusLost(FocusEvent e) {
352 // Not used
353 }
[3720]354 }
355
[12301]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 */
[6500]361 public static EastNorth parseEastNorth(String s) {
[4314]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
[12301]373 /**
374 * Gets the text entered in the lat/lon text field.
375 * @return The text the user entered
376 */
[4314]377 public String getLatLonText() {
[3720]378 return tfLatLon.getText();
379 }
380
[12301]381 /**
382 * Set the text in the lat/lon text field.
383 * @param text The new text
384 */
[4314]385 public void setLatLonText(String text) {
[3720]386 tfLatLon.setText(text);
387 }
[4314]388
[12301]389 /**
390 * Gets the text entered in the east/north text field.
391 * @return The text the user entered
392 */
[4314]393 public String getEastNorthText() {
394 return tfEastNorth.getText();
395 }
396
[12301]397 /**
398 * Set the text in the east/north text field.
399 * @param text The new text
400 */
[4314]401 public void setEastNorthText(String text) {
402 tfEastNorth.setText(text);
403 }
[3720]404}
Note: See TracBrowser for help on using the repository browser.