1 /* 2 Copyright (C) 2000 - 2007 Grid Systems, S.A. 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License, version 2, as 6 published by the Free Software Foundation. 7 8 This program is distributed in the hope that it will be useful, 9 but WITHOUT ANY WARRANTY; without even the implied warranty of 10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License 14 along with this program; if not, write to the Free Software 15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 */ 17 18 package com.gridsystems.config.tools.swing; 19 20 import java.awt.Dimension; 21 import java.awt.GridBagLayout; 22 import java.awt.GridBagConstraints; 23 import java.awt.event.FocusEvent; 24 import java.awt.event.FocusListener; 25 import java.util.ResourceBundle; 26 import javax.swing.InputVerifier; 27 import javax.swing.JLabel; 28 import javax.swing.JOptionPane; 29 import javax.swing.JPanel; 30 import javax.swing.JPasswordField; 31 32 /** 33 * Component that asks the passwords twice to avoid errors. 34 * 35 * This component presents two password fields. The user must fill both fields 36 * with the same value in order to change the value managed by the component. 37 * The component automatically compares the two values and launches an error 38 * popup when they are not the same. The comparison is done every time one of 39 * the fields loses the focus and it is not gained by the other field of the 40 * component. 41 * 42 * @warning It is pending the implementation of a system to report to the upper 43 * layer that the value managed by the component has been successfully changed 44 * (see #postCheckChanges()) 45 * 46 * @author SJM 47 */ 48 public class PasswordField extends JPanel implements FocusListener { 49 /** 50 * Fields width in characters. 51 */ 52 private static final int PASSWORD_FIELD_LENGTH = 15; 53 54 /** 55 * First password field. 56 */ 57 private JPasswordField jPasswordField1 = new JPasswordField(PASSWORD_FIELD_LENGTH); 58 59 /** 60 * Second password field (for confirmation). 61 */ 62 private JPasswordField jPasswordField2 = new JPasswordField(PASSWORD_FIELD_LENGTH); 63 64 /** 65 * Label for the first field. 66 */ 67 private JLabel jLabel1 = new JLabel(); 68 69 /** 70 * Label for the second field. 71 */ 72 private JLabel jLabel2 = new JLabel(); 73 74 /** 75 * Previous value. 76 */ 77 private String previousValue = null; 78 79 /** 80 * I18N resource bundle. 81 */ 82 private ResourceBundle resourceBundle = null; 83 84 /** 85 * Constructor. 86 * 87 * @param resourceBundle the ResourceBundle from where the component gets 88 * the texts to display for labels and messages. If it is null, default 89 * messages (in english) are used. 90 * @param originalValue a String with the original value of the password. It 91 * can be null. 92 */ 93 public PasswordField(ResourceBundle resourceBundle, String originalValue) { 94 this.resourceBundle = resourceBundle; 95 this.setPreferredSize(new Dimension(250, 40)); 96 this.jPasswordField1.addFocusListener(this); 97 this.jPasswordField2.addFocusListener(this); 98 if (resourceBundle == null) { 99 this.jLabel1.setText(resourceBundle.getString("password.ask")); 100 this.jLabel2.setText(resourceBundle.getString("password.repeat")); 101 } else { 102 // SJM: Default values. 103 this.jLabel1.setText("Enter password"); 104 this.jLabel2.setText("Repeat password"); 105 } 106 this.setLayout(new GridBagLayout()); 107 GridBagConstraints gridBagConstraints = new GridBagConstraints(); 108 gridBagConstraints.fill = GridBagConstraints.BOTH; 109 gridBagConstraints.weightx = 1.0; 110 gridBagConstraints.gridwidth = GridBagConstraints.RELATIVE; 111 this.add(this.jLabel1, gridBagConstraints); 112 gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER; 113 this.add(this.jPasswordField1, gridBagConstraints); 114 gridBagConstraints.gridwidth = GridBagConstraints.RELATIVE; 115 this.add(this.jLabel2, gridBagConstraints); 116 gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER; 117 this.add(this.jPasswordField2, gridBagConstraints); 118 } 119 120 /** 121 * Sets an input verifier that affects each of both password fields. 122 * 123 * Sets an input verifier that affects each of both password fields. It 124 * should only check that the field values are correct (length, allowed 125 * characters) but must not check that both fields match, that comparison is 126 * done automatically by the control when needed. 127 * 128 * @param inputVerifier the InputVerifier to check each of the password 129 * fields. 130 */ 131 public void setInputVerifier(InputVerifier inputVerifier) { 132 this.jPasswordField1.setInputVerifier(inputVerifier); 133 this.jPasswordField2.setInputVerifier(inputVerifier); 134 } 135 136 /** 137 * Gets the input verifier that affects each of both password fields. 138 * 139 * @return the InputVerifier that checks each of the password fields. 140 */ 141 public InputVerifier getInputVerifier() { 142 return this.jPasswordField1.getInputVerifier(); 143 } 144 145 /** 146 * Returns the password managed by the component. 147 * 148 * @return an String with the password. 149 */ 150 public String getValue() { 151 return new String(this.jPasswordField1.getPassword()); 152 } 153 154 /** 155 * Sets the password managed by the component. 156 * 157 * Sets the password managed by the component. Sets the value to both fields 158 * of the component to the value passed. 159 * 160 * @param value a String with the password. 161 */ 162 public void setValue(String value) { 163 if (value == null) { 164 this.jPasswordField1.setText(""); 165 this.jPasswordField2.setText(""); 166 } else { 167 this.jPasswordField1.setText(value.toString()); 168 this.jPasswordField2.setText(value.toString()); 169 } 170 } 171 172 /** 173 * Stores the value of the component value to check for future changes. 174 * 175 * Stores the current value of the component value to check for changes. This 176 * method should be called before every possible ocassion in which the value 177 * managed by the component could be modified. In the default implementation, 178 * it is before opening the popup or when the text field gets the focus. The 179 * checks are made in the postCheckChanges method. 180 * 181 * @see #postCheckChanges() 182 */ 183 public final void preCheckChanges() { 184 this.previousValue = this.getValue(); 185 } 186 187 /** 188 * Checks for changes in the value and, in that case, fires an event. 189 * 190 * Checks for changes in the value managed by the component. The check is 191 * made calling the hasChanged() method. If a change is detected, a 192 * PropertyChanged event reporting this is fired. This method should be 193 * called after every possible ocassion in which the value managed by the 194 * component could have been modified. In the default implementation, it is 195 * after calling openPopup() or when the text field loses the focus. 196 */ 197 public final void postCheckChanges() { 198 String password1 = new String(this.jPasswordField1.getPassword()); 199 String password2 = new String(this.jPasswordField2.getPassword()); 200 if (!password1.equals(password2)) { 201 if (this.resourceBundle == null) { 202 JOptionPane.showMessageDialog(this, "The passwords do not match", 203 "Error", JOptionPane.ERROR_MESSAGE); 204 } else { 205 JOptionPane.showMessageDialog(this, 206 this.resourceBundle.getString("error.password.mismatch"), "Error", 207 JOptionPane.ERROR_MESSAGE); 208 } 209 this.setValue(null); 210 } 211 } 212 213 /** 214 * Sets the previous value as the current one. 215 */ 216 public final void discard() { 217 this.setValue(this.previousValue); 218 } 219 220 /** 221 * Reacts to a Component losing the focus. 222 * 223 * Reacts to a Component losing the focus. Objects of this class should not 224 * be used as FocusListener of any object (even themselves); its 225 * implementation of FocusListener is for internal use only. It allows them 226 * to know if a field loses focus to the other field of the componenent (in 227 * that case there is no need for checks) or to an external component (in 228 * that case the fields are checked to make sure that both values match). 229 * 230 * @param event the FocusEvent with the information of the event that 231 * happened. 232 */ 233 public void focusLost(FocusEvent event) { 234 // SJM: If the focus passes from one of the text fields to another, ignore 235 // it. It is still inside the control. We use == instead of equals() 236 // because we must check that is exactly the same object, not an equivalent 237 // one. 238 if ((event.getOppositeComponent() == this.jPasswordField1) 239 || (event.getOppositeComponent() == this.jPasswordField2)) { 240 return; 241 } 242 this.postCheckChanges(); 243 } 244 245 /** 246 * Reacts to a Component gaining the focus. 247 * 248 * Reacts to a Component gaining the focus. Objects of this class should not 249 * be used as FocusListener of any object (even themselves); its 250 * implementation of FocusListener is for internal use only. It allows them 251 * to know if a field gains focus from the other field of the componenent (in 252 * that case there is no need for checks) or from an external component (in 253 * that case the component current value is stored to compare the values for 254 * changes and/or to discard the changes). 255 * 256 * @param event the FocusEvent with the information of the event that 257 * happened. 258 */ 259 public void focusGained(FocusEvent event) { 260 // SJM: If the focus comes from one of the fields, it is still inside the 261 // component so we do not need to do nothing. 262 if (event.getOppositeComponent() == this.jPasswordField1 263 || event.getOppositeComponent() == this.jPasswordField2) { 264 return; 265 } 266 // Store the current value just in case we must cancel the changes. 267 this.preCheckChanges(); 268 } 269 }