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.gui.widgets.JosmTextField;
026import org.openstreetmap.josm.io.ChangesetQuery;
027import org.openstreetmap.josm.io.ChangesetQuery.ChangesetQueryUrlException;
028import org.openstreetmap.josm.io.OsmApi;
029import org.openstreetmap.josm.tools.ImageProvider;
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        String apiUrl = OsmApi.getOsmApi().getBaseUrl();
069        HtmlPanel pnl = new HtmlPanel();
070        pnl.setText(
071                "<html><body>"
072                + tr("Please enter or paste an URL to retrieve changesets from the OSM API.")
073                + "<p><strong>" + tr("Examples") + "</strong></p>"
074                + "<ul>"
075                + "<li><a href=\""+Main.getOSMWebsite()+"/history?open=true\">"+Main.getOSMWebsite()+"/history?open=true</a></li>"
076                + "<li><a href=\""+apiUrl+"/changesets?open=true\">"+apiUrl+"/changesets?open=true</a></li>"
077                + "</ul>"
078                + tr("Note that changeset queries are currently always submitted to ''{0}'', regardless of the "
079                        + "host, port and path of the URL entered below.", apiUrl)
080                        + "</body></html>"
081        );
082        pnl.getEditorPane().addHyperlinkListener(
083                new HyperlinkListener() {
084                    @Override
085                    public void hyperlinkUpdate(HyperlinkEvent e) {
086                        if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
087                            tfUrl.setText(e.getDescription());
088                            tfUrl.requestFocusInWindow();
089                        }
090                    }
091                }
092        );
093        return pnl;
094    }
095
096    protected final void build() {
097        setLayout(new GridBagLayout());
098        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
099
100        GridBagConstraints gc = new GridBagConstraints();
101        gc.weightx = 1.0;
102        gc.fill = GridBagConstraints.HORIZONTAL;
103        gc.insets = new Insets(0, 0, 10, 0);
104        add(buildHelpPanel(), gc);
105
106        gc.gridy = 1;
107        gc.weightx = 1.0;
108        gc.fill = GridBagConstraints.HORIZONTAL;
109        add(buildURLPanel(), gc);
110
111        gc.gridy = 2;
112        gc.weightx = 1.0;
113        gc.weighty = 1.0;
114        gc.fill = GridBagConstraints.BOTH;
115        add(new JPanel(), gc);
116    }
117
118    /**
119     * Constructs a new {@code UrlBasedQueryPanel}.
120     */
121    public UrlBasedQueryPanel() {
122        build();
123    }
124
125    protected boolean isValidChangesetQueryUrl(String text) {
126        return buildChangesetQuery(text) != null;
127    }
128
129    protected ChangesetQuery buildChangesetQuery(String text) {
130        URL url = null;
131        try {
132            url = new URL(text);
133        } catch (MalformedURLException e) {
134            return null;
135        }
136        String path = url.getPath();
137        String query = url.getQuery();
138        if (path == null || !path.endsWith("/changesets")) return null;
139
140        try {
141            return ChangesetQuery.buildFromUrlQuery(query);
142        } catch (ChangesetQueryUrlException e) {
143            Main.warn(e.getMessage());
144            return null;
145        }
146    }
147
148    /**
149     * Replies the {@link ChangesetQuery} specified in this panel. null, if no valid changeset query
150     * is specified.
151     *
152     * @return the changeset query
153     */
154    public ChangesetQuery buildChangesetQuery() {
155        String value = tfUrl.getText().trim();
156        return buildChangesetQuery(value);
157    }
158
159    public void startUserInput() {
160        tfUrl.requestFocusInWindow();
161    }
162
163    /**
164     * Validates text entered in the changeset query URL field on the fly
165     */
166    class ChangetQueryUrlValidator implements DocumentListener {
167        protected String getCurrentFeedback() {
168            String fb = (String) lblValid.getClientProperty("valid");
169            return fb == null ? "none" : fb;
170        }
171
172        protected void feedbackValid() {
173            if ("valid".equals(getCurrentFeedback())) return;
174            lblValid.setIcon(ImageProvider.get("dialogs", "valid"));
175            lblValid.setToolTipText(null);
176            lblValid.putClientProperty("valid", "valid");
177        }
178
179        protected void feedbackInvalid() {
180            if ("invalid".equals(getCurrentFeedback())) return;
181            lblValid.setIcon(ImageProvider.get("warning-small"));
182            lblValid.setToolTipText(tr("This changeset query URL is invalid"));
183            lblValid.putClientProperty("valid", "invalid");
184        }
185
186        protected void feedbackNone() {
187            lblValid.setIcon(null);
188            lblValid.putClientProperty("valid", "none");
189        }
190
191        protected void validate() {
192            String value = tfUrl.getText();
193            if (value.trim().isEmpty()) {
194                feedbackNone();
195                return;
196            }
197            value = value.trim();
198            if (isValidChangesetQueryUrl(value)) {
199                feedbackValid();
200            } else {
201                feedbackInvalid();
202            }
203        }
204
205        @Override
206        public void changedUpdate(DocumentEvent e) {
207            validate();
208        }
209
210        @Override
211        public void insertUpdate(DocumentEvent e) {
212            validate();
213        }
214
215        @Override
216        public void removeUpdate(DocumentEvent e) {
217            validate();
218        }
219    }
220}