001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset.query;
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;
010import java.awt.event.FocusAdapter;
011import java.awt.event.FocusEvent;
012import java.net.MalformedURLException;
013import java.net.URL;
014
015import javax.swing.BorderFactory;
016import javax.swing.JLabel;
017import javax.swing.JPanel;
018import javax.swing.event.DocumentEvent;
019import javax.swing.event.DocumentListener;
020import javax.swing.event.HyperlinkEvent;
021import javax.swing.event.HyperlinkListener;
022
023import org.openstreetmap.josm.Main;
024import org.openstreetmap.josm.gui.widgets.HtmlPanel;
025import org.openstreetmap.josm.io.ChangesetQuery;
026import org.openstreetmap.josm.io.OsmApi;
027import org.openstreetmap.josm.io.ChangesetQuery.ChangesetQueryUrlException;
028import org.openstreetmap.josm.tools.ImageProvider;
029import org.openstreetmap.josm.gui.widgets.JosmTextField;
030
031
032public class UrlBasedQueryPanel extends JPanel {
033
034    private JosmTextField tfUrl;
035    private JLabel lblValid;
036
037    protected JPanel buildURLPanel() {
038        JPanel pnl = new JPanel(new GridBagLayout());
039        GridBagConstraints gc = new GridBagConstraints();
040        gc.weightx = 0.0;
041        gc.fill = GridBagConstraints.HORIZONTAL;
042        gc.insets  = new Insets(0,0,0,5);
043        pnl.add(new JLabel(tr("URL: ")), gc);
044
045        gc.gridx = 1;
046        gc.weightx = 1.0;
047        gc.fill = GridBagConstraints.HORIZONTAL;
048        pnl.add(tfUrl = new JosmTextField(), gc);
049        tfUrl.getDocument().addDocumentListener(new ChangetQueryUrlValidator());
050        tfUrl.addFocusListener(
051                new FocusAdapter() {
052                    @Override
053                    public void focusGained(FocusEvent e) {
054                        tfUrl.selectAll();
055                    }
056                }
057        );
058
059        gc.gridx = 2;
060        gc.weightx = 0.0;
061        gc.fill = GridBagConstraints.HORIZONTAL;
062        pnl.add(lblValid = new JLabel(), gc);
063        lblValid.setPreferredSize(new Dimension(20,20));
064        return pnl;
065    }
066
067    protected JPanel buildHelpPanel() {
068        HtmlPanel pnl = new HtmlPanel();
069        pnl.setText(
070                "<html><body>"
071                + tr("Please enter or paste an URL to retrieve changesets from the OSM API.")
072                + "<p><strong>" + tr("Examples") + "</strong></p>"
073                + "<ul>"
074                + "<li><a href=\""+Main.OSM_WEBSITE+"/browse/changesets?open=true\">"+Main.OSM_WEBSITE+"/browse/changesets?open=true</a></li>"
075                + "<li><a href=\"http://api.openstreetmap.org/api/0.6/changesets?open=true\">http://api.openstreetmap.org/api/0.6/changesets?open=true</a></li>"
076                + "</ul>"
077                + tr("Note that changeset queries are currently always submitted to ''{0}'', regardless of the "
078                        + "host, port and path of the URL entered below.", OsmApi.getOsmApi().getBaseUrl())
079                        + "</body></html>"
080        );
081        pnl.getEditorPane().addHyperlinkListener(
082                new HyperlinkListener() {
083                    @Override
084                    public void hyperlinkUpdate(HyperlinkEvent e) {
085                        if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
086                            tfUrl.setText(e.getDescription());
087                            tfUrl.requestFocusInWindow();
088                        }
089                    }
090                }
091        );
092        return pnl;
093    }
094
095    protected void build() {
096        setLayout(new GridBagLayout());
097        setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
098
099        GridBagConstraints gc = new GridBagConstraints();
100        gc.weightx = 1.0;
101        gc.fill = GridBagConstraints.HORIZONTAL;
102        gc.insets = new Insets(0,0,10,0);
103        add(buildHelpPanel(),gc);
104
105        gc.gridy = 1;
106        gc.weightx = 1.0;
107        gc.fill = GridBagConstraints.HORIZONTAL;
108        add(buildURLPanel(),gc);
109
110        gc.gridy = 2;
111        gc.weightx = 1.0;
112        gc.weighty = 1.0;
113        gc.fill = GridBagConstraints.BOTH;
114        add(new JPanel(),gc);
115
116    }
117    public UrlBasedQueryPanel() {
118        build();
119    }
120
121    protected boolean isValidChangesetQueryUrl(String text) {
122        return buildChangesetQuery(text) != null;
123    }
124
125    protected ChangesetQuery buildChangesetQuery(String text) {
126        URL url = null;
127        try {
128            url = new URL(text);
129        } catch(MalformedURLException e) {
130            return null;
131        }
132        String path = url.getPath();
133        String query = url.getQuery();
134        if (path == null || ! path.endsWith("/changesets")) return null;
135
136        try {
137            return ChangesetQuery.buildFromUrlQuery(query);
138        } catch(ChangesetQueryUrlException e) {
139            return null;
140        }
141    }
142
143    /**
144     * Replies the {@link ChangesetQuery} specified in this panel. null, if no valid changeset query
145     * is specified.
146     *
147     * @return the changeset query
148     */
149    public ChangesetQuery buildChangesetQuery() {
150        String value = tfUrl.getText().trim();
151        return buildChangesetQuery(value);
152    }
153
154    public void startUserInput() {
155        tfUrl.requestFocusInWindow();
156    }
157
158    /**
159     * Validates text entered in the changeset query URL field on the fly
160     */
161    class ChangetQueryUrlValidator implements DocumentListener {
162        protected String getCurrentFeedback() {
163            String fb = (String)lblValid.getClientProperty("valid");
164            return fb == null ? "none" : fb;
165        }
166        protected void feedbackValid() {
167            if (getCurrentFeedback().equals("valid")) return;
168            lblValid.setIcon(ImageProvider.get("dialogs/changeset", "valid"));
169            lblValid.setToolTipText("");
170            lblValid.putClientProperty("valid", "valid");
171        }
172
173        protected void feedbackInvalid() {
174            if (getCurrentFeedback().equals("invalid")) return;
175            lblValid.setIcon(ImageProvider.get("warning-small"));
176            lblValid.setToolTipText(tr("This changeset query URL is invalid"));
177            lblValid.putClientProperty("valid", "invalid");
178        }
179
180        protected void feedbackNone() {
181            lblValid.setIcon(null);
182            lblValid.putClientProperty("valid", "none");
183        }
184
185        protected void validate() {
186            String value = tfUrl.getText();
187            if (value.trim().isEmpty()) {
188                feedbackNone();
189                return;
190            }
191            value = value.trim();
192            if (isValidChangesetQueryUrl(value)) {
193                feedbackValid();
194            } else {
195                feedbackInvalid();
196            }
197        }
198        @Override
199        public void changedUpdate(DocumentEvent e) {
200            validate();
201        }
202
203        @Override
204        public void insertUpdate(DocumentEvent e) {
205            validate();
206        }
207
208        @Override
209        public void removeUpdate(DocumentEvent e) {
210            validate();
211        }
212    }
213}