001    /*
002     * $Id: JXLoginPane.java 3350 2009-05-25 04:31:40Z kschaefe $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     *
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx;
022    
023    import java.awt.BorderLayout;
024    import java.awt.CardLayout;
025    import java.awt.Component;
026    import java.awt.ComponentOrientation;
027    import java.awt.Container;
028    import java.awt.Cursor;
029    import java.awt.Dialog;
030    import java.awt.Dimension;
031    import java.awt.EventQueue;
032    import java.awt.FlowLayout;
033    import java.awt.Font;
034    import java.awt.Frame;
035    import java.awt.GridBagConstraints;
036    import java.awt.GridBagLayout;
037    import java.awt.GridLayout;
038    import java.awt.Image;
039    import java.awt.Insets;
040    import java.awt.KeyEventDispatcher;
041    import java.awt.KeyboardFocusManager;
042    import java.awt.LayoutManager;
043    import java.awt.Robot;
044    import java.awt.Window;
045    import java.awt.event.ActionEvent;
046    import java.awt.event.ActionListener;
047    import java.awt.event.ItemEvent;
048    import java.awt.event.ItemListener;
049    import java.awt.event.KeyAdapter;
050    import java.awt.event.KeyEvent;
051    import java.awt.event.WindowAdapter;
052    import java.awt.event.WindowEvent;
053    import java.awt.event.WindowFocusListener;
054    import java.awt.event.WindowListener;
055    import java.beans.PropertyChangeEvent;
056    import java.beans.PropertyChangeListener;
057    import java.util.ArrayList;
058    import java.util.Arrays;
059    import java.util.Collections;
060    import java.util.List;
061    import java.util.Locale;
062    import java.util.logging.Level;
063    import java.util.logging.Logger;
064    
065    import javax.swing.AbstractListModel;
066    import javax.swing.Action;
067    import javax.swing.BorderFactory;
068    import javax.swing.Box;
069    import javax.swing.BoxLayout;
070    import javax.swing.ComboBoxModel;
071    import javax.swing.DefaultComboBoxModel;
072    import javax.swing.JButton;
073    import javax.swing.JCheckBox;
074    import javax.swing.JComboBox;
075    import javax.swing.JComponent;
076    import javax.swing.JDialog;
077    import javax.swing.JFrame;
078    import javax.swing.JLabel;
079    import javax.swing.JPanel;
080    import javax.swing.JPasswordField;
081    import javax.swing.JProgressBar;
082    import javax.swing.JTextField;
083    import javax.swing.KeyStroke;
084    import javax.swing.SwingConstants;
085    import javax.swing.SwingUtilities;
086    import javax.swing.UIManager;
087    import javax.swing.border.EmptyBorder;
088    import javax.swing.plaf.basic.BasicHTML;
089    import javax.swing.text.View;
090    
091    import org.jdesktop.swingx.action.AbstractActionExt;
092    import org.jdesktop.swingx.auth.DefaultUserNameStore;
093    import org.jdesktop.swingx.auth.LoginAdapter;
094    import org.jdesktop.swingx.auth.LoginEvent;
095    import org.jdesktop.swingx.auth.LoginListener;
096    import org.jdesktop.swingx.auth.LoginService;
097    import org.jdesktop.swingx.auth.PasswordStore;
098    import org.jdesktop.swingx.auth.UserNameStore;
099    import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
100    import org.jdesktop.swingx.painter.MattePainter;
101    import org.jdesktop.swingx.plaf.LoginPaneAddon;
102    import org.jdesktop.swingx.plaf.LoginPaneUI;
103    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
104    import org.jdesktop.swingx.plaf.UIManagerExt;
105    import org.jdesktop.swingx.util.WindowUtils;
106    
107    /**
108     *  <p>JXLoginPane is a specialized JPanel that implements a Login dialog with
109     *  support for saving passwords supplied for future use in a secure
110     *  manner. <strong>LoginService</strong> is invoked to perform authentication
111     *  and optional <strong>PasswordStore</strong> can be provided to store the user
112     *  login information.</p>
113     *
114     *  <p> In order to perform the authentication, <strong>JXLoginPane</strong>
115     *  calls the <code>authenticate</code> method of the <strong>LoginService
116     *  </strong>. In order to perform the persistence of the password,
117     *  <strong>JXLoginPane</strong> calls the put method of the
118     *  <strong>PasswordStore</strong> object that is supplied. If
119     *  the <strong>PasswordStore</strong> is <code>null</code>, then the password
120     *  is not saved. Similarly, if a <strong>PasswordStore</strong> is
121     *  supplied and the password is null, then the <strong>PasswordStore</strong>
122     *  will be queried for the password using the <code>get</code> method.
123     *
124     *  Example:
125     *  <code><pre>
126     *         final JXLoginPane panel = new JXLoginPane(new LoginService() {
127     *                      public boolean authenticate(String name, char[] password,
128     *                                      String server) throws Exception {
129     *                              // perform authentication and return true on success.
130     *                              return false;
131     *                      }});
132     *      final JFrame frame = JXLoginPane.showLoginFrame(panel);
133     * </pre></code>
134     *
135     * @author Bino George
136     * @author Shai Almog
137     * @author rbair
138     * @author Karl Schaefer
139     * @author rah003
140     * @author Jonathan Giles
141     */
142    public class JXLoginPane extends JXPanel {
143    
144        /**
145         * The Logger
146         */
147        private static final Logger LOG = Logger.getLogger(JXLoginPane.class.getName());
148        /**
149         * Comment for <code>serialVersionUID</code>
150         */
151        private static final long serialVersionUID = 3544949969896288564L;
152        /**
153         * UI Class ID
154         */
155        public final static String uiClassID = "LoginPaneUI";
156        /**
157         * Action key for an Action in the ActionMap that initiates the Login
158         * procedure
159         */
160        public static final String LOGIN_ACTION_COMMAND = "login";
161        /**
162         * Action key for an Action in the ActionMap that cancels the Login
163         * procedure
164         */
165        public static final String CANCEL_LOGIN_ACTION_COMMAND = "cancel-login";
166        /**
167         * The JXLoginPane can attempt to save certain user information such as
168         * the username, password, or both to their respective stores.
169         * This type specifies what type of save should be performed.
170         */
171        public static enum SaveMode {NONE, USER_NAME, PASSWORD, BOTH}
172        /**
173         * Returns the status of the login process
174         */
175        public enum Status {NOT_STARTED, IN_PROGRESS, FAILED, CANCELLED, SUCCEEDED}
176        /**
177         * Used as a prefix when pulling data out of UIManager for i18n
178         */
179        private static String CLASS_NAME = JXLoginPane.class.getSimpleName();
180    
181        /**
182         * The current login status for this panel
183         */
184        private Status status = Status.NOT_STARTED;
185        /**
186         * An optional banner at the top of the panel
187         */
188        private JXImagePanel banner;
189        /**
190         * Text that should appear on the banner
191         */
192        private String bannerText;
193        /**
194         * Custom label allowing the developer to display some message to the user
195         */
196        private JLabel messageLabel;
197        /**
198         * Shows an error message such as "user name or password incorrect" or
199         * "could not contact server" or something like that if something
200         * goes wrong
201         */
202        private JXLabel errorMessageLabel;
203        /**
204         * A Panel containing all of the input fields, check boxes, etc necessary
205         * for the user to do their job. The items on this panel change whenever
206         * the SaveMode changes, so this panel must be recreated at runtime if the
207         * SaveMode changes. Thus, I must maintain this reference so I can remove
208         * this panel from the content panel at runtime.
209         */
210        private JXPanel loginPanel;
211        /**
212         * The panel on which the input fields, messageLabel, and errorMessageLabel
213         * are placed. While the login thread is running, this panel is removed
214         * from the dialog and replaced by the progressPanel
215         */
216        private JXPanel contentPanel;
217        /**
218         * This is the area in which the name field is placed. That way it can toggle on the fly
219         * between text field and a combo box depending on the situation, and have a simple
220         * way to get the user name
221         */
222        private NameComponent namePanel;
223        /**
224         * The password field presented allowing the user to enter their password
225         */
226        private JPasswordField passwordField;
227        /**
228         * A combo box presenting the user with a list of servers to which they
229         * may log in. This is an optional feature, which is only enabled if
230         * the List of servers supplied to the JXLoginPane has a length greater
231         * than 1.
232         */
233        private JComboBox serverCombo;
234        /**
235         * Check box presented if a PasswordStore is used, allowing the user to decide whether to
236         * save their password
237         */
238        private JCheckBox saveCB;
239        
240        /**
241         * Label displayed whenever caps lock is on.
242         */
243        private JLabel capsOn;
244        /**
245         * A special panel that displays a progress bar and cancel button, and
246         * which notify the user of the login process, and allow them to cancel
247         * that process.
248         */
249        private JXPanel progressPanel;
250        /**
251         * A JLabel on the progressPanel that is used for informing the user
252         * of the status of the login procedure (logging in..., canceling login...)
253         */
254        private JLabel progressMessageLabel;
255        /**
256         * The LoginService to use. This must be specified for the login dialog to operate.
257         * If no LoginService is defined, a default login service is used that simply
258         * allows all users access. This is useful for demos or prototypes where a proper login
259         * server is not available.
260         */
261        private LoginService loginService;
262        /**
263         * Optional: a PasswordStore to use for storing and retrieving passwords for a specific
264         * user.
265         */
266        private PasswordStore passwordStore;
267        /**
268         * Optional: a UserNameStore to use for storing user names and retrieving them
269         */
270        private UserNameStore userNameStore;
271        /**
272         * A list of servers where each server is represented by a String. If the
273         * list of Servers is greater than 1, then a combo box will be presented to
274         * the user to choose from. If any servers are specified, the selected one
275         * (or the only one if servers.size() == 1) will be passed to the LoginService
276         */
277        private List<String> servers;
278        /**
279         *  Whether to save password or username or both.
280         */
281        private SaveMode saveMode;
282        /**
283         * Tracks the cursor at the time that authentication was started, and restores to that
284         * cursor after authentication ends, or is canceled;
285         */
286        private Cursor oldCursor;
287        
288        private boolean namePanelEnabled = true;
289    
290        /**
291         * The default login listener used by this panel.
292         */
293        private LoginListener defaultLoginListener;
294        private final CapsOnTest capsOnTest;
295        private boolean caps;
296        private boolean isTestingCaps;
297        private final KeyEventDispatcher capsOnListener;
298        /**
299         * Caps lock detection support
300         */
301        private boolean capsLockSupport = true;
302    
303        /**
304         * Login/cancel control pane;
305         */
306        private JXBtnPanel buttonPanel;
307        /**
308         * Window event listener responsible for triggering caps lock test on vindow activation and
309         * focus changes.
310         */
311        private final CapsOnWinListener capsOnWinListener;
312        
313        /**
314         * Card pane holding user/pwd fields view and the progress view.
315         */
316        private JPanel contentCardPane;
317        private boolean isErrorMessageSet;
318    
319        /**
320         * Creates a default JXLoginPane instance
321         */
322        static {
323            LookAndFeelAddons.contribute(new LoginPaneAddon());
324        }
325    
326        /**
327         * Populates UIDefaults with the localizable Strings we will use
328         * in the Login panel.
329         */
330        private void reinitLocales(Locale l) {
331            // PENDING: JW - use the locale given as parameter
332            // as this probably (?) should be called before super.setLocale
333            setBannerText(UIManagerExt.getString(CLASS_NAME + ".bannerString", getLocale()));
334            banner.setImage(createLoginBanner());
335            if (!isErrorMessageSet) {
336                errorMessageLabel.setText(UIManager.getString(CLASS_NAME + ".errorMessage", getLocale()));
337            }
338            progressMessageLabel.setText(UIManagerExt.getString(CLASS_NAME + ".pleaseWait", getLocale()));
339            recreateLoginPanel();
340            Window w = SwingUtilities.getWindowAncestor(this);
341            if (w instanceof JXLoginFrame) {
342                JXLoginFrame f = (JXLoginFrame) w;
343                f.setTitle(UIManagerExt.getString(CLASS_NAME + ".titleString", getLocale()));
344                if (buttonPanel != null) {
345                    buttonPanel.getOk().setText(UIManagerExt.getString(CLASS_NAME + ".loginString", getLocale()));
346                    buttonPanel.getCancel().setText(UIManagerExt.getString(CLASS_NAME + ".cancelString", getLocale()));
347                }
348            }
349            JLabel lbl = (JLabel) passwordField.getClientProperty("labeledBy");
350            if (lbl != null) {
351                lbl.setText(UIManagerExt.getString(CLASS_NAME + ".passwordString", getLocale()));
352            }
353            lbl = (JLabel) namePanel.getComponent().getClientProperty("labeledBy");
354            if (lbl != null) {
355                lbl.setText(UIManagerExt.getString(CLASS_NAME + ".nameString", getLocale()));
356            }
357            if (serverCombo != null) {
358                lbl = (JLabel) serverCombo.getClientProperty("labeledBy");
359                if (lbl != null) {
360                    lbl.setText(UIManagerExt.getString(CLASS_NAME + ".serverString", getLocale()));
361                }
362            }
363            saveCB.setText(UIManagerExt.getString(CLASS_NAME + ".rememberPasswordString", getLocale()));
364            // by default, caps is initialized in off state - i.e. without warning. Setting to
365            // whitespace preserves formatting of the panel.
366            capsOn.setText(isCapsLockOn() ? UIManagerExt.getString(CLASS_NAME + ".capsOnWarning", getLocale()) : " ");
367    
368            getActionMap().get(LOGIN_ACTION_COMMAND).putValue(Action.NAME, UIManagerExt.getString(CLASS_NAME + ".loginString", getLocale()));
369            getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND).putValue(Action.NAME, UIManagerExt.getString(CLASS_NAME + ".cancelString", getLocale()));
370    
371        }
372    
373        //--------------------------------------------------------- Constructors
374        /**
375         * Create a {@code JXLoginPane} that always accepts the user, never stores
376         * passwords or user ids, and has no target servers.
377         * <p>
378         * This constructor should <i>NOT</i> be used in a real application. It is
379         * provided for compliance to the bean specification and for use with visual
380         * editors.
381         */
382        public JXLoginPane() {
383            this(null);
384        }
385    
386        /**
387         * Create a {@code JXLoginPane} with the specified {@code LoginService}
388         * that does not store user ids or passwords and has no target servers.
389         *
390         * @param service
391         *            the {@code LoginService} to use for logging in
392         */
393        public JXLoginPane(LoginService service) {
394            this(service, null, null);
395        }
396    
397        /**
398         * Create a {@code JXLoginPane} with the specified {@code LoginService},
399         * {@code PasswordStore}, and {@code UserNameStore}, but without a server
400         * list.
401         * <p>
402         * If you do not want to store passwords or user ids, those parameters can
403         * be {@code null}. {@code SaveMode} is autoconfigured from passed in store
404         * parameters.
405         *
406         * @param service
407         *            the {@code LoginService} to use for logging in
408         * @param passwordStore
409         *            the {@code PasswordStore} to use for storing password
410         *            information
411         * @param userStore
412         *            the {@code UserNameStore} to use for storing user information
413         */
414        public JXLoginPane(LoginService service, PasswordStore passwordStore, UserNameStore userStore) {
415            this(service, passwordStore, userStore, null);
416        }
417    
418        /**
419         * Create a {@code JXLoginPane} with the specified {@code LoginService},
420         * {@code PasswordStore}, {@code UserNameStore}, and server list.
421         * <p>
422         * If you do not want to store passwords or user ids, those parameters can
423         * be {@code null}. {@code SaveMode} is autoconfigured from passed in store
424         * parameters.
425         * <p>
426         * Setting the server list to {@code null} will unset all of the servers.
427         * The server list is guaranteed to be non-{@code null}.
428         *
429         * @param service
430         *            the {@code LoginService} to use for logging in
431         * @param passwordStore
432         *            the {@code PasswordStore} to use for storing password
433         *            information
434         * @param userStore
435         *            the {@code UserNameStore} to use for storing user information
436         * @param servers
437         *            a list of servers to authenticate against
438         */
439        public JXLoginPane(LoginService service, PasswordStore passwordStore, UserNameStore userStore, List<String> servers) {
440            //init capslock detection support
441            if (Boolean.parseBoolean(System.getProperty("swingx.enableCapslockTesting"))) {
442                capsOnTest = new CapsOnTest();
443                capsOnListener = new KeyEventDispatcher() {
444                    public boolean dispatchKeyEvent(KeyEvent e) {
445                        if (e.getID() != KeyEvent.KEY_PRESSED) {
446                            return false;
447                        }
448                        if (e.getKeyCode() == 20) {
449                            setCapsLock(!isCapsLockOn());
450                        }
451                        return false;
452                    }};
453                capsOnWinListener = new CapsOnWinListener(capsOnTest);
454            } else {
455                capsOnTest = null;
456                capsOnListener = null;
457                capsOnWinListener = null;
458                capsLockSupport = false;
459            }
460            setLoginService(service);
461            setPasswordStore(passwordStore);
462            setUserNameStore(userStore);
463            setServers(servers);
464    
465    
466            //create the login and cancel actions, and add them to the action map
467            getActionMap().put(LOGIN_ACTION_COMMAND, createLoginAction());
468            getActionMap().put(CANCEL_LOGIN_ACTION_COMMAND, createCancelAction());
469    
470            //initialize the save mode
471            if (passwordStore != null && userStore != null) {
472                saveMode = SaveMode.BOTH;
473            } else if (passwordStore != null) {
474                saveMode = SaveMode.PASSWORD;
475            } else if (userStore != null) {
476                saveMode = SaveMode.USER_NAME;
477            } else {
478                saveMode = SaveMode.NONE;
479            }
480    
481            // #732 set all internal components opacity to false in order to allow top level (frame's content pane) background painter to have any effect.
482            setOpaque(false);
483            initComponents();
484        }
485    
486        /**
487         * Sets current state of the caps lock key as detected by the component.
488         * @param b True when caps lock is turned on, false otherwise.
489         */
490        private void setCapsLock(boolean b) {
491            caps = b;
492            capsOn.setText(caps ? UIManagerExt.getString(CLASS_NAME + ".capsOnWarning", getLocale()) : " ");
493        }
494    
495        /**
496         * Gets current state of the caps lock as seen by the login panel. The state seen by the login
497         * panel and therefore returned by this method can be delayed in comparison to the real caps
498         * lock state and displayed by the keyboard light. This is usually the case when component or
499         * its text fields are not focused.
500         *
501         * @return True when caps lock is on, false otherwise. Returns always false when
502         * <code>isCapsLockDetectionSupported()</code> returns false.
503         */
504        public boolean isCapsLockOn() {
505            return caps;
506        }
507    
508        /**
509         * Check current state of the caps lock state detection. Note that the value can change after
510         * component have been made visible. Due to current problems in locking key state detection by
511         * core java detection of the changes in caps lock can be always reliably determined. When
512         * component can't guarantee reliable detection it will switch it off. This is usually the case
513         * for unsigned applets and webstart invoked application. Since your users are going to pass
514         * their password in the component you should always sign it when distributing application over
515         * the network.
516         * @return True if changes in caps lock state can be monitored by the component, false otherwise.
517         */
518        public boolean isCapsLockDetectionSupported() {
519            return capsLockSupport;
520        }
521    
522        //------------------------------------------------------------- UI Logic
523    
524        /**
525         * {@inheritDoc}
526         */
527        @Override
528        public LoginPaneUI getUI() {
529            return (LoginPaneUI) super.getUI();
530        }
531    
532        /**
533         * Sets the look and feel (L&F) object that renders this component.
534         *
535         * @param ui the LoginPaneUI L&F object
536         * @see javax.swing.UIDefaults#getUI
537         */
538        public void setUI(LoginPaneUI ui) {
539            // initialized here due to implicit updateUI call from JPanel
540            if (banner == null) {
541                banner = new JXImagePanel();
542            }
543            if (errorMessageLabel == null) {
544                errorMessageLabel = new JXLabel(UIManagerExt.getString(CLASS_NAME + ".errorMessage", getLocale()));
545            }
546            super.setUI(ui);
547            banner.setImage(createLoginBanner());
548        }
549    
550        /**
551         * Notification from the <code>UIManager</code> that the L&F has changed.
552         * Replaces the current UI object with the latest version from the
553         * <code>UIManager</code>.
554         *
555         * @see javax.swing.JComponent#updateUI
556         */
557        @Override
558        public void updateUI() {
559            setUI((LoginPaneUI) LookAndFeelAddons.getUI(this, LoginPaneUI.class));
560        }
561    
562        /**
563         * Returns the name of the L&F class that renders this component.
564         *
565         * @return the string {@link #uiClassID}
566         * @see javax.swing.JComponent#getUIClassID
567         * @see javax.swing.UIDefaults#getUI
568         */
569        @Override
570        public String getUIClassID() {
571            return uiClassID;
572        }
573    
574        /**
575         * Recreates the login panel, and replaces the current one with the new one
576         */
577        protected void recreateLoginPanel() {
578            JXPanel old = loginPanel;
579            loginPanel = createLoginPanel();
580            loginPanel.setBorder(BorderFactory.createEmptyBorder(0, 36, 7, 11));
581            contentPanel.remove(old);
582            contentPanel.add(loginPanel, 1);
583        }
584    
585        /**
586         * Creates and returns a new LoginPanel, based on the SaveMode state of
587         * the login panel. Whenever the SaveMode changes, the panel is recreated.
588         * I do this rather than hiding/showing components, due to a cleaner
589         * implementation (no invisible components, components are not sharing
590         * locations in the LayoutManager, etc).
591         */
592        private JXPanel createLoginPanel() {
593            JXPanel loginPanel = new JXPanel();
594            
595            JPasswordField oldPwd = passwordField;
596            //create the password component
597            passwordField = new JPasswordField("", 15);
598            JLabel passwordLabel = new JLabel(UIManagerExt.getString(CLASS_NAME + ".passwordString", getLocale()));
599            passwordLabel.setLabelFor(passwordField);
600            if (oldPwd != null) {
601                passwordField.setText(new String(oldPwd.getPassword()));
602            }
603    
604            NameComponent oldPanel = namePanel;
605            //create the NameComponent
606            if (saveMode == SaveMode.NONE) {
607                namePanel = new SimpleNamePanel();
608            } else {
609                namePanel = new ComboNamePanel();
610            }
611            if (oldPanel != null) {
612                // need to reset here otherwise value will get lost during LAF change as panel gets recreated.
613                namePanel.setUserName(oldPanel.getUserName());
614                namePanel.setEnabled(oldPanel.isEnabled());
615                namePanel.setEditable(oldPanel.isEditable());
616            } else {
617                namePanel.setEnabled(namePanelEnabled);
618                namePanel.setEditable(namePanelEnabled);
619            }
620            JLabel nameLabel = new JLabel(UIManagerExt.getString(CLASS_NAME + ".nameString", getLocale()));
621            nameLabel.setLabelFor(namePanel.getComponent());
622    
623            //create the server combo box if necessary
624            JLabel serverLabel = new JLabel(UIManagerExt.getString(CLASS_NAME + ".serverString", getLocale()));
625            if (servers.size() > 1) {
626                serverCombo = new JComboBox(servers.toArray());
627                serverLabel.setLabelFor(serverCombo);
628            } else {
629                serverCombo = null;
630            }
631    
632            //create the save check box. By default, it is not selected
633            saveCB = new JCheckBox(UIManagerExt.getString(CLASS_NAME + ".rememberPasswordString", getLocale()));
634            saveCB.setIconTextGap(10);
635            //TODO should get this from preferences!!! And, it should be based on the user
636            saveCB.setSelected(false); 
637            //determine whether to show/hide the save check box based on the SaveMode
638            saveCB.setVisible(saveMode == SaveMode.PASSWORD || saveMode == SaveMode.BOTH);
639            saveCB.setOpaque(false);
640    
641            capsOn = new JLabel(" ");
642            // don't show by default. We perform test when login panel gets focus.
643    
644            int lShift = 3;// lShift is used to align all other components with the checkbox
645            GridLayout grid = new GridLayout(2,1);
646            grid.setVgap(5);
647            JPanel fields = new JPanel(grid);
648            fields.setOpaque(false);
649            fields.add(namePanel.getComponent());
650            fields.add(passwordField);
651    
652            loginPanel.setLayout(new GridBagLayout());
653            GridBagConstraints gridBagConstraints = new GridBagConstraints();
654            gridBagConstraints.gridx = 0;
655            gridBagConstraints.gridy = 0;
656            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
657            gridBagConstraints.insets = new Insets(4, lShift, 5, 11);
658            loginPanel.add(nameLabel, gridBagConstraints);
659    
660            gridBagConstraints = new GridBagConstraints();
661            gridBagConstraints.gridx = 1;
662            gridBagConstraints.gridy = 0;
663            gridBagConstraints.gridwidth = 1;
664            gridBagConstraints.gridheight = 2;
665            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
666            gridBagConstraints.fill = GridBagConstraints.BOTH;
667            gridBagConstraints.weightx = 1.0;
668            gridBagConstraints.insets = new Insets(0, 0, 5, 0);
669            loginPanel.add(fields, gridBagConstraints);
670    
671            gridBagConstraints = new GridBagConstraints();
672            gridBagConstraints.gridx = 0;
673            gridBagConstraints.gridy = 1;
674            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
675            gridBagConstraints.insets = new Insets(5, lShift, 5, 11);
676            loginPanel.add(passwordLabel, gridBagConstraints);
677            
678            if (serverCombo != null) {
679                gridBagConstraints = new GridBagConstraints();
680                gridBagConstraints.gridx = 0;
681                gridBagConstraints.gridy = 2;
682                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
683                gridBagConstraints.insets = new Insets(0, lShift, 5, 11);
684                loginPanel.add(serverLabel, gridBagConstraints);
685    
686                gridBagConstraints = new GridBagConstraints();
687                gridBagConstraints.gridx = 1;
688                gridBagConstraints.gridy = 2;
689                gridBagConstraints.gridwidth = 1;
690                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
691                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
692                gridBagConstraints.weightx = 1.0;
693                gridBagConstraints.insets = new Insets(0, 0, 5, 0);
694                loginPanel.add(serverCombo, gridBagConstraints);
695                
696                gridBagConstraints = new GridBagConstraints();
697                gridBagConstraints.gridx = 0;
698                gridBagConstraints.gridy = 3;
699                gridBagConstraints.gridwidth = 2;
700                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
701                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
702                gridBagConstraints.weightx = 1.0;
703                gridBagConstraints.insets = new Insets(0, 0, 4, 0);
704                loginPanel.add(saveCB, gridBagConstraints);
705    
706                gridBagConstraints = new GridBagConstraints();
707                gridBagConstraints.gridx = 0;
708                gridBagConstraints.gridy = 4;
709                gridBagConstraints.gridwidth = 2;
710                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
711                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
712                gridBagConstraints.weightx = 1.0;
713                gridBagConstraints.insets = new Insets(0, lShift, 0, 11);
714                loginPanel.add(capsOn, gridBagConstraints);
715            } else {
716                gridBagConstraints = new GridBagConstraints();
717                gridBagConstraints.gridx = 0;
718                gridBagConstraints.gridy = 2;
719                gridBagConstraints.gridwidth = 2;
720                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
721                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
722                gridBagConstraints.weightx = 1.0;
723                gridBagConstraints.insets = new Insets(0, 0, 4, 0);
724                loginPanel.add(saveCB, gridBagConstraints);
725    
726                gridBagConstraints = new GridBagConstraints();
727                gridBagConstraints.gridx = 0;
728                gridBagConstraints.gridy = 3;
729                gridBagConstraints.gridwidth = 2;
730                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
731                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
732                gridBagConstraints.weightx = 1.0;
733                gridBagConstraints.insets = new Insets(0, lShift, 0, 11);
734                loginPanel.add(capsOn, gridBagConstraints);
735            }
736            loginPanel.setOpaque(false);
737            return loginPanel;
738        }
739    
740        /**
741         * This method adds functionality to support bidi languages within this
742         * component
743         */
744        @Override
745        public void setComponentOrientation(ComponentOrientation orient) {
746            // this if is used to avoid needless creations of the image
747            if(orient != super.getComponentOrientation()) {
748                super.setComponentOrientation(orient);
749                banner.setImage(createLoginBanner());
750                progressPanel.applyComponentOrientation(orient);
751            }
752        }
753    
754        /**
755         * Create all of the UI components for the login panel
756         */
757        private void initComponents() {
758            //create the default banner
759            banner.setImage(createLoginBanner());
760    
761            //create the default label
762            messageLabel = new JLabel(" ");
763            messageLabel.setOpaque(false);
764            messageLabel.setFont(messageLabel.getFont().deriveFont(Font.BOLD));
765    
766            //create the main components
767            loginPanel = createLoginPanel();
768    
769            //create the message and hyperlink and hide them
770            errorMessageLabel.setIcon(UIManager.getIcon(CLASS_NAME + ".errorIcon", getLocale()));
771            errorMessageLabel.setVerticalTextPosition(SwingConstants.TOP);
772            errorMessageLabel.setLineWrap(true);
773            errorMessageLabel.setPaintBorderInsets(false);
774            errorMessageLabel.setBackgroundPainter(new MattePainter(UIManager.getColor(CLASS_NAME + ".errorBackground", getLocale()), true));
775            errorMessageLabel.setMaxLineSpan(320);
776            errorMessageLabel.setVisible(false);
777    
778            //aggregate the optional message label, content, and error label into
779            //the contentPanel
780            contentPanel = new JXPanel(new LoginPaneLayout());
781            contentPanel.setOpaque(false);
782            messageLabel.setBorder(BorderFactory.createEmptyBorder(12, 12, 7, 11));
783            contentPanel.add(messageLabel);
784            loginPanel.setBorder(BorderFactory.createEmptyBorder(0, 36, 7, 11));
785            contentPanel.add(loginPanel);
786            errorMessageLabel.setBorder(UIManager.getBorder(CLASS_NAME + ".errorBorder", getLocale()));
787            contentPanel.add(errorMessageLabel);
788    
789            //create the progress panel
790            progressPanel = new JXPanel(new GridBagLayout());
791            progressPanel.setOpaque(false);
792            progressMessageLabel = new JLabel(UIManagerExt.getString(CLASS_NAME + ".pleaseWait", getLocale()));
793            progressMessageLabel.setFont(UIManager.getFont(CLASS_NAME +".pleaseWaitFont", getLocale()));
794            JProgressBar pb = new JProgressBar();
795            pb.setIndeterminate(true);
796            JButton cancelButton = new JButton(getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND));
797            progressPanel.add(progressMessageLabel, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 12, 11, 11), 0, 0));
798            progressPanel.add(pb, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 24, 11, 7), 0, 0));
799            progressPanel.add(cancelButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 11, 11), 0, 0));
800    
801            //layout the panel
802            setLayout(new BorderLayout());
803            add(banner, BorderLayout.NORTH);
804            contentCardPane = new JPanel(new CardLayout());
805            contentCardPane.setOpaque(false);
806            contentCardPane.add(contentPanel, "0");
807            contentCardPane.add(progressPanel, "1");
808            add(contentCardPane, BorderLayout.CENTER);
809    
810        }
811    
812        private final class LoginPaneLayout extends VerticalLayout implements LayoutManager {
813            @Override
814        public Dimension preferredLayoutSize(Container parent) {
815                Insets insets = parent.getInsets();
816                Dimension pref = new Dimension(0, 0);
817                int gap = getGap();
818                for (int i = 0, c = parent.getComponentCount(); i < c; i++) {
819                  Component m = parent.getComponent(i);
820                  if (m.isVisible()) {
821                    Dimension componentPreferredSize = m.getPreferredSize();
822                    // swingx-917 - don't let jlabel to force width due to long text
823                    if (m instanceof JLabel) {
824                        View view = (View) ((JLabel)m).getClientProperty(BasicHTML.propertyKey);
825                        if (view != null) {
826                            view.setSize(pref.width, m.getHeight());
827                            // get fresh preferred size since we have forced new size on label
828                            componentPreferredSize = m.getPreferredSize();
829                        }
830                    } else {
831                        pref.width = Math.max(pref.width, componentPreferredSize.width);
832                    }
833                    pref.height += componentPreferredSize.height + gap;
834                  }
835                }
836    
837                pref.width += insets.left + insets.right;
838                pref.height += insets.top + insets.bottom;
839    
840                return pref;
841              }
842        }
843    
844        /**
845         * Create and return an image to use for the Banner. This may be overridden
846         * to return any image you like
847         */
848        protected Image createLoginBanner() {
849            return getUI() == null ? null : getUI().getBanner();
850        }
851    
852        /**
853         * Create and return an Action for logging in
854         */
855        protected Action createLoginAction() {
856            return new LoginAction(this);
857        }
858    
859        /**
860         * Create and return an Action for canceling login
861         */
862        protected Action createCancelAction() {
863            return new CancelAction(this);
864        }
865    
866        //------------------------------------------------------ Bean Properties
867        //REMEMBER: when adding new methods, they need to fire property change events!!!
868        /**
869         * @return Returns the saveMode.
870         */
871        public SaveMode getSaveMode() {
872            return saveMode;
873        }
874    
875        /**
876         * The save mode indicates whether the "save" password is checked by default. This method
877         * makes no difference if the passwordStore is null.
878         *
879         * @param saveMode The saveMode to set either SAVE_NONE, SAVE_PASSWORD or SAVE_USERNAME
880         */
881        public void setSaveMode(SaveMode saveMode) {
882            if (this.saveMode != saveMode) {
883                SaveMode oldMode = getSaveMode();
884                this.saveMode = saveMode;
885                recreateLoginPanel();
886                firePropertyChange("saveMode", oldMode, getSaveMode());
887            }
888        }
889        
890        public boolean isRememberPassword() {
891        return saveCB.isVisible() && saveCB.isSelected();
892        }
893    
894        /**
895         * @return the List of servers
896         */
897        public List<String> getServers() {
898            return Collections.unmodifiableList(servers);
899        }
900    
901        /**
902         * Sets the list of servers. See the servers field javadoc for more info.
903         */
904        public void setServers(List<String> servers) {
905            //only at startup
906            if (this.servers == null) {
907                this.servers = servers == null ? new ArrayList<String>() : servers;
908            } else if (this.servers != servers) {
909                List<String> old = getServers();
910                this.servers = servers == null ? new ArrayList<String>() : servers;
911                recreateLoginPanel();
912                firePropertyChange("servers", old, getServers());
913            }
914        }
915    
916        private LoginListener getDefaultLoginListener() {
917            if (defaultLoginListener == null) {
918                defaultLoginListener = new LoginListenerImpl();
919            }
920    
921            return defaultLoginListener;
922        }
923    
924        /**
925         * Sets the {@code LoginService} for this panel. Setting the login service
926         * to {@code null} will actually set the service to use
927         * {@code NullLoginService}.
928         *
929         * @param service
930         *            the service to set. If {@code service == null}, then a
931         *            {@code NullLoginService} is used.
932         */
933        public void setLoginService(LoginService service) {
934            LoginService oldService = getLoginService();
935            LoginService newService = service == null ? new NullLoginService() : service;
936    
937            //newService is guaranteed to be nonnull
938            if (!newService.equals(oldService)) {
939                if (oldService != null) {
940                    oldService.removeLoginListener(getDefaultLoginListener());
941                }
942    
943                loginService = newService;
944                this.loginService.addLoginListener(getDefaultLoginListener());
945    
946                firePropertyChange("loginService", oldService, getLoginService());
947            }
948        }
949    
950        /**
951         * Gets the <strong>LoginService</strong> for this panel.
952         *
953         * @return service service
954         */
955        public LoginService getLoginService() {
956            return loginService;
957        }
958    
959        /**
960         * Sets the <strong>PasswordStore</strong> for this panel.
961         *
962         * @param store PasswordStore
963         */
964        public void setPasswordStore(PasswordStore store) {
965            PasswordStore oldStore = getPasswordStore();
966            PasswordStore newStore = store == null ? new NullPasswordStore() : store;
967    
968            //newStore is guaranteed to be nonnull
969            if (!newStore.equals(oldStore)) {
970                passwordStore = newStore;
971    
972                firePropertyChange("passwordStore", oldStore, getPasswordStore());
973            }
974        }
975    
976        /**
977         * Gets the {@code UserNameStore} for this panel.
978         *
979         * @return the {@code UserNameStore}
980         */
981        public UserNameStore getUserNameStore() {
982            return userNameStore;
983        }
984    
985        /**
986         * Sets the user name store for this panel.
987         * @param store
988         */
989        public void setUserNameStore(UserNameStore store) {
990            UserNameStore oldStore = getUserNameStore();
991            UserNameStore newStore = store == null ? new DefaultUserNameStore() : store;
992    
993            //newStore is guaranteed to be nonnull
994            if (!newStore.equals(oldStore)) {
995                userNameStore = newStore;
996    
997                firePropertyChange("userNameStore", oldStore, getUserNameStore());
998            }
999        }
1000    
1001        /**
1002         * Gets the <strong>PasswordStore</strong> for this panel.
1003         *
1004         * @return store PasswordStore
1005         */
1006        public PasswordStore getPasswordStore() {
1007            return passwordStore;
1008        }
1009    
1010        /**
1011         * Sets the <strong>User name</strong> for this panel.
1012         *
1013         * @param username User name
1014         */
1015        public void setUserName(String username) {
1016            if (namePanel != null) {
1017                String old = getUserName();
1018                namePanel.setUserName(username);
1019                firePropertyChange("userName", old, getUserName());
1020            }
1021        }
1022    
1023        /**
1024         * Enables or disables <strong>User name</strong> for this panel.
1025         *
1026         * @param enabled 
1027         */
1028        public void setUserNameEnabled(boolean enabled) {
1029            boolean old = isUserNameEnabled();
1030            this.namePanelEnabled = enabled;
1031            if (namePanel != null) {
1032                namePanel.setEnabled(enabled);
1033                namePanel.setEditable(enabled);
1034            }
1035            firePropertyChange("userNameEnabled", old, isUserNameEnabled());
1036        }
1037        
1038        /**
1039         * Gets current state of the user name field. Field can be either disabled (false) for editing or enabled (true).
1040         * @return True when user name field is enabled and editable, false otherwise.
1041         */
1042        public boolean isUserNameEnabled() {
1043            return this.namePanelEnabled;
1044        }
1045    
1046        /**
1047         * Gets the <strong>User name</strong> for this panel.
1048         * @return the user name
1049         */
1050        public String getUserName() {
1051            return namePanel == null ? null : namePanel.getUserName();
1052        }
1053    
1054        /**
1055         * Sets the <strong>Password</strong> for this panel.
1056         *
1057         * @param password Password
1058         */
1059        public void setPassword(char[] password) {
1060            passwordField.setText(new String(password));
1061        }
1062    
1063        /**
1064         * Gets the <strong>Password</strong> for this panel.
1065         *
1066         * @return password Password
1067         */
1068        public char[] getPassword() {
1069            return passwordField.getPassword();
1070        }
1071    
1072        /**
1073         * Return the image used as the banner
1074         */
1075        public Image getBanner() {
1076            return banner.getImage();
1077        }
1078    
1079        /**
1080         * Set the image to use for the banner. If the {@code img} is {@code null},
1081         * then no image will be displayed.
1082         *
1083         * @param img
1084         *            the image to display
1085         */
1086        public void setBanner(Image img) {
1087            // we do not expose the ImagePanel, so we will produce property change
1088            // events here
1089            Image oldImage = getBanner();
1090    
1091            if (oldImage != img) {
1092                banner.setImage(img);
1093                firePropertyChange("banner", oldImage, getBanner());
1094            }
1095        }
1096    
1097        /**
1098         * Set the text to use when creating the banner. If a custom banner image is
1099         * specified, then this is ignored. If {@code text} is {@code null}, then
1100         * no text is displayed.
1101         *
1102         * @param text
1103         *            the text to display
1104         */
1105        public void setBannerText(String text) {
1106            if (text == null) {
1107                text = "";
1108            }
1109    
1110            if (!text.equals(this.bannerText)) {
1111                String oldText = this.bannerText;
1112                this.bannerText = text;
1113                //fix the login banner
1114                this.banner.setImage(createLoginBanner());
1115                firePropertyChange("bannerText", oldText, text);
1116            }
1117        }
1118    
1119        /**
1120         * Returns text used when creating the banner
1121         */
1122        public String getBannerText() {
1123            return bannerText;
1124        }
1125    
1126        /**
1127         * Returns the custom message for this login panel
1128         */
1129        public String getMessage() {
1130            return messageLabel.getText();
1131        }
1132    
1133        /**
1134         * Sets a custom message for this login panel
1135         */
1136        public void setMessage(String message) {
1137            String old = messageLabel.getText();
1138            messageLabel.setText(message);
1139            firePropertyChange("message", old, messageLabel.getText());
1140        }
1141    
1142        /**
1143         * Returns the error message for this login panel
1144         */
1145        public String getErrorMessage() {
1146            return errorMessageLabel.getText();
1147        }
1148    
1149        /**
1150         * Sets the error message for this login panel
1151         */
1152        public void setErrorMessage(String errorMessage) {
1153            isErrorMessageSet = true;
1154            String old = errorMessageLabel.getText();
1155            errorMessageLabel.setText(errorMessage);
1156            firePropertyChange("errorMessage", old, errorMessageLabel.getText());
1157        }
1158    
1159        /**
1160         * Returns the panel's status
1161         */
1162        public Status getStatus() {
1163            return status;
1164        }
1165    
1166        /**
1167         * Change the status
1168         */
1169        protected void setStatus(Status newStatus) {
1170            if (status != newStatus) {
1171                Status oldStatus = status;
1172                status = newStatus;
1173                firePropertyChange("status", oldStatus, newStatus);
1174            }
1175        }
1176    
1177        @Override
1178        public void setLocale(Locale l) {
1179            super.setLocale(l);
1180            reinitLocales(l);
1181        }
1182        //-------------------------------------------------------------- Methods
1183    
1184        /**
1185         * Initiates the login procedure. This method is called internally by
1186         * the LoginAction. This method handles cursor management, and actually
1187         * calling the LoginService's startAuthentication method. Method will return
1188         * immediately if asynchronous login is enabled or will block until
1189         * authentication finishes if <code>getSynchronous()</code> returns true.
1190         */
1191        protected void startLogin() {
1192            oldCursor = getCursor();
1193            try {
1194                setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1195                progressMessageLabel.setText(UIManagerExt.getString(CLASS_NAME + ".pleaseWait", getLocale()));
1196                String name = getUserName();
1197                char[] password = getPassword();
1198                String server = servers.size() == 1 ? servers.get(0) : serverCombo == null ? null : (String)serverCombo.getSelectedItem();
1199                
1200                loginService.startAuthentication(name, password, server);
1201            } catch(Exception ex) {
1202            //The status is set via the loginService listener, so no need to set
1203            //the status here. Just log the error.
1204            LOG.log(Level.WARNING, "Authentication exception while logging in", ex);
1205            } finally {
1206                setCursor(oldCursor);
1207            }
1208        }
1209    
1210        /**
1211         * Cancels the login procedure. Handles cursor management and interfacing
1212         * with the LoginService's cancelAuthentication method. Calling this method
1213         * has an effect only when authentication is still in progress (i.e. after
1214         * previous call to <code>startAuthentications()</code> and only when
1215         * authentication is performed asynchronously (<code>getSynchronous()</code>
1216         * returns false).
1217         */
1218        protected void cancelLogin() {
1219            progressMessageLabel.setText(UIManagerExt.getString(CLASS_NAME + ".cancelWait", getLocale()));
1220            getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND).setEnabled(false);
1221            loginService.cancelAuthentication();
1222            setCursor(oldCursor);
1223        }
1224    
1225        /**
1226         * Puts the password into the password store. If password store is not set, method will do
1227         * nothing.
1228         */
1229        protected void savePassword() {
1230            if (saveCB.isSelected()
1231                && (saveMode == SaveMode.BOTH || saveMode == SaveMode.PASSWORD)
1232                && passwordStore != null) {
1233                passwordStore.set(getUserName(),getLoginService().getServer(),getPassword());
1234            }
1235        }
1236    
1237        @Override
1238        public void removeNotify() {
1239            try {
1240                // TODO: keep it here until all ui stuff is moved to uidelegate.
1241                if (capsLockSupport)
1242                    KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(capsOnListener);
1243                Container c = getTLA();
1244                if (c instanceof Window) {
1245                    Window w = (Window) c;
1246                    w.removeWindowFocusListener(capsOnWinListener );
1247                    w.removeWindowListener(capsOnWinListener );
1248                }
1249            } catch (Exception e) {
1250                // bail out; probably running unsigned app distributed over web
1251            }
1252            super.removeNotify();
1253        }
1254    
1255        private Window getTLA() {
1256            Container c = JXLoginPane.this;
1257            // #810 bail out on first window found up the hierarchy (modal dialogs)
1258            while (!(c.getParent() == null || c instanceof Window)) {
1259                c = c.getParent();
1260            }
1261            return (Window) c;
1262        }
1263    
1264        @Override
1265        public void addNotify() {
1266            try {
1267                KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(
1268                        capsOnListener);
1269                Container c = getTLA();
1270                if (c instanceof Window) {
1271                    Window w = (Window) c;
1272                    w.addWindowFocusListener(capsOnWinListener );
1273                    w.addWindowListener(capsOnWinListener);
1274                }
1275            } catch (Exception e) {
1276                // probably unsigned app over web, disable capslock support and bail out
1277                capsLockSupport = false;
1278            }
1279            super.addNotify();
1280        }
1281        //--------------------------------------------- Listener Implementations
1282        /*
1283    
1284         For Login (initiated in LoginAction):
1285            0) set the status
1286            1) Immediately disable the login action
1287            2) Immediately disable the close action (part of enclosing window)
1288            3) initialize the progress pane
1289              a) enable the cancel login action
1290              b) set the message text
1291            4) hide the content pane, show the progress pane
1292    
1293         When cancelling (initiated in CancelAction):
1294             0) set the status
1295             1) Disable the cancel login action
1296             2) Change the message text on the progress pane
1297    
1298         When cancel finishes (handled in LoginListener):
1299             0) set the status
1300             1) hide the progress pane, show the content pane
1301             2) enable the close action (part of enclosing window)
1302             3) enable the login action
1303    
1304         When login fails (handled in LoginListener):
1305             0) set the status
1306             1) hide the progress pane, show the content pane
1307             2) enable the close action (part of enclosing window)
1308             3) enable the login action
1309             4) Show the error message
1310             5) resize the window (part of enclosing window)
1311    
1312         When login succeeds (handled in LoginListener):
1313             0) set the status
1314             1) close the dialog/frame (part of enclosing window)
1315         */
1316        /**
1317         * Listener class to track state in the LoginService
1318         */
1319        protected class LoginListenerImpl extends LoginAdapter {
1320            @Override
1321        public void loginSucceeded(LoginEvent source) {
1322                //save the user names and passwords
1323                String userName = namePanel.getUserName();
1324                if ((getSaveMode() == SaveMode.USER_NAME || getSaveMode() == SaveMode.BOTH)
1325                        && userName != null && !userName.trim().equals("")) {
1326                    userNameStore.addUserName(userName);
1327                    userNameStore.saveUserNames();
1328                }
1329                
1330                // if the user and/or password store knows of this user, 
1331                // and the checkbox is unchecked, we remove them, otherwise
1332                // we save the password
1333                if (saveCB.isSelected()) {
1334                savePassword();
1335                } else {
1336                // remove the password from the password store
1337                if (passwordStore != null) {
1338                    passwordStore.removeUserPassword(userName);
1339                }
1340                }
1341                
1342                setStatus(Status.SUCCEEDED);
1343            }
1344    
1345            @Override
1346        public void loginStarted(LoginEvent source) {
1347                assert EventQueue.isDispatchThread();
1348                getActionMap().get(LOGIN_ACTION_COMMAND).setEnabled(false);
1349                getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND).setEnabled(true);
1350    //            remove(contentPanel);
1351    //            add(progressPanel, BorderLayout.CENTER);
1352                ((CardLayout) contentCardPane.getLayout()).last(contentCardPane);
1353                revalidate();
1354                repaint();
1355                setStatus(Status.IN_PROGRESS);
1356            }
1357    
1358            @Override
1359        public void loginFailed(LoginEvent source) {
1360                assert EventQueue.isDispatchThread();
1361    //            remove(progressPanel);
1362    //            add(contentPanel, BorderLayout.CENTER);
1363                ((CardLayout) contentCardPane.getLayout()).first(contentCardPane);
1364                getActionMap().get(LOGIN_ACTION_COMMAND).setEnabled(true);
1365                errorMessageLabel.setVisible(true);
1366                revalidate();
1367                repaint();
1368                setStatus(Status.FAILED);
1369            }
1370    
1371            @Override
1372        public void loginCanceled(LoginEvent source) {
1373                assert EventQueue.isDispatchThread();
1374    //            remove(progressPanel);
1375    //            add(contentPanel, BorderLayout.CENTER);
1376                ((CardLayout) contentCardPane.getLayout()).first(contentCardPane);
1377                getActionMap().get(LOGIN_ACTION_COMMAND).setEnabled(true);
1378                errorMessageLabel.setVisible(false);
1379                revalidate();
1380                repaint();
1381                setStatus(Status.CANCELLED);
1382            }
1383        }
1384    
1385        //---------------------------------------------- Default Implementations
1386        /**
1387         * Action that initiates a login procedure. Delegates to JXLoginPane.startLogin
1388         */
1389        private static final class LoginAction extends AbstractActionExt {
1390            private static final long serialVersionUID = 7256761187925982485L;
1391            private JXLoginPane panel;
1392            public LoginAction(JXLoginPane p) {
1393                super(UIManagerExt.getString(CLASS_NAME + ".loginString", p.getLocale()), LOGIN_ACTION_COMMAND);
1394                this.panel = p;
1395            }
1396            public void actionPerformed(ActionEvent e) {
1397                panel.startLogin();
1398            }
1399            @Override
1400        public void itemStateChanged(ItemEvent e) {}
1401        }
1402    
1403        /**
1404         * Action that cancels the login procedure.
1405         */
1406        private static final class CancelAction extends AbstractActionExt {
1407            private static final long serialVersionUID = 4040029973355439229L;
1408            private JXLoginPane panel;
1409            public CancelAction(JXLoginPane p) {
1410                super(UIManagerExt.getString(CLASS_NAME + ".cancelLogin", p.getLocale()), CANCEL_LOGIN_ACTION_COMMAND);
1411                this.panel = p;
1412                this.setEnabled(false);
1413            }
1414            public void actionPerformed(ActionEvent e) {
1415                panel.cancelLogin();
1416            }
1417            @Override
1418            public void itemStateChanged(ItemEvent e) {}
1419        }
1420    
1421        /**
1422         * Simple login service that allows everybody to login. This is useful in demos and allows
1423         * us to avoid having to check for LoginService being null
1424         */
1425        private static final class NullLoginService extends LoginService {
1426            public boolean authenticate(String name, char[] password, String server) throws Exception {
1427                return true;
1428            }
1429    
1430        @Override
1431            public boolean equals(Object obj) {
1432                return obj instanceof NullLoginService;
1433            }
1434    
1435        @Override
1436            public int hashCode() {
1437                return 7;
1438            }
1439        }
1440    
1441        /**
1442         * Simple PasswordStore that does not remember passwords
1443         */
1444        private static final class NullPasswordStore extends PasswordStore {
1445        @Override
1446            public boolean set(String username, String server, char[] password) {
1447                //null op
1448                return false;
1449            }
1450        
1451        @Override
1452            public char[] get(String username, String server) {
1453                return new char[0];
1454            }
1455        
1456        @Override
1457        public void removeUserPassword(String username) {
1458            return;
1459        }
1460    
1461        @Override
1462            public boolean equals(Object obj) {
1463                return obj instanceof NullPasswordStore;
1464            }
1465    
1466        @Override
1467            public int hashCode() {
1468                return 7;
1469            }
1470        }
1471    
1472        //--------------------------------- Default NamePanel Implementations
1473        private static interface NameComponent {
1474            public String getUserName();
1475            public boolean isEnabled();
1476            public boolean isEditable();
1477            public void setEditable(boolean enabled);
1478            public void setEnabled(boolean enabled);
1479            public void setUserName(String userName);
1480            public JComponent getComponent();
1481        }
1482        
1483        private void updatePassword(final String username) {
1484            String password = "";
1485            if (username != null) {
1486                char[] pw = passwordStore.get(username, null);
1487                password = pw == null ? "" : new String(pw);
1488                
1489                // if the userstore has this username, we should change the 
1490                // 'remember me' checkbox to be selected. Unselecting this will
1491                // result in the user being 'forgotten'.
1492                saveCB.setSelected(userNameStore.containsUserName(username));
1493            }
1494            
1495            passwordField.setText(password);
1496        }
1497    
1498        /**
1499         * If a UserNameStore is not used, then this text field is presented allowing the user
1500         * to simply enter their user name
1501         */
1502        private final class SimpleNamePanel extends JTextField implements NameComponent {
1503            private static final long serialVersionUID = 6513437813612641002L;
1504    
1505        public SimpleNamePanel() {
1506            super("", 15);
1507            
1508            // auto-complete based on the users input
1509            // AutoCompleteDecorator.decorate(this, Arrays.asList(userNameStore.getUserNames()), false);
1510    
1511            // listen to text input, and offer password suggestion based on current
1512            // text
1513            if (passwordStore != null && passwordField!=null) {
1514            addKeyListener(new KeyAdapter() {
1515                @Override
1516                public void keyReleased(KeyEvent e) {
1517                updatePassword(getText());
1518                }
1519            });
1520            }
1521        }
1522            
1523            public String getUserName() {
1524                return getText();
1525            }
1526            public void setUserName(String userName) {
1527                setText(userName);
1528            }
1529            public JComponent getComponent() {
1530                return this;
1531            }
1532        }
1533        
1534        /**
1535         * If a UserNameStore is used, then this combo box is presented allowing the user
1536         * to select a previous login name, or type in a new login name
1537         */
1538        private final class ComboNamePanel extends JComboBox implements NameComponent {
1539            private static final long serialVersionUID = 2511649075486103959L;
1540    
1541            public ComboNamePanel() {
1542                super();
1543                setModel(new NameComboBoxModel());
1544                setEditable(true);
1545                
1546                // auto-complete based on the users input
1547                AutoCompleteDecorator.decorate(this);
1548    
1549                // listen to selection or text input, and offer password suggestion based on current
1550                // text
1551                if (passwordStore != null && passwordField!=null) {
1552                final JTextField textfield = (JTextField) getEditor().getEditorComponent();
1553                textfield.addKeyListener(new KeyAdapter() {
1554                    @Override
1555                    public void keyReleased(KeyEvent e) {
1556                    updatePassword(textfield.getText());
1557                    }
1558                });
1559                
1560                super.addItemListener(new ItemListener() {
1561                    public void itemStateChanged(ItemEvent e) {
1562                    updatePassword((String)getSelectedItem());
1563                    }
1564                });
1565                }
1566            }
1567            
1568            public String getUserName() {
1569                Object item = getModel().getSelectedItem();
1570                return item == null ? null : item.toString();
1571            }
1572            public void setUserName(String userName) {
1573                getModel().setSelectedItem(userName);
1574            }
1575            public void setUserNames(String[] names) {
1576                setModel(new DefaultComboBoxModel(names));
1577            }
1578            public JComponent getComponent() {
1579                return this;
1580            }
1581            
1582            private final class NameComboBoxModel extends AbstractListModel implements ComboBoxModel {
1583                private static final long serialVersionUID = 7097674687536018633L;
1584                private Object selectedItem;
1585                public void setSelectedItem(Object anItem) {
1586                    selectedItem = anItem;
1587                    fireContentsChanged(this, -1, -1);
1588                }
1589                public Object getSelectedItem() {
1590                    return selectedItem;
1591                }
1592                public Object getElementAt(int index) {
1593                    return userNameStore.getUserNames()[index];
1594                }
1595                public int getSize() {
1596                    return userNameStore.getUserNames().length;
1597                }
1598            }
1599        }
1600    
1601        //------------------------------------------ Static Construction Methods
1602        /**
1603         * Shows a login dialog. This method blocks.
1604         * @return The status of the login operation
1605         */
1606        public static Status showLoginDialog(Component parent, LoginService svc) {
1607            return showLoginDialog(parent, svc, null, null);
1608        }
1609    
1610        /**
1611         * Shows a login dialog. This method blocks.
1612         * @return The status of the login operation
1613         */
1614        public static Status showLoginDialog(Component parent, LoginService svc, PasswordStore ps, UserNameStore us) {
1615            return showLoginDialog(parent, svc, ps, us, null);
1616        }
1617    
1618        /**
1619         * Shows a login dialog. This method blocks.
1620         * @return The status of the login operation
1621         */
1622        public static Status showLoginDialog(Component parent, LoginService svc, PasswordStore ps, UserNameStore us, List<String> servers) {
1623            JXLoginPane panel = new JXLoginPane(svc, ps, us, servers);
1624            return showLoginDialog(parent, panel);
1625        }
1626    
1627        /**
1628         * Shows a login dialog. This method blocks.
1629         * @return The status of the login operation
1630         */
1631        public static Status showLoginDialog(Component parent, JXLoginPane panel) {
1632            Window w = WindowUtils.findWindow(parent);
1633            JXLoginDialog dlg =  null;
1634            if (w == null) {
1635                dlg = new JXLoginDialog((Frame)null, panel);
1636            } else if (w instanceof Dialog) {
1637                dlg = new JXLoginDialog((Dialog)w, panel);
1638            } else if (w instanceof Frame) {
1639                dlg = new JXLoginDialog((Frame)w, panel);
1640            } else {
1641                throw new AssertionError("Shouldn't be able to happen");
1642            }
1643            dlg.setVisible(true);
1644            return dlg.getStatus();
1645        }
1646    
1647        /**
1648         * Shows a login frame. A JFrame is not modal, and thus does not block
1649         */
1650        public static JXLoginFrame showLoginFrame(LoginService svc) {
1651            return showLoginFrame(svc, null, null);
1652        }
1653    
1654        /**
1655         */
1656        public static JXLoginFrame showLoginFrame(LoginService svc, PasswordStore ps, UserNameStore us) {
1657            return showLoginFrame(svc, ps, us, null);
1658        }
1659    
1660        /**
1661         */
1662        public static JXLoginFrame showLoginFrame(LoginService svc, PasswordStore ps, UserNameStore us, List<String> servers) {
1663            JXLoginPane panel = new JXLoginPane(svc, ps, us, servers);
1664            return showLoginFrame(panel);
1665        }
1666    
1667        /**
1668         */
1669        public static JXLoginFrame showLoginFrame(JXLoginPane panel) {
1670            return new JXLoginFrame(panel);
1671        }
1672    
1673        public static final class JXLoginDialog extends JDialog {
1674            private static final long serialVersionUID = -3185639594267828103L;
1675            private JXLoginPane panel;
1676    
1677            public JXLoginDialog(Frame parent, JXLoginPane p) {
1678                super(parent, true);
1679                init(p);
1680            }
1681    
1682            public JXLoginDialog(Dialog parent, JXLoginPane p) {
1683                super(parent, true);
1684                init(p);
1685            }
1686    
1687            protected void init(JXLoginPane p) {
1688                setTitle(UIManagerExt.getString(CLASS_NAME + ".titleString", getLocale()));
1689                this.panel = p;
1690                initWindow(this, panel);
1691            }
1692        
1693            public JXLoginPane.Status getStatus() {
1694                return panel.getStatus();
1695            }
1696        }
1697    
1698        public static final class JXLoginFrame extends JXFrame {
1699            private static final long serialVersionUID = -9016407314342050807L;
1700            private JXLoginPane panel;
1701    
1702            public JXLoginFrame(JXLoginPane p) {
1703                super(UIManagerExt.getString(CLASS_NAME + ".titleString", p.getLocale()));
1704                JXPanel cp = new JXPanel();
1705                cp.setOpaque(true);
1706                setContentPane(cp);
1707                this.panel = p;
1708                initWindow(this, panel);
1709            }
1710    
1711            @Override
1712        public JXPanel getContentPane() {
1713                return (JXPanel) super.getContentPane();
1714            }
1715    
1716            public JXLoginPane.Status getStatus() {
1717                return panel.getStatus();
1718            }
1719    
1720            public JXLoginPane getPanel() {
1721                return panel;
1722            }
1723        }
1724    
1725        /**
1726         * Utility method for initializing a Window for displaying a LoginDialog.
1727         * This is particularly useful because the differences between JFrame and
1728         * JDialog are so minor.
1729         *
1730         * Note: This method is package private for use by JXLoginDialog (proper,
1731         * not JXLoginPane.JXLoginDialog). Change to private if JXLoginDialog is
1732         * removed.
1733         */
1734        static void initWindow(final Window w, final JXLoginPane panel) {
1735            w.setLayout(new BorderLayout());
1736            w.add(panel, BorderLayout.CENTER);
1737            JButton okButton = new JButton(panel.getActionMap().get(LOGIN_ACTION_COMMAND));
1738            final JButton cancelButton = new JButton(
1739                    UIManagerExt.getString(CLASS_NAME + ".cancelString", panel.getLocale()));
1740            cancelButton.addActionListener(new ActionListener() {
1741                public void actionPerformed(ActionEvent e) {
1742                    //change panel status to canceled!
1743                    panel.status = JXLoginPane.Status.CANCELLED;
1744                    w.setVisible(false);
1745                    w.dispose();
1746                }
1747            });
1748            panel.addPropertyChangeListener("status", new PropertyChangeListener() {
1749                public void propertyChange(PropertyChangeEvent evt) {
1750                    JXLoginPane.Status status = (JXLoginPane.Status)evt.getNewValue();
1751                    switch (status) {
1752                        case NOT_STARTED:
1753                            break;
1754                        case IN_PROGRESS:
1755                            cancelButton.setEnabled(false);
1756                            break;
1757                        case CANCELLED:
1758                            cancelButton.setEnabled(true);
1759                            w.pack();
1760                            break;
1761                        case FAILED:
1762                            cancelButton.setEnabled(true);
1763                            panel.passwordField.requestFocusInWindow();
1764                            w.pack();
1765                            break;
1766                        case SUCCEEDED:
1767                            w.setVisible(false);
1768                            w.dispose();
1769                    }
1770                    for (PropertyChangeListener l : w.getPropertyChangeListeners("status")) {
1771                        PropertyChangeEvent pce = new PropertyChangeEvent(w, "status", evt.getOldValue(), evt.getNewValue());
1772                        l.propertyChange(pce);
1773                    }
1774                }
1775            });
1776            // FIX for #663 - commented out two lines below. Not sure why they were here in a first place.
1777            // cancelButton.setText(UIManager.getString(CLASS_NAME + ".cancelString"));
1778            // okButton.setText(UIManager.getString(CLASS_NAME + ".loginString"));
1779            JXBtnPanel buttonPanel = new JXBtnPanel(okButton, cancelButton);
1780            buttonPanel.setOpaque(false);
1781            panel.setButtonPanel(buttonPanel);
1782            JXPanel controls = new JXPanel(new FlowLayout(FlowLayout.RIGHT));
1783            controls.setOpaque(false);
1784            new BoxLayout(controls, BoxLayout.X_AXIS);
1785            controls.add(Box.createHorizontalGlue());
1786            controls.add(buttonPanel);
1787            w.add(controls, BorderLayout.SOUTH);
1788            w.addWindowListener(new WindowAdapter() {
1789                public void windowClosing(java.awt.event.WindowEvent e) {
1790                    panel.cancelLogin();
1791                }
1792            });
1793    
1794            if (w instanceof JFrame) {
1795                final JFrame f = (JFrame)w;
1796                f.getRootPane().setDefaultButton(okButton);
1797                f.setResizable(false);
1798                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1799                KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
1800                ActionListener closeAction = new ActionListener() {
1801                    public void actionPerformed(ActionEvent e) {
1802                        f.setVisible(false);
1803                        f.dispose();
1804                    }
1805                };
1806                f.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW);
1807            } else if (w instanceof JDialog) {
1808                final JDialog d = (JDialog)w;
1809                d.getRootPane().setDefaultButton(okButton);
1810                d.setResizable(false);
1811                KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
1812                ActionListener closeAction = new ActionListener() {
1813                    public void actionPerformed(ActionEvent e) {
1814                        d.setVisible(false);
1815                    }
1816                };
1817                d.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW);
1818            }
1819            w.pack();
1820            w.setLocation(WindowUtils.getPointForCentering(w));
1821        }
1822    
1823        private void setButtonPanel(JXBtnPanel buttonPanel) {
1824            this.buttonPanel = buttonPanel;
1825        }
1826        
1827        private static class JXBtnPanel extends JXPanel {
1828            private static final long serialVersionUID = 4136611099721189372L;
1829            private JButton cancel;
1830            private JButton ok;
1831    
1832            public JXBtnPanel(JButton okButton, JButton cancelButton) {
1833                GridLayout layout = new GridLayout(1,2);
1834                layout.setHgap(5);
1835                setLayout(layout);
1836                this.ok = okButton;
1837                this.cancel = cancelButton;
1838                add(okButton);
1839                add(cancelButton);
1840                setBorder(new EmptyBorder(0,0,7,11));
1841            }
1842    
1843            /**
1844             * @return the cancel button.
1845             */
1846            public JButton getCancel() {
1847                return cancel;
1848            }
1849    
1850            /**
1851             * @return the ok button.
1852             */
1853            public JButton getOk() {
1854                return ok;
1855            }
1856    
1857        }
1858    
1859        private final class CapsOnTest {
1860    
1861            RemovableKeyEventDispatcher ked;
1862    
1863            public void runTest() {
1864                boolean success = false;
1865                // there's an issue with this - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4414164
1866                // TODO: check the progress from time to time
1867                //try {
1868                //     java.awt.Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK);
1869                //} catch (Exception ex) {
1870                //ex.printStackTrace();
1871                //success = false;
1872                //}
1873                if (!success) {
1874                    try {
1875                        KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
1876                        // #swingx-697
1877                        // In some cases panel is not focused after the creation leaving bogus dispatcher in place. If found remove it.
1878                        if (ked != null) {
1879                            kfm.removeKeyEventDispatcher(ked);
1880                        }
1881                        // Temporarily installed listener with auto-uninstall after
1882                        // test is finished.
1883                        ked = new RemovableKeyEventDispatcher(this);
1884                        kfm.addKeyEventDispatcher(ked);
1885                        Robot r = new Robot();
1886                        isTestingCaps = true;
1887                        r.keyPress(65);
1888                        r.keyRelease(65);
1889                        r.keyPress(KeyEvent.VK_BACK_SPACE);
1890                        r.keyRelease(KeyEvent.VK_BACK_SPACE);
1891                    } catch (Exception e1) {
1892                        // this can happen for example due to security reasons in unsigned applets
1893                        // when we can't test caps lock state programatically bail out silently
1894    
1895                        // no matter what's the cause - uninstall
1896                        ked.uninstall();
1897                    }
1898                }
1899            }
1900    
1901            public void clean() {
1902                if (ked != null) {
1903                    ked.cleanOnBogusFocus();
1904                }
1905            }
1906        }
1907    
1908        /**
1909         * Window event listener to invoke capslock test when login panel get
1910         * activated.
1911         */
1912        private final class CapsOnWinListener extends WindowAdapter implements
1913                WindowFocusListener, WindowListener {
1914            private CapsOnTest cot;
1915    
1916            private long stamp;
1917    
1918            public CapsOnWinListener(CapsOnTest cot) {
1919                this.cot = cot;
1920            }
1921    
1922            public void windowActivated(WindowEvent e) {
1923                cot.runTest();
1924                stamp = System.currentTimeMillis();
1925            }
1926    
1927            public void windowGainedFocus(WindowEvent e) {
1928                // repeat test only if more then 20ms passed between activation test
1929                // and now.
1930                if (stamp + 20 < System.currentTimeMillis()) {
1931                    cot.runTest();
1932                }
1933            }
1934    
1935            @Override
1936            public void windowOpened(WindowEvent arg0) {
1937                cot.clean();
1938            }
1939    
1940        }
1941    
1942        private class RemovableKeyEventDispatcher implements KeyEventDispatcher {
1943    
1944            private CapsOnTest cot;
1945    
1946            private boolean tested = false;
1947    
1948            private int retry = 0;
1949    
1950            public RemovableKeyEventDispatcher(CapsOnTest capsOnTest) {
1951                this.cot = capsOnTest;
1952            }
1953    
1954            public boolean dispatchKeyEvent(KeyEvent e) {
1955                tested = true;
1956                if (e.getID() != KeyEvent.KEY_PRESSED) {
1957                    return true;
1958                }
1959                if (isTestingCaps && e.getKeyCode() > 64 && e.getKeyCode() < 91) {
1960                    setCapsLock(!e.isShiftDown()
1961                            && Character.isUpperCase(e.getKeyChar()));
1962                }
1963                if (isTestingCaps && (e.getKeyCode() == KeyEvent.VK_BACK_SPACE)) {
1964                    // uninstall
1965                    uninstall();
1966                    retry = 0;
1967                }
1968                return true;
1969            }
1970    
1971            void uninstall() {
1972                isTestingCaps = false;
1973                KeyboardFocusManager.getCurrentKeyboardFocusManager()
1974                        .removeKeyEventDispatcher(this);
1975                if (cot.ked == this) {
1976                    cot.ked = null;
1977                }
1978            }
1979    
1980            void cleanOnBogusFocus() {
1981                // #799 happens on windows when focus is lost during initialization of program since focusLost() even is not issued by jvm in this case (WinXP-WinVista only)
1982                SwingUtilities.invokeLater(new Runnable() {
1983                    public void run() {
1984                        if (!tested) {
1985                            uninstall();
1986                            if (retry < 3) {
1987                                // try 3 times to regain the focus
1988                                Window w = JXLoginPane.this.getTLA();
1989                                if (w != null) {
1990                                    w.toFront();
1991                                }
1992                                cot.runTest();
1993                                retry++;
1994                            }
1995                        }
1996                    }
1997                });
1998            }
1999        }
2000    }