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