1 /*
2 * Copyright 2000-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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 }