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