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.FlowLayout;
008import java.awt.GridBagConstraints;
009import java.awt.GridBagLayout;
010import java.awt.Insets;
011import java.awt.event.ActionEvent;
012import java.beans.PropertyChangeEvent;
013import java.beans.PropertyChangeListener;
014
015import javax.swing.AbstractAction;
016import javax.swing.BorderFactory;
017import javax.swing.JCheckBox;
018import javax.swing.JLabel;
019import javax.swing.JPanel;
020import javax.swing.JTabbedPane;
021import javax.swing.event.DocumentEvent;
022import javax.swing.event.DocumentListener;
023import javax.swing.text.JTextComponent;
024
025import org.openstreetmap.josm.Main;
026import org.openstreetmap.josm.data.oauth.OAuthToken;
027import org.openstreetmap.josm.gui.SideButton;
028import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
029import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
030import org.openstreetmap.josm.gui.widgets.HtmlPanel;
031import org.openstreetmap.josm.gui.widgets.JosmTextField;
032import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
033import org.openstreetmap.josm.tools.ImageProvider;
034
035/**
036 * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully
037 * manual process.
038 *
039 * @since 2746
040 */
041public class ManualAuthorizationUI extends AbstractAuthorizationUI {
042
043    private JosmTextField tfAccessTokenKey;
044    private transient AccessTokenKeyValidator valAccessTokenKey;
045    private JosmTextField tfAccessTokenSecret;
046    private transient AccessTokenSecretValidator valAccessTokenSecret;
047    private JCheckBox cbSaveToPreferences;
048    private HtmlPanel pnlMessage;
049
050    protected JPanel buildAccessTokenPanel() {
051        JPanel pnl = new JPanel(new GridBagLayout());
052        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
053        GridBagConstraints gc = new GridBagConstraints();
054        AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder();
055
056        // the access token key input field
057        gc.anchor = GridBagConstraints.NORTHWEST;
058        gc.fill = GridBagConstraints.HORIZONTAL;
059        gc.weightx = 0.0;
060        gc.gridwidth = 2;
061        gc.insets = new Insets(0, 0, 5, 0);
062        pnlMessage = new HtmlPanel();
063        pnlMessage.setText("<html><body>"
064                + tr("Please enter an OAuth Access Token which is authorized to access the OSM server "
065                        + "''{0}''.",
066                        getApiUrl()) + "</body></html>");
067        pnl.add(pnlMessage, gc);
068
069        // the access token key input field
070        gc.gridy = 1;
071        gc.weightx = 0.0;
072        gc.gridwidth = 1;
073        gc.insets = new Insets(0, 0, 0, 3);
074        pnl.add(new JLabel(tr("Access Token Key:")), gc);
075
076        gc.gridx = 1;
077        gc.weightx = 1.0;
078        pnl.add(tfAccessTokenKey = new JosmTextField(), gc);
079        SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenKey);
080        valAccessTokenKey = new AccessTokenKeyValidator(tfAccessTokenKey);
081        valAccessTokenKey.validate();
082        tfAccessTokenKey.getDocument().addDocumentListener(accessTokenBuilder);
083
084        // the access token key input field
085        gc.gridy = 2;
086        gc.gridx = 0;
087        gc.weightx = 0.0;
088        pnl.add(new JLabel(tr("Access Token Secret:")), gc);
089
090        gc.gridx = 1;
091        gc.weightx = 1.0;
092        pnl.add(tfAccessTokenSecret = new JosmTextField(), gc);
093        SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenSecret);
094        valAccessTokenSecret = new AccessTokenSecretValidator(tfAccessTokenSecret);
095        valAccessTokenSecret.validate();
096        tfAccessTokenSecret.getDocument().addDocumentListener(accessTokenBuilder);
097
098        // the checkbox for saving to preferences
099        gc.gridy = 3;
100        gc.gridx = 0;
101        gc.gridwidth = 2;
102        gc.weightx = 1.0;
103        pnl.add(cbSaveToPreferences = new JCheckBox(tr("Save Access Token in preferences")), gc);
104        cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
105
106        // filler - grab remaining space
107        gc.gridy = 3;
108        gc.gridx = 0;
109        gc.gridwidth = 2;
110        gc.weightx = 1.0;
111        gc.weighty = 1.0;
112        gc.fill = GridBagConstraints.BOTH;
113        pnl.add(new JPanel(), gc);
114        return pnl;
115    }
116
117    protected JPanel buildTabbedPreferencesPanel() {
118        JPanel pnl = new JPanel(new BorderLayout());
119
120        JTabbedPane tp = new JTabbedPane();
121        tp.add(buildAccessTokenPanel());
122        tp.add(getAdvancedPropertiesPanel());
123
124        tp.setTitleAt(0, tr("Access Token"));
125        tp.setTitleAt(1, tr("Advanced OAuth parameters"));
126
127        tp.setToolTipTextAt(0, tr("Enter the OAuth Access Token"));
128        tp.setToolTipTextAt(1, tr("Enter advanced OAuth properties"));
129
130        pnl.add(tp, BorderLayout.CENTER);
131        return pnl;
132    }
133
134    protected JPanel buildActionsPanel() {
135        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
136        TestAccessTokenAction actTestAccessToken = new TestAccessTokenAction();
137        pnl.add(new SideButton(actTestAccessToken));
138        this.addPropertyChangeListener(actTestAccessToken);
139        return pnl;
140    }
141
142    @Override
143    public void setApiUrl(String apiUrl) {
144        super.setApiUrl(apiUrl);
145        if (pnlMessage != null) {
146            pnlMessage.setText(tr("<html><body>"
147                    + "Please enter an OAuth Access Token which is authorized to access the OSM server "
148                    + "''{0}''."
149                    + "</body></html>",
150                    getApiUrl()
151            ));
152        }
153    }
154
155    protected final void build() {
156        setLayout(new BorderLayout());
157        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
158        add(buildTabbedPreferencesPanel(), BorderLayout.CENTER);
159        add(buildActionsPanel(), BorderLayout.SOUTH);
160    }
161
162    /**
163     * Constructs a new {@code ManualAuthorizationUI} for the given API URL.
164     * @param apiUrl The OSM API URL
165     * @since 5422
166     */
167    public ManualAuthorizationUI(String apiUrl) {
168        super(apiUrl);
169        build();
170    }
171
172    @Override
173    public boolean isSaveAccessTokenToPreferences() {
174        return cbSaveToPreferences.isSelected();
175    }
176
177    private static class AccessTokenKeyValidator extends AbstractTextComponentValidator {
178
179        AccessTokenKeyValidator(JTextComponent tc) {
180            super(tc);
181        }
182
183        @Override
184        public boolean isValid() {
185            return !getComponent().getText().trim().isEmpty();
186        }
187
188        @Override
189        public void validate() {
190            if (isValid()) {
191                feedbackValid(tr("Please enter an Access Token Key"));
192            } else {
193                feedbackInvalid(tr("The Access Token Key must not be empty. Please enter an Access Token Key"));
194            }
195        }
196    }
197
198    private static class AccessTokenSecretValidator extends AbstractTextComponentValidator {
199        AccessTokenSecretValidator(JTextComponent tc) {
200            super(tc);
201        }
202
203        @Override
204        public boolean isValid() {
205            return !getComponent().getText().trim().isEmpty();
206        }
207
208        @Override
209        public void validate() {
210            if (isValid()) {
211                feedbackValid(tr("Please enter an Access Token Secret"));
212            } else {
213                feedbackInvalid(tr("The Access Token Secret must not be empty. Please enter an Access Token Secret"));
214            }
215        }
216    }
217
218    class AccessTokenBuilder implements DocumentListener {
219
220        public void build() {
221            if (!valAccessTokenKey.isValid() || !valAccessTokenSecret.isValid()) {
222                setAccessToken(null);
223            } else {
224                setAccessToken(new OAuthToken(tfAccessTokenKey.getText().trim(), tfAccessTokenSecret.getText().trim()));
225            }
226        }
227
228        @Override
229        public void changedUpdate(DocumentEvent e) {
230            build();
231        }
232
233        @Override
234        public void insertUpdate(DocumentEvent e) {
235            build();
236        }
237
238        @Override
239        public void removeUpdate(DocumentEvent e) {
240            build();
241        }
242    }
243
244    /**
245     * Action for testing an Access Token
246     */
247    class TestAccessTokenAction extends AbstractAction implements PropertyChangeListener {
248        TestAccessTokenAction() {
249            putValue(NAME, tr("Test Access Token"));
250            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
251            putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token"));
252            updateEnabledState();
253        }
254
255        @Override
256        public void actionPerformed(ActionEvent evt) {
257            TestAccessTokenTask task = new TestAccessTokenTask(
258                    ManualAuthorizationUI.this,
259                    getApiUrl(),
260                    getAdvancedPropertiesPanel().getAdvancedParameters(),
261                    getAccessToken()
262            );
263            Main.worker.submit(task);
264        }
265
266        protected final void updateEnabledState() {
267            setEnabled(hasAccessToken());
268        }
269
270        @Override
271        public void propertyChange(PropertyChangeEvent evt) {
272            if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
273                return;
274            updateEnabledState();
275        }
276    }
277}