source: josm/trunk/src/org/openstreetmap/josm/actions/AddNodeAction.java@ 2702

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

Fixed some of the warnings found by FindBugs

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