001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.server;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Font;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.Insets;
010import java.awt.event.ActionEvent;
011import java.awt.event.ActionListener;
012import java.awt.event.FocusAdapter;
013import java.awt.event.FocusEvent;
014import java.awt.event.ItemEvent;
015import java.awt.event.ItemListener;
016import java.net.MalformedURLException;
017import java.net.URL;
018
019import javax.swing.AbstractAction;
020import javax.swing.JCheckBox;
021import javax.swing.JComponent;
022import javax.swing.JLabel;
023import javax.swing.JPanel;
024import javax.swing.SwingUtilities;
025import javax.swing.event.DocumentEvent;
026import javax.swing.event.DocumentListener;
027import javax.swing.text.JTextComponent;
028
029import org.openstreetmap.josm.Main;
030import org.openstreetmap.josm.gui.SideButton;
031import org.openstreetmap.josm.gui.help.HelpUtil;
032import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
033import org.openstreetmap.josm.gui.widgets.JosmTextField;
034import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
035import org.openstreetmap.josm.io.OsmApi;
036import org.openstreetmap.josm.tools.ImageProvider;
037import org.openstreetmap.josm.tools.Utils;
038
039/**
040 * Component allowing input os OSM API URL.
041 */
042public class OsmApiUrlInputPanel extends JPanel {
043
044    /**
045     * OSM API URL property key.
046     */
047    public static final String API_URL_PROP = OsmApiUrlInputPanel.class.getName() + ".apiUrl";
048
049    private JLabel lblValid;
050    private JLabel lblApiUrl;
051    private JosmTextField tfOsmServerUrl;
052    private transient ApiUrlValidator valOsmServerUrl;
053    private SideButton btnTest;
054    /** indicates whether to use the default OSM URL or not */
055    private JCheckBox cbUseDefaultServerUrl;
056
057    private transient ApiUrlPropagator propagator;
058
059    protected JComponent buildDefaultServerUrlPanel() {
060        cbUseDefaultServerUrl = new JCheckBox(tr("<html>Use the default OSM server URL (<strong>{0}</strong>)</html>", OsmApi.DEFAULT_API_URL));
061        cbUseDefaultServerUrl.addItemListener(new UseDefaultServerUrlChangeHandler());
062        cbUseDefaultServerUrl.setFont(cbUseDefaultServerUrl.getFont().deriveFont(Font.PLAIN));
063        return cbUseDefaultServerUrl;
064    }
065
066    protected final void build() {
067        setLayout(new GridBagLayout());
068        GridBagConstraints gc = new GridBagConstraints();
069
070        // the checkbox for the default UL
071        gc.fill = GridBagConstraints.HORIZONTAL;
072        gc.anchor = GridBagConstraints.NORTHWEST;
073        gc.weightx = 1.0;
074        gc.insets = new Insets(0, 0, 0, 0);
075        gc.gridwidth  = 4;
076        add(buildDefaultServerUrlPanel(), gc);
077
078
079        // the input field for the URL
080        gc.gridx = 0;
081        gc.gridy = 1;
082        gc.gridwidth = 1;
083        gc.weightx = 0.0;
084        gc.insets = new Insets(0, 0, 0, 3);
085        add(lblApiUrl = new JLabel(tr("OSM Server URL:")), gc);
086
087        gc.gridx = 1;
088        gc.weightx = 1.0;
089        add(tfOsmServerUrl = new JosmTextField(), gc);
090        lblApiUrl.setLabelFor(tfOsmServerUrl);
091        SelectAllOnFocusGainedDecorator.decorate(tfOsmServerUrl);
092        valOsmServerUrl = new ApiUrlValidator(tfOsmServerUrl);
093        valOsmServerUrl.validate();
094        propagator = new ApiUrlPropagator();
095        tfOsmServerUrl.addActionListener(propagator);
096        tfOsmServerUrl.addFocusListener(propagator);
097
098        gc.gridx = 2;
099        gc.weightx = 0.0;
100        add(lblValid = new JLabel(), gc);
101
102        gc.gridx = 3;
103        gc.weightx = 0.0;
104        ValidateApiUrlAction actTest = new ValidateApiUrlAction();
105        tfOsmServerUrl.getDocument().addDocumentListener(actTest);
106        add(btnTest = new SideButton(actTest), gc);
107    }
108
109    /**
110     * Constructs a new {@code OsmApiUrlInputPanel}.
111     */
112    public OsmApiUrlInputPanel() {
113        build();
114        HelpUtil.setHelpContext(this, HelpUtil.ht("/Preferences/Connection#ApiUrl"));
115    }
116
117    /**
118     * Initializes the configuration panel with values from the preferences
119     */
120    public void initFromPreferences() {
121        String url =  Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL);
122        if (OsmApi.DEFAULT_API_URL.equals(url.trim())) {
123            cbUseDefaultServerUrl.setSelected(true);
124            propagator.propagate(OsmApi.DEFAULT_API_URL);
125        } else {
126            cbUseDefaultServerUrl.setSelected(false);
127            tfOsmServerUrl.setText(url);
128            propagator.propagate(url);
129        }
130    }
131
132    /**
133     * Saves the values to the preferences
134     */
135    public void saveToPreferences() {
136        String oldUrl = Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL);
137        String hmiUrl = getStrippedApiUrl();
138        if (cbUseDefaultServerUrl.isSelected()) {
139            Main.pref.put("osm-server.url", null);
140        } else if (OsmApi.DEFAULT_API_URL.equals(hmiUrl)) {
141            Main.pref.put("osm-server.url", null);
142        } else {
143            Main.pref.put("osm-server.url", hmiUrl);
144        }
145        String newUrl = Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL);
146
147        // When API URL changes, re-initialize API connection so we may adjust
148        // server-dependent settings.
149        if (!oldUrl.equals(newUrl)) {
150            try {
151                OsmApi.getOsmApi().initialize(null);
152            } catch (Exception x) {
153                Main.warn(x);
154            }
155        }
156    }
157
158    /**
159     * Returns the entered API URL, stripped of leading and trailing white characters.
160     * @return the entered API URL, stripped of leading and trailing white characters.
161     *         May be an empty string if nothing has been entered. In this case, it means the user wants to use {@link OsmApi#DEFAULT_API_URL}.
162     * @see Utils#strip(String)
163     * @since 6602
164     */
165    public final String getStrippedApiUrl() {
166        return Utils.strip(tfOsmServerUrl.getText());
167    }
168
169    class ValidateApiUrlAction extends AbstractAction implements DocumentListener {
170        private String lastTestedUrl;
171
172        ValidateApiUrlAction() {
173            putValue(NAME, tr("Validate"));
174            putValue(SHORT_DESCRIPTION, tr("Test the API URL"));
175            updateEnabledState();
176        }
177
178        @Override
179        public void actionPerformed(ActionEvent arg0) {
180            final String url = getStrippedApiUrl();
181            final ApiUrlTestTask task = new ApiUrlTestTask(OsmApiUrlInputPanel.this, url);
182            Main.worker.submit(task);
183            Runnable r = new Runnable() {
184                @Override
185                public void run() {
186                    if (task.isCanceled())
187                        return;
188                    Runnable r = new Runnable() {
189                        @Override
190                        public void run() {
191                            if (task.isSuccess()) {
192                                lblValid.setIcon(ImageProvider.get("dialogs", "valid"));
193                                lblValid.setToolTipText(tr("The API URL is valid."));
194                                lastTestedUrl = url;
195                                updateEnabledState();
196                            } else {
197                                lblValid.setIcon(ImageProvider.get("warning-small"));
198                                lblValid.setToolTipText(tr("Validation failed. The API URL seems to be invalid."));
199                            }
200                        }
201                    };
202                    SwingUtilities.invokeLater(r);
203                }
204            };
205            Main.worker.submit(r);
206        }
207
208        protected final void updateEnabledState() {
209            String url = getStrippedApiUrl();
210            boolean enabled = !url.isEmpty() && !url.equals(lastTestedUrl);
211            if (enabled) {
212                lblValid.setIcon(null);
213            }
214            setEnabled(enabled);
215        }
216
217        @Override
218        public void changedUpdate(DocumentEvent arg0) {
219            updateEnabledState();
220        }
221
222        @Override
223        public void insertUpdate(DocumentEvent arg0) {
224            updateEnabledState();
225        }
226
227        @Override
228        public void removeUpdate(DocumentEvent arg0) {
229            updateEnabledState();
230        }
231    }
232
233    /**
234     * Enables or disables the API URL input.
235     * @param enabled {@code true} to enable input, {@code false} otherwise
236     */
237    public void setApiUrlInputEnabled(boolean enabled) {
238        lblApiUrl.setEnabled(enabled);
239        tfOsmServerUrl.setEnabled(enabled);
240        lblValid.setEnabled(enabled);
241        btnTest.setEnabled(enabled);
242    }
243
244    private static class ApiUrlValidator extends AbstractTextComponentValidator {
245        ApiUrlValidator(JTextComponent tc) {
246            super(tc);
247        }
248
249        @Override
250        public boolean isValid() {
251            if (getComponent().getText().trim().isEmpty())
252                return false;
253
254            try {
255                new URL(getComponent().getText().trim());
256                return true;
257            } catch (MalformedURLException e) {
258                return false;
259            }
260        }
261
262        @Override
263        public void validate() {
264            if (getComponent().getText().trim().isEmpty()) {
265                feedbackInvalid(tr("OSM API URL must not be empty. Please enter the OSM API URL."));
266                return;
267            }
268            if (!isValid()) {
269                feedbackInvalid(tr("The current value is not a valid URL"));
270            } else {
271                feedbackValid(tr("Please enter the OSM API URL."));
272            }
273        }
274    }
275
276    /**
277     * Handles changes in the default URL
278     */
279    class UseDefaultServerUrlChangeHandler implements ItemListener {
280        @Override
281        public void itemStateChanged(ItemEvent e) {
282            switch(e.getStateChange()) {
283            case ItemEvent.SELECTED:
284                setApiUrlInputEnabled(false);
285                propagator.propagate(OsmApi.DEFAULT_API_URL);
286                break;
287            case ItemEvent.DESELECTED:
288                setApiUrlInputEnabled(true);
289                valOsmServerUrl.validate();
290                tfOsmServerUrl.requestFocusInWindow();
291                propagator.propagate();
292                break;
293            }
294        }
295    }
296
297    class ApiUrlPropagator extends FocusAdapter implements ActionListener {
298        public void propagate() {
299            propagate(getStrippedApiUrl());
300        }
301
302        public void propagate(String url) {
303            firePropertyChange(API_URL_PROP, null, url);
304        }
305
306        @Override
307        public void actionPerformed(ActionEvent e) {
308            propagate();
309        }
310
311        @Override
312        public void focusLost(FocusEvent arg0) {
313            propagate();
314        }
315    }
316}