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}