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