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.BorderLayout;
21  import java.awt.Dimension;
22  import java.awt.event.ActionEvent;
23  import java.awt.event.ActionListener;
24  import java.awt.event.FocusEvent;
25  import java.awt.event.FocusListener;
26  
27  import javax.swing.JButton;
28  import javax.swing.JPanel;
29  import javax.swing.JTextField;
30  
31  /**
32   * Abstract class for fields that get its contents from a popup component.
33   *
34   * Implementations of this class should define a graphic component that will
35   * display in a popup windows. The openPopup() method must be able to launch
36   * this popup window. If the popup is modalless, this method must also call
37   * postCheckChange() once the data manipulation has finished.
38   *
39   * The default implementation works if the value managed by the component is
40   * the String shown at the text field. In any other case, the methods
41   * setValue() and getValue() should be implemented to discern between the value
42   * and its String representation. IMPORTANT: getValue() should return a copy of
43   * the object if it can be modified from outside (the original implementation
44   * returns String, that are inmutable).
45   * Changes are detected at the method hasChanged() from comparing the values
46   * stored before and after a possible modification (at the private fields
47   * <code>previousValue</code> and <code>newValue</code>). These values are
48   * set by the calls to preCheckChanges() and postCheckChanges().
49   * postCheckChanges() also calls the hasChangedMethod() and, if it returns
50   * true, launches a PropertyChanged event reporting the change.
51   * Finally, the implementation of FocusListener only reacts to focus events
52   * originated by the text field. Objects from this class and its
53   * implementations should not be added as focus listeners of any component.
54   *
55   * @author SJM
56   */
57  public abstract class PopupTextField extends JPanel implements FocusListener {
58    /**
59     * Default right button text.
60     */
61    private static final String DEFAULT_BUTTON_TEXT = "...";
62  
63    /**
64     * Popup button.
65     */
66    private JButton popupLauncher = new JButton(DEFAULT_BUTTON_TEXT);
67  
68    /**
69     * The text field.
70     */
71    private JTextField textField = new JTextField(10);
72  
73    /**
74     * The name of a property for storing the text field value.
75     */
76    private String propertyName = "internalPopupTextFieldValue";
77  
78    /**
79     * The previous value.
80     */
81    private Object previousValue = null;
82  
83    /**
84     * The new value.
85     */
86    private Object newValue = null;
87  
88    /**
89     * Default constructor.
90     *
91     * Default constructor. It should be called for every constructor of the
92     * implementations of this abstract class.
93     */
94    public PopupTextField() {
95      super(new BorderLayout());
96  
97      Dimension d = this.textField.getPreferredSize();
98      d.width = 220;
99      this.setPreferredSize(d.getSize());
100     this.textField.setEditable(true);
101     this.textField.addFocusListener(this);
102 
103     d.width = d.height;
104     this.popupLauncher.setPreferredSize(d.getSize());
105 
106     this.popupLauncher.addActionListener(new ActionListener() {
107       public void actionPerformed(ActionEvent actionEvent) {
108         preCheckChanges();
109         PopupTextField.this.openPopup();
110         postCheckChanges();
111       }
112     });
113 
114     this.add(this.textField, BorderLayout.CENTER);
115     this.add(this.popupLauncher, BorderLayout.LINE_END);
116   }
117 
118   /**
119    * {@inheritDoc}
120    */
121   public void setEnabled(boolean enabled) {
122     super.setEnabled(enabled);
123     this.textField.setEnabled(enabled);
124     this.popupLauncher.setEnabled(enabled);
125   }
126 
127   /**
128    * Returns the value stored by the component.
129    *
130    * Returns the value managed by the component. The default implementation
131    * just returns the String displayed at the text field; reimplement this
132    * method if the value shown is not the same than the actual value. This is
133    * the value used to check for changes in hasChanged();
134    *
135    * @return an Object with the value managed by the component.
136    *
137    * @see #hasChanged()
138    */
139   public Object getValue() {
140     return this.textField.getText();
141   }
142 
143   /**
144    * Sets the value managed by the component.
145    * <p>
146    * The default implementation just sets the text of the text field to the result
147    * of doing toString to <code>value</code>(or the empty String if <code>value</code>
148    * is null). Reimplement this method if the value shown is not the same than the actual
149    * value. This is the value used to check for changes in hasChanged();
150    *
151    * @param value The value to set
152    * @see #hasChanged()
153    */
154   public void setValue(Object value) {
155     if (value == null) {
156       this.textField.setText("");
157     } else {
158       this.textField.setText(value.toString());
159     }
160   }
161 
162   /**
163    * Gets the property name of the component.
164    *
165    * Gets the property name of the component. The only use of the property name
166    * is as a parameter when firing an event of property change. The default
167    * value is ""; but as BasePanel#watch(PopupTextField) just ignores the
168    * property name, it usually does not need to be changed. This method is
169    * provided for the case that more sophisticated PropertyChangeListener are
170    * added to the list of listeners of this object.
171    *
172    * @return a String with the property name of the component.
173    */
174   public final String getPropertyName() {
175     return this.propertyName;
176   }
177 
178   /**
179    * Sets the text to display at the button.
180    *
181    * @param buttonText a String with the new text to display at the button.
182    */
183   public final void setButtonText(String buttonText) {
184     this.popupLauncher.setText(buttonText);
185   }
186 
187   /**
188    * Gets the text to display at the button.
189    *
190    * @return a String with the text displayed at the button.
191    */
192   public final String getButtonText() {
193     return this.popupLauncher.getText();
194   }
195 
196   /**
197    * Sets if the field can be modified by the user or not.
198    *
199    * @param textEditable a boolean that is true if and only if the user should
200    * be able to edit the text field.
201    */
202   public final void setFieldEditable(boolean textEditable) {
203     this.textField.setEditable(textEditable);
204   }
205 
206   /**
207    * Gets if the field can be modified by the user or not.
208    *
209    * @return a boolean that is true if and only if the user can edit the text
210    * field.
211    */
212   public final boolean isFieldEditable() {
213     return this.textField.isEditable();
214   }
215 
216   /**
217    * Stores the value of the component value to check for future changes.
218    *
219    * Stores the current value of the component value to check for changes. This
220    * method should be called before every possible ocassion in which the value
221    * managed by the component could be modified. In the default implementation,
222    * it is before opening the popup or when the text field gets the focus. The
223    * checks are made in the postCheckChanges method.
224    *
225    * @see #postCheckChanges()
226    */
227   public final void preCheckChanges() {
228     this.previousValue = this.getValue();
229   }
230 
231   /**
232    * Checks for changes in the value and, in that case, fires an event.
233    *
234    * Checks for changes in the value managed by the component. The check is
235    * made calling the hasChanged() method. If a change is detected, a
236    * PropertyChanged event reporting this is fired. This method should be
237    * called after every possible ocassion in which the value managed by the
238    * component could have been modified. In the default implementation, it is
239    * after calling openPopup() or when the text field loses the focus.
240    */
241   public final void postCheckChanges() {
242     this.newValue = this.getValue();
243     if (hasChanged()) {
244       this.firePropertyChange(
245         this.propertyName, this.previousValue, this.newValue);
246     }
247   }
248 
249   /**
250    * Sets the previous value as the current one.
251    */
252   public final void discard() {
253     this.setValue(this.previousValue);
254   }
255 
256   /**
257    * Opens a popup to ask the user for data.
258    *
259    * Opens a popup with the UI specific for the data type. Once this
260    * method returns, the old and new values of the component will be checked.
261    * If the popup is not modal (the method returns before the popup has been
262    * closed), the popup must call the postCheckChange() method after changing
263    * the value managed by the component.
264    */
265   protected abstract void openPopup();
266 
267   /**
268    * Tells if the value of the component has changed.
269    *
270    * Tells if the value of the component has changed. This must be done by
271    * comparing the <code>previousValue</code> and <code>newValue</code> set by
272    * <code>preCheckChanges()</code> and <code>postCheckChanges()</code>.
273    * The default implementation returns true if and only if both objects are
274    * null or if the equals() method of them returns false.
275    *
276    * @return a boolean that is true if the previous and new values are not the
277    * same.
278    * @see #preCheckChanges()
279    * @see #postCheckChanges()
280    */
281   public boolean hasChanged() {
282     if (this.previousValue == null) {
283       return (this.newValue != null);
284     }
285     return (!this.previousValue.equals(this.newValue));
286   }
287 
288   /**
289    * Receives an event of focus gained.
290    *
291    * Receives an event of focus gained. This method will only react to focus
292    * events originated by the text field of the component, and does it by
293    * storing its current value for later comparisons of changes.
294    *
295    * @param focusEvent the FocusEvent with the information of what has
296    * happened.
297    *
298    * @see #preCheckChanges()
299    */
300   public final void focusGained(FocusEvent focusEvent) {
301     // SJM: Only reacts to the textField.
302     if (focusEvent.getComponent() == this.textField) {
303       this.preCheckChanges();
304     }
305   }
306 
307   /**
308    * Receives an event of focus lost.
309    *
310    * Receives an event of focus lost. This method will only react to focus
311    * events originated by the text field of the component, and does it by
312    * storing checking for changes in its value.
313    *
314    * @param focusEvent the FocusEvent with the information of what has
315    * happened.
316    *
317    * @see #postCheckChanges()
318    */
319   public final void focusLost(FocusEvent focusEvent) {
320     // SJM: Only reacts to the textField.
321     if (focusEvent.getComponent() == this.textField) {
322       this.postCheckChanges();
323     }
324   }
325 }