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