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}