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 }