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

Last change on this file since 2602 was 2548, checked in by Gubaer, 14 years ago

fixed #3064: Importing images into the "Downloaded GPX Data" layer gives a silly error message
Fixed help topic names in AddNodeAction

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