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 minlat = Double.parseDouble(tfLatLon[0].getText().trim());
110 minlon = Double.parseDouble(tfLatLon[1].getText().trim());
111 maxlat = Double.parseDouble(tfLatLon[2].getText().trim());
112 maxlon = 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 }