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 = null;
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    public BoundingBoxSelectionPanel() {
081        build();
082    }
083
084    public void setBoundingBox(Bounds area) {
085        updateBboxFields(area);
086    }
087
088    public Bounds getBoundingBox() {
089        double minlon, minlat, maxlon,maxlat;
090        try {
091            minlat = Double.parseDouble(tfLatLon[0].getText().trim());
092            minlon = Double.parseDouble(tfLatLon[1].getText().trim());
093            maxlat = Double.parseDouble(tfLatLon[2].getText().trim());
094            maxlon = Double.parseDouble(tfLatLon[3].getText().trim());
095        } catch(NumberFormatException e) {
096            return null;
097        }
098        if (!LatLon.isValidLon(minlon) || !LatLon.isValidLon(maxlon)
099                || !LatLon.isValidLat(minlat) || ! LatLon.isValidLat(maxlat))
100            return null;
101        if (minlon > maxlon)
102            return null;
103        if (minlat > maxlat)
104            return null;
105        return new Bounds(minlon,minlat,maxlon,maxlat);
106    }
107
108    private boolean parseURL() {
109        Bounds b = OsmUrlToBounds.parse(tfOsmUrl.getText());
110        if(b == null) return false;
111        updateBboxFields(b);
112        return true;
113    }
114
115    private void updateBboxFields(Bounds area) {
116        if (area == null) return;
117        tfLatLon[0].setText(area.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES));
118        tfLatLon[1].setText(area.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES));
119        tfLatLon[2].setText(area.getMax().latToString(CoordinateFormat.DECIMAL_DEGREES));
120        tfLatLon[3].setText(area.getMax().lonToString(CoordinateFormat.DECIMAL_DEGREES));
121    }
122
123    private static class LatitudeValidator extends AbstractTextComponentValidator {
124
125        public static void decorate(JTextComponent tc) {
126            new LatitudeValidator(tc);
127        }
128
129        public LatitudeValidator(JTextComponent tc) {
130            super(tc);
131        }
132
133        @Override
134        public void validate() {
135            double value = 0;
136            try {
137                value = Double.parseDouble(getComponent().getText());
138            } catch(NumberFormatException ex) {
139                feedbackInvalid(tr("The string ''{0}'' is not a valid double value.", getComponent().getText()));
140                return;
141            }
142            if (!LatLon.isValidLat(value)) {
143                feedbackInvalid(tr("Value for latitude in range [-90,90] required.", getComponent().getText()));
144                return;
145            }
146            feedbackValid("");
147        }
148
149        @Override
150        public boolean isValid() {
151            double value = 0;
152            try {
153                value = Double.parseDouble(getComponent().getText());
154            } catch(NumberFormatException ex) {
155                return false;
156            }
157            if (!LatLon.isValidLat(value))
158                return false;
159            return true;
160        }
161    }
162
163    private static class LongitudeValidator extends AbstractTextComponentValidator{
164
165        public static void decorate(JTextComponent tc) {
166            new LongitudeValidator(tc);
167        }
168
169        public LongitudeValidator(JTextComponent tc) {
170            super(tc);
171        }
172
173        @Override
174        public void validate() {
175            double value = 0;
176            try {
177                value = Double.parseDouble(getComponent().getText());
178            } catch(NumberFormatException ex) {
179                feedbackInvalid(tr("The string ''{0}'' is not a valid double value.", getComponent().getText()));
180                return;
181            }
182            if (!LatLon.isValidLon(value)) {
183                feedbackInvalid(tr("Value for longitude in range [-180,180] required.", getComponent().getText()));
184                return;
185            }
186            feedbackValid("");
187        }
188
189        @Override
190        public boolean isValid() {
191            double value = 0;
192            try {
193                value = Double.parseDouble(getComponent().getText());
194            } catch(NumberFormatException ex) {
195                return false;
196            }
197            if (!LatLon.isValidLon(value))
198                return false;
199            return true;
200        }
201    }
202
203    class OsmUrlRefresher implements DocumentListener {
204        @Override
205        public void changedUpdate(DocumentEvent e) { parseURL(); }
206        @Override
207        public void insertUpdate(DocumentEvent e) { parseURL(); }
208        @Override
209        public void removeUpdate(DocumentEvent e) { parseURL(); }
210    }
211}