- /*
- * @(#)JFormattedTextField.java 1.17 03/01/23
- *
- * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing;
-
- import java.awt.*;
- import java.awt.event.*;
- import java.awt.im.InputContext;
- import java.io.*;
- import java.text.*;
- import java.util.*;
- import javax.swing.UIManager;
- import javax.swing.event.*;
- import javax.swing.plaf.UIResource;
- import javax.swing.text.*;
-
- /**
- * <code>JFormattedTextField</code> extends <code>JTextField</code> adding
- * support for formatting arbitrary values, as well as retrieving a particular
- * object once the user has edited the text. The following illustrates
- * configuring a <code>JFormattedTextField</code> to edit dates:
- * <pre>
- * JFormattedTextField ftf = new JFormattedTextField();
- * ftf.setValue(new Date());
- * </pre>
- * <p>
- * Once a <code>JFormattedTextField</code> has been created, you can
- * listen for editing changes by way of adding
- * a <code>PropertyChangeListener</code> and listening for
- * <code>PropertyChangeEvent</code>s with the property name <code>value</code>.
- * <p>
- * <code>JFormattedTextField</code> allows
- * configuring what action should be taken when focus is lost. The possible
- * configurations are:
- * <table summary="Possible JFormattedTextField configurations and their descriptions">
- * <tr><th><p align="left">Value</p></th><th><p align="left">Description</p></th></tr>
- * <tr><td>JFormattedTextField.REVERT
- * <td>Revert the display to match that of <code>getValue</code>,
- * possibly losing the current edit.
- * <tr><td>JFormattedTextField.COMMIT
- * <td>Commits the current value. If the value being edited
- * isn't considered a legal value by the
- * <code>AbstractFormatter</code> that is, a
- * <code>ParseException</code> is thrown, then the value
- * will not change, and then edited value will persist.
- * <tr><td>JFormattedTextField.COMMIT_OR_REVERT
- * <td>Similar to <code>COMMIT</code>, but if the value isn't
- * legal, behave like <code>REVERT</code>.
- * <tr><td>JFormattedTextField.PERSIST
- * <td>Do nothing, don't obtain a new
- * <code>AbstractFormatter</code>, and don't update the value.
- * </table>
- * The default is <code>JFormattedTextField.COMMIT_OR_REVERT</code>,
- * refer to {@link #setFocusLostBehavior} for more information on this.
- * <p>
- * <code>JFormattedTextField</code> allows the focus to leave, even if
- * the currently edited value is invalid. To lock the focus down while the
- * <code>JFormattedTextField</code> is an invalid edit state
- * you can attach an <code>InputVerifier</code>. The following code snippet
- * shows a potential implementation of such an <code>InputVerifier</code>:
- * <pre>
- * public class FormattedTextFieldVerifier extends InputVerifier {
- * public boolean verify(JComponent input) {
- * if (input instanceof JFormattedTextField) {
- * JFormattedTextField ftf = (JFormattedTextField)input;
- * AbstractFormatter formatter = ftf.getFormatter();
- * if (formatter != null) {
- * String text = ftf.getText();
- * try {
- * formatter.stringToValue(text);
- * return true;
- * } catch (ParseException pe) {
- * return false;
- * }
- * }
- * }
- * return true;
- * }
- * public boolean shouldYieldFocus(JComponent input) {
- * return verify(input);
- * }
- * }
- * </pre>
- * <p>
- * Alternatively, you could invoke <code>commitEdit</code>, which would also
- * commit the value.
- * <p>
- * <code>JFormattedTextField</code> does not do the formatting it self,
- * rather formatting is done through an instance of
- * <code>JFormattedTextField.AbstractFormatter</code> which is obtained from
- * an instance of <code>JFormattedTextField.AbstractFormatterFactory</code>.
- * Instances of <code>JFormattedTextField.AbstractFormatter</code> are
- * notified when they become active by way of the
- * <code>install</code> method, at which point the
- * <code>JFormattedTextField.AbstractFormatter</code> can install whatever
- * it needs to, typically a <code>DocumentFilter</code>. Similarly when
- * <code>JFormattedTextField</code> no longer
- * needs the <code>AbstractFormatter</code>, it will invoke
- * <code>uninstall</code>.
- * <p>
- * <code>JFormattedTextField</code> typically
- * queries the <code>AbstractFormatterFactory</code> for an
- * <code>AbstractFormat</code> when it gains or loses focus. Although this
- * can change based on the focus lost policy. If the focus lost
- * policy is <code>JFormattedTextField.PERSIST</code>
- * and the <code>JFormattedTextField</code> has been edited, the
- * <code>AbstractFormatterFactory</code> will not be queried until the
- * value has been commited. Similarly if the focus lost policy is
- * <code>JFormattedTextField.COMMIT</code> and an exception
- * is thrown from <code>stringToValue</code>, the
- * <code>AbstractFormatterFactory</code> will not be querired when focus is
- * lost or gained.
- * <p>
- * <code>JFormattedTextField.AbstractFormatter</code>
- * is also responsible for determining when values are commited to
- * the <code>JFormattedTextField</code>. Some
- * <code>JFormattedTextField.AbstractFormatter</code>s will make new values
- * available on every edit, and others will never commit the value. You can
- * force the current value to be obtained
- * from the current <code>JFormattedTextField.AbstractFormatter</code>
- * by way of invoking <code>commitEdit</code>. <code>commitEdit</code> will
- * be invoked whenever return is pressed in the
- * <code>JFormattedTextField</code>.
- * <p>
- * If an <code>AbstractFormatterFactory</code> has not been explicitly
- * set, one will be set based on the <code>Class</code> of the value type after
- * <code>setValue</code> has been invoked (assuming value is non-null).
- * For example, in the following code an appropriate
- * <code>AbstractFormatterFactory</code> and <code>AbstractFormatter</code>
- * will be created to handle formatting of numbers:
- * <pre>
- * JFormattedTextField tf = new JFormattedTextField();
- * tf.setValue(new Number(100));
- * </pre>
- * <p>
- * <strong>Warning:</strong> As the <code>AbstractFormatter</code> will
- * typically install a <code>DocumentFilter</code> on the
- * <code>Document</code>, and a <code>NavigationFilter</code> on the
- * <code>JFormattedTextField</code> you should not install your own. If you do,
- * you are likely to see odd behavior in that the editing policy of the
- * <code>AbstractFormatter</code> will not be enforced.
- * <p>
- * <strong>Warning:</strong>
- * Serialized objects of this class will not be compatible with
- * future Swing releases. The current serialization support is
- * appropriate for short term storage or RMI between applications running
- * the same version of Swing. As of 1.4, support for long term storage
- * of all JavaBeans<sup><font size="-2">TM</font></sup>
- * has been added to the <code>java.beans</code> package.
- * Please see {@link java.beans.XMLEncoder}.
- *
- * @version 1.17 01/23/03
- * @since 1.4
- */
- public class JFormattedTextField extends JTextField {
- private static final String uiClassID = "FormattedTextFieldUI";
- private static final Action[] defaultActions =
- { new CommitAction(), new CancelAction() };
-
- /**
- * Constant identifying that when focus is lost,
- * <code>commitEdit</code> should be invoked. If in commiting the
- * new value a <code>ParseException</code> is thrown, the invalid
- * value will remain.
- *
- * @see #setFocusLostBehavior
- */
- public static final int COMMIT = 0;
-
- /**
- * Constant identifying that when focus is lost,
- * <code>commitEdit</code> should be invoked. If in commiting the new
- * value a <code>ParseException</code> is thrown, the value will be
- * reverted.
- *
- * @see #setFocusLostBehavior
- */
- public static final int COMMIT_OR_REVERT = 1;
-
- /**
- * Constant identifying that when focus is lost, editing value should
- * be reverted to current value set on the
- * <code>JFormattedTextField</code>.
- *
- * @see #setFocusLostBehavior
- */
- public static final int REVERT = 2;
-
- /**
- * Constant identifying that when focus is lost, the edited value
- * should be left.
- *
- * @see #setFocusLostBehavior
- */
- public static final int PERSIST = 3;
-
-
- /**
- * Factory used to obtain an instance of AbstractFormatter.
- */
- private AbstractFormatterFactory factory;
- /**
- * Object responsible for formatting the current value.
- */
- private AbstractFormatter format;
- /**
- * Last valid value.
- */
- private Object value;
- /**
- * True while the value being edited is valid.
- */
- private boolean editValid;
- /**
- * Behavior when focus is lost.
- */
- private int focusLostBehavior;
- /**
- * Indicates the current value has been edited.
- */
- private boolean edited;
- /**
- * Used to set the dirty state.
- */
- private DocumentListener documentListener;
- /**
- * Masked used to set the AbstractFormatterFactory.
- */
- private Object mask;
- /**
- * ActionMap that the TextFormatter Actions are added to.
- */
- private ActionMap textFormatterActionMap;
- /**
- * Indicates the input method composed text is in the document
- */
- private boolean composedTextExists = false;
- /**
- * A handler for FOCUS_LOST event
- */
- private FocusLostHandler focusLostHandler;
-
-
- /**
- * Creates a <code>JFormattedTextField</code> with no
- * <code>AbstractFormatterFactory</code>. Use <code>setMask</code> or
- * <code>setFormatterFactory</code> to configure the
- * <code>JFormattedTextField</code> to edit a particular type of
- * value.
- */
- public JFormattedTextField() {
- super();
- enableEvents(AWTEvent.FOCUS_EVENT_MASK);
- setFocusLostBehavior(COMMIT_OR_REVERT);
- }
-
- /**
- * Creates a JFormattedTextField with the specified value. This will
- * create an <code>AbstractFormatterFactory</code> based on the
- * type of <code>value</code>.
- *
- * @param value Initial value for the JFormattedTextField
- */
- public JFormattedTextField(Object value) {
- this();
- setValue(value);
- }
-
- /**
- * Creates a <code>JFormattedTextField</code>. <code>format</code> is
- * wrapped in an appropriate <code>AbstractFormatter</code> which is
- * then wrapped in an <code>AbstractFormatterFactory</code>.
- *
- * @param format Format used to look up an AbstractFormatter
- */
- public JFormattedTextField(java.text.Format format) {
- this();
- setFormatterFactory(getDefaultFormatterFactory(format));
- }
-
- /**
- * Creates a <code>JFormattedTextField</code> with the specified
- * <code>AbstractFormatter</code>. The <code>AbstractFormatter</code>
- * is placed in an <code>AbstractFormatterFactory</code>.
- *
- * @param formatter AbstractFormatter to use for formatting.
- */
- public JFormattedTextField(AbstractFormatter formatter) {
- this(new DefaultFormatterFactory(formatter));
- }
-
- /**
- * Creates a <code>JFormattedTextField</code> with the specified
- * <code>AbstractFormatterFactory</code>.
- *
- * @param factory AbstractFormatterFactory used for formatting.
- */
- public JFormattedTextField(AbstractFormatterFactory factory) {
- this();
- setFormatterFactory(factory);
- }
-
- /**
- * Creates a <code>JFormattedTextField</code> with the specified
- * <code>AbstractFormatterFactory</code> and initial value.
- *
- * @param factory <code>AbstractFormatterFactory</code> used for
- * formatting.
- * @param currentValue Initial value to use
- */
- public JFormattedTextField(AbstractFormatterFactory factory,
- Object currentValue) {
- this(currentValue);
- setFormatterFactory(factory);
- }
-
- /**
- * Sets the behavior when focus is lost. This will be one of
- * <code>JFormattedTextField.COMMIT_OR_REVERT</code>,
- * <code>JFormattedTextField.REVERT</code>,
- * <code>JFormattedTextField.COMMIT</code> or
- * <code>JFormattedTextField.PERSIST</code>
- * Note that some <code>AbstractFormatter</code>s may push changes as
- * they occur, so that the value of this will have no effect.
- * <p>
- * This will throw an <code>IllegalArgumentException</code> if the object
- * passed in is not one of the afore mentioned values.
- * <p>
- * The default value of this property is
- * <code>JFormattedTextField.COMMIT_OR_REVERT</code>.
- *
- * @param behavior Identifies behavior when focus is lost
- * @throws IllegalArgumentException if behavior is not one of the known
- * values
- * @beaninfo
- * enum: COMMIT JFormattedTextField.COMMIT
- * COMMIT_OR_REVERT JFormattedTextField.COMMIT_OR_REVERT
- * REVERT JFormattedTextField.REVERT
- * PERSIST JFormattedTextField.PERSIST
- * description: Behavior when component loses focus
- */
- public void setFocusLostBehavior(int behavior) {
- if (behavior != COMMIT && behavior != COMMIT_OR_REVERT &&
- behavior != PERSIST && behavior != REVERT) {
- throw new IllegalArgumentException("setFocusLostBehavior must be one of: JFormattedTextField.COMMIT, JFormattedTextField.COMMIT_OR_REVERT, JFormattedTextField.PERSIST or JFormattedTextField.REVERT");
- }
- focusLostBehavior = behavior;
- }
-
- /**
- * Returns the behavior when focus is lost. This will be one of
- * <code>COMMIT_OR_REVERT</code>,
- * <code>COMMIT</code>,
- * <code>REVERT</code> or
- * <code>PERSIST</code>
- * Note that some <code>AbstractFormatter</code>s may push changes as
- * they occur, so that the value of this will have no effect.
- *
- * @return returns behavior when focus is lost
- */
- public int getFocusLostBehavior() {
- return focusLostBehavior;
- }
-
- /**
- * Sets the <code>AbstractFormatterFactory</code>.
- * <code>AbstractFormatterFactory</code> is
- * able to return an instance of <code>AbstractFormatter</code> that is
- * used to format a value for display, as well an enforcing an editing
- * policy.
- * <p>
- * If you have not explicitly set an <code>AbstractFormatterFactory</code>
- * by way of this method (or a constructor) an
- * <code>AbstractFormatterFactory</code> and consequently an
- * <code>AbstractFormatter</code> will be used based on the
- * <code>Class</code> of the value. <code>NumberFormatter</code> will
- * be used for <code>Number</code>s, <code>DateFormatter</code> will
- * be used for <code>Dates</code>, otherwise <code>DefaultFormatter</code>
- * will be used.
- * <p>
- * This is a JavaBeans bound property.
- *
- * @param tf <code>AbstractFormatterFactory</code> used to lookup
- * instances of <code>AbstractFormatter</code>
- * @beaninfo
- * bound: true
- * attribute: visualUpdate true
- * description: AbstractFormatterFactory, responsible for returning an
- * AbstractFormatter that can format the current value.
- */
- public void setFormatterFactory(AbstractFormatterFactory tf) {
- AbstractFormatterFactory oldFactory = factory;
-
- factory = tf;
- firePropertyChange("formatterFactory", oldFactory, tf);
- setValue(getValue(), true);
- }
-
- /**
- * Returns the current <code>AbstractFormatterFactory</code>.
- *
- * @see #setFormatterFactory
- * @return <code>AbstractFormatterFactory</code> used to determine
- * <code>AbstractFormatter</code>s
- */
- public AbstractFormatterFactory getFormatterFactory() {
- return factory;
- }
-
- /**
- * Sets the current <code>AbstractFormatter</code>.
- * <p>
- * You should not normally invoke this, instead set the
- * <code>AbstractFormatterFactory</code> or set the value.
- * <code>JFormattedTextField</code> will
- * invoke this as the state of the <code>JFormattedTextField</code>
- * changes and requires the value to be reset.
- * <code>JFormattedTextField</code> passes in the
- * <code>AbstractFormatter</code> obtained from the
- * <code>AbstractFormatterFactory</code>.
- * <p>
- * This is a JavaBeans bound property.
- *
- * @see #setFormatterFactory
- * @param format AbstractFormatter to use for formatting
- * @beaninfo
- * bound: true
- * attribute: visualUpdate true
- * description: TextFormatter, responsible for formatting the current value
- */
- protected void setFormatter(AbstractFormatter format) {
- AbstractFormatter oldFormat = this.format;
-
- if (oldFormat != null) {
- oldFormat.uninstall();
- }
- setEditValid(true);
- this.format = format;
- if (format != null) {
- format.install(this);
- }
- setEdited(false);
- firePropertyChange("textFormatter", oldFormat, format);
- }
-
- /**
- * Returns the <code>AbstractFormatter</code> that is used to format and
- * parse the current value.
- *
- * @return AbstractFormatter used for formatting
- */
- public AbstractFormatter getFormatter() {
- return format;
- }
-
- /**
- * Sets the value that will be formatted by an
- * <code>AbstractFormatter</code> obtained from the current
- * <code>AbstractFormatterFactory</code>. If no
- * <code>AbstractFormatterFactory</code> has been specified, this will
- * attempt to create one based on the type of <code>value</code>.
- * <p>
- * The default value of this property is null.
- * <p>
- * This is a JavaBeans bound property.
- *
- * @param value Current value to display
- * @beaninfo
- * bound: true
- * attribute: visualUpdate true
- * description: The value to be formatted.
- */
- public void setValue(Object value) {
- if (value != null && getFormatterFactory() == null) {
- setFormatterFactory(getDefaultFormatterFactory(value));
- }
- setValue(value, true);
- }
-
- /**
- * Returns the last valid value. Based on the editing policy of
- * the <code>AbstractFormatter</code> this may not return the current
- * value. The currently edited value can be obtained by invoking
- * <code>commitEdit</code> followed by <code>getValue</code>.
- *
- * @return Last valid value
- */
- public Object getValue() {
- return value;
- }
-
- /**
- * Forces the current value to be taken from the
- * <code>AbstractFormatter</code> and set as the current value.
- * This has no effect if there is no current
- * <code>AbstractFormatter</code> installed.
- *
- * @throws ParseException if the <code>AbstractFormatter</code> is not able
- * to format the current value
- */
- public void commitEdit() throws ParseException {
- AbstractFormatter format = getFormatter();
-
- if (format != null) {
- setValue(format.stringToValue(getText()), false);
- }
- }
-
- /**
- * Sets the validity of the edit on the receiver. You should not normally
- * invoke this. This will be invoked by the
- * <code>AbstractFormatter</code> as the user edits the value.
- * <p>
- * Not all formatters will allow the component to get into an invalid
- * state, and thus this may never be invoked.
- * <p>
- * Based on the look and feel this may visually change the state of
- * the receiver.
- *
- * @param isValid boolean indicating if the currently edited value is
- * valid.
- * @beaninfo
- * bound: true
- * attribute: visualUpdate true
- * description: True indicates the edited value is valid
- */
- private void setEditValid(boolean isValid) {
- if (isValid != editValid) {
- editValid = isValid;
- firePropertyChange("editValid", Boolean.valueOf(!isValid),
- Boolean.valueOf(isValid));
- }
- }
-
- /**
- * Returns true if the current value being edited is valid. The value of
- * this is managed by the current <code>AbstractFormatter</code>, as such
- * there is no public setter for it.
- *
- * @return true if the current value being edited is valid.
- */
- public boolean isEditValid() {
- return editValid;
- }
-
- /**
- * Invoked when the user inputs an invalid value. This gives the
- * component a chance to provide feedback. The default
- * implementation beeps.
- */
- protected void invalidEdit() {
- UIManager.getLookAndFeel().provideErrorFeedback(JFormattedTextField.this);
- }
-
- /**
- * Processes any input method events, such as
- * <code>InputMethodEvent.INPUT_METHOD_TEXT_CHANGED</code> or
- * <code>InputMethodEvent.CARET_POSITION_CHANGED</code>.
- *
- * @param e the <code>InputMethodEvent</code>
- * @see InputMethodEvent
- */
- protected void processInputMethodEvent(InputMethodEvent e) {
- AttributedCharacterIterator text = e.getText();
- int commitCount = e.getCommittedCharacterCount();
-
- // Keep track of the composed text
- if (text != null) {
- int begin = text.getBeginIndex();
- int end = text.getEndIndex();
- composedTextExists = ((end - begin) > commitCount);
- } else {
- composedTextExists = false;
- }
-
- super.processInputMethodEvent(e);
- }
-
- /**
- * Processes any focus events, such as
- * <code>FocusEvent.FOCUS_GAINED</code> or
- * <code>FocusEvent.FOCUS_LOST</code>.
- *
- * @param e the <code>FocusEvent</code>
- * @see FocusEvent
- */
- protected void processFocusEvent(FocusEvent e) {
- super.processFocusEvent(e);
-
- // ignore temporary focus event
- if (e.isTemporary()) {
- return;
- }
-
- if (isEdited() && e.getID() == FocusEvent.FOCUS_LOST) {
- InputContext ic = getInputContext();
- if (focusLostHandler == null) {
- focusLostHandler = new FocusLostHandler();
- }
-
- // if there is a composed text, process it first
- if ((ic != null) && composedTextExists) {
- ic.endComposition();
- EventQueue.invokeLater(focusLostHandler);
- } else {
- focusLostHandler.run();
- }
- }
- else if (!isEdited()) {
- // reformat
- setValue(getValue(), true);
- }
- }
-
- /**
- * FOCUS_LOST behavior implementation
- */
- private class FocusLostHandler implements Runnable, Serializable {
- public void run() {
- int fb = JFormattedTextField.this.getFocusLostBehavior();
- if (fb == JFormattedTextField.COMMIT ||
- fb == JFormattedTextField.COMMIT_OR_REVERT) {
- try {
- JFormattedTextField.this.commitEdit();
- // Give it a chance to reformat.
- JFormattedTextField.this.setValue(
- JFormattedTextField.this.getValue(), true);
- } catch (ParseException pe) {
- if (fb == JFormattedTextField.this.COMMIT_OR_REVERT) {
- JFormattedTextField.this.setValue(
- JFormattedTextField.this.getValue(), true);
- }
- }
- }
- else if (fb == JFormattedTextField.REVERT) {
- JFormattedTextField.this.setValue(
- JFormattedTextField.this.getValue(), true);
- }
- }
- }
-
- /**
- * Fetches the command list for the editor. This is
- * the list of commands supported by the plugged-in UI
- * augmented by the collection of commands that the
- * editor itself supports. These are useful for binding
- * to events, such as in a keymap.
- *
- * @return the command list
- */
- public Action[] getActions() {
- return TextAction.augmentList(super.getActions(), defaultActions);
- }
-
- /**
- * Gets the class ID for a UI.
- *
- * @return the string "FormattedTextFieldUI"
- * @see JComponent#getUIClassID
- */
- public String getUIClassID() {
- return uiClassID;
- }
-
- /**
- * Associates the editor with a text document.
- * The currently registered factory is used to build a view for
- * the document, which gets displayed by the editor after revalidation.
- * A PropertyChange event ("document") is propagated to each listener.
- *
- * @param doc the document to display/edit
- * @see #getDocument
- * @beaninfo
- * description: the text document model
- * bound: true
- * expert: true
- */
- public void setDocument(Document doc) {
- if (documentListener != null && getDocument() != null) {
- getDocument().removeDocumentListener(documentListener);
- }
- super.setDocument(doc);
- if (documentListener == null) {
- documentListener = new DocumentHandler();
- }
- doc.addDocumentListener(documentListener);
- }
-
- /*
- * See readObject and writeObject in JComponent for more
- * information about serialization in Swing.
- *
- * @param s Stream to write to
- */
- private void writeObject(ObjectOutputStream s) throws IOException {
- s.defaultWriteObject();
- if (getUIClassID().equals(uiClassID)) {
- byte count = JComponent.getWriteObjCounter(this);
- JComponent.setWriteObjCounter(this, --count);
- if (count == 0 && ui != null) {
- ui.installUI(this);
- }
- }
- }
-
- /**
- * Resets the Actions that come from the TextFormatter to
- * <code>actions</code>.
- */
- private void setFormatterActions(Action[] actions) {
- if (actions == null) {
- if (textFormatterActionMap != null) {
- textFormatterActionMap.clear();
- }
- }
- else {
- if (textFormatterActionMap == null) {
- ActionMap map = getActionMap();
-
- textFormatterActionMap = new ActionMap();
- while (map != null) {
- ActionMap parent = map.getParent();
-
- if (parent instanceof UIResource || parent == null) {
- map.setParent(textFormatterActionMap);
- textFormatterActionMap.setParent(parent);
- break;
- }
- map = parent;
- }
- }
- for (int counter = actions.length - 1; counter >= 0;
- counter--) {
- Object key = actions[counter].getValue(Action.NAME);
-
- if (key != null) {
- textFormatterActionMap.put(key, actions[counter]);
- }
- }
- }
- }
-
- /**
- * Does the setting of the value and firing the event. If
- * <code>createFormat</code> is true, this will also obtain
- * a new <code>AbstractFormatter</code> from the current
- * factory.
- */
- private void setValue(Object value, boolean createFormat) {
- Object oldValue = this.value;
-
- this.value = value;
-
- if (createFormat) {
- AbstractFormatterFactory factory = getFormatterFactory();
- AbstractFormatter atf;
-
- if (factory != null) {
- atf = factory.getFormatter(this);
- }
- else {
- atf = null;
- }
- setFormatter(atf);
- }
- else {
- // Assumed to be valid
- setEditValid(true);
- }
-
- setEdited(false);
-
- firePropertyChange("value", oldValue, value);
- }
-
- /**
- * Sets the edited state of the receiver.
- */
- private void setEdited(boolean edited) {
- this.edited = edited;
- }
-
- /**
- * Returns true if the receiver has been edited.
- */
- private boolean isEdited() {
- return edited;
- }
-
- /**
- * Returns an AbstractFormatterFactory suitable for the passed in
- * Object type.
- */
- private AbstractFormatterFactory getDefaultFormatterFactory(Object type) {
- if (type instanceof DateFormat) {
- return new DefaultFormatterFactory(new DateFormatter
- ((DateFormat)type));
- }
- if (type instanceof NumberFormat) {
- return new DefaultFormatterFactory(new NumberFormatter(
- (NumberFormat)type));
- }
- if (type instanceof Format) {
- return new DefaultFormatterFactory(new InternationalFormatter(
- (Format)type));
- }
- if (type instanceof Date) {
- return new DefaultFormatterFactory(new DateFormatter());
- }
- if (type instanceof Number) {
- AbstractFormatter displayFormatter = new NumberFormatter();
- AbstractFormatter editFormatter = new NumberFormatter(
- new DecimalFormat("#.#"));
-
- return new DefaultFormatterFactory(displayFormatter,
- displayFormatter,editFormatter);
- }
- return new DefaultFormatterFactory(new DefaultFormatter());
- }
-
-
- /**
- * Instances of <code>AbstractFormatterFactory</code> are used by
- * <code>JFormattedTextField</code> to obtain instances of
- * <code>AbstractFormatter</code> which in turn are used to format values.
- * <code>AbstractFormatterFactory</code> can return different
- * <code>AbstractFormatter</code>s based on the state of the
- * <code>JFormattedTextField</code>, perhaps returning different
- * <code>AbstractFormatter</code>s when the
- * <code>JFormattedTextField</code> has focus vs when it
- * doesn't have focus.
- */
- public static abstract class AbstractFormatterFactory {
- /**
- * Returns an <code>AbstractFormatter</code> that can handle formatting
- * of the passed in <code>JFormattedTextField</code>.
- *
- * @param tf JFormattedTextField requesting AbstractFormatter
- * @return AbstractFormatter to handle formatting duties, a null
- * return value implies the JFormattedTextField should behave
- * like a normal JTextField
- */
- public abstract AbstractFormatter getFormatter(JFormattedTextField tf);
- }
-
-
- /**
- * Instances of <code>AbstractFormatter</code> are used by
- * <code>JFormattedTextField</code> to handle the conversion both
- * from an Object to a String, and back from a String to an Object.
- * <code>AbstractFormatter</code>s can also enfore editing policies,
- * or navigation policies, or manipulate the
- * <code>JFormattedTextField</code> in any way it sees fit to
- * enforce the desired policy.
- * <p>
- * An <code>AbstractFormatter</code> can only be active in
- * one <code>JFormattedTextField</code> at a time.
- * <code>JFormattedTextField</code> invokes
- * <code>install</code> when it is ready to use it followed
- * by <code>uninstall</code> when done. Subclasses
- * that wish to install additional state should override
- * <code>install</code> and message super appropriately.
- * <p>
- * Subclasses must override the conversion methods
- * <code>stringToValue</code> and <code>valueToString</code>. Optionally
- * they can override <code>getActions</code>,
- * <code>getNavigationFilter</code> and <code>getDocumentFilter</code>
- * to restrict the <code>JFormattedTextField</code> in a particular
- * way.
- * <p>
- * Subclasses that allow the <code>JFormattedTextField</code> to be in
- * a temporarily invalid state should invoke <code>setEditValid</code>
- * at the appropriate times.
- */
- public static abstract class AbstractFormatter implements Serializable {
- private JFormattedTextField ftf;
-
- /**
- * Installs the <code>AbstractFormatter</code> onto a particular
- * <code>JFormattedTextField</code>.
- * This will invoke <code>valueToString</code> to convert the
- * current value from the <code>JFormattedTextField</code> to
- * a String. This will then install the <code>Action</code>s from
- * <code>getActions</code>, the <code>DocumentFilter</code>
- * returned from <code>getDocumentFilter</code> and the
- * <code>NavigationFilter</code> returned from
- * <code>getNavigationFilter</code> onto the
- * <code>JFormattedTextField</code>.
- * <p>
- * Subclasses will typically only need to override this if they
- * wish to install additional listeners on the
- * <code>JFormattedTextField</code>.
- * <p>
- * If there is a <code>ParseException</code> in converting the
- * current value to a String, this will set the text to an empty
- * String, and mark the <code>JFormattedTextField</code> as being
- * in an invalid state.
- * <p>
- * While this is a public method, this is typically only useful
- * for subclassers of <code>JFormattedTextField</code>.
- * <code>JFormattedTextField</code> will invoke this method at
- * the appropriate times when the value changes, or its internal
- * state changes. You will only need to invoke this yourself if
- * you are subclassing <code>JFormattedTextField</code> and
- * installing/uninstalling <code>AbstractFormatter</code> at a
- * different time than <code>JFormattedTextField</code> does.
- *
- * @param ftf JFormattedTextField to format for, may be null indicating
- * uninstall from current JFormattedTextField.
- */
- public void install(JFormattedTextField ftf) {
- if (this.ftf != null) {
- uninstall();
- }
- this.ftf = ftf;
- if (ftf != null) {
- try {
- ftf.setText(valueToString(ftf.getValue()));
- } catch (ParseException pe) {
- ftf.setText("");
- setEditValid(false);
- }
- installDocumentFilter(getDocumentFilter());
- ftf.setNavigationFilter(getNavigationFilter());
- ftf.setFormatterActions(getActions());
- }
- }
-
- /**
- * Uninstalls any state the <code>AbstractFormatter</code> may have
- * installed on the <code>JFormattedTextField</code>. This resets the
- * <code>DocumentFilter</code>, <code>NavigationFilter</code>
- * and additional <code>Action</code>s installed on the
- * <code>JFormattedTextField</code>.
- */
- public void uninstall() {
- if (this.ftf != null) {
- installDocumentFilter(null);
- this.ftf.setNavigationFilter(null);
- this.ftf.setFormatterActions(null);
- }
- }
-
- /**
- * Parses <code>text</code> returning an arbitrary Object. Some
- * formatters may return null.
- *
- * @throws ParseException if there is an error in the conversion
- * @param text String to convert
- * @return Object representation of text
- */
- public abstract Object stringToValue(String text) throws
- ParseException;
-
- /**
- * Returns the string value to display for <code>value</code>.
- *
- * @throws ParseException if there is an error in the conversion
- * @param value Value to convert
- * @return String representation of value
- */
- public abstract String valueToString(Object value) throws
- ParseException;
-
- /**
- * Returns the current <code>JFormattedTextField</code> the
- * <code>AbstractFormatter</code> is installed on.
- *
- * @return JFormattedTextField formatting for.
- */
- protected JFormattedTextField getFormattedTextField() {
- return ftf;
- }
-
- /**
- * This should be invoked when the user types an invalid character.
- * This forwards the call to the current JFormattedTextField.
- */
- protected void invalidEdit() {
- JFormattedTextField ftf = getFormattedTextField();
-
- if (ftf != null) {
- ftf.invalidEdit();
- }
- }
-
- /**
- * Invoke this to update the <code>editValid</code> property of the
- * <code>JFormattedTextField</code>. If you an enforce a policy
- * such that the <code>JFormattedTextField</code> is always in a
- * valid state, you will never need to invoke this.
- *
- * @param valid Valid state of the JFormattedTextField
- */
- protected void setEditValid(boolean valid) {
- JFormattedTextField ftf = getFormattedTextField();
-
- if (ftf != null) {
- ftf.setEditValid(valid);
- }
- }
-
- /**
- * Subclass and override if you wish to provide a custom set of
- * <code>Action</code>s. <code>install</code> will install these
- * on the <code>JFormattedTextField</code>'s <code>ActionMap</code>.
- *
- * @return Array of Actions to install on JFormattedTextField
- */
- protected Action[] getActions() {
- return null;
- }
-
- /**
- * Subclass and override if you wish to provide a
- * <code>DocumentFilter</code> to restrict what can be input.
- * <code>install</code> will install the returned value onto
- * the <code>JFormattedTextField</code>.
- *
- * @return DocumentFilter to restrict edits
- */
- protected DocumentFilter getDocumentFilter() {
- return null;
- }
-
- /**
- * Subclass and override if you wish to provide a filter to restrict
- * where the user can navigate to.
- * <code>install</code> will install the returned value onto
- * the <code>JFormattedTextField</code>.
- *
- * @return NavigationFilter to restrict navigation
- */
- protected NavigationFilter getNavigationFilter() {
- return null;
- }
-
- /**
- * Clones the <code>AbstractFormatter</code>. The returned instance
- * is not associated with a <code>JFormattedTextField</code>.
- *
- * @return Copy of the AbstractFormatter
- */
- protected Object clone() throws CloneNotSupportedException {
- AbstractFormatter formatter = (AbstractFormatter)super.clone();
-
- formatter.ftf = null;
- return formatter;
- }
-
- /**
- * Installs the <code>DocumentFilter</code> <code>filter</code>
- * onto the current <code>JFormattedTextField</code>.
- *
- * @param filter DocumentFilter to install on the Document.
- */
- private void installDocumentFilter(DocumentFilter filter) {
- JFormattedTextField ftf = getFormattedTextField();
-
- if (ftf != null) {
- Document doc = ftf.getDocument();
-
- if (doc instanceof AbstractDocument) {
- ((AbstractDocument)doc).setDocumentFilter(filter);
- }
- doc.putProperty(DocumentFilter.class, null);
- }
- }
- }
-
-
- /**
- * Used to commit the edit. This extends JTextField.NotifyAction
- * so that <code>isEnabled</code> is true while a JFormattedTextField
- * has focus, and extends <code>actionPerformed</code> to invoke
- * commitEdit.
- */
- static class CommitAction extends JTextField.NotifyAction {
- public void actionPerformed(ActionEvent e) {
- JTextComponent target = getFocusedComponent();
-
- if (target instanceof JFormattedTextField) {
- // Attempt to commit the value
- try {
- ((JFormattedTextField)target).commitEdit();
- } catch (ParseException pe) {
- // value not commited, don't notify ActionListeners
- return;
- }
- }
- // Super behavior.
- super.actionPerformed(e);
- }
-
- public boolean isEnabled() {
- JTextComponent target = getFocusedComponent();
- if (target instanceof JFormattedTextField) {
- return true;
- }
- return super.isEnabled();
- }
- }
-
-
- /**
- * CancelAction will reset the value in the JFormattedTextField when
- * <code>actionPerformed</code> is invoked. It will only be
- * enabled if the focused component is an instance of
- * JFormattedTextField.
- */
- private static class CancelAction extends TextAction {
- public CancelAction() {
- super("reset-field-edit");
- }
-
- public void actionPerformed(ActionEvent e) {
- JTextComponent target = getFocusedComponent();
-
- if (target instanceof JFormattedTextField) {
- JFormattedTextField ftf = (JFormattedTextField)target;
- ftf.setValue(ftf.getValue());
- }
- }
-
- public boolean isEnabled() {
- JTextComponent target = getFocusedComponent();
- return (target instanceof JFormattedTextField);
- }
- }
-
-
- /**
- * Sets the dirty state as the document changes.
- */
- private class DocumentHandler implements DocumentListener, Serializable {
- public void insertUpdate(DocumentEvent e) {
- setEdited(true);
- }
- public void removeUpdate(DocumentEvent e) {
- setEdited(true);
- }
- public void changedUpdate(DocumentEvent e) {}
- }
- }