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