001 // License: GPL. Copyright 2007 by Immanuel Scholz and others
002 package org.openstreetmap.josm.gui.download;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.Color;
007 import java.awt.Dimension;
008 import java.awt.GridBagLayout;
009 import java.awt.Toolkit;
010 import java.awt.datatransfer.FlavorEvent;
011 import java.awt.datatransfer.FlavorListener;
012 import java.awt.event.ActionEvent;
013 import java.awt.event.ActionListener;
014 import java.awt.event.FocusAdapter;
015 import java.awt.event.FocusEvent;
016 import java.awt.event.MouseAdapter;
017 import java.awt.event.MouseEvent;
018
019 import javax.swing.AbstractAction;
020 import javax.swing.BorderFactory;
021 import javax.swing.JButton;
022 import javax.swing.JLabel;
023 import javax.swing.JPanel;
024 import javax.swing.JPopupMenu;
025 import javax.swing.JTextArea;
026 import javax.swing.JTextField;
027 import javax.swing.UIManager;
028 import javax.swing.border.Border;
029 import javax.swing.event.DocumentEvent;
030 import javax.swing.event.DocumentListener;
031 import javax.swing.text.JTextComponent;
032
033 import org.openstreetmap.josm.data.Bounds;
034 import org.openstreetmap.josm.data.coor.CoordinateFormat;
035 import org.openstreetmap.josm.data.coor.LatLon;
036 import org.openstreetmap.josm.tools.GBC;
037 import org.openstreetmap.josm.tools.ImageProvider;
038 import org.openstreetmap.josm.tools.OsmUrlToBounds;
039 import org.openstreetmap.josm.tools.Utils;
040
041 /**
042 * Bounding box selector.
043 *
044 * Provides max/min lat/lon input fields as well as the "URL from www.openstreetmap.org" text field.
045 *
046 * @author Frederik Ramm <frederik@remote.org>
047 *
048 */
049 public class BoundingBoxSelection implements DownloadSelection {
050
051 private JTextField[] latlon = null;
052 private final JTextArea tfOsmUrl = new JTextArea();
053 private final JTextArea showUrl = new JTextArea();
054 private DownloadDialog parent;
055
056 protected void registerBoundingBoxBuilder() {
057 BoundingBoxBuilder bboxbuilder = new BoundingBoxBuilder();
058 for (int i = 0;i < latlon.length; i++) {
059 latlon[i].addFocusListener(bboxbuilder);
060 latlon[i].addActionListener(bboxbuilder);
061 }
062 }
063
064 protected void buildDownloadAreaInputFields() {
065 latlon = new JTextField[4];
066 for(int i=0; i< 4; i++) {
067 latlon[i] = new JTextField(11);
068 latlon[i].setMinimumSize(new Dimension(100,new JTextField().getMinimumSize().height));
069 latlon[i].addFocusListener(new SelectAllOnFocusHandler(latlon[i]));
070 }
071 LatValueChecker latChecker = new LatValueChecker(latlon[0]);
072 latlon[0].addFocusListener(latChecker);
073 latlon[0].addActionListener(latChecker);
074
075 latChecker = new LatValueChecker(latlon[2]);
076 latlon[2].addFocusListener(latChecker);
077 latlon[2].addActionListener(latChecker);
078
079 LonValueChecker lonChecker = new LonValueChecker(latlon[1]);
080 latlon[1].addFocusListener(lonChecker);
081 latlon[1].addActionListener(lonChecker);
082
083 lonChecker = new LonValueChecker(latlon[3]);
084 latlon[3].addFocusListener(lonChecker);
085 latlon[3].addActionListener(lonChecker);
086
087 registerBoundingBoxBuilder();
088 }
089
090 public void addGui(final DownloadDialog gui) {
091 buildDownloadAreaInputFields();
092 final JPanel dlg = new JPanel(new GridBagLayout());
093
094 tfOsmUrl.getDocument().addDocumentListener(new OsmUrlRefresher());
095
096 // select content on receiving focus. this seems to be the default in the
097 // windows look+feel but not for others. needs invokeLater to avoid strange
098 // side effects that will cancel out the newly made selection otherwise.
099 tfOsmUrl.addFocusListener(new SelectAllOnFocusHandler(tfOsmUrl));
100 tfOsmUrl.setLineWrap(true);
101 tfOsmUrl.setBorder(latlon[0].getBorder());
102
103 dlg.add(new JLabel(tr("min lat")), GBC.std().insets(10,20,5,0));
104 dlg.add(latlon[0], GBC.std().insets(0,20,0,0));
105 dlg.add(new JLabel(tr("min lon")), GBC.std().insets(10,20,5,0));
106 dlg.add(latlon[1], GBC.eol().insets(0,20,0,0));
107 dlg.add(new JLabel(tr("max lat")), GBC.std().insets(10,0,5,0));
108 dlg.add(latlon[2], GBC.std());
109 dlg.add(new JLabel(tr("max lon")), GBC.std().insets(10,0,5,0));
110 dlg.add(latlon[3], GBC.eol());
111
112 final JButton btnClear = new JButton(tr("Clear textarea"));
113 btnClear.addMouseListener(new MouseAdapter() {
114 @Override
115 public void mouseClicked(MouseEvent arg0) {
116 tfOsmUrl.setText("");
117 }
118 });
119 dlg.add(btnClear, GBC.eol().insets(10,20,0,0));
120
121 dlg.add(new JLabel(tr("URL from www.openstreetmap.org (you can paste an URL here to download the area)")), GBC.eol().insets(10,5,5,0));
122 dlg.add(tfOsmUrl, GBC.eop().insets(10,0,5,0).fill());
123 tfOsmUrl.addMouseListener(
124 new MouseAdapter() {
125 @Override
126 public void mousePressed(MouseEvent e) {
127 checkPopup(e);
128 }
129
130 @Override
131 public void mouseClicked(MouseEvent e) {
132 checkPopup(e);
133 }
134
135 @Override
136 public void mouseReleased(MouseEvent e) {
137 checkPopup(e);
138 }
139
140 private void checkPopup(MouseEvent e) {
141 if (e.isPopupTrigger()) {
142 OsmUrlPopup popup = new OsmUrlPopup();
143 popup.show(tfOsmUrl, e.getX(), e.getY());
144 }
145 }
146 }
147 );
148 dlg.add(showUrl, GBC.eop().insets(10,0,5,5));
149 showUrl.setEditable(false);
150 showUrl.setBackground(dlg.getBackground());
151 showUrl.addFocusListener(new SelectAllOnFocusHandler(showUrl));
152
153 gui.addDownloadAreaSelector(dlg, tr("Bounding Box"));
154 this.parent = gui;
155 }
156
157 public void setDownloadArea(Bounds area) {
158 updateBboxFields(area);
159 updateUrl(area);
160 }
161
162 public Bounds getDownloadArea() {
163 double[] values = new double[4];
164 for (int i=0; i < 4; i++) {
165 try {
166 values[i] = Double.parseDouble(latlon[i].getText());
167 } catch(NumberFormatException x) {
168 return null;
169 }
170 }
171 if (!LatLon.isValidLat(values[0]) || !LatLon.isValidLon(values[1]))
172 return null;
173 if (!LatLon.isValidLat(values[2]) || !LatLon.isValidLon(values[3]))
174 return null;
175 return new Bounds(values);
176 }
177
178 private boolean parseURL(DownloadDialog gui) {
179 Bounds b = OsmUrlToBounds.parse(tfOsmUrl.getText());
180 if(b == null) return false;
181 gui.boundingBoxChanged(b,BoundingBoxSelection.this);
182 updateBboxFields(b);
183 updateUrl(b);
184 return true;
185 }
186
187 private void updateBboxFields(Bounds area) {
188 if (area == null) return;
189 latlon[0].setText(area.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES));
190 latlon[1].setText(area.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES));
191 latlon[2].setText(area.getMax().latToString(CoordinateFormat.DECIMAL_DEGREES));
192 latlon[3].setText(area.getMax().lonToString(CoordinateFormat.DECIMAL_DEGREES));
193 for (JTextField tf: latlon) {
194 resetErrorMessage(tf);
195 }
196 }
197
198 private void updateUrl(Bounds area) {
199 if (area == null) return;
200 showUrl.setText(OsmUrlToBounds.getURL(area));
201 }
202
203 private Border errorBorder = BorderFactory.createLineBorder(Color.RED, 1);
204
205 protected void setErrorMessage(JTextField tf, String msg) {
206 tf.setBorder(errorBorder);
207 tf.setToolTipText(msg);
208 }
209
210 protected void resetErrorMessage(JTextField tf) {
211 tf.setBorder(UIManager.getBorder("TextField.border"));
212 tf.setToolTipText("");
213 }
214
215 class LatValueChecker extends FocusAdapter implements ActionListener{
216 private JTextField tfLatValue;
217
218 public LatValueChecker(JTextField tfLatValue) {
219 this.tfLatValue = tfLatValue;
220 }
221
222 protected void check() {
223 double value = 0;
224 try {
225 value = Double.parseDouble(tfLatValue.getText());
226 } catch(NumberFormatException ex) {
227 setErrorMessage(tfLatValue,tr("The string ''{0}'' is not a valid double value.", tfLatValue.getText()));
228 return;
229 }
230 if (!LatLon.isValidLat(value)) {
231 setErrorMessage(tfLatValue,tr("Value for latitude in range [-90,90] required.", tfLatValue.getText()));
232 return;
233 }
234 resetErrorMessage(tfLatValue);
235 }
236
237 @Override
238 public void focusLost(FocusEvent e) {
239 check();
240 }
241
242 public void actionPerformed(ActionEvent e) {
243 check();
244 }
245 }
246
247 class LonValueChecker extends FocusAdapter implements ActionListener {
248 private JTextField tfLonValue;
249
250 public LonValueChecker(JTextField tfLonValue) {
251 this.tfLonValue = tfLonValue;
252 }
253
254 protected void check() {
255 double value = 0;
256 try {
257 value = Double.parseDouble(tfLonValue.getText());
258 } catch(NumberFormatException ex) {
259 setErrorMessage(tfLonValue,tr("The string ''{0}'' is not a valid double value.", tfLonValue.getText()));
260 return;
261 }
262 if (!LatLon.isValidLon(value)) {
263 setErrorMessage(tfLonValue,tr("Value for longitude in range [-180,180] required.", tfLonValue.getText()));
264 return;
265 }
266 resetErrorMessage(tfLonValue);
267 }
268
269 @Override
270 public void focusLost(FocusEvent e) {
271 check();
272 }
273
274 public void actionPerformed(ActionEvent e) {
275 check();
276 }
277 }
278
279 static class SelectAllOnFocusHandler extends FocusAdapter {
280 private JTextComponent tfTarget;
281 public SelectAllOnFocusHandler(JTextComponent tfTarget) {
282 this.tfTarget = tfTarget;
283 }
284
285 @Override
286 public void focusGained(FocusEvent e) {
287 tfTarget.selectAll();
288 }
289 }
290
291 class OsmUrlRefresher implements DocumentListener {
292 public void changedUpdate(DocumentEvent e) { parseURL(parent); }
293 public void insertUpdate(DocumentEvent e) { parseURL(parent); }
294 public void removeUpdate(DocumentEvent e) { parseURL(parent); }
295 }
296
297 class PasteUrlAction extends AbstractAction implements FlavorListener {
298
299 public PasteUrlAction() {
300 putValue(NAME, tr("Paste"));
301 putValue(SMALL_ICON, ImageProvider.get("paste"));
302 putValue(SHORT_DESCRIPTION, tr("Paste URL from clipboard"));
303 Toolkit.getDefaultToolkit().getSystemClipboard().addFlavorListener(this);
304 }
305
306 public void actionPerformed(ActionEvent e) {
307 String content = Utils.getClipboardContent();
308 if (content != null) {
309 tfOsmUrl.setText(content);
310 }
311 }
312
313 protected void updateEnabledState() {
314 setEnabled(Utils.getClipboardContent() != null);
315 }
316
317 public void flavorsChanged(FlavorEvent e) {
318 updateEnabledState();
319 }
320 }
321
322 class OsmUrlPopup extends JPopupMenu {
323 public OsmUrlPopup() {
324 add(new PasteUrlAction());
325 }
326 }
327
328 class BoundingBoxBuilder extends FocusAdapter implements ActionListener {
329 protected Bounds build() {
330 double minlon, minlat, maxlon,maxlat;
331 try {
332 minlat = Double.parseDouble(latlon[0].getText().trim());
333 minlon = Double.parseDouble(latlon[1].getText().trim());
334 maxlat = Double.parseDouble(latlon[2].getText().trim());
335 maxlon = Double.parseDouble(latlon[3].getText().trim());
336 } catch(NumberFormatException e) {
337 return null;
338 }
339 if (!LatLon.isValidLon(minlon) || !LatLon.isValidLon(maxlon)
340 || !LatLon.isValidLat(minlat) || ! LatLon.isValidLat(maxlat))
341 return null;
342 if (minlon > maxlon)
343 return null;
344 if (minlat > maxlat)
345 return null;
346 return new Bounds(minlat,minlon,maxlat,maxlon);
347 }
348
349 protected void refreshBounds() {
350 Bounds b = build();
351 parent.boundingBoxChanged(b, BoundingBoxSelection.this);
352 }
353
354 @Override
355 public void focusLost(FocusEvent e) {
356 refreshBounds();
357 }
358
359 public void actionPerformed(ActionEvent e) {
360 refreshBounds();
361 }
362 }
363 }