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 }