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 woth 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 JLabel lbl = new JLabel(); 104 lbl.setIcon(ImageProvider.get("oauth", "oauth-logo")); 105 lbl.setOpaque(true); 106 pnl.add(lbl, gc); 107 108 // OAuth in a nutshell ... 109 gc.gridy = 1; 110 gc.insets = new Insets(5,0,0,5); 111 HtmlPanel pnlMessage = new HtmlPanel(); 112 pnlMessage.setText("<html><body>" 113 + tr("With OAuth you grant JOSM the right to upload map data and GPS tracks " 114 + "on your behalf (<a href=\"{0}\">more info...</a>).", "http://oauth.net/") 115 + "</body></html>" 116 ); 117 pnlMessage.getEditorPane().addHyperlinkListener(new ExternalBrowserLauncher()); 118 pnl.add(pnlMessage, gc); 119 120 // the authorisation procedure 121 gc.gridy = 2; 122 gc.gridwidth = 1; 123 gc.weightx = 0.0; 124 lbl = new JLabel(tr("Please select an authorization procedure: ")); 125 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); 126 pnl.add(lbl,gc); 127 128 gc.gridx = 1; 129 gc.gridwidth = 1; 130 gc.weightx = 1.0; 131 pnl.add(cbAuthorisationProcedure = new AuthorizationProcedureComboBox(),gc); 132 cbAuthorisationProcedure.addItemListener(new AuthorisationProcedureChangeListener()); 133 return pnl; 134 } 135 136 /** 137 * Refreshes the view of the authorisation panel, depending on the authorisation procedure 138 * currently selected 139 */ 140 protected void refreshAuthorisationProcedurePanel() { 141 AuthorizationProcedure procedure = (AuthorizationProcedure)cbAuthorisationProcedure.getSelectedItem(); 142 switch(procedure) { 143 case FULLY_AUTOMATIC: 144 spAuthorisationProcedureUI.getViewport().setView(pnlFullyAutomaticAuthorisationUI); 145 pnlFullyAutomaticAuthorisationUI.revalidate(); 146 break; 147 case SEMI_AUTOMATIC: 148 spAuthorisationProcedureUI.getViewport().setView(pnlSemiAutomaticAuthorisationUI); 149 pnlSemiAutomaticAuthorisationUI.revalidate(); 150 break; 151 case MANUALLY: 152 spAuthorisationProcedureUI.getViewport().setView(pnlManualAuthorisationUI); 153 pnlManualAuthorisationUI.revalidate(); 154 break; 155 } 156 validate(); 157 repaint(); 158 } 159 160 /** 161 * builds the UI 162 */ 163 protected final void build() { 164 getContentPane().setLayout(new BorderLayout()); 165 getContentPane().add(buildHeaderInfoPanel(), BorderLayout.NORTH); 166 167 setTitle(tr("Get an Access Token for ''{0}''", apiUrl)); 168 169 pnlFullyAutomaticAuthorisationUI = new FullyAutomaticAuthorizationUI(apiUrl); 170 pnlSemiAutomaticAuthorisationUI = new SemiAutomaticAuthorizationUI(apiUrl); 171 pnlManualAuthorisationUI = new ManualAuthorizationUI(apiUrl); 172 173 spAuthorisationProcedureUI = GuiHelper.embedInVerticalScrollPane(new JPanel()); 174 spAuthorisationProcedureUI.getVerticalScrollBar().addComponentListener( 175 new ComponentListener() { 176 @Override 177 public void componentShown(ComponentEvent e) { 178 spAuthorisationProcedureUI.setBorder(UIManager.getBorder("ScrollPane.border")); 179 } 180 181 @Override 182 public void componentHidden(ComponentEvent e) { 183 spAuthorisationProcedureUI.setBorder(null); 184 } 185 186 @Override 187 public void componentResized(ComponentEvent e) {} 188 @Override 189 public void componentMoved(ComponentEvent e) {} 190 } 191 ); 192 getContentPane().add(spAuthorisationProcedureUI, BorderLayout.CENTER); 193 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 194 195 addWindowListener(new WindowEventHandler()); 196 getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); 197 getRootPane().getActionMap().put("cancel", new CancelAction()); 198 199 refreshAuthorisationProcedurePanel(); 200 201 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/OAuthAuthorisationWizard")); 202 } 203 204 /** 205 * Creates the wizard. 206 * 207 * @param apiUrl the API URL. Must not be null. 208 * @throws IllegalArgumentException thrown if apiUrl is null 209 */ 210 public OAuthAuthorizationWizard(String apiUrl) throws IllegalArgumentException { 211 this(Main.parent, apiUrl); 212 } 213 214 /** 215 * Creates the wizard. 216 * 217 * @param parent the component relative to which the dialog is displayed 218 * @param apiUrl the API URL. Must not be null. 219 * @throws IllegalArgumentException thrown if apiUrl is null 220 */ 221 public OAuthAuthorizationWizard(Component parent, String apiUrl) { 222 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 223 CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); 224 this.apiUrl = apiUrl; 225 build(); 226 } 227 228 /** 229 * Replies true if the dialog was canceled 230 * 231 * @return true if the dialog was canceled 232 */ 233 public boolean isCanceled() { 234 return canceled; 235 } 236 237 protected AbstractAuthorizationUI getCurrentAuthorisationUI() { 238 switch((AuthorizationProcedure)cbAuthorisationProcedure.getSelectedItem()) { 239 case FULLY_AUTOMATIC: return pnlFullyAutomaticAuthorisationUI; 240 case MANUALLY: return pnlManualAuthorisationUI; 241 case SEMI_AUTOMATIC: return pnlSemiAutomaticAuthorisationUI; 242 default: return null; 243 } 244 } 245 246 /** 247 * Replies the Access Token entered using the wizard 248 * 249 * @return the access token. May be null if the wizard was canceled. 250 */ 251 public OAuthToken getAccessToken() { 252 return getCurrentAuthorisationUI().getAccessToken(); 253 } 254 255 /** 256 * Replies the current OAuth parameters. 257 * 258 * @return the current OAuth parameters. 259 */ 260 public OAuthParameters getOAuthParameters() { 261 return getCurrentAuthorisationUI().getOAuthParameters(); 262 } 263 264 /** 265 * Replies true if the currently selected Access Token shall be saved to 266 * the preferences. 267 * 268 * @return true if the currently selected Access Token shall be saved to 269 * the preferences 270 */ 271 public boolean isSaveAccessTokenToPreferences() { 272 return getCurrentAuthorisationUI().isSaveAccessTokenToPreferences(); 273 } 274 275 /** 276 * Initializes the dialog with values from the preferences 277 * 278 */ 279 public void initFromPreferences() { 280 // Copy current JOSM preferences to update API url with the one used in this wizard 281 Preferences copyPref = CustomConfigurator.clonePreferences(Main.pref); 282 copyPref.put("osm-server.url", apiUrl); 283 pnlFullyAutomaticAuthorisationUI.initFromPreferences(copyPref); 284 pnlSemiAutomaticAuthorisationUI.initFromPreferences(copyPref); 285 pnlManualAuthorisationUI.initFromPreferences(copyPref); 286 } 287 288 @Override 289 public void setVisible(boolean visible) { 290 if (visible) { 291 new WindowGeometry( 292 getClass().getName() + ".geometry", 293 WindowGeometry.centerInWindow( 294 Main.parent, 295 new Dimension(450,540) 296 ) 297 ).applySafe(this); 298 initFromPreferences(); 299 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 300 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 301 } 302 super.setVisible(visible); 303 } 304 305 protected void setCanceled(boolean canceled) { 306 this.canceled = canceled; 307 } 308 309 class AuthorisationProcedureChangeListener implements ItemListener { 310 @Override 311 public void itemStateChanged(ItemEvent arg0) { 312 refreshAuthorisationProcedurePanel(); 313 } 314 } 315 316 class CancelAction extends AbstractAction { 317 public CancelAction() { 318 putValue(NAME, tr("Cancel")); 319 putValue(SMALL_ICON, ImageProvider.get("cancel")); 320 putValue(SHORT_DESCRIPTION, tr("Close the dialog and cancel authorization")); 321 } 322 323 public void cancel() { 324 setCanceled(true); 325 setVisible(false); 326 } 327 328 @Override 329 public void actionPerformed(ActionEvent evt) { 330 cancel(); 331 } 332 } 333 334 class AcceptAccessTokenAction extends AbstractAction implements PropertyChangeListener { 335 private OAuthToken token; 336 337 public AcceptAccessTokenAction() { 338 putValue(NAME, tr("Accept Access Token")); 339 putValue(SMALL_ICON, ImageProvider.get("ok")); 340 putValue(SHORT_DESCRIPTION, tr("Close the dialog and accept the Access Token")); 341 updateEnabledState(null); 342 } 343 344 @Override 345 public void actionPerformed(ActionEvent evt) { 346 setCanceled(false); 347 setVisible(false); 348 } 349 350 public final void updateEnabledState(OAuthToken token) { 351 setEnabled(token != null); 352 } 353 354 @Override 355 public void propertyChange(PropertyChangeEvent evt) { 356 if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP)) 357 return; 358 token = (OAuthToken)evt.getNewValue(); 359 updateEnabledState(token); 360 } 361 } 362 363 class WindowEventHandler extends WindowAdapter { 364 @Override 365 public void windowClosing(WindowEvent arg0) { 366 new CancelAction().cancel(); 367 } 368 } 369 370 static class ExternalBrowserLauncher implements HyperlinkListener { 371 @Override 372 public void hyperlinkUpdate(HyperlinkEvent e) { 373 if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { 374 OpenBrowser.displayUrl(e.getDescription()); 375 } 376 } 377 } 378}