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.SelectAllOnFocusGainedDecorator;
032import org.openstreetmap.josm.tools.ImageProvider;
033import org.openstreetmap.josm.gui.widgets.JosmTextField;
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 AccessTokenKeyValidator valAccessTokenKey;
045    private JosmTextField tfAccessTokenSecret;
046    private 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 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    static private class AccessTokenKeyValidator extends AbstractTextComponentValidator {
178
179        public AccessTokenKeyValidator(JTextComponent tc) throws IllegalArgumentException {
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    static private class AccessTokenSecretValidator extends AbstractTextComponentValidator {
199        public AccessTokenSecretValidator(JTextComponent tc) throws IllegalArgumentException {
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        @Override
228        public void changedUpdate(DocumentEvent e) {
229            build();
230        }
231
232        @Override
233        public void insertUpdate(DocumentEvent e) {
234            build();
235        }
236
237        @Override
238        public void removeUpdate(DocumentEvent e) {
239            build();
240        }
241    }
242
243    /**
244     * Action for testing an Access Token
245     */
246    class TestAccessTokenAction extends AbstractAction implements PropertyChangeListener {
247        public TestAccessTokenAction() {
248            putValue(NAME, tr("Test Access Token"));
249            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth"));
250            putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token"));
251            updateEnabledState();
252        }
253
254        @Override
255        public void actionPerformed(ActionEvent evt) {
256            TestAccessTokenTask task = new TestAccessTokenTask(
257                    ManualAuthorizationUI.this,
258                    getApiUrl(),
259                    getAdvancedPropertiesPanel().getAdvancedParameters(),
260                    getAccessToken()
261            );
262            Main.worker.submit(task);
263        }
264
265        protected void updateEnabledState() {
266            setEnabled(hasAccessToken());
267        }
268
269        @Override
270        public void propertyChange(PropertyChangeEvent evt) {
271            if (! evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
272                return;
273            updateEnabledState();
274        }
275    }
276}