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