Showing the changes to java/pkg/javax/swing/JFormattedTextField.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,1187 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | package javax.swing; | ||
| 26 | |||
| 27 | import java.awt.*; | ||
| 28 | import java.awt.event.*; | ||
| 29 | import java.awt.im.InputContext; | ||
| 30 | import java.io.*; | ||
| 31 | import java.text.*; | ||
| 32 | import java.util.*; | ||
| 33 | import javax.swing.UIManager; | ||
| 34 | import javax.swing.event.*; | ||
| 35 | import javax.swing.plaf.UIResource; | ||
| 36 | import javax.swing.text.*; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * <code>JFormattedTextField</code> extends <code>JTextField</code> adding | ||
| 40 | * support for formatting arbitrary values, as well as retrieving a particular | ||
| 41 | * object once the user has edited the text. The following illustrates | ||
| 42 | * configuring a <code>JFormattedTextField</code> to edit dates: | ||
| 43 | * <pre> | ||
| 44 | * JFormattedTextField ftf = new JFormattedTextField(); | ||
| 45 | * ftf.setValue(new Date()); | ||
| 46 | * </pre> | ||
| 47 | * <p> | ||
| 48 | * Once a <code>JFormattedTextField</code> has been created, you can | ||
| 49 | * listen for editing changes by way of adding | ||
| 50 | * a <code>PropertyChangeListener</code> and listening for | ||
| 51 | * <code>PropertyChangeEvent</code>s with the property name <code>value</code>. | ||
| 52 | * <p> | ||
| 53 | * <code>JFormattedTextField</code> allows | ||
| 54 | * configuring what action should be taken when focus is lost. The possible | ||
| 55 | * configurations are: | ||
| 56 | * <table summary="Possible JFormattedTextField configurations and their descriptions"> | ||
| 57 | * <tr><th><p align="left">Value</p></th><th><p align="left">Description</p></th></tr> | ||
| 58 | * <tr><td>JFormattedTextField.REVERT | ||
| 59 | * <td>Revert the display to match that of <code>getValue</code>, | ||
| 60 | * possibly losing the current edit. | ||
| 61 | * <tr><td>JFormattedTextField.COMMIT | ||
| 62 | * <td>Commits the current value. If the value being edited | ||
| 63 | * isn't considered a legal value by the | ||
| 64 | * <code>AbstractFormatter</code> that is, a | ||
| 65 | * <code>ParseException</code> is thrown, then the value | ||
| 66 | * will not change, and then edited value will persist. | ||
| 67 | * <tr><td>JFormattedTextField.COMMIT_OR_REVERT | ||
| 68 | * <td>Similar to <code>COMMIT</code>, but if the value isn't | ||
| 69 | * legal, behave like <code>REVERT</code>. | ||
| 70 | * <tr><td>JFormattedTextField.PERSIST | ||
| 71 | * <td>Do nothing, don't obtain a new | ||
| 72 | * <code>AbstractFormatter</code>, and don't update the value. | ||
| 73 | * </table> | ||
| 74 | * The default is <code>JFormattedTextField.COMMIT_OR_REVERT</code>, | ||
| 75 | * refer to {@link #setFocusLostBehavior} for more information on this. | ||
| 76 | * <p> | ||
| 77 | * <code>JFormattedTextField</code> allows the focus to leave, even if | ||
| 78 | * the currently edited value is invalid. To lock the focus down while the | ||
| 79 | * <code>JFormattedTextField</code> is an invalid edit state | ||
| 80 | * you can attach an <code>InputVerifier</code>. The following code snippet | ||
| 81 | * shows a potential implementation of such an <code>InputVerifier</code>: | ||
| 82 | * <pre> | ||
| 83 | * public class FormattedTextFieldVerifier extends InputVerifier { | ||
| 84 | * public boolean verify(JComponent input) { | ||
| 85 | * if (input instanceof JFormattedTextField) { | ||
| 86 | * JFormattedTextField ftf = (JFormattedTextField)input; | ||
| 87 | * AbstractFormatter formatter = ftf.getFormatter(); | ||
| 88 | * if (formatter != null) { | ||
| 89 | * String text = ftf.getText(); | ||
| 90 | * try { | ||
| 91 | * formatter.stringToValue(text); | ||
| 92 | * return true; | ||
| 93 | * } catch (ParseException pe) { | ||
| 94 | * return false; | ||
| 95 | * } | ||
| 96 | * } | ||
| 97 | * } | ||
| 98 | * return true; | ||
| 99 | * } | ||
| 100 | * public boolean shouldYieldFocus(JComponent input) { | ||
| 101 | * return verify(input); | ||
| 102 | * } | ||
| 103 | * } | ||
| 104 | * </pre> | ||
| 105 | * <p> | ||
| 106 | * Alternatively, you could invoke <code>commitEdit</code>, which would also | ||
| 107 | * commit the value. | ||
| 108 | * <p> | ||
| 109 | * <code>JFormattedTextField</code> does not do the formatting it self, | ||
| 110 | * rather formatting is done through an instance of | ||
| 111 | * <code>JFormattedTextField.AbstractFormatter</code> which is obtained from | ||
| 112 | * an instance of <code>JFormattedTextField.AbstractFormatterFactory</code>. | ||
| 113 | * Instances of <code>JFormattedTextField.AbstractFormatter</code> are | ||
| 114 | * notified when they become active by way of the | ||
| 115 | * <code>install</code> method, at which point the | ||
| 116 | * <code>JFormattedTextField.AbstractFormatter</code> can install whatever | ||
| 117 | * it needs to, typically a <code>DocumentFilter</code>. Similarly when | ||
| 118 | * <code>JFormattedTextField</code> no longer | ||
| 119 | * needs the <code>AbstractFormatter</code>, it will invoke | ||
| 120 | * <code>uninstall</code>. | ||
| 121 | * <p> | ||
| 122 | * <code>JFormattedTextField</code> typically | ||
| 123 | * queries the <code>AbstractFormatterFactory</code> for an | ||
| 124 | * <code>AbstractFormat</code> when it gains or loses focus. Although this | ||
| 125 | * can change based on the focus lost policy. If the focus lost | ||
| 126 | * policy is <code>JFormattedTextField.PERSIST</code> | ||
| 127 | * and the <code>JFormattedTextField</code> has been edited, the | ||
| 128 | * <code>AbstractFormatterFactory</code> will not be queried until the | ||
| 129 | * value has been commited. Similarly if the focus lost policy is | ||
| 130 | * <code>JFormattedTextField.COMMIT</code> and an exception | ||
| 131 | * is thrown from <code>stringToValue</code>, the | ||
| 132 | * <code>AbstractFormatterFactory</code> will not be querired when focus is | ||
| 133 | * lost or gained. | ||
| 134 | * <p> | ||
| 135 | * <code>JFormattedTextField.AbstractFormatter</code> | ||
| 136 | * is also responsible for determining when values are commited to | ||
| 137 | * the <code>JFormattedTextField</code>. Some | ||
| 138 | * <code>JFormattedTextField.AbstractFormatter</code>s will make new values | ||
| 139 | * available on every edit, and others will never commit the value. You can | ||
| 140 | * force the current value to be obtained | ||
| 141 | * from the current <code>JFormattedTextField.AbstractFormatter</code> | ||
| 142 | * by way of invoking <code>commitEdit</code>. <code>commitEdit</code> will | ||
| 143 | * be invoked whenever return is pressed in the | ||
| 144 | * <code>JFormattedTextField</code>. | ||
| 145 | * <p> | ||
| 146 | * If an <code>AbstractFormatterFactory</code> has not been explicitly | ||
| 147 | * set, one will be set based on the <code>Class</code> of the value type after | ||
| 148 | * <code>setValue</code> has been invoked (assuming value is non-null). | ||
| 149 | * For example, in the following code an appropriate | ||
| 150 | * <code>AbstractFormatterFactory</code> and <code>AbstractFormatter</code> | ||
| 151 | * will be created to handle formatting of numbers: | ||
| 152 | * <pre> | ||
| 153 | * JFormattedTextField tf = new JFormattedTextField(); | ||
| 154 | * tf.setValue(new Number(100)); | ||
| 155 | * </pre> | ||
| 156 | * <p> | ||
| 157 | * <strong>Warning:</strong> As the <code>AbstractFormatter</code> will | ||
| 158 | * typically install a <code>DocumentFilter</code> on the | ||
| 159 | * <code>Document</code>, and a <code>NavigationFilter</code> on the | ||
| 160 | * <code>JFormattedTextField</code> you should not install your own. If you do, | ||
| 161 | * you are likely to see odd behavior in that the editing policy of the | ||
| 162 | * <code>AbstractFormatter</code> will not be enforced. | ||
| 163 | * <p> | ||
| 164 | * <strong>Warning:</strong> Swing is not thread safe. For more | ||
| 165 | * information see <a | ||
| 166 | * href="package-summary.html#threading">Swing's Threading | ||
| 167 | * Policy</a>. | ||
| 168 | * <p> | ||
| 169 | * <strong>Warning:</strong> | ||
| 170 | * Serialized objects of this class will not be compatible with | ||
| 171 | * future Swing releases. The current serialization support is | ||
| 172 | * appropriate for short term storage or RMI between applications running | ||
| 173 | * the same version of Swing. As of 1.4, support for long term storage | ||
| 174 | * of all JavaBeans<sup><font size="-2">TM</font></sup> | ||
| 175 | * has been added to the <code>java.beans</code> package. | ||
| 176 | * Please see {@link java.beans.XMLEncoder}. | ||
| 177 | * | ||
| 178 | * @since 1.4 | ||
| 179 | */ | ||
| 180 | public class JFormattedTextField extends JTextField { | ||
| 181 | private static final String uiClassID = "FormattedTextFieldUI"; | ||
| 182 | private static final Action[] defaultActions = | ||
| 183 | { new CommitAction(), new CancelAction() }; | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Constant identifying that when focus is lost, | ||
| 187 | * <code>commitEdit</code> should be invoked. If in commiting the | ||
| 188 | * new value a <code>ParseException</code> is thrown, the invalid | ||
| 189 | * value will remain. | ||
| 190 | * | ||
| 191 | * @see #setFocusLostBehavior | ||
| 192 | */ | ||
| 193 | public static final int COMMIT = 0; | ||
| 194 | |||
| 195 | /** | ||
| 196 | * Constant identifying that when focus is lost, | ||
| 197 | * <code>commitEdit</code> should be invoked. If in commiting the new | ||
| 198 | * value a <code>ParseException</code> is thrown, the value will be | ||
| 199 | * reverted. | ||
| 200 | * | ||
| 201 | * @see #setFocusLostBehavior | ||
| 202 | */ | ||
| 203 | public static final int COMMIT_OR_REVERT = 1; | ||
| 204 | |||
| 205 | /** | ||
| 206 | * Constant identifying that when focus is lost, editing value should | ||
| 207 | * be reverted to current value set on the | ||
| 208 | * <code>JFormattedTextField</code>. | ||
| 209 | * | ||
| 210 | * @see #setFocusLostBehavior | ||
| 211 | */ | ||
| 212 | public static final int REVERT = 2; | ||
| 213 | |||
| 214 | /** | ||
| 215 | * Constant identifying that when focus is lost, the edited value | ||
| 216 | * should be left. | ||
| 217 | * | ||
| 218 | * @see #setFocusLostBehavior | ||
| 219 | */ | ||
| 220 | public static final int PERSIST = 3; | ||
| 221 | |||
| 222 | |||
| 223 | /** | ||
| 224 | * Factory used to obtain an instance of AbstractFormatter. | ||
| 225 | */ | ||
| 226 | private AbstractFormatterFactory factory; | ||
| 227 | /** | ||
| 228 | * Object responsible for formatting the current value. | ||
| 229 | */ | ||
| 230 | private AbstractFormatter format; | ||
| 231 | /** | ||
| 232 | * Last valid value. | ||
| 233 | */ | ||
| 234 | private Object value; | ||
| 235 | /** | ||
| 236 | * True while the value being edited is valid. | ||
| 237 | */ | ||
| 238 | private boolean editValid; | ||
| 239 | /** | ||
| 240 | * Behavior when focus is lost. | ||
| 241 | */ | ||
| 242 | private int focusLostBehavior; | ||
| 243 | /** | ||
| 244 | * Indicates the current value has been edited. | ||
| 245 | */ | ||
| 246 | private boolean edited; | ||
| 247 | /** | ||
| 248 | * Used to set the dirty state. | ||
| 249 | */ | ||
| 250 | private DocumentListener documentListener; | ||
| 251 | /** | ||
| 252 | * Masked used to set the AbstractFormatterFactory. | ||
| 253 | */ | ||
| 254 | private Object mask; | ||
| 255 | /** | ||
| 256 | * ActionMap that the TextFormatter Actions are added to. | ||
| 257 | */ | ||
| 258 | private ActionMap textFormatterActionMap; | ||
| 259 | /** | ||
| 260 | * Indicates the input method composed text is in the document | ||
| 261 | */ | ||
| 262 | private boolean composedTextExists = false; | ||
| 263 | /** | ||
| 264 | * A handler for FOCUS_LOST event | ||
| 265 | */ | ||
| 266 | private FocusLostHandler focusLostHandler; | ||
| 267 | |||
| 268 | |||
| 269 | /** | ||
| 270 | * Creates a <code>JFormattedTextField</code> with no | ||
| 271 | * <code>AbstractFormatterFactory</code>. Use <code>setMask</code> or | ||
| 272 | * <code>setFormatterFactory</code> to configure the | ||
| 273 | * <code>JFormattedTextField</code> to edit a particular type of | ||
| 274 | * value. | ||
| 275 | */ | ||
| 276 | public JFormattedTextField() { | ||
| 277 | super(); | ||
| 278 | enableEvents(AWTEvent.FOCUS_EVENT_MASK); | ||
| 279 | setFocusLostBehavior(COMMIT_OR_REVERT); | ||
| 280 | } | ||
| 281 | |||
| 282 | /** | ||
| 283 | * Creates a JFormattedTextField with the specified value. This will | ||
| 284 | * create an <code>AbstractFormatterFactory</code> based on the | ||
| 285 | * type of <code>value</code>. | ||
| 286 | * | ||
| 287 | * @param value Initial value for the JFormattedTextField | ||
| 288 | */ | ||
| 289 | public JFormattedTextField(Object value) { | ||
| 290 | this(); | ||
| 291 | setValue(value); | ||
| 292 | } | ||
| 293 | |||
| 294 | /** | ||
| 295 | * Creates a <code>JFormattedTextField</code>. <code>format</code> is | ||
| 296 | * wrapped in an appropriate <code>AbstractFormatter</code> which is | ||
| 297 | * then wrapped in an <code>AbstractFormatterFactory</code>. | ||
| 298 | * | ||
| 299 | * @param format Format used to look up an AbstractFormatter | ||
| 300 | */ | ||
| 301 | public JFormattedTextField(java.text.Format format) { | ||
| 302 | this(); | ||
| 303 | setFormatterFactory(getDefaultFormatterFactory(format)); | ||
| 304 | } | ||
| 305 | |||
| 306 | /** | ||
| 307 | * Creates a <code>JFormattedTextField</code> with the specified | ||
| 308 | * <code>AbstractFormatter</code>. The <code>AbstractFormatter</code> | ||
| 309 | * is placed in an <code>AbstractFormatterFactory</code>. | ||
| 310 | * | ||
| 311 | * @param formatter AbstractFormatter to use for formatting. | ||
| 312 | */ | ||
| 313 | public JFormattedTextField(AbstractFormatter formatter) { | ||
| 314 | this(new DefaultFormatterFactory(formatter)); | ||
| 315 | } | ||
| 316 | |||
| 317 | /** | ||
| 318 | * Creates a <code>JFormattedTextField</code> with the specified | ||
| 319 | * <code>AbstractFormatterFactory</code>. | ||
| 320 | * | ||
| 321 | * @param factory AbstractFormatterFactory used for formatting. | ||
| 322 | */ | ||
| 323 | public JFormattedTextField(AbstractFormatterFactory factory) { | ||
| 324 | this(); | ||
| 325 | setFormatterFactory(factory); | ||
| 326 | } | ||
| 327 | |||
| 328 | /** | ||
| 329 | * Creates a <code>JFormattedTextField</code> with the specified | ||
| 330 | * <code>AbstractFormatterFactory</code> and initial value. | ||
| 331 | * | ||
| 332 | * @param factory <code>AbstractFormatterFactory</code> used for | ||
| 333 | * formatting. | ||
| 334 | * @param currentValue Initial value to use | ||
| 335 | */ | ||
| 336 | public JFormattedTextField(AbstractFormatterFactory factory, | ||
| 337 | Object currentValue) { | ||
| 338 | this(currentValue); | ||
| 339 | setFormatterFactory(factory); | ||
| 340 | } | ||
| 341 | |||
| 342 | /** | ||
| 343 | * Sets the behavior when focus is lost. This will be one of | ||
| 344 | * <code>JFormattedTextField.COMMIT_OR_REVERT</code>, | ||
| 345 | * <code>JFormattedTextField.REVERT</code>, | ||
| 346 | * <code>JFormattedTextField.COMMIT</code> or | ||
| 347 | * <code>JFormattedTextField.PERSIST</code> | ||
| 348 | * Note that some <code>AbstractFormatter</code>s may push changes as | ||
| 349 | * they occur, so that the value of this will have no effect. | ||
| 350 | * <p> | ||
| 351 | * This will throw an <code>IllegalArgumentException</code> if the object | ||
| 352 | * passed in is not one of the afore mentioned values. | ||
| 353 | * <p> | ||
| 354 | * The default value of this property is | ||
| 355 | * <code>JFormattedTextField.COMMIT_OR_REVERT</code>. | ||
| 356 | * | ||
| 357 | * @param behavior Identifies behavior when focus is lost | ||
| 358 | * @throws IllegalArgumentException if behavior is not one of the known | ||
| 359 | * values | ||
| 360 | * @beaninfo | ||
| 361 | * enum: COMMIT JFormattedTextField.COMMIT | ||
| 362 | * COMMIT_OR_REVERT JFormattedTextField.COMMIT_OR_REVERT | ||
| 363 | * REVERT JFormattedTextField.REVERT | ||
| 364 | * PERSIST JFormattedTextField.PERSIST | ||
| 365 | * description: Behavior when component loses focus | ||
| 366 | */ | ||
| 367 | public void setFocusLostBehavior(int behavior) { | ||
| 368 | if (behavior != COMMIT && behavior != COMMIT_OR_REVERT && | ||
| 369 | behavior != PERSIST && behavior != REVERT) { | ||
| 370 | throw new IllegalArgumentException("setFocusLostBehavior must be one of: JFormattedTextField.COMMIT, JFormattedTextField.COMMIT_OR_REVERT, JFormattedTextField.PERSIST or JFormattedTextField.REVERT"); | ||
| 371 | } | ||
| 372 | focusLostBehavior = behavior; | ||
| 373 | } | ||
| 374 | |||
| 375 | /** | ||
| 376 | * Returns the behavior when focus is lost. This will be one of | ||
| 377 | * <code>COMMIT_OR_REVERT</code>, | ||
| 378 | * <code>COMMIT</code>, | ||
| 379 | * <code>REVERT</code> or | ||
| 380 | * <code>PERSIST</code> | ||
| 381 | * Note that some <code>AbstractFormatter</code>s may push changes as | ||
| 382 | * they occur, so that the value of this will have no effect. | ||
| 383 | * | ||
| 384 | * @return returns behavior when focus is lost | ||
| 385 | */ | ||
| 386 | public int getFocusLostBehavior() { | ||
| 387 | return focusLostBehavior; | ||
| 388 | } | ||
| 389 | |||
| 390 | /** | ||
| 391 | * Sets the <code>AbstractFormatterFactory</code>. | ||
| 392 | * <code>AbstractFormatterFactory</code> is | ||
| 393 | * able to return an instance of <code>AbstractFormatter</code> that is | ||
| 394 | * used to format a value for display, as well an enforcing an editing | ||
| 395 | * policy. | ||
| 396 | * <p> | ||
| 397 | * If you have not explicitly set an <code>AbstractFormatterFactory</code> | ||
| 398 | * by way of this method (or a constructor) an | ||
| 399 | * <code>AbstractFormatterFactory</code> and consequently an | ||
| 400 | * <code>AbstractFormatter</code> will be used based on the | ||
| 401 | * <code>Class</code> of the value. <code>NumberFormatter</code> will | ||
| 402 | * be used for <code>Number</code>s, <code>DateFormatter</code> will | ||
| 403 | * be used for <code>Dates</code>, otherwise <code>DefaultFormatter</code> | ||
| 404 | * will be used. | ||
| 405 | * <p> | ||
| 406 | * This is a JavaBeans bound property. | ||
| 407 | * | ||
| 408 | * @param tf <code>AbstractFormatterFactory</code> used to lookup | ||
| 409 | * instances of <code>AbstractFormatter</code> | ||
| 410 | * @beaninfo | ||
| 411 | * bound: true | ||
| 412 | * attribute: visualUpdate true | ||
| 413 | * description: AbstractFormatterFactory, responsible for returning an | ||
| 414 | * AbstractFormatter that can format the current value. | ||
| 415 | */ | ||
| 416 | public void setFormatterFactory(AbstractFormatterFactory tf) { | ||
| 417 | AbstractFormatterFactory oldFactory = factory; | ||
| 418 | |||
| 419 | factory = tf; | ||
| 420 | firePropertyChange("formatterFactory", oldFactory, tf); | ||
| 421 | setValue(getValue(), true, false); | ||
| 422 | } | ||
| 423 | |||
| 424 | /** | ||
| 425 | * Returns the current <code>AbstractFormatterFactory</code>. | ||
| 426 | * | ||
| 427 | * @see #setFormatterFactory | ||
| 428 | * @return <code>AbstractFormatterFactory</code> used to determine | ||
| 429 | * <code>AbstractFormatter</code>s | ||
| 430 | */ | ||
| 431 | public AbstractFormatterFactory getFormatterFactory() { | ||
| 432 | return factory; | ||
| 433 | } | ||
| 434 | |||
| 435 | /** | ||
| 436 | * Sets the current <code>AbstractFormatter</code>. | ||
| 437 | * <p> | ||
| 438 | * You should not normally invoke this, instead set the | ||
| 439 | * <code>AbstractFormatterFactory</code> or set the value. | ||
| 440 | * <code>JFormattedTextField</code> will | ||
| 441 | * invoke this as the state of the <code>JFormattedTextField</code> | ||
| 442 | * changes and requires the value to be reset. | ||
| 443 | * <code>JFormattedTextField</code> passes in the | ||
| 444 | * <code>AbstractFormatter</code> obtained from the | ||
| 445 | * <code>AbstractFormatterFactory</code>. | ||
| 446 | * <p> | ||
| 447 | * This is a JavaBeans bound property. | ||
| 448 | * | ||
| 449 | * @see #setFormatterFactory | ||
| 450 | * @param format AbstractFormatter to use for formatting | ||
| 451 | * @beaninfo | ||
| 452 | * bound: true | ||
| 453 | * attribute: visualUpdate true | ||
| 454 | * description: TextFormatter, responsible for formatting the current value | ||
| 455 | */ | ||
| 456 | protected void setFormatter(AbstractFormatter format) { | ||
| 457 | AbstractFormatter oldFormat = this.format; | ||
| 458 | |||
| 459 | if (oldFormat != null) { | ||
| 460 | oldFormat.uninstall(); | ||
| 461 | } | ||
| 462 | setEditValid(true); | ||
| 463 | this.format = format; | ||
| 464 | if (format != null) { | ||
| 465 | format.install(this); | ||
| 466 | } | ||
| 467 | setEdited(false); | ||
| 468 | firePropertyChange("textFormatter", oldFormat, format); | ||
| 469 | } | ||
| 470 | |||
| 471 | /** | ||
| 472 | * Returns the <code>AbstractFormatter</code> that is used to format and | ||
| 473 | * parse the current value. | ||
| 474 | * | ||
| 475 | * @return AbstractFormatter used for formatting | ||
| 476 | */ | ||
| 477 | public AbstractFormatter getFormatter() { | ||
| 478 | return format; | ||
| 479 | } | ||
| 480 | |||
| 481 | /** | ||
| 482 | * Sets the value that will be formatted by an | ||
| 483 | * <code>AbstractFormatter</code> obtained from the current | ||
| 484 | * <code>AbstractFormatterFactory</code>. If no | ||
| 485 | * <code>AbstractFormatterFactory</code> has been specified, this will | ||
| 486 | * attempt to create one based on the type of <code>value</code>. | ||
| 487 | * <p> | ||
| 488 | * The default value of this property is null. | ||
| 489 | * <p> | ||
| 490 | * This is a JavaBeans bound property. | ||
| 491 | * | ||
| 492 | * @param value Current value to display | ||
| 493 | * @beaninfo | ||
| 494 | * bound: true | ||
| 495 | * attribute: visualUpdate true | ||
| 496 | * description: The value to be formatted. | ||
| 497 | */ | ||
| 498 | public void setValue(Object value) { | ||
| 499 | if (value != null && getFormatterFactory() == null) { | ||
| 500 | setFormatterFactory(getDefaultFormatterFactory(value)); | ||
| 501 | } | ||
| 502 | setValue(value, true, true); | ||
| 503 | } | ||
| 504 | |||
| 505 | /** | ||
| 506 | * Returns the last valid value. Based on the editing policy of | ||
| 507 | * the <code>AbstractFormatter</code> this may not return the current | ||
| 508 | * value. The currently edited value can be obtained by invoking | ||
| 509 | * <code>commitEdit</code> followed by <code>getValue</code>. | ||
| 510 | * | ||
| 511 | * @return Last valid value | ||
| 512 | */ | ||
| 513 | public Object getValue() { | ||
| 514 | return value; | ||
| 515 | } | ||
| 516 | |||
| 517 | /** | ||
| 518 | * Forces the current value to be taken from the | ||
| 519 | * <code>AbstractFormatter</code> and set as the current value. | ||
| 520 | * This has no effect if there is no current | ||
| 521 | * <code>AbstractFormatter</code> installed. | ||
| 522 | * | ||
| 523 | * @throws ParseException if the <code>AbstractFormatter</code> is not able | ||
| 524 | * to format the current value | ||
| 525 | */ | ||
| 526 | public void commitEdit() throws ParseException { | ||
| 527 | AbstractFormatter format = getFormatter(); | ||
| 528 | |||
| 529 | if (format != null) { | ||
| 530 | setValue(format.stringToValue(getText()), false, true); | ||
| 531 | } | ||
| 532 | } | ||
| 533 | |||
| 534 | /** | ||
| 535 | * Sets the validity of the edit on the receiver. You should not normally | ||
| 536 | * invoke this. This will be invoked by the | ||
| 537 | * <code>AbstractFormatter</code> as the user edits the value. | ||
| 538 | * <p> | ||
| 539 | * Not all formatters will allow the component to get into an invalid | ||
| 540 | * state, and thus this may never be invoked. | ||
| 541 | * <p> | ||
| 542 | * Based on the look and feel this may visually change the state of | ||
| 543 | * the receiver. | ||
| 544 | * | ||
| 545 | * @param isValid boolean indicating if the currently edited value is | ||
| 546 | * valid. | ||
| 547 | * @beaninfo | ||
| 548 | * bound: true | ||
| 549 | * attribute: visualUpdate true | ||
| 550 | * description: True indicates the edited value is valid | ||
| 551 | */ | ||
| 552 | private void setEditValid(boolean isValid) { | ||
| 553 | if (isValid != editValid) { | ||
| 554 | editValid = isValid; | ||
| 555 | firePropertyChange("editValid", Boolean.valueOf(!isValid), | ||
| 556 | Boolean.valueOf(isValid)); | ||
| 557 | } | ||
| 558 | } | ||
| 559 | |||
| 560 | /** | ||
| 561 | * Returns true if the current value being edited is valid. The value of | ||
| 562 | * this is managed by the current <code>AbstractFormatter</code>, as such | ||
| 563 | * there is no public setter for it. | ||
| 564 | * | ||
| 565 | * @return true if the current value being edited is valid. | ||
| 566 | */ | ||
| 567 | public boolean isEditValid() { | ||
| 568 | return editValid; | ||
| 569 | } | ||
| 570 | |||
| 571 | /** | ||
| 572 | * Invoked when the user inputs an invalid value. This gives the | ||
| 573 | * component a chance to provide feedback. The default | ||
| 574 | * implementation beeps. | ||
| 575 | */ | ||
| 576 | protected void invalidEdit() { | ||
| 577 | UIManager.getLookAndFeel().provideErrorFeedback(JFormattedTextField.this); | ||
| 578 | } | ||
| 579 | |||
| 580 | /** | ||
| 581 | * Processes any input method events, such as | ||
| 582 | * <code>InputMethodEvent.INPUT_METHOD_TEXT_CHANGED</code> or | ||
| 583 | * <code>InputMethodEvent.CARET_POSITION_CHANGED</code>. | ||
| 584 | * | ||
| 585 | * @param e the <code>InputMethodEvent</code> | ||
| 586 | * @see InputMethodEvent | ||
| 587 | */ | ||
| 588 | protected void processInputMethodEvent(InputMethodEvent e) { | ||
| 589 | AttributedCharacterIterator text = e.getText(); | ||
| 590 | int commitCount = e.getCommittedCharacterCount(); | ||
| 591 | |||
| 592 | // Keep track of the composed text | ||
| 593 | if (text != null) { | ||
| 594 | int begin = text.getBeginIndex(); | ||
| 595 | int end = text.getEndIndex(); | ||
| 596 | composedTextExists = ((end - begin) > commitCount); | ||
| 597 | } else { | ||
| 598 | composedTextExists = false; | ||
| 599 | } | ||
| 600 | |||
| 601 | super.processInputMethodEvent(e); | ||
| 602 | } | ||
| 603 | |||
| 604 | /** | ||
| 605 | * Processes any focus events, such as | ||
| 606 | * <code>FocusEvent.FOCUS_GAINED</code> or | ||
| 607 | * <code>FocusEvent.FOCUS_LOST</code>. | ||
| 608 | * | ||
| 609 | * @param e the <code>FocusEvent</code> | ||
| 610 | * @see FocusEvent | ||
| 611 | */ | ||
| 612 | protected void processFocusEvent(FocusEvent e) { | ||
| 613 | super.processFocusEvent(e); | ||
| 614 | |||
| 615 | // ignore temporary focus event | ||
| 616 | if (e.isTemporary()) { | ||
| 617 | return; | ||
| 618 | } | ||
| 619 | |||
| 620 | if (isEdited() && e.getID() == FocusEvent.FOCUS_LOST) { | ||
| 621 | InputContext ic = getInputContext(); | ||
| 622 | if (focusLostHandler == null) { | ||
| 623 | focusLostHandler = new FocusLostHandler(); | ||
| 624 | } | ||
| 625 | |||
| 626 | // if there is a composed text, process it first | ||
| 627 | if ((ic != null) && composedTextExists) { | ||
| 628 | ic.endComposition(); | ||
| 629 | EventQueue.invokeLater(focusLostHandler); | ||
| 630 | } else { | ||
| 631 | focusLostHandler.run(); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | else if (!isEdited()) { | ||
| 635 | // reformat | ||
| 636 | setValue(getValue(), true, true); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | |||
| 640 | /** | ||
| 641 | * FOCUS_LOST behavior implementation | ||
| 642 | */ | ||
| 643 | private class FocusLostHandler implements Runnable, Serializable { | ||
| 644 | public void run() { | ||
| 645 | int fb = JFormattedTextField.this.getFocusLostBehavior(); | ||
| 646 | if (fb == JFormattedTextField.COMMIT || | ||
| 647 | fb == JFormattedTextField.COMMIT_OR_REVERT) { | ||
| 648 | try { | ||
| 649 | JFormattedTextField.this.commitEdit(); | ||
| 650 | // Give it a chance to reformat. | ||
| 651 | JFormattedTextField.this.setValue( | ||
| 652 | JFormattedTextField.this.getValue(), true, true); | ||
| 653 | } catch (ParseException pe) { | ||
| 654 | if (fb == JFormattedTextField.this.COMMIT_OR_REVERT) { | ||
| 655 | JFormattedTextField.this.setValue( | ||
| 656 | JFormattedTextField.this.getValue(), true, true); | ||
| 657 | } | ||
| 658 | } | ||
| 659 | } | ||
| 660 | else if (fb == JFormattedTextField.REVERT) { | ||
| 661 | JFormattedTextField.this.setValue( | ||
| 662 | JFormattedTextField.this.getValue(), true, true); | ||
| 663 | } | ||
| 664 | } | ||
| 665 | } | ||
| 666 | |||
| 667 | /** | ||
| 668 | * Fetches the command list for the editor. This is | ||
| 669 | * the list of commands supported by the plugged-in UI | ||
| 670 | * augmented by the collection of commands that the | ||
| 671 | * editor itself supports. These are useful for binding | ||
| 672 | * to events, such as in a keymap. | ||
| 673 | * | ||
| 674 | * @return the command list | ||
| 675 | */ | ||
| 676 | public Action[] getActions() { | ||
| 677 | return TextAction.augmentList(super.getActions(), defaultActions); | ||
| 678 | } | ||
| 679 | |||
| 680 | /** | ||
| 681 | * Gets the class ID for a UI. | ||
| 682 | * | ||
| 683 | * @return the string "FormattedTextFieldUI" | ||
| 684 | * @see JComponent#getUIClassID | ||
| 685 | */ | ||
| 686 | public String getUIClassID() { | ||
| 687 | return uiClassID; | ||
| 688 | } | ||
| 689 | |||
| 690 | /** | ||
| 691 | * Associates the editor with a text document. | ||
| 692 | * The currently registered factory is used to build a view for | ||
| 693 | * the document, which gets displayed by the editor after revalidation. | ||
| 694 | * A PropertyChange event ("document") is propagated to each listener. | ||
| 695 | * | ||
| 696 | * @param doc the document to display/edit | ||
| 697 | * @see #getDocument | ||
| 698 | * @beaninfo | ||
| 699 | * description: the text document model | ||
| 700 | * bound: true | ||
| 701 | * expert: true | ||
| 702 | */ | ||
| 703 | public void setDocument(Document doc) { | ||
| 704 | if (documentListener != null && getDocument() != null) { | ||
| 705 | getDocument().removeDocumentListener(documentListener); | ||
| 706 | } | ||
| 707 | super.setDocument(doc); | ||
| 708 | if (documentListener == null) { | ||
| 709 | documentListener = new DocumentHandler(); | ||
| 710 | } | ||
| 711 | doc.addDocumentListener(documentListener); | ||
| 712 | } | ||
| 713 | |||
| 714 | /* | ||
| 715 | * See readObject and writeObject in JComponent for more | ||
| 716 | * information about serialization in Swing. | ||
| 717 | * | ||
| 718 | * @param s Stream to write to | ||
| 719 | */ | ||
| 720 | private void writeObject(ObjectOutputStream s) throws IOException { | ||
| 721 | s.defaultWriteObject(); | ||
| 722 | if (getUIClassID().equals(uiClassID)) { | ||
| 723 | byte count = JComponent.getWriteObjCounter(this); | ||
| 724 | JComponent.setWriteObjCounter(this, --count); | ||
| 725 | if (count == 0 && ui != null) { | ||
| 726 | ui.installUI(this); | ||
| 727 | } | ||
| 728 | } | ||
| 729 | } | ||
| 730 | |||
| 731 | /** | ||
| 732 | * Resets the Actions that come from the TextFormatter to | ||
| 733 | * <code>actions</code>. | ||
| 734 | */ | ||
| 735 | private void setFormatterActions(Action[] actions) { | ||
| 736 | if (actions == null) { | ||
| 737 | if (textFormatterActionMap != null) { | ||
| 738 | textFormatterActionMap.clear(); | ||
| 739 | } | ||
| 740 | } | ||
| 741 | else { | ||
| 742 | if (textFormatterActionMap == null) { | ||
| 743 | ActionMap map = getActionMap(); | ||
| 744 | |||
| 745 | textFormatterActionMap = new ActionMap(); | ||
| 746 | while (map != null) { | ||
| 747 | ActionMap parent = map.getParent(); | ||
| 748 | |||
| 749 | if (parent instanceof UIResource || parent == null) { | ||
| 750 | map.setParent(textFormatterActionMap); | ||
| 751 | textFormatterActionMap.setParent(parent); | ||
| 752 | break; | ||
| 753 | } | ||
| 754 | map = parent; | ||
| 755 | } | ||
| 756 | } | ||
| 757 | for (int counter = actions.length - 1; counter >= 0; | ||
| 758 | counter--) { | ||
| 759 | Object key = actions[counter].getValue(Action.NAME); | ||
| 760 | |||
| 761 | if (key != null) { | ||
| 762 | textFormatterActionMap.put(key, actions[counter]); | ||
| 763 | } | ||
| 764 | } | ||
| 765 | } | ||
| 766 | } | ||
| 767 | |||
| 768 | /** | ||
| 769 | * Does the setting of the value. If <code>createFormat</code> is true, | ||
| 770 | * this will also obtain a new <code>AbstractFormatter</code> from the | ||
| 771 | * current factory. The property change event will be fired if | ||
| 772 | * <code>firePC</code> is true. | ||
| 773 | */ | ||
| 774 | private void setValue(Object value, boolean createFormat, boolean firePC) { | ||
| 775 | Object oldValue = this.value; | ||
| 776 | |||
| 777 | this.value = value; | ||
| 778 | |||
| 779 | if (createFormat) { | ||
| 780 | AbstractFormatterFactory factory = getFormatterFactory(); | ||
| 781 | AbstractFormatter atf; | ||
| 782 | |||
| 783 | if (factory != null) { | ||
| 784 | atf = factory.getFormatter(this); | ||
| 785 | } | ||
| 786 | else { | ||
| 787 | atf = null; | ||
| 788 | } | ||
| 789 | setFormatter(atf); | ||
| 790 | } | ||
| 791 | else { | ||
| 792 | // Assumed to be valid | ||
| 793 | setEditValid(true); | ||
| 794 | } | ||
| 795 | |||
| 796 | setEdited(false); | ||
| 797 | |||
| 798 | if (firePC) { | ||
| 799 | firePropertyChange("value", oldValue, value); | ||
| 800 | } | ||
| 801 | } | ||
| 802 | |||
| 803 | /** | ||
| 804 | * Sets the edited state of the receiver. | ||
| 805 | */ | ||
| 806 | private void setEdited(boolean edited) { | ||
| 807 | this.edited = edited; | ||
| 808 | } | ||
| 809 | |||
| 810 | /** | ||
| 811 | * Returns true if the receiver has been edited. | ||
| 812 | */ | ||
| 813 | private boolean isEdited() { | ||
| 814 | return edited; | ||
| 815 | } | ||
| 816 | |||
| 817 | /** | ||
| 818 | * Returns an AbstractFormatterFactory suitable for the passed in | ||
| 819 | * Object type. | ||
| 820 | */ | ||
| 821 | private AbstractFormatterFactory getDefaultFormatterFactory(Object type) { | ||
| 822 | if (type instanceof DateFormat) { | ||
| 823 | return new DefaultFormatterFactory(new DateFormatter | ||
| 824 | ((DateFormat)type)); | ||
| 825 | } | ||
| 826 | if (type instanceof NumberFormat) { | ||
| 827 | return new DefaultFormatterFactory(new NumberFormatter( | ||
| 828 | (NumberFormat)type)); | ||
| 829 | } | ||
| 830 | if (type instanceof Format) { | ||
| 831 | return new DefaultFormatterFactory(new InternationalFormatter( | ||
| 832 | (Format)type)); | ||
| 833 | } | ||
| 834 | if (type instanceof Date) { | ||
| 835 | return new DefaultFormatterFactory(new DateFormatter()); | ||
| 836 | } | ||
| 837 | if (type instanceof Number) { | ||
| 838 | AbstractFormatter displayFormatter = new NumberFormatter(); | ||
| 839 | ((NumberFormatter)displayFormatter).setValueClass(type.getClass()); | ||
| 840 | AbstractFormatter editFormatter = new NumberFormatter( | ||
| 841 | new DecimalFormat("#.#")); | ||
| 842 | ((NumberFormatter)editFormatter).setValueClass(type.getClass()); | ||
| 843 | |||
| 844 | return new DefaultFormatterFactory(displayFormatter, | ||
| 845 | displayFormatter,editFormatter); | ||
| 846 | } | ||
| 847 | return new DefaultFormatterFactory(new DefaultFormatter()); | ||
| 848 | } | ||
| 849 | |||
| 850 | |||
| 851 | /** | ||
| 852 | * Instances of <code>AbstractFormatterFactory</code> are used by | ||
| 853 | * <code>JFormattedTextField</code> to obtain instances of | ||
| 854 | * <code>AbstractFormatter</code> which in turn are used to format values. | ||
| 855 | * <code>AbstractFormatterFactory</code> can return different | ||
| 856 | * <code>AbstractFormatter</code>s based on the state of the | ||
| 857 | * <code>JFormattedTextField</code>, perhaps returning different | ||
| 858 | * <code>AbstractFormatter</code>s when the | ||
| 859 | * <code>JFormattedTextField</code> has focus vs when it | ||
| 860 | * doesn't have focus. | ||
| 861 | * @since 1.4 | ||
| 862 | */ | ||
| 863 | public static abstract class AbstractFormatterFactory { | ||
| 864 | /** | ||
| 865 | * Returns an <code>AbstractFormatter</code> that can handle formatting | ||
| 866 | * of the passed in <code>JFormattedTextField</code>. | ||
| 867 | * | ||
| 868 | * @param tf JFormattedTextField requesting AbstractFormatter | ||
| 869 | * @return AbstractFormatter to handle formatting duties, a null | ||
| 870 | * return value implies the JFormattedTextField should behave | ||
| 871 | * like a normal JTextField | ||
| 872 | */ | ||
| 873 | public abstract AbstractFormatter getFormatter(JFormattedTextField tf); | ||
| 874 | } | ||
| 875 | |||
| 876 | |||
| 877 | /** | ||
| 878 | * Instances of <code>AbstractFormatter</code> are used by | ||
| 879 | * <code>JFormattedTextField</code> to handle the conversion both | ||
| 880 | * from an Object to a String, and back from a String to an Object. | ||
| 881 | * <code>AbstractFormatter</code>s can also enfore editing policies, | ||
| 882 | * or navigation policies, or manipulate the | ||
| 883 | * <code>JFormattedTextField</code> in any way it sees fit to | ||
| 884 | * enforce the desired policy. | ||
| 885 | * <p> | ||
| 886 | * An <code>AbstractFormatter</code> can only be active in | ||
| 887 | * one <code>JFormattedTextField</code> at a time. | ||
| 888 | * <code>JFormattedTextField</code> invokes | ||
| 889 | * <code>install</code> when it is ready to use it followed | ||
| 890 | * by <code>uninstall</code> when done. Subclasses | ||
| 891 | * that wish to install additional state should override | ||
| 892 | * <code>install</code> and message super appropriately. | ||
| 893 | * <p> | ||
| 894 | * Subclasses must override the conversion methods | ||
| 895 | * <code>stringToValue</code> and <code>valueToString</code>. Optionally | ||
| 896 | * they can override <code>getActions</code>, | ||
| 897 | * <code>getNavigationFilter</code> and <code>getDocumentFilter</code> | ||
| 898 | * to restrict the <code>JFormattedTextField</code> in a particular | ||
| 899 | * way. | ||
| 900 | * <p> | ||
| 901 | * Subclasses that allow the <code>JFormattedTextField</code> to be in | ||
| 902 | * a temporarily invalid state should invoke <code>setEditValid</code> | ||
| 903 | * at the appropriate times. | ||
| 904 | * @since 1.4 | ||
| 905 | */ | ||
| 906 | public static abstract class AbstractFormatter implements Serializable { | ||
| 907 | private JFormattedTextField ftf; | ||
| 908 | |||
| 909 | /** | ||
| 910 | * Installs the <code>AbstractFormatter</code> onto a particular | ||
| 911 | * <code>JFormattedTextField</code>. | ||
| 912 | * This will invoke <code>valueToString</code> to convert the | ||
| 913 | * current value from the <code>JFormattedTextField</code> to | ||
| 914 | * a String. This will then install the <code>Action</code>s from | ||
| 915 | * <code>getActions</code>, the <code>DocumentFilter</code> | ||
| 916 | * returned from <code>getDocumentFilter</code> and the | ||
| 917 | * <code>NavigationFilter</code> returned from | ||
| 918 | * <code>getNavigationFilter</code> onto the | ||
| 919 | * <code>JFormattedTextField</code>. | ||
| 920 | * <p> | ||
| 921 | * Subclasses will typically only need to override this if they | ||
| 922 | * wish to install additional listeners on the | ||
| 923 | * <code>JFormattedTextField</code>. | ||
| 924 | * <p> | ||
| 925 | * If there is a <code>ParseException</code> in converting the | ||
| 926 | * current value to a String, this will set the text to an empty | ||
| 927 | * String, and mark the <code>JFormattedTextField</code> as being | ||
| 928 | * in an invalid state. | ||
| 929 | * <p> | ||
| 930 | * While this is a public method, this is typically only useful | ||
| 931 | * for subclassers of <code>JFormattedTextField</code>. | ||
| 932 | * <code>JFormattedTextField</code> will invoke this method at | ||
| 933 | * the appropriate times when the value changes, or its internal | ||
| 934 | * state changes. You will only need to invoke this yourself if | ||
| 935 | * you are subclassing <code>JFormattedTextField</code> and | ||
| 936 | * installing/uninstalling <code>AbstractFormatter</code> at a | ||
| 937 | * different time than <code>JFormattedTextField</code> does. | ||
| 938 | * | ||
| 939 | * @param ftf JFormattedTextField to format for, may be null indicating | ||
| 940 | * uninstall from current JFormattedTextField. | ||
| 941 | */ | ||
| 942 | public void install(JFormattedTextField ftf) { | ||
| 943 | if (this.ftf != null) { | ||
| 944 | uninstall(); | ||
| 945 | } | ||
| 946 | this.ftf = ftf; | ||
| 947 | if (ftf != null) { | ||
| 948 | try { | ||
| 949 | ftf.setText(valueToString(ftf.getValue())); | ||
| 950 | } catch (ParseException pe) { | ||
| 951 | ftf.setText(""); | ||
| 952 | setEditValid(false); | ||
| 953 | } | ||
| 954 | installDocumentFilter(getDocumentFilter()); | ||
| 955 | ftf.setNavigationFilter(getNavigationFilter()); | ||
| 956 | ftf.setFormatterActions(getActions()); | ||
| 957 | } | ||
| 958 | } | ||
| 959 | |||
| 960 | /** | ||
| 961 | * Uninstalls any state the <code>AbstractFormatter</code> may have | ||
| 962 | * installed on the <code>JFormattedTextField</code>. This resets the | ||
| 963 | * <code>DocumentFilter</code>, <code>NavigationFilter</code> | ||
| 964 | * and additional <code>Action</code>s installed on the | ||
| 965 | * <code>JFormattedTextField</code>. | ||
| 966 | */ | ||
| 967 | public void uninstall() { | ||
| 968 | if (this.ftf != null) { | ||
| 969 | installDocumentFilter(null); | ||
| 970 | this.ftf.setNavigationFilter(null); | ||
| 971 | this.ftf.setFormatterActions(null); | ||
| 972 | } | ||
| 973 | } | ||
| 974 | |||
| 975 | /** | ||
| 976 | * Parses <code>text</code> returning an arbitrary Object. Some | ||
| 977 | * formatters may return null. | ||
| 978 | * | ||
| 979 | * @throws ParseException if there is an error in the conversion | ||
| 980 | * @param text String to convert | ||
| 981 | * @return Object representation of text | ||
| 982 | */ | ||
| 983 | public abstract Object stringToValue(String text) throws | ||
| 984 | ParseException; | ||
| 985 | |||
| 986 | /** | ||
| 987 | * Returns the string value to display for <code>value</code>. | ||
| 988 | * | ||
| 989 | * @throws ParseException if there is an error in the conversion | ||
| 990 | * @param value Value to convert | ||
| 991 | * @return String representation of value | ||
| 992 | */ | ||
| 993 | public abstract String valueToString(Object value) throws | ||
| 994 | ParseException; | ||
| 995 | |||
| 996 | /** | ||
| 997 | * Returns the current <code>JFormattedTextField</code> the | ||
| 998 | * <code>AbstractFormatter</code> is installed on. | ||
| 999 | * | ||
| 1000 | * @return JFormattedTextField formatting for. | ||
| 1001 | */ | ||
| 1002 | protected JFormattedTextField getFormattedTextField() { | ||
| 1003 | return ftf; | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | /** | ||
| 1007 | * This should be invoked when the user types an invalid character. | ||
| 1008 | * This forwards the call to the current JFormattedTextField. | ||
| 1009 | */ | ||
| 1010 | protected void invalidEdit() { | ||
| 1011 | JFormattedTextField ftf = getFormattedTextField(); | ||
| 1012 | |||
| 1013 | if (ftf != null) { | ||
| 1014 | ftf.invalidEdit(); | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | /** | ||
| 1019 | * Invoke this to update the <code>editValid</code> property of the | ||
| 1020 | * <code>JFormattedTextField</code>. If you an enforce a policy | ||
| 1021 | * such that the <code>JFormattedTextField</code> is always in a | ||
| 1022 | * valid state, you will never need to invoke this. | ||
| 1023 | * | ||
| 1024 | * @param valid Valid state of the JFormattedTextField | ||
| 1025 | */ | ||
| 1026 | protected void setEditValid(boolean valid) { | ||
| 1027 | JFormattedTextField ftf = getFormattedTextField(); | ||
| 1028 | |||
| 1029 | if (ftf != null) { | ||
| 1030 | ftf.setEditValid(valid); | ||
| 1031 | } | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | /** | ||
| 1035 | * Subclass and override if you wish to provide a custom set of | ||
| 1036 | * <code>Action</code>s. <code>install</code> will install these | ||
| 1037 | * on the <code>JFormattedTextField</code>'s <code>ActionMap</code>. | ||
| 1038 | * | ||
| 1039 | * @return Array of Actions to install on JFormattedTextField | ||
| 1040 | */ | ||
| 1041 | protected Action[] getActions() { | ||
| 1042 | return null; | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | /** | ||
| 1046 | * Subclass and override if you wish to provide a | ||
| 1047 | * <code>DocumentFilter</code> to restrict what can be input. | ||
| 1048 | * <code>install</code> will install the returned value onto | ||
| 1049 | * the <code>JFormattedTextField</code>. | ||
| 1050 | * | ||
| 1051 | * @return DocumentFilter to restrict edits | ||
| 1052 | */ | ||
| 1053 | protected DocumentFilter getDocumentFilter() { | ||
| 1054 | return null; | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | /** | ||
| 1058 | * Subclass and override if you wish to provide a filter to restrict | ||
| 1059 | * where the user can navigate to. | ||
| 1060 | * <code>install</code> will install the returned value onto | ||
| 1061 | * the <code>JFormattedTextField</code>. | ||
| 1062 | * | ||
| 1063 | * @return NavigationFilter to restrict navigation | ||
| 1064 | */ | ||
| 1065 | protected NavigationFilter getNavigationFilter() { | ||
| 1066 | return null; | ||
| 1067 | } | ||
| 1068 | |||
| 1069 | /** | ||
| 1070 | * Clones the <code>AbstractFormatter</code>. The returned instance | ||
| 1071 | * is not associated with a <code>JFormattedTextField</code>. | ||
| 1072 | * | ||
| 1073 | * @return Copy of the AbstractFormatter | ||
| 1074 | */ | ||
| 1075 | protected Object clone() throws CloneNotSupportedException { | ||
| 1076 | AbstractFormatter formatter = (AbstractFormatter)super.clone(); | ||
| 1077 | |||
| 1078 | formatter.ftf = null; | ||
| 1079 | return formatter; | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | /** | ||
| 1083 | * Installs the <code>DocumentFilter</code> <code>filter</code> | ||
| 1084 | * onto the current <code>JFormattedTextField</code>. | ||
| 1085 | * | ||
| 1086 | * @param filter DocumentFilter to install on the Document. | ||
| 1087 | */ | ||
| 1088 | private void installDocumentFilter(DocumentFilter filter) { | ||
| 1089 | JFormattedTextField ftf = getFormattedTextField(); | ||
| 1090 | |||
| 1091 | if (ftf != null) { | ||
| 1092 | Document doc = ftf.getDocument(); | ||
| 1093 | |||
| 1094 | if (doc instanceof AbstractDocument) { | ||
| 1095 | ((AbstractDocument)doc).setDocumentFilter(filter); | ||
| 1096 | } | ||
| 1097 | doc.putProperty(DocumentFilter.class, null); | ||
| 1098 | } | ||
| 1099 | } | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | |||
| 1103 | /** | ||
| 1104 | * Used to commit the edit. This extends JTextField.NotifyAction | ||
| 1105 | * so that <code>isEnabled</code> is true while a JFormattedTextField | ||
| 1106 | * has focus, and extends <code>actionPerformed</code> to invoke | ||
| 1107 | * commitEdit. | ||
| 1108 | */ | ||
| 1109 | static class CommitAction extends JTextField.NotifyAction { | ||
| 1110 | public void actionPerformed(ActionEvent e) { | ||
| 1111 | JTextComponent target = getFocusedComponent(); | ||
| 1112 | |||
| 1113 | if (target instanceof JFormattedTextField) { | ||
| 1114 | // Attempt to commit the value | ||
| 1115 | try { | ||
| 1116 | ((JFormattedTextField)target).commitEdit(); | ||
| 1117 | } catch (ParseException pe) { | ||
| 1118 | ((JFormattedTextField)target).invalidEdit(); | ||
| 1119 | // value not commited, don't notify ActionListeners | ||
| 1120 | return; | ||
| 1121 | } | ||
| 1122 | } | ||
| 1123 | // Super behavior. | ||
| 1124 | super.actionPerformed(e); | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | public boolean isEnabled() { | ||
| 1128 | JTextComponent target = getFocusedComponent(); | ||
| 1129 | if (target instanceof JFormattedTextField) { | ||
| 1130 | JFormattedTextField ftf = (JFormattedTextField)target; | ||
| 1131 | if (!ftf.isEdited()) { | ||
| 1132 | return false; | ||
| 1133 | } | ||
| 1134 | return true; | ||
| 1135 | } | ||
| 1136 | return super.isEnabled(); | ||
| 1137 | } | ||
| 1138 | } | ||
| 1139 | |||
| 1140 | |||
| 1141 | /** | ||
| 1142 | * CancelAction will reset the value in the JFormattedTextField when | ||
| 1143 | * <code>actionPerformed</code> is invoked. It will only be | ||
| 1144 | * enabled if the focused component is an instance of | ||
| 1145 | * JFormattedTextField. | ||
| 1146 | */ | ||
| 1147 | private static class CancelAction extends TextAction { | ||
| 1148 | public CancelAction() { | ||
| 1149 | super("reset-field-edit"); | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | public void actionPerformed(ActionEvent e) { | ||
| 1153 | JTextComponent target = getFocusedComponent(); | ||
| 1154 | |||
| 1155 | if (target instanceof JFormattedTextField) { | ||
| 1156 | JFormattedTextField ftf = (JFormattedTextField)target; | ||
| 1157 | ftf.setValue(ftf.getValue()); | ||
| 1158 | } | ||
| 1159 | } | ||
| 1160 | |||
| 1161 | public boolean isEnabled() { | ||
| 1162 | JTextComponent target = getFocusedComponent(); | ||
| 1163 | if (target instanceof JFormattedTextField) { | ||
| 1164 | JFormattedTextField ftf = (JFormattedTextField)target; | ||
| 1165 | if (!ftf.isEdited()) { | ||
| 1166 | return false; | ||
| 1167 | } | ||
| 1168 | return true; | ||
| 1169 | } | ||
| 1170 | return super.isEnabled(); | ||
| 1171 | } | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | |||
| 1175 | /** | ||
| 1176 | * Sets the dirty state as the document changes. | ||
| 1177 | */ | ||
| 1178 | private class DocumentHandler implements DocumentListener, Serializable { | ||
| 1179 | public void insertUpdate(DocumentEvent e) { | ||
| 1180 | setEdited(true); | ||
| 1181 | } | ||
| 1182 | public void removeUpdate(DocumentEvent e) { | ||
| 1183 | setEdited(true); | ||
| 1184 | } | ||
| 1185 | public void changedUpdate(DocumentEvent e) {} | ||
| 1186 | } | ||
| 1187 | } | ||