source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java@ 13250

Last change on this file since 13250 was 13111, checked in by Don-vip, 7 years ago

fix #11217, fix #12623 - major rework of notes tooltips:

  • display clickable links
  • allow to copy text from notes, including comments
  • Property svn:eol-style set to native
File size: 15.8 KB
RevLine 
[2801]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.oauth;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.Font;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Insets;
14import java.awt.event.ActionEvent;
[10173]15import java.awt.event.ComponentAdapter;
[2801]16import java.awt.event.ComponentEvent;
17import java.awt.event.ItemEvent;
18import java.awt.event.ItemListener;
19import java.awt.event.WindowAdapter;
20import java.awt.event.WindowEvent;
21import java.beans.PropertyChangeEvent;
22import java.beans.PropertyChangeListener;
[12803]23import java.lang.reflect.InvocationTargetException;
24import java.net.URL;
[9352]25import java.util.concurrent.Executor;
[12803]26import java.util.concurrent.FutureTask;
[2801]27
28import javax.swing.AbstractAction;
29import javax.swing.BorderFactory;
[10367]30import javax.swing.JButton;
[2801]31import javax.swing.JDialog;
32import javax.swing.JLabel;
33import javax.swing.JPanel;
34import javax.swing.JScrollPane;
[12803]35import javax.swing.SwingUtilities;
[2801]36import javax.swing.UIManager;
[9356]37import javax.swing.text.html.HTMLEditorKit;
[2801]38
39import org.openstreetmap.josm.Main;
[12686]40import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
[2801]41import org.openstreetmap.josm.data.oauth.OAuthParameters;
42import org.openstreetmap.josm.data.oauth.OAuthToken;
43import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
44import org.openstreetmap.josm.gui.help.HelpUtil;
[6666]45import org.openstreetmap.josm.gui.util.GuiHelper;
[12678]46import org.openstreetmap.josm.gui.util.WindowGeometry;
[2801]47import org.openstreetmap.josm.gui.widgets.HtmlPanel;
[9356]48import org.openstreetmap.josm.io.OsmApi;
[2801]49import org.openstreetmap.josm.tools.CheckParameterUtil;
50import org.openstreetmap.josm.tools.ImageProvider;
[10791]51import org.openstreetmap.josm.tools.InputMapUtils;
[9352]52import org.openstreetmap.josm.tools.UserCancelException;
[12803]53import org.openstreetmap.josm.tools.Utils;
[2801]54
55/**
56 * This wizard walks the user to the necessary steps to retrieve an OAuth Access Token which
57 * allows JOSM to access the OSM API on the users behalf.
[10254]58 * @since 2746
[2801]59 */
[2861]60public class OAuthAuthorizationWizard extends JDialog {
[2801]61 private boolean canceled;
[5422]62 private final String apiUrl;
[2801]63
[10183]64 private final AuthorizationProcedureComboBox cbAuthorisationProcedure = new AuthorizationProcedureComboBox();
[2861]65 private FullyAutomaticAuthorizationUI pnlFullyAutomaticAuthorisationUI;
66 private SemiAutomaticAuthorizationUI pnlSemiAutomaticAuthorisationUI;
67 private ManualAuthorizationUI pnlManualAuthorisationUI;
[2801]68 private JScrollPane spAuthorisationProcedureUI;
[10254]69 private final transient Executor executor;
[2801]70
71 /**
[9352]72 * Launches the wizard, {@link OAuthAccessTokenHolder#setAccessToken(OAuthToken) sets the token}
73 * and {@link OAuthAccessTokenHolder#setSaveToPreferences(boolean) saves to preferences}.
74 * @throws UserCancelException if user cancels the operation
75 */
76 public void showDialog() throws UserCancelException {
77 setVisible(true);
78 if (isCanceled()) {
79 throw new UserCancelException();
80 }
81 OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
82 holder.setAccessToken(getAccessToken());
83 holder.setSaveToPreferences(isSaveAccessTokenToPreferences());
84 }
85
86 /**
[2801]87 * Builds the row with the action buttons
[3385]88 *
[5899]89 * @return panel with buttons
[2801]90 */
[8510]91 protected JPanel buildButtonRow() {
[2801]92 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
93
94 AcceptAccessTokenAction actAcceptAccessToken = new AcceptAccessTokenAction();
95 pnlFullyAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken);
96 pnlSemiAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken);
97 pnlManualAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken);
98
[10367]99 pnl.add(new JButton(actAcceptAccessToken));
100 pnl.add(new JButton(new CancelAction()));
101 pnl.add(new JButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/OAuthAuthorisationWizard"))));
[2801]102
103 return pnl;
104 }
105
106 /**
107 * Builds the panel with general information in the header
[3385]108 *
[8442]109 * @return panel with information display
[2801]110 */
111 protected JPanel buildHeaderInfoPanel() {
112 JPanel pnl = new JPanel(new GridBagLayout());
[8510]113 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
[2801]114 GridBagConstraints gc = new GridBagConstraints();
115
116 // the oauth logo in the header
117 gc.anchor = GridBagConstraints.NORTHWEST;
118 gc.fill = GridBagConstraints.HORIZONTAL;
119 gc.weightx = 1.0;
120 gc.gridwidth = 2;
[9960]121 ImageProvider logoProv = new ImageProvider("oauth", "oauth-logo").setMaxHeight(100);
[8442]122 JLabel lbl = new JLabel(logoProv.get());
123 lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
[2801]124 lbl.setOpaque(true);
125 pnl.add(lbl, gc);
126
127 // OAuth in a nutshell ...
[10378]128 gc.gridy = 1;
[8510]129 gc.insets = new Insets(5, 0, 0, 5);
[6267]130 HtmlPanel pnlMessage = new HtmlPanel();
[2801]131 pnlMessage.setText("<html><body>"
[3080]132 + tr("With OAuth you grant JOSM the right to upload map data and GPS tracks "
[10378]133 + "on your behalf (<a href=\"{0}\">more info...</a>).", "http://oauth.net/")
[3080]134 + "</body></html>"
[2801]135 );
[13111]136 pnlMessage.enableClickableHyperlinks();
[2801]137 pnl.add(pnlMessage, gc);
138
139 // the authorisation procedure
[10378]140 gc.gridy = 2;
[2801]141 gc.gridwidth = 1;
142 gc.weightx = 0.0;
[2877]143 lbl = new JLabel(tr("Please select an authorization procedure: "));
[2801]144 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
[8510]145 pnl.add(lbl, gc);
[2801]146
147 gc.gridx = 1;
148 gc.gridwidth = 1;
149 gc.weightx = 1.0;
[10183]150 pnl.add(cbAuthorisationProcedure, gc);
[2801]151 cbAuthorisationProcedure.addItemListener(new AuthorisationProcedureChangeListener());
[8426]152 lbl.setLabelFor(cbAuthorisationProcedure);
[9356]153
154 if (!OsmApi.DEFAULT_API_URL.equals(apiUrl)) {
155 gc.gridy = 3;
156 gc.gridwidth = 2;
157 gc.gridx = 0;
158 final HtmlPanel pnlWarning = new HtmlPanel();
159 final HTMLEditorKit kit = (HTMLEditorKit) pnlWarning.getEditorPane().getEditorKit();
160 kit.getStyleSheet().addRule(".warning-body {"
161 + "background-color:rgb(253,255,221);padding: 10pt; "
162 + "border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}");
163 kit.getStyleSheet().addRule("ol {margin-left: 1cm}");
164 pnlWarning.setText("<html><body>"
165 + "<p class=\"warning-body\">"
166 + tr("<strong>Warning:</strong> Since you are using not the default OSM API, " +
167 "make sure to set an OAuth consumer key and secret in the <i>Advanced OAuth parameters</i>.")
168 + "</p>"
169 + "</body></html>");
170 pnl.add(pnlWarning, gc);
171 }
172
[2801]173 return pnl;
174 }
175
176 /**
177 * Refreshes the view of the authorisation panel, depending on the authorisation procedure
178 * currently selected
179 */
180 protected void refreshAuthorisationProcedurePanel() {
[8510]181 AuthorizationProcedure procedure = (AuthorizationProcedure) cbAuthorisationProcedure.getSelectedItem();
[2801]182 switch(procedure) {
183 case FULLY_AUTOMATIC:
184 spAuthorisationProcedureUI.getViewport().setView(pnlFullyAutomaticAuthorisationUI);
185 pnlFullyAutomaticAuthorisationUI.revalidate();
186 break;
187 case SEMI_AUTOMATIC:
188 spAuthorisationProcedureUI.getViewport().setView(pnlSemiAutomaticAuthorisationUI);
189 pnlSemiAutomaticAuthorisationUI.revalidate();
190 break;
191 case MANUALLY:
192 spAuthorisationProcedureUI.getViewport().setView(pnlManualAuthorisationUI);
193 pnlManualAuthorisationUI.revalidate();
194 break;
195 }
196 validate();
197 repaint();
198 }
199
200 /**
201 * builds the UI
202 */
[6890]203 protected final void build() {
[2801]204 getContentPane().setLayout(new BorderLayout());
205 getContentPane().add(buildHeaderInfoPanel(), BorderLayout.NORTH);
206
[5422]207 setTitle(tr("Get an Access Token for ''{0}''", apiUrl));
[9300]208 this.setMinimumSize(new Dimension(600, 420));
[2801]209
[9352]210 pnlFullyAutomaticAuthorisationUI = new FullyAutomaticAuthorizationUI(apiUrl, executor);
211 pnlSemiAutomaticAuthorisationUI = new SemiAutomaticAuthorizationUI(apiUrl, executor);
212 pnlManualAuthorisationUI = new ManualAuthorizationUI(apiUrl, executor);
[2801]213
[6666]214 spAuthorisationProcedureUI = GuiHelper.embedInVerticalScrollPane(new JPanel());
[2801]215 spAuthorisationProcedureUI.getVerticalScrollBar().addComponentListener(
[10173]216 new ComponentAdapter() {
[6084]217 @Override
[2801]218 public void componentShown(ComponentEvent e) {
219 spAuthorisationProcedureUI.setBorder(UIManager.getBorder("ScrollPane.border"));
220 }
221
[6084]222 @Override
[2801]223 public void componentHidden(ComponentEvent e) {
224 spAuthorisationProcedureUI.setBorder(null);
225 }
226 }
227 );
228 getContentPane().add(spAuthorisationProcedureUI, BorderLayout.CENTER);
229 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
230
231 addWindowListener(new WindowEventHandler());
[10791]232 InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
[2801]233
234 refreshAuthorisationProcedurePanel();
235
236 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/OAuthAuthorisationWizard"));
237 }
238
239 /**
240 * Creates the wizard.
[3385]241 *
[2801]242 * @param parent the component relative to which the dialog is displayed
243 * @param apiUrl the API URL. Must not be null.
[9352]244 * @param executor the executor used for running the HTTP requests for the authorization
[8291]245 * @throws IllegalArgumentException if apiUrl is null
[2801]246 */
[9352]247 public OAuthAuthorizationWizard(Component parent, String apiUrl, Executor executor) {
[10035]248 super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
[2801]249 CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl");
[5422]250 this.apiUrl = apiUrl;
[9352]251 this.executor = executor;
[2801]252 build();
253 }
254
255 /**
[4310]256 * Replies true if the dialog was canceled
[3385]257 *
[4310]258 * @return true if the dialog was canceled
[2801]259 */
260 public boolean isCanceled() {
261 return canceled;
262 }
263
[2861]264 protected AbstractAuthorizationUI getCurrentAuthorisationUI() {
[8510]265 switch((AuthorizationProcedure) cbAuthorisationProcedure.getSelectedItem()) {
[2801]266 case FULLY_AUTOMATIC: return pnlFullyAutomaticAuthorisationUI;
267 case MANUALLY: return pnlManualAuthorisationUI;
268 case SEMI_AUTOMATIC: return pnlSemiAutomaticAuthorisationUI;
269 default: return null;
270 }
271 }
272
273 /**
274 * Replies the Access Token entered using the wizard
[3385]275 *
[2801]276 * @return the access token. May be null if the wizard was canceled.
277 */
278 public OAuthToken getAccessToken() {
279 return getCurrentAuthorisationUI().getAccessToken();
280 }
281
282 /**
283 * Replies the current OAuth parameters.
[3385]284 *
[2801]285 * @return the current OAuth parameters.
286 */
287 public OAuthParameters getOAuthParameters() {
288 return getCurrentAuthorisationUI().getOAuthParameters();
289 }
290
291 /**
292 * Replies true if the currently selected Access Token shall be saved to
293 * the preferences.
[3385]294 *
[2801]295 * @return true if the currently selected Access Token shall be saved to
296 * the preferences
297 */
298 public boolean isSaveAccessTokenToPreferences() {
299 return getCurrentAuthorisationUI().isSaveAccessTokenToPreferences();
300 }
301
302 /**
303 * Initializes the dialog with values from the preferences
[3385]304 *
[2801]305 */
306 public void initFromPreferences() {
[12928]307 pnlFullyAutomaticAuthorisationUI.initialize(apiUrl);
308 pnlSemiAutomaticAuthorisationUI.initialize(apiUrl);
309 pnlManualAuthorisationUI.initialize(apiUrl);
[2801]310 }
311
312 @Override
313 public void setVisible(boolean visible) {
314 if (visible) {
315 new WindowGeometry(
316 getClass().getName() + ".geometry",
317 WindowGeometry.centerInWindow(
318 Main.parent,
[8510]319 new Dimension(450, 540)
[2801]320 )
[2824]321 ).applySafe(this);
[2801]322 initFromPreferences();
[5998]323 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
[2801]324 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
325 }
326 super.setVisible(visible);
327 }
328
329 protected void setCanceled(boolean canceled) {
330 this.canceled = canceled;
331 }
332
[12803]333 /**
334 * Obtains an OAuth access token for the connection. Afterwards, the token is accessible via {@link OAuthAccessTokenHolder}.
335 * @param serverUrl the URL to OSM server
336 * @throws InterruptedException if we're interrupted while waiting for the event dispatching thread to finish OAuth authorization task
337 * @throws InvocationTargetException if an exception is thrown while running OAuth authorization task
338 * @since 12803
339 */
340 public static void obtainAccessToken(final URL serverUrl) throws InvocationTargetException, InterruptedException {
341 final Runnable authTask = new FutureTask<>(() -> {
342 // Concerning Utils.newDirectExecutor: Main worker cannot be used since this connection is already
343 // executed via main worker. The OAuth connections would block otherwise.
344 final OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
345 Main.parent, serverUrl.toExternalForm(), Utils.newDirectExecutor());
346 wizard.showDialog();
347 return wizard;
348 });
349 // exception handling differs from implementation at GuiHelper.runInEDTAndWait()
350 if (SwingUtilities.isEventDispatchThread()) {
351 authTask.run();
352 } else {
353 SwingUtilities.invokeAndWait(authTask);
354 }
355 }
356
[2801]357 class AuthorisationProcedureChangeListener implements ItemListener {
[6084]358 @Override
[2801]359 public void itemStateChanged(ItemEvent arg0) {
360 refreshAuthorisationProcedurePanel();
361 }
362 }
363
364 class CancelAction extends AbstractAction {
[8468]365
366 /**
367 * Constructs a new {@code CancelAction}.
368 */
[8836]369 CancelAction() {
[2801]370 putValue(NAME, tr("Cancel"));
[10369]371 new ImageProvider("cancel").getResource().attachImageIcon(this);
[2877]372 putValue(SHORT_DESCRIPTION, tr("Close the dialog and cancel authorization"));
[2801]373 }
374
375 public void cancel() {
376 setCanceled(true);
377 setVisible(false);
378 }
379
[6084]380 @Override
[2801]381 public void actionPerformed(ActionEvent evt) {
382 cancel();
383 }
384 }
385
386 class AcceptAccessTokenAction extends AbstractAction implements PropertyChangeListener {
387
[8468]388 /**
389 * Constructs a new {@code AcceptAccessTokenAction}.
390 */
[8836]391 AcceptAccessTokenAction() {
[2801]392 putValue(NAME, tr("Accept Access Token"));
[10369]393 new ImageProvider("ok").getResource().attachImageIcon(this);
[2801]394 putValue(SHORT_DESCRIPTION, tr("Close the dialog and accept the Access Token"));
395 updateEnabledState(null);
396 }
397
[6084]398 @Override
[2801]399 public void actionPerformed(ActionEvent evt) {
400 setCanceled(false);
401 setVisible(false);
402 }
403
[6890]404 public final void updateEnabledState(OAuthToken token) {
[2801]405 setEnabled(token != null);
406 }
407
[6084]408 @Override
[2801]409 public void propertyChange(PropertyChangeEvent evt) {
[2861]410 if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
[2801]411 return;
[8510]412 updateEnabledState((OAuthToken) evt.getNewValue());
[2801]413 }
414 }
415
416 class WindowEventHandler extends WindowAdapter {
417 @Override
[8468]418 public void windowClosing(WindowEvent e) {
[2801]419 new CancelAction().cancel();
420 }
421 }
422}
Note: See TracBrowser for help on using the repository browser.