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.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 style="text-align:left">Value</p></th><th><p style="text-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 committed. 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 queried when focus is 133 * lost or gained. 134 * <p> 135 * <code>JFormattedTextField.AbstractFormatter</code> 136 * is also responsible for determining when values are committed 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™ 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 committing 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 committing 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.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 enforce 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 committed, 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 }