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

Last change on this file since 10035 was 10035, checked in by Don-vip, 8 years ago

code refactoring to ease creation of unit tests by avoiding as much as possible HeadlessException

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