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