001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.gui.widgets; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Dimension; 007 import java.awt.GridBagConstraints; 008 import java.awt.GridBagLayout; 009 import java.awt.Insets; 010 import java.awt.Toolkit; 011 import java.awt.datatransfer.FlavorEvent; 012 import java.awt.datatransfer.FlavorListener; 013 import java.awt.event.ActionEvent; 014 import java.awt.event.MouseEvent; 015 016 import javax.swing.AbstractAction; 017 import javax.swing.BorderFactory; 018 import javax.swing.JLabel; 019 import javax.swing.JPanel; 020 import javax.swing.JPopupMenu; 021 import javax.swing.JTextField; 022 import javax.swing.event.DocumentEvent; 023 import javax.swing.event.DocumentListener; 024 import javax.swing.text.JTextComponent; 025 026 import org.openstreetmap.josm.data.Bounds; 027 import org.openstreetmap.josm.data.coor.CoordinateFormat; 028 import org.openstreetmap.josm.data.coor.LatLon; 029 import org.openstreetmap.josm.gui.JMultilineLabel; 030 import org.openstreetmap.josm.tools.GBC; 031 import org.openstreetmap.josm.tools.ImageProvider; 032 import org.openstreetmap.josm.tools.OsmUrlToBounds; 033 import org.openstreetmap.josm.tools.Utils; 034 035 /** 036 * 037 * 038 */ 039 public class BoundingBoxSelectionPanel extends JPanel { 040 041 private JTextField[] tfLatLon = null; 042 private final JTextField tfOsmUrl = new JTextField(); 043 044 protected void buildInputFields() { 045 tfLatLon = new JTextField[4]; 046 for(int i=0; i< 4; i++) { 047 tfLatLon[i] = new JTextField(11); 048 tfLatLon[i].setMinimumSize(new Dimension(100,new JTextField().getMinimumSize().height)); 049 SelectAllOnFocusGainedDecorator.decorate(tfLatLon[i]); 050 } 051 LatitudeValidator.decorate(tfLatLon[0]); 052 LatitudeValidator.decorate(tfLatLon[2]); 053 LongitudeValidator.decorate(tfLatLon[1]); 054 LongitudeValidator.decorate(tfLatLon[3]); 055 } 056 057 protected void build() { 058 buildInputFields(); 059 setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 060 setLayout(new GridBagLayout()); 061 tfOsmUrl.getDocument().addDocumentListener(new OsmUrlRefresher()); 062 063 // select content on receiving focus. this seems to be the default in the 064 // windows look+feel but not for others. needs invokeLater to avoid strange 065 // side effects that will cancel out the newly made selection otherwise. 066 tfOsmUrl.addFocusListener(new SelectAllOnFocusGainedDecorator()); 067 068 add(new JLabel(tr("Min. latitude")), GBC.std().insets(0,0,3,5)); 069 add(tfLatLon[0], GBC.std().insets(0,0,3,5)); 070 add(new JLabel(tr("Min. longitude")), GBC.std().insets(0,0,3,5)); 071 add(tfLatLon[1], GBC.eol()); 072 add(new JLabel(tr("Max. latitude")), GBC.std().insets(0,0,3,5)); 073 add(tfLatLon[2], GBC.std().insets(0,0,3,5)); 074 add(new JLabel(tr("Max. longitude")), GBC.std().insets(0,0,3,5)); 075 add(tfLatLon[3], GBC.eol()); 076 077 GridBagConstraints gc = new GridBagConstraints(); 078 gc.gridx = 0; 079 gc.gridy = 2; 080 gc.gridwidth = 4; 081 gc.fill = GridBagConstraints.HORIZONTAL; 082 gc.weightx = 1.0; 083 gc.insets = new Insets(10,0,0,3); 084 add(new JMultilineLabel(tr("URL from www.openstreetmap.org (you can paste a download URL here to specify a bounding box)")), gc); 085 086 gc.gridy = 3; 087 gc.insets = new Insets(3,0,0,3); 088 add(tfOsmUrl, gc); 089 tfOsmUrl.addMouseListener(new PopupMenuLauncher() { 090 @Override 091 public void launch(MouseEvent e) { 092 OsmUrlPopup popup = new OsmUrlPopup(); 093 popup.show(tfOsmUrl, e.getX(), e.getY()); 094 } 095 }); 096 } 097 098 public BoundingBoxSelectionPanel() { 099 build(); 100 } 101 102 public void setBoundingBox(Bounds area) { 103 updateBboxFields(area); 104 } 105 106 public Bounds getBoundingBox() { 107 double minlon, minlat, maxlon,maxlat; 108 try { 109 minlon = Double.parseDouble(tfLatLon[0].getText().trim()); 110 minlat = Double.parseDouble(tfLatLon[1].getText().trim()); 111 maxlon = Double.parseDouble(tfLatLon[2].getText().trim()); 112 maxlat = Double.parseDouble(tfLatLon[3].getText().trim()); 113 } catch(NumberFormatException e) { 114 return null; 115 } 116 if (!LatLon.isValidLon(minlon) || !LatLon.isValidLon(maxlon) 117 || !LatLon.isValidLat(minlat) || ! LatLon.isValidLat(maxlat)) 118 return null; 119 if (minlon > maxlon) 120 return null; 121 if (minlat > maxlat) 122 return null; 123 return new Bounds(minlon,minlat,maxlon,maxlat); 124 } 125 126 private boolean parseURL() { 127 Bounds b = OsmUrlToBounds.parse(tfOsmUrl.getText()); 128 if(b == null) return false; 129 updateBboxFields(b); 130 return true; 131 } 132 133 private void updateBboxFields(Bounds area) { 134 if (area == null) return; 135 tfLatLon[0].setText(area.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES)); 136 tfLatLon[1].setText(area.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES)); 137 tfLatLon[2].setText(area.getMax().latToString(CoordinateFormat.DECIMAL_DEGREES)); 138 tfLatLon[3].setText(area.getMax().lonToString(CoordinateFormat.DECIMAL_DEGREES)); 139 } 140 141 static private class LatitudeValidator extends AbstractTextComponentValidator { 142 143 public static void decorate(JTextComponent tc) { 144 new LatitudeValidator(tc); 145 } 146 147 public LatitudeValidator(JTextComponent tc) { 148 super(tc); 149 } 150 151 @Override 152 public void validate() { 153 double value = 0; 154 try { 155 value = Double.parseDouble(getComponent().getText()); 156 } catch(NumberFormatException ex) { 157 feedbackInvalid(tr("The string ''{0}'' is not a valid double value.", getComponent().getText())); 158 return; 159 } 160 if (!LatLon.isValidLat(value)) { 161 feedbackInvalid(tr("Value for latitude in range [-90,90] required.", getComponent().getText())); 162 return; 163 } 164 feedbackValid(""); 165 } 166 167 @Override 168 public boolean isValid() { 169 double value = 0; 170 try { 171 value = Double.parseDouble(getComponent().getText()); 172 } catch(NumberFormatException ex) { 173 return false; 174 } 175 if (!LatLon.isValidLat(value)) 176 return false; 177 return true; 178 } 179 } 180 181 static private class LongitudeValidator extends AbstractTextComponentValidator{ 182 183 public static void decorate(JTextComponent tc) { 184 new LongitudeValidator(tc); 185 } 186 187 public LongitudeValidator(JTextComponent tc) { 188 super(tc); 189 } 190 191 @Override 192 public void validate() { 193 double value = 0; 194 try { 195 value = Double.parseDouble(getComponent().getText()); 196 } catch(NumberFormatException ex) { 197 feedbackInvalid(tr("The string ''{0}'' is not a valid double value.", getComponent().getText())); 198 return; 199 } 200 if (!LatLon.isValidLon(value)) { 201 feedbackInvalid(tr("Value for longitude in range [-180,180] required.", getComponent().getText())); 202 return; 203 } 204 feedbackValid(""); 205 } 206 207 @Override 208 public boolean isValid() { 209 double value = 0; 210 try { 211 value = Double.parseDouble(getComponent().getText()); 212 } catch(NumberFormatException ex) { 213 return false; 214 } 215 if (!LatLon.isValidLon(value)) 216 return false; 217 return true; 218 } 219 } 220 221 class OsmUrlRefresher implements DocumentListener { 222 public void changedUpdate(DocumentEvent e) { parseURL(); } 223 public void insertUpdate(DocumentEvent e) { parseURL(); } 224 public void removeUpdate(DocumentEvent e) { parseURL(); } 225 } 226 227 class PasteUrlAction extends AbstractAction implements FlavorListener { 228 229 public PasteUrlAction() { 230 putValue(NAME, tr("Paste")); 231 putValue(SMALL_ICON, ImageProvider.get("paste")); 232 putValue(SHORT_DESCRIPTION, tr("Paste URL from clipboard")); 233 Toolkit.getDefaultToolkit().getSystemClipboard().addFlavorListener(this); 234 } 235 236 public void actionPerformed(ActionEvent e) { 237 String content = Utils.getClipboardContent(); 238 if (content != null) { 239 tfOsmUrl.setText(content); 240 } 241 } 242 243 protected void updateEnabledState() { 244 setEnabled(Utils.getClipboardContent() != null); 245 } 246 247 public void flavorsChanged(FlavorEvent e) { 248 updateEnabledState(); 249 } 250 } 251 252 class OsmUrlPopup extends JPopupMenu { 253 public OsmUrlPopup() { 254 add(new PasteUrlAction()); 255 } 256 } 257 }