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 }