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 }