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 }