View Javadoc

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 }