001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.oauth; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.BorderLayout; 007 import java.awt.Component; 008 import java.awt.Dimension; 009 import java.awt.FlowLayout; 010 import java.awt.Font; 011 import java.awt.GridBagConstraints; 012 import java.awt.GridBagLayout; 013 import java.awt.Insets; 014 import java.awt.event.ActionEvent; 015 import java.awt.event.ComponentEvent; 016 import java.awt.event.ComponentListener; 017 import java.awt.event.ItemEvent; 018 import java.awt.event.ItemListener; 019 import java.awt.event.KeyEvent; 020 import java.awt.event.WindowAdapter; 021 import java.awt.event.WindowEvent; 022 import java.beans.PropertyChangeEvent; 023 import java.beans.PropertyChangeListener; 024 025 import javax.swing.AbstractAction; 026 import javax.swing.BorderFactory; 027 import javax.swing.JComponent; 028 import javax.swing.JDialog; 029 import javax.swing.JLabel; 030 import javax.swing.JOptionPane; 031 import javax.swing.JPanel; 032 import javax.swing.JScrollPane; 033 import javax.swing.KeyStroke; 034 import javax.swing.UIManager; 035 import javax.swing.event.HyperlinkEvent; 036 import javax.swing.event.HyperlinkListener; 037 038 import org.openstreetmap.josm.Main; 039 import org.openstreetmap.josm.data.CustomConfigurator; 040 import org.openstreetmap.josm.data.Preferences; 041 import org.openstreetmap.josm.data.oauth.OAuthParameters; 042 import org.openstreetmap.josm.data.oauth.OAuthToken; 043 import org.openstreetmap.josm.gui.SideButton; 044 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 045 import org.openstreetmap.josm.gui.help.HelpUtil; 046 import org.openstreetmap.josm.gui.widgets.HtmlPanel; 047 import org.openstreetmap.josm.tools.CheckParameterUtil; 048 import org.openstreetmap.josm.tools.ImageProvider; 049 import org.openstreetmap.josm.tools.OpenBrowser; 050 import org.openstreetmap.josm.tools.WindowGeometry; 051 052 /** 053 * This wizard walks the user to the necessary steps to retrieve an OAuth Access Token which 054 * allows JOSM to access the OSM API on the users behalf. 055 * 056 */ 057 public class OAuthAuthorizationWizard extends JDialog { 058 private HtmlPanel pnlMessage; 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 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 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 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 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 = new JScrollPane(new JPanel()); 174 spAuthorisationProcedureUI.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 175 spAuthorisationProcedureUI.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 176 spAuthorisationProcedureUI.getVerticalScrollBar().addComponentListener( 177 new ComponentListener() { 178 public void componentShown(ComponentEvent e) { 179 spAuthorisationProcedureUI.setBorder(UIManager.getBorder("ScrollPane.border")); 180 } 181 182 public void componentHidden(ComponentEvent e) { 183 spAuthorisationProcedureUI.setBorder(null); 184 } 185 186 public void componentResized(ComponentEvent e) {} 187 public void componentMoved(ComponentEvent e) {} 188 } 189 ); 190 getContentPane().add(spAuthorisationProcedureUI, BorderLayout.CENTER); 191 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 192 193 addWindowListener(new WindowEventHandler()); 194 getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); 195 getRootPane().getActionMap().put("cancel", new CancelAction()); 196 197 refreshAuthorisationProcedurePanel(); 198 199 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/OAuthAuthorisationWizard")); 200 } 201 202 /** 203 * Creates the wizard. 204 * 205 * @param apiUrl the API URL. Must not be null. 206 * @throws IllegalArgumentException thrown if apiUrl is null 207 */ 208 public OAuthAuthorizationWizard(String apiUrl) throws IllegalArgumentException { 209 this(Main.parent, apiUrl); 210 } 211 212 /** 213 * Creates the wizard. 214 * 215 * @param parent the component relative to which the dialog is displayed 216 * @param apiUrl the API URL. Must not be null. 217 * @throws IllegalArgumentException thrown if apiUrl is null 218 */ 219 public OAuthAuthorizationWizard(Component parent, String apiUrl) { 220 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 221 CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); 222 this.apiUrl = apiUrl; 223 build(); 224 } 225 226 /** 227 * Replies true if the dialog was canceled 228 * 229 * @return true if the dialog was canceled 230 */ 231 public boolean isCanceled() { 232 return canceled; 233 } 234 235 protected AbstractAuthorizationUI getCurrentAuthorisationUI() { 236 switch((AuthorizationProcedure)cbAuthorisationProcedure.getSelectedItem()) { 237 case FULLY_AUTOMATIC: return pnlFullyAutomaticAuthorisationUI; 238 case MANUALLY: return pnlManualAuthorisationUI; 239 case SEMI_AUTOMATIC: return pnlSemiAutomaticAuthorisationUI; 240 default: return null; 241 } 242 } 243 244 /** 245 * Replies the Access Token entered using the wizard 246 * 247 * @return the access token. May be null if the wizard was canceled. 248 */ 249 public OAuthToken getAccessToken() { 250 return getCurrentAuthorisationUI().getAccessToken(); 251 } 252 253 /** 254 * Replies the current OAuth parameters. 255 * 256 * @return the current OAuth parameters. 257 */ 258 public OAuthParameters getOAuthParameters() { 259 return getCurrentAuthorisationUI().getOAuthParameters(); 260 } 261 262 /** 263 * Replies true if the currently selected Access Token shall be saved to 264 * the preferences. 265 * 266 * @return true if the currently selected Access Token shall be saved to 267 * the preferences 268 */ 269 public boolean isSaveAccessTokenToPreferences() { 270 return getCurrentAuthorisationUI().isSaveAccessTokenToPreferences(); 271 } 272 273 /** 274 * Initializes the dialog with values from the preferences 275 * 276 */ 277 public void initFromPreferences() { 278 // Copy current JOSM preferences to update API url with the one used in this wizard 279 Preferences copyPref = CustomConfigurator.clonePreferences(Main.pref); 280 copyPref.put("osm-server-url", apiUrl); 281 pnlFullyAutomaticAuthorisationUI.initFromPreferences(copyPref); 282 pnlSemiAutomaticAuthorisationUI.initFromPreferences(copyPref); 283 pnlManualAuthorisationUI.initFromPreferences(copyPref); 284 } 285 286 @Override 287 public void setVisible(boolean visible) { 288 if (visible) { 289 new WindowGeometry( 290 getClass().getName() + ".geometry", 291 WindowGeometry.centerInWindow( 292 Main.parent, 293 new Dimension(450,540) 294 ) 295 ).applySafe(this); 296 initFromPreferences(); 297 } else if (!visible && isShowing()){ 298 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 299 } 300 super.setVisible(visible); 301 } 302 303 protected void setCanceled(boolean canceled) { 304 this.canceled = canceled; 305 } 306 307 class AuthorisationProcedureChangeListener implements ItemListener { 308 public void itemStateChanged(ItemEvent arg0) { 309 refreshAuthorisationProcedurePanel(); 310 } 311 } 312 313 class CancelAction extends AbstractAction { 314 public CancelAction() { 315 putValue(NAME, tr("Cancel")); 316 putValue(SMALL_ICON, ImageProvider.get("cancel")); 317 putValue(SHORT_DESCRIPTION, tr("Close the dialog and cancel authorization")); 318 } 319 320 public void cancel() { 321 setCanceled(true); 322 setVisible(false); 323 } 324 325 public void actionPerformed(ActionEvent evt) { 326 cancel(); 327 } 328 } 329 330 class AcceptAccessTokenAction extends AbstractAction implements PropertyChangeListener { 331 private OAuthToken token; 332 333 public AcceptAccessTokenAction() { 334 putValue(NAME, tr("Accept Access Token")); 335 putValue(SMALL_ICON, ImageProvider.get("ok")); 336 putValue(SHORT_DESCRIPTION, tr("Close the dialog and accept the Access Token")); 337 updateEnabledState(null); 338 } 339 340 public void actionPerformed(ActionEvent evt) { 341 setCanceled(false); 342 setVisible(false); 343 } 344 345 public void updateEnabledState(OAuthToken token) { 346 setEnabled(token != null); 347 } 348 349 public void propertyChange(PropertyChangeEvent evt) { 350 if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP)) 351 return; 352 token = (OAuthToken)evt.getNewValue(); 353 updateEnabledState(token); 354 } 355 } 356 357 class WindowEventHandler extends WindowAdapter { 358 @Override 359 public void windowClosing(WindowEvent arg0) { 360 new CancelAction().cancel(); 361 } 362 } 363 364 static class ExternalBrowserLauncher implements HyperlinkListener { 365 public void hyperlinkUpdate(HyperlinkEvent e) { 366 if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { 367 OpenBrowser.displayUrl(e.getDescription()); 368 } 369 } 370 } 371 }