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

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

fixed #4082: Upload-Dialog shows in diseabled display
Replaced WindowGeometry.apply() by WindowGeometry.applySafe(), as suggested by bomm

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