001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.oauth; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.FlowLayout; 010import java.awt.Font; 011import java.awt.GridBagConstraints; 012import java.awt.GridBagLayout; 013import java.awt.Insets; 014import java.awt.event.ActionEvent; 015import java.awt.event.ComponentEvent; 016import java.awt.event.ComponentListener; 017import java.awt.event.ItemEvent; 018import java.awt.event.ItemListener; 019import java.awt.event.KeyEvent; 020import java.awt.event.WindowAdapter; 021import java.awt.event.WindowEvent; 022import java.beans.PropertyChangeEvent; 023import java.beans.PropertyChangeListener; 024 025import javax.swing.AbstractAction; 026import javax.swing.BorderFactory; 027import javax.swing.JComponent; 028import javax.swing.JDialog; 029import javax.swing.JLabel; 030import javax.swing.JOptionPane; 031import javax.swing.JPanel; 032import javax.swing.JScrollPane; 033import javax.swing.KeyStroke; 034import javax.swing.UIManager; 035import javax.swing.event.HyperlinkEvent; 036import javax.swing.event.HyperlinkListener; 037 038import org.openstreetmap.josm.Main; 039import org.openstreetmap.josm.data.CustomConfigurator; 040import org.openstreetmap.josm.data.Preferences; 041import org.openstreetmap.josm.data.oauth.OAuthParameters; 042import org.openstreetmap.josm.data.oauth.OAuthToken; 043import org.openstreetmap.josm.gui.SideButton; 044import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 045import org.openstreetmap.josm.gui.help.HelpUtil; 046import org.openstreetmap.josm.gui.util.GuiHelper; 047import org.openstreetmap.josm.gui.widgets.HtmlPanel; 048import org.openstreetmap.josm.tools.CheckParameterUtil; 049import org.openstreetmap.josm.tools.ImageProvider; 050import org.openstreetmap.josm.tools.OpenBrowser; 051import org.openstreetmap.josm.tools.WindowGeometry; 052 053/** 054 * This wizard walks the user to the necessary steps to retrieve an OAuth Access Token which 055 * allows JOSM to access the OSM API on the users behalf. 056 * 057 */ 058public class OAuthAuthorizationWizard extends JDialog { 059 private boolean canceled; 060 private final String apiUrl; 061 062 private AuthorizationProcedureComboBox cbAuthorisationProcedure; 063 private FullyAutomaticAuthorizationUI pnlFullyAutomaticAuthorisationUI; 064 private SemiAutomaticAuthorizationUI pnlSemiAutomaticAuthorisationUI; 065 private ManualAuthorizationUI pnlManualAuthorisationUI; 066 private JScrollPane spAuthorisationProcedureUI; 067 068 /** 069 * Builds the row with the action buttons 070 * 071 * @return panel with buttons 072 */ 073 protected JPanel buildButtonRow() { 074 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER)); 075 076 AcceptAccessTokenAction actAcceptAccessToken = new AcceptAccessTokenAction(); 077 pnlFullyAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken); 078 pnlSemiAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken); 079 pnlManualAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken); 080 081 pnl.add(new SideButton(actAcceptAccessToken)); 082 pnl.add(new SideButton(new CancelAction())); 083 pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/OAuthAuthorisationWizard")))); 084 085 return pnl; 086 } 087 088 /** 089 * Builds the panel with general information in the header 090 * 091 * @return panel with information display 092 */ 093 protected JPanel buildHeaderInfoPanel() { 094 JPanel pnl = new JPanel(new GridBagLayout()); 095 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 096 GridBagConstraints gc = new GridBagConstraints(); 097 098 // the oauth logo in the header 099 gc.anchor = GridBagConstraints.NORTHWEST; 100 gc.fill = GridBagConstraints.HORIZONTAL; 101 gc.weightx = 1.0; 102 gc.gridwidth = 2; 103 ImageProvider logoProv = new ImageProvider("oauth", "oauth-logo"); 104 JLabel lbl = new JLabel(logoProv.get()); 105 lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 106 lbl.setOpaque(true); 107 pnl.add(lbl, gc); 108 109 // OAuth in a nutshell ... 110 gc.gridy = 1; 111 gc.insets = new Insets(5, 0, 0, 5); 112 HtmlPanel pnlMessage = new HtmlPanel(); 113 pnlMessage.setText("<html><body>" 114 + tr("With OAuth you grant JOSM the right to upload map data and GPS tracks " 115 + "on your behalf (<a href=\"{0}\">more info...</a>).", "http://oauth.net/") 116 + "</body></html>" 117 ); 118 pnlMessage.getEditorPane().addHyperlinkListener(new ExternalBrowserLauncher()); 119 pnl.add(pnlMessage, gc); 120 121 // the authorisation procedure 122 gc.gridy = 2; 123 gc.gridwidth = 1; 124 gc.weightx = 0.0; 125 lbl = new JLabel(tr("Please select an authorization procedure: ")); 126 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); 127 pnl.add(lbl, gc); 128 129 gc.gridx = 1; 130 gc.gridwidth = 1; 131 gc.weightx = 1.0; 132 pnl.add(cbAuthorisationProcedure = new AuthorizationProcedureComboBox(), gc); 133 cbAuthorisationProcedure.addItemListener(new AuthorisationProcedureChangeListener()); 134 lbl.setLabelFor(cbAuthorisationProcedure); 135 return pnl; 136 } 137 138 /** 139 * Refreshes the view of the authorisation panel, depending on the authorisation procedure 140 * currently selected 141 */ 142 protected void refreshAuthorisationProcedurePanel() { 143 AuthorizationProcedure procedure = (AuthorizationProcedure) cbAuthorisationProcedure.getSelectedItem(); 144 switch(procedure) { 145 case FULLY_AUTOMATIC: 146 spAuthorisationProcedureUI.getViewport().setView(pnlFullyAutomaticAuthorisationUI); 147 pnlFullyAutomaticAuthorisationUI.revalidate(); 148 break; 149 case SEMI_AUTOMATIC: 150 spAuthorisationProcedureUI.getViewport().setView(pnlSemiAutomaticAuthorisationUI); 151 pnlSemiAutomaticAuthorisationUI.revalidate(); 152 break; 153 case MANUALLY: 154 spAuthorisationProcedureUI.getViewport().setView(pnlManualAuthorisationUI); 155 pnlManualAuthorisationUI.revalidate(); 156 break; 157 } 158 validate(); 159 repaint(); 160 } 161 162 /** 163 * builds the UI 164 */ 165 protected final void build() { 166 getContentPane().setLayout(new BorderLayout()); 167 getContentPane().add(buildHeaderInfoPanel(), BorderLayout.NORTH); 168 169 setTitle(tr("Get an Access Token for ''{0}''", apiUrl)); 170 this.setMinimumSize(new Dimension(420, 400)); 171 172 pnlFullyAutomaticAuthorisationUI = new FullyAutomaticAuthorizationUI(apiUrl); 173 pnlSemiAutomaticAuthorisationUI = new SemiAutomaticAuthorizationUI(apiUrl); 174 pnlManualAuthorisationUI = new ManualAuthorizationUI(apiUrl); 175 176 spAuthorisationProcedureUI = GuiHelper.embedInVerticalScrollPane(new JPanel()); 177 spAuthorisationProcedureUI.getVerticalScrollBar().addComponentListener( 178 new ComponentListener() { 179 @Override 180 public void componentShown(ComponentEvent e) { 181 spAuthorisationProcedureUI.setBorder(UIManager.getBorder("ScrollPane.border")); 182 } 183 184 @Override 185 public void componentHidden(ComponentEvent e) { 186 spAuthorisationProcedureUI.setBorder(null); 187 } 188 189 @Override 190 public void componentResized(ComponentEvent e) {} 191 192 @Override 193 public void componentMoved(ComponentEvent e) {} 194 } 195 ); 196 getContentPane().add(spAuthorisationProcedureUI, BorderLayout.CENTER); 197 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 198 199 addWindowListener(new WindowEventHandler()); 200 getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); 201 getRootPane().getActionMap().put("cancel", new CancelAction()); 202 203 refreshAuthorisationProcedurePanel(); 204 205 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/OAuthAuthorisationWizard")); 206 } 207 208 /** 209 * Creates the wizard. 210 * 211 * @param apiUrl the API URL. Must not be null. 212 * @throws IllegalArgumentException if apiUrl is null 213 */ 214 public OAuthAuthorizationWizard(String apiUrl) { 215 this(Main.parent, apiUrl); 216 } 217 218 /** 219 * Creates the wizard. 220 * 221 * @param parent the component relative to which the dialog is displayed 222 * @param apiUrl the API URL. Must not be null. 223 * @throws IllegalArgumentException if apiUrl is null 224 */ 225 public OAuthAuthorizationWizard(Component parent, String apiUrl) { 226 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 227 CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); 228 this.apiUrl = apiUrl; 229 build(); 230 } 231 232 /** 233 * Replies true if the dialog was canceled 234 * 235 * @return true if the dialog was canceled 236 */ 237 public boolean isCanceled() { 238 return canceled; 239 } 240 241 protected AbstractAuthorizationUI getCurrentAuthorisationUI() { 242 switch((AuthorizationProcedure) cbAuthorisationProcedure.getSelectedItem()) { 243 case FULLY_AUTOMATIC: return pnlFullyAutomaticAuthorisationUI; 244 case MANUALLY: return pnlManualAuthorisationUI; 245 case SEMI_AUTOMATIC: return pnlSemiAutomaticAuthorisationUI; 246 default: return null; 247 } 248 } 249 250 /** 251 * Replies the Access Token entered using the wizard 252 * 253 * @return the access token. May be null if the wizard was canceled. 254 */ 255 public OAuthToken getAccessToken() { 256 return getCurrentAuthorisationUI().getAccessToken(); 257 } 258 259 /** 260 * Replies the current OAuth parameters. 261 * 262 * @return the current OAuth parameters. 263 */ 264 public OAuthParameters getOAuthParameters() { 265 return getCurrentAuthorisationUI().getOAuthParameters(); 266 } 267 268 /** 269 * Replies true if the currently selected Access Token shall be saved to 270 * the preferences. 271 * 272 * @return true if the currently selected Access Token shall be saved to 273 * the preferences 274 */ 275 public boolean isSaveAccessTokenToPreferences() { 276 return getCurrentAuthorisationUI().isSaveAccessTokenToPreferences(); 277 } 278 279 /** 280 * Initializes the dialog with values from the preferences 281 * 282 */ 283 public void initFromPreferences() { 284 // Copy current JOSM preferences to update API url with the one used in this wizard 285 Preferences copyPref = CustomConfigurator.clonePreferences(Main.pref); 286 copyPref.put("osm-server.url", apiUrl); 287 pnlFullyAutomaticAuthorisationUI.initFromPreferences(copyPref); 288 pnlSemiAutomaticAuthorisationUI.initFromPreferences(copyPref); 289 pnlManualAuthorisationUI.initFromPreferences(copyPref); 290 } 291 292 @Override 293 public void setVisible(boolean visible) { 294 if (visible) { 295 new WindowGeometry( 296 getClass().getName() + ".geometry", 297 WindowGeometry.centerInWindow( 298 Main.parent, 299 new Dimension(450, 540) 300 ) 301 ).applySafe(this); 302 initFromPreferences(); 303 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 304 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 305 } 306 super.setVisible(visible); 307 } 308 309 protected void setCanceled(boolean canceled) { 310 this.canceled = canceled; 311 } 312 313 class AuthorisationProcedureChangeListener implements ItemListener { 314 @Override 315 public void itemStateChanged(ItemEvent arg0) { 316 refreshAuthorisationProcedurePanel(); 317 } 318 } 319 320 class CancelAction extends AbstractAction { 321 322 /** 323 * Constructs a new {@code CancelAction}. 324 */ 325 CancelAction() { 326 putValue(NAME, tr("Cancel")); 327 putValue(SMALL_ICON, ImageProvider.get("cancel")); 328 putValue(SHORT_DESCRIPTION, tr("Close the dialog and cancel authorization")); 329 } 330 331 public void cancel() { 332 setCanceled(true); 333 setVisible(false); 334 } 335 336 @Override 337 public void actionPerformed(ActionEvent evt) { 338 cancel(); 339 } 340 } 341 342 class AcceptAccessTokenAction extends AbstractAction implements PropertyChangeListener { 343 344 /** 345 * Constructs a new {@code AcceptAccessTokenAction}. 346 */ 347 AcceptAccessTokenAction() { 348 putValue(NAME, tr("Accept Access Token")); 349 putValue(SMALL_ICON, ImageProvider.get("ok")); 350 putValue(SHORT_DESCRIPTION, tr("Close the dialog and accept the Access Token")); 351 updateEnabledState(null); 352 } 353 354 @Override 355 public void actionPerformed(ActionEvent evt) { 356 setCanceled(false); 357 setVisible(false); 358 } 359 360 public final void updateEnabledState(OAuthToken token) { 361 setEnabled(token != null); 362 } 363 364 @Override 365 public void propertyChange(PropertyChangeEvent evt) { 366 if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP)) 367 return; 368 updateEnabledState((OAuthToken) evt.getNewValue()); 369 } 370 } 371 372 class WindowEventHandler extends WindowAdapter { 373 @Override 374 public void windowClosing(WindowEvent e) { 375 new CancelAction().cancel(); 376 } 377 } 378 379 static class ExternalBrowserLauncher implements HyperlinkListener { 380 @Override 381 public void hyperlinkUpdate(HyperlinkEvent e) { 382 if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { 383 OpenBrowser.displayUrl(e.getDescription()); 384 } 385 } 386 } 387}