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 }