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

Last change on this file since 3479 was 3479, checked in by jttt, 14 years ago

cosmetics

File size: 10.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.BorderLayout;
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.FlowLayout;
10import java.awt.GridBagLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.FocusEvent;
13import java.awt.event.FocusListener;
14import java.awt.event.KeyEvent;
15import java.awt.event.WindowAdapter;
16import java.awt.event.WindowEvent;
17import java.text.NumberFormat;
18import java.text.ParsePosition;
19import java.util.Locale;
20
21import javax.swing.AbstractAction;
22import javax.swing.BorderFactory;
23import javax.swing.JComponent;
24import javax.swing.JDialog;
25import javax.swing.JLabel;
26import javax.swing.JOptionPane;
27import javax.swing.JPanel;
28import javax.swing.JTextField;
29import javax.swing.KeyStroke;
30import javax.swing.UIManager;
31import javax.swing.event.DocumentEvent;
32import javax.swing.event.DocumentListener;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.data.coor.CoordinateFormat;
36import org.openstreetmap.josm.data.coor.LatLon;
37import org.openstreetmap.josm.gui.SideButton;
38import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
39import org.openstreetmap.josm.gui.help.HelpUtil;
40import org.openstreetmap.josm.tools.GBC;
41import org.openstreetmap.josm.tools.ImageProvider;
42import org.openstreetmap.josm.tools.WindowGeometry;
43
44public class LatLonDialog extends JDialog {
45 private static final Color BG_COLOR_ERROR = new Color(255,224,224);
46
47 private JTextField tfLat;
48 private JTextField tfLon;
49 private String help;
50 private boolean canceled = false;
51 private LatLon coordinates;
52 private OKAction actOK;
53 private CancelAction actCancel;
54
55 protected JPanel buildInputForm() {
56 JPanel pnl = new JPanel(new GridBagLayout());
57 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
58 pnl.add(new JLabel("<html>"+
59 tr("Enter the coordinates for the new node.") +
60 "<br>" + tr("Use decimal degrees.") +
61 "<br>" + tr("Negative values denote Western/Southern hemisphere.")),
62 GBC.eol());
63
64 pnl.add(new JLabel(tr("Latitude")), GBC.std().insets(0,10,5,0));
65 tfLat = new JTextField(12);
66 pnl.add(tfLat, GBC.eol().insets(0,10,0,0));
67 pnl.add(new JLabel(tr("Longitude")), GBC.std().insets(0,0,5,10));
68 tfLon = new JTextField(12);
69 pnl.add(tfLon, GBC.eol().insets(0,0,0,10));
70
71 // parse and verify input on the fly
72 //
73 LatLonInputVerifier inputVerifier = new LatLonInputVerifier();
74 tfLat.getDocument().addDocumentListener(inputVerifier);
75 tfLon.getDocument().addDocumentListener(inputVerifier);
76
77 // select the text in the field on focus
78 //
79 TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
80 tfLat.addFocusListener(focusHandler);
81 tfLon.addFocusListener(focusHandler);
82 return pnl;
83 }
84
85 protected JPanel buildButtonRow() {
86 JPanel pnl = new JPanel(new FlowLayout());
87
88 SideButton btn;
89 pnl.add(btn = new SideButton(actOK = new OKAction()));
90 makeButtonRespondToEnter(btn);
91 pnl.add(btn = new SideButton(actCancel = new CancelAction()));
92 makeButtonRespondToEnter(btn);
93 pnl.add(new SideButton(new ContextSensitiveHelpAction(help)));
94 return pnl;
95 }
96
97 protected void makeButtonRespondToEnter(SideButton btn) {
98 btn.setFocusable(true);
99 btn.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
100 btn.getActionMap().put("enter", btn.getAction());
101 }
102
103 protected void build() {
104 getContentPane().setLayout(new BorderLayout());
105 getContentPane().add(buildInputForm(), BorderLayout.CENTER);
106 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
107 pack();
108
109 // make dialog respond to ESCAPE
110 //
111 getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "escape");
112 getRootPane().getActionMap().put("escape", actCancel);
113
114 // make dialog respond to F1
115 //
116 HelpUtil.setHelpContext(getRootPane(), help);
117 }
118
119 public LatLonDialog(Component parent, String title, String help) {
120 super(JOptionPane.getFrameForComponent(parent), true /* modal */);
121 this.help = help;
122 setTitle(title);
123 build();
124 addWindowListener(new WindowEventHandler());
125 setCoordinates(null);
126 }
127
128 public void setCoordinates(LatLon coordinates) {
129 if (coordinates == null) {
130 coordinates = new LatLon(0,0);
131 }
132 this.coordinates = coordinates;
133 tfLat.setText(coordinates.latToString(CoordinateFormat.DECIMAL_DEGREES));
134 tfLon.setText(coordinates.lonToString(CoordinateFormat.DECIMAL_DEGREES));
135 actOK.setEnabled(true);
136 }
137
138 public LatLon getCoordinates() {
139 return coordinates;
140 }
141
142 protected void setErrorFeedback(JTextField tf, String message) {
143 tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
144 tf.setToolTipText(message);
145 tf.setBackground(BG_COLOR_ERROR);
146 }
147
148 protected void clearErrorFeedback(JTextField tf, String message) {
149 tf.setBorder(UIManager.getBorder("TextField.border"));
150 tf.setToolTipText(message);
151 tf.setBackground(UIManager.getColor("TextField.background"));
152 }
153
154 protected Double parseDoubleFromUserInput(String input) {
155 if (input == null) return null;
156 // remove white space and an optional degree symbol
157 //
158 input = input.trim();
159 input = input.replaceAll("\u00B0", ""); // the degree symbol
160
161 // try to parse using the current locale
162 //
163 NumberFormat f = NumberFormat.getNumberInstance();
164 Number n=null;
165 ParsePosition pp = new ParsePosition(0);
166 n = f.parse(input,pp);
167 if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length()) {
168 // fall back - try to parse with the english locale
169 //
170 pp = new ParsePosition(0);
171 f = NumberFormat.getNumberInstance(Locale.ENGLISH);
172 n = f.parse(input, pp);
173 if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length())
174 return null;
175 }
176 return n== null ? null : n.doubleValue();
177 }
178
179 protected Double parseLatFromUserInput() {
180 Double d = parseDoubleFromUserInput(tfLat.getText());
181 if (d == null || ! LatLon.isValidLat(d)) {
182 setErrorFeedback(tfLat, tr("Please enter a valid latitude in the range -90..90"));
183 return null;
184 } else {
185 clearErrorFeedback(tfLat,tr("Please enter a latitude in the range -90..90"));
186 }
187 return d;
188 }
189
190 protected Double parseLonFromUserInput() {
191 Double d = parseDoubleFromUserInput(tfLon.getText());
192 if (d == null || ! LatLon.isValidLon(d)) {
193 setErrorFeedback(tfLon, tr("Please enter a valid longitude in the range -180..180"));
194 return null;
195 } else {
196 clearErrorFeedback(tfLon,tr("Please enter a longitude in the range -180..180"));
197 }
198 return d;
199 }
200
201 protected void parseUserInput() {
202 Double lat = parseLatFromUserInput();
203 Double lon = parseLonFromUserInput();
204 if (lat == null || lon == null) {
205 coordinates = null;
206 actOK.setEnabled(false);
207 } else {
208 coordinates = new LatLon(lat,lon);
209 actOK.setEnabled(true);
210 }
211 }
212
213 public boolean isCanceled() {
214 return canceled;
215 }
216
217 protected void setCanceled(boolean canceled) {
218 this.canceled = canceled;
219 }
220
221 @Override
222 public void setVisible(boolean visible) {
223 if (visible) {
224 setCanceled(false);
225 WindowGeometry.centerInWindow(Main.parent, getSize()).applySafe(this);
226 }
227 super.setVisible(visible);
228 }
229
230 class OKAction extends AbstractAction {
231 public OKAction() {
232 putValue(NAME, tr("OK"));
233 putValue(SHORT_DESCRIPTION, tr("Close the dialog and create a new node"));
234 putValue(SMALL_ICON, ImageProvider.get("ok"));
235 }
236
237 public void actionPerformed(ActionEvent e) {
238 setCanceled(false);
239 setVisible(false);
240 }
241 }
242
243 class CancelAction extends AbstractAction {
244 public CancelAction() {
245 putValue(NAME, tr("Cancel"));
246 putValue(SHORT_DESCRIPTION, tr("Close the dialog, do not create a new node"));
247 putValue(SMALL_ICON, ImageProvider.get("cancel"));
248 }
249
250 public void actionPerformed(ActionEvent e) {
251 setCanceled(true);
252 setVisible(false);
253 }
254 }
255
256 class LatLonInputVerifier implements DocumentListener {
257 public void changedUpdate(DocumentEvent e) {
258 parseUserInput();
259 }
260
261 public void insertUpdate(DocumentEvent e) {
262 parseUserInput();
263 }
264
265 public void removeUpdate(DocumentEvent e) {
266 parseUserInput();
267 }
268 }
269
270 static class TextFieldFocusHandler implements FocusListener {
271 public void focusGained(FocusEvent e) {
272 Component c = e.getComponent();
273 if (c instanceof JTextField) {
274 JTextField tf = (JTextField)c;
275 tf.selectAll();
276 }
277 }
278 public void focusLost(FocusEvent e) {}
279 }
280
281 class WindowEventHandler extends WindowAdapter {
282 @Override
283 public void windowClosing(WindowEvent e) {
284 setCanceled(true);
285 setVisible(false);
286 }
287
288 @Override
289 public void windowOpened(WindowEvent e) {
290 tfLat.requestFocusInWindow();
291 }
292 }
293
294}
Note: See TracBrowser for help on using the repository browser.