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