001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.preferences.server;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Font;
007    import java.awt.GridBagConstraints;
008    import java.awt.GridBagLayout;
009    import java.awt.Insets;
010    import java.awt.event.ActionEvent;
011    import java.awt.event.ActionListener;
012    import java.awt.event.FocusAdapter;
013    import java.awt.event.FocusEvent;
014    import java.awt.event.ItemEvent;
015    import java.awt.event.ItemListener;
016    import java.net.MalformedURLException;
017    import java.net.URL;
018    
019    import javax.swing.AbstractAction;
020    import javax.swing.JCheckBox;
021    import javax.swing.JLabel;
022    import javax.swing.JPanel;
023    import javax.swing.JTextField;
024    import javax.swing.SwingUtilities;
025    import javax.swing.event.DocumentEvent;
026    import javax.swing.event.DocumentListener;
027    import javax.swing.text.JTextComponent;
028    
029    import org.openstreetmap.josm.Main;
030    import org.openstreetmap.josm.gui.SideButton;
031    import org.openstreetmap.josm.gui.help.HelpUtil;
032    import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
033    import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
034    import org.openstreetmap.josm.io.OsmApi;
035    import org.openstreetmap.josm.tools.ImageProvider;
036    
037    public 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 JTextField 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 JTextField(), 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        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", null);
121            if (url == null) {
122                cbUseDefaultServerUrl.setSelected(true);
123                firePropertyChange(API_URL_PROP, null, OsmApi.DEFAULT_API_URL);
124            } else if (url.trim().equals(OsmApi.DEFAULT_API_URL)) {
125                cbUseDefaultServerUrl.setSelected(true);
126                firePropertyChange(API_URL_PROP, null, OsmApi.DEFAULT_API_URL);
127            } else {
128                cbUseDefaultServerUrl.setSelected(false);
129                tfOsmServerUrl.setText(url);
130                firePropertyChange(API_URL_PROP, null, url);
131            }
132        }
133    
134        /**
135         * Saves the values to the preferences
136         */
137        public void saveToPreferences() {
138            String old_url = Main.pref.get("osm-server.url", null);
139            if (cbUseDefaultServerUrl.isSelected()) {
140                Main.pref.put("osm-server.url", null);
141            } else if (tfOsmServerUrl.getText().trim().equals(OsmApi.DEFAULT_API_URL)) {
142                Main.pref.put("osm-server.url", null);
143            } else {
144                Main.pref.put("osm-server.url", tfOsmServerUrl.getText().trim());
145            }
146            String new_url = Main.pref.get("osm-server.url", null);
147    
148            // When API URL changes, re-initialize API connection so we may adjust
149            // server-dependent settings.
150            if ((old_url == null && new_url != null) || (old_url != null && !old_url.equals(new_url))) {
151                try {
152                    OsmApi.getOsmApi().initialize(null);
153                } catch (Exception x) {
154                    // ignore;
155                }
156            }
157        }
158    
159        class ValidateApiUrlAction extends AbstractAction implements DocumentListener {
160            private String lastTestedUrl = null;
161    
162            public ValidateApiUrlAction() {
163                putValue(NAME, tr("Validate"));
164                putValue(SHORT_DESCRIPTION, tr("Test the API URL"));
165                updateEnabledState();
166            }
167    
168            public void actionPerformed(ActionEvent arg0) {
169                final String url = tfOsmServerUrl.getText().trim();
170                final ApiUrlTestTask task = new ApiUrlTestTask(OsmApiUrlInputPanel.this, url);
171                Main.worker.submit(task);
172                Runnable r = new Runnable() {
173                    public void run() {
174                        if (task.isCanceled())
175                            return;
176                        Runnable r = new Runnable() {
177                            public void run() {
178                                if (task.isSuccess()) {
179                                    lblValid.setIcon(ImageProvider.get("dialogs/changeset", "valid"));
180                                    lblValid.setToolTipText(tr("The API URL is valid."));
181                                    lastTestedUrl = url;
182                                    updateEnabledState();
183                                } else {
184                                    lblValid.setIcon(ImageProvider.get("warning-small"));
185                                    lblValid.setToolTipText(tr("Validation failed. The API URL seems to be invalid."));
186                                }
187                            }
188                        };
189                        SwingUtilities.invokeLater(r);
190                    }
191                };
192                Main.worker.submit(r);
193            }
194    
195            protected void updateEnabledState() {
196                boolean enabled =
197                    !tfOsmServerUrl.getText().trim().equals("")
198                    && !tfOsmServerUrl.getText().trim().equals(lastTestedUrl);
199                if (enabled) {
200                    lblValid.setIcon(null);
201                }
202                setEnabled(enabled);
203            }
204    
205            public void changedUpdate(DocumentEvent arg0) {
206                updateEnabledState();
207            }
208    
209            public void insertUpdate(DocumentEvent arg0) {
210                updateEnabledState();
211            }
212    
213            public void removeUpdate(DocumentEvent arg0) {
214                updateEnabledState();
215            }
216        }
217    
218        public void setApiUrlInputEnabled(boolean enabled) {
219            lblApiUrl.setEnabled(enabled);
220            tfOsmServerUrl.setEnabled(enabled);
221            lblValid.setEnabled(enabled);
222            btnTest.setEnabled(enabled);
223        }
224    
225        static private class ApiUrlValidator extends AbstractTextComponentValidator {
226            public ApiUrlValidator(JTextComponent tc) throws IllegalArgumentException {
227                super(tc);
228            }
229    
230            @Override
231            public boolean isValid() {
232                if (getComponent().getText().trim().equals(""))
233                    return false;
234    
235                try {
236                    new URL(getComponent().getText().trim());
237                    return true;
238                } catch(MalformedURLException e) {
239                    return false;
240                }
241            }
242    
243            @Override
244            public void validate() {
245                if (getComponent().getText().trim().equals("")) {
246                    feedbackInvalid(tr("OSM API URL must not be empty. Please enter the OSM API URL."));
247                    return;
248                }
249                if (!isValid()) {
250                    feedbackInvalid(tr("The current value is not a valid URL"));
251                } else {
252                    feedbackValid(tr("Please enter the OSM API URL."));
253                }
254            }
255        }
256    
257        /**
258         * Handles changes in the default URL
259         */
260        class UseDefaultServerUrlChangeHandler implements ItemListener {
261            public void itemStateChanged(ItemEvent e) {
262                switch(e.getStateChange()) {
263                case ItemEvent.SELECTED:
264                    setApiUrlInputEnabled(false);
265                    firePropertyChange(API_URL_PROP, null, OsmApi.DEFAULT_API_URL);
266                    break;
267                case ItemEvent.DESELECTED:
268                    setApiUrlInputEnabled(true);
269                    valOsmServerUrl.validate();
270                    tfOsmServerUrl.requestFocusInWindow();
271                    firePropertyChange(API_URL_PROP, null, tfOsmServerUrl.getText());
272                    break;
273                }
274            }
275        }
276    
277        class ApiUrlPropagator extends FocusAdapter implements ActionListener {
278            public void propagate() {
279                firePropertyChange(API_URL_PROP, null, tfOsmServerUrl.getText());
280            }
281    
282            public void actionPerformed(ActionEvent e) {
283                propagate();
284            }
285    
286            @Override
287            public void focusLost(FocusEvent arg0) {
288                propagate();
289            }
290        }
291    }