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