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