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