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