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}