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 ApiUrlValidator valOsmServerUrl;
053    private SideButton btnTest;
054    /** indicates whether to use the default OSM URL or not */
055    private JCheckBox cbUseDefaultServerUrl;
056
057    private 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        SelectAllOnFocusGainedDecorator.decorate(tfOsmServerUrl);
091        valOsmServerUrl = new ApiUrlValidator(tfOsmServerUrl);
092        valOsmServerUrl.validate();
093        propagator = new ApiUrlPropagator();
094        tfOsmServerUrl.addActionListener(propagator);
095        tfOsmServerUrl.addFocusListener(propagator);
096
097        gc.gridx = 2;
098        gc.weightx = 0.0;
099        add(lblValid = new JLabel(), gc);
100
101        gc.gridx = 3;
102        gc.weightx = 0.0;
103        ValidateApiUrlAction actTest = new ValidateApiUrlAction();
104        tfOsmServerUrl.getDocument().addDocumentListener(actTest);
105        add(btnTest = new SideButton(actTest), gc);
106    }
107
108    /**
109     * Constructs a new {@code OsmApiUrlInputPanel}.
110     */
111    public OsmApiUrlInputPanel() {
112        build();
113        HelpUtil.setHelpContext(this, HelpUtil.ht("/Preferences/Connection#ApiUrl"));
114    }
115
116    /**
117     * Initializes the configuration panel with values from the preferences
118     */
119    public void initFromPreferences() {
120        String url =  Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL);
121        if (url.trim().equals(OsmApi.DEFAULT_API_URL)) {
122            cbUseDefaultServerUrl.setSelected(true);
123            propagator.propagate(OsmApi.DEFAULT_API_URL);
124        } else {
125            cbUseDefaultServerUrl.setSelected(false);
126            tfOsmServerUrl.setText(url);
127            propagator.propagate(url);
128        }
129    }
130
131    /**
132     * Saves the values to the preferences
133     */
134    public void saveToPreferences() {
135        String oldUrl = Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL);
136        String hmiUrl = getStrippedApiUrl();
137        if (cbUseDefaultServerUrl.isSelected()) {
138            Main.pref.put("osm-server.url", null);
139        } else if (hmiUrl.equals(OsmApi.DEFAULT_API_URL)) {
140            Main.pref.put("osm-server.url", null);
141        } else {
142            Main.pref.put("osm-server.url", hmiUrl);
143        }
144        String newUrl = Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL);
145
146        // When API URL changes, re-initialize API connection so we may adjust
147        // server-dependent settings.
148        if (!oldUrl.equals(newUrl)) {
149            try {
150                OsmApi.getOsmApi().initialize(null);
151            } catch (Exception x) {
152                Main.warn(x);
153            }
154        }
155    }
156
157    /**
158     * Returns the entered API URL, stripped of leading and trailing white characters.
159     * @return the entered API URL, stripped of leading and trailing white characters.
160     *         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}.
161     * @see Utils#strip(String)
162     * @since 6602
163     */
164    public final String getStrippedApiUrl() {
165        return Utils.strip(tfOsmServerUrl.getText());
166    }
167
168    class ValidateApiUrlAction extends AbstractAction implements DocumentListener {
169        private String lastTestedUrl = null;
170
171        public ValidateApiUrlAction() {
172            putValue(NAME, tr("Validate"));
173            putValue(SHORT_DESCRIPTION, tr("Test the API URL"));
174            updateEnabledState();
175        }
176
177        @Override
178        public void actionPerformed(ActionEvent arg0) {
179            final String url = getStrippedApiUrl();
180            final ApiUrlTestTask task = new ApiUrlTestTask(OsmApiUrlInputPanel.this, url);
181            Main.worker.submit(task);
182            Runnable r = new Runnable() {
183                @Override
184                public void run() {
185                    if (task.isCanceled())
186                        return;
187                    Runnable r = new Runnable() {
188                        @Override
189                        public void run() {
190                            if (task.isSuccess()) {
191                                lblValid.setIcon(ImageProvider.get("dialogs", "valid"));
192                                lblValid.setToolTipText(tr("The API URL is valid."));
193                                lastTestedUrl = url;
194                                updateEnabledState();
195                            } else {
196                                lblValid.setIcon(ImageProvider.get("warning-small"));
197                                lblValid.setToolTipText(tr("Validation failed. The API URL seems to be invalid."));
198                            }
199                        }
200                    };
201                    SwingUtilities.invokeLater(r);
202                }
203            };
204            Main.worker.submit(r);
205        }
206
207        protected final void updateEnabledState() {
208            String url = getStrippedApiUrl();
209            boolean enabled = !url.isEmpty() && !url.equals(lastTestedUrl);
210            if (enabled) {
211                lblValid.setIcon(null);
212            }
213            setEnabled(enabled);
214        }
215
216        @Override
217        public void changedUpdate(DocumentEvent arg0) {
218            updateEnabledState();
219        }
220
221        @Override
222        public void insertUpdate(DocumentEvent arg0) {
223            updateEnabledState();
224        }
225
226        @Override
227        public void removeUpdate(DocumentEvent arg0) {
228            updateEnabledState();
229        }
230    }
231
232    /**
233     * Enables or disables the API URL input.
234     * @param enabled {@code true} to enable input, {@code false} otherwise
235     */
236    public void setApiUrlInputEnabled(boolean enabled) {
237        lblApiUrl.setEnabled(enabled);
238        tfOsmServerUrl.setEnabled(enabled);
239        lblValid.setEnabled(enabled);
240        btnTest.setEnabled(enabled);
241    }
242
243    private static class ApiUrlValidator extends AbstractTextComponentValidator {
244        public ApiUrlValidator(JTextComponent tc) throws IllegalArgumentException {
245            super(tc);
246        }
247
248        @Override
249        public boolean isValid() {
250            if (getComponent().getText().trim().isEmpty())
251                return false;
252
253            try {
254                new URL(getComponent().getText().trim());
255                return true;
256            } catch(MalformedURLException e) {
257                return false;
258            }
259        }
260
261        @Override
262        public void validate() {
263            if (getComponent().getText().trim().isEmpty()) {
264                feedbackInvalid(tr("OSM API URL must not be empty. Please enter the OSM API URL."));
265                return;
266            }
267            if (!isValid()) {
268                feedbackInvalid(tr("The current value is not a valid URL"));
269            } else {
270                feedbackValid(tr("Please enter the OSM API URL."));
271            }
272        }
273    }
274
275    /**
276     * Handles changes in the default URL
277     */
278    class UseDefaultServerUrlChangeHandler implements ItemListener {
279        @Override
280        public void itemStateChanged(ItemEvent e) {
281            switch(e.getStateChange()) {
282            case ItemEvent.SELECTED:
283                setApiUrlInputEnabled(false);
284                propagator.propagate(OsmApi.DEFAULT_API_URL);
285                break;
286            case ItemEvent.DESELECTED:
287                setApiUrlInputEnabled(true);
288                valOsmServerUrl.validate();
289                tfOsmServerUrl.requestFocusInWindow();
290                propagator.propagate();
291                break;
292            }
293        }
294    }
295
296    class ApiUrlPropagator extends FocusAdapter implements ActionListener {
297        public void propagate() {
298            propagate(getStrippedApiUrl());
299        }
300
301        public void propagate(String url) {
302            firePropertyChange(API_URL_PROP, null, url);
303        }
304
305        @Override
306        public void actionPerformed(ActionEvent e) {
307            propagate();
308        }
309
310        @Override
311        public void focusLost(FocusEvent arg0) {
312            propagate();
313        }
314    }
315}