/* |
|
* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
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.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 style="text-align:left">Value</p></th><th><p style="text-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 committed. 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 queried when focus is |
|
* lost or gained. |
|
* <p> |
|
* <code>JFormattedTextField.AbstractFormatter</code> |
|
* is also responsible for determining when values are committed 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(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> Swing is not thread safe. For more |
|
* information see <a |
|
* href="package-summary.html#threading">Swing's Threading |
|
* Policy</a>. |
|
* <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™ |
|
* has been added to the <code>java.beans</code> package. |
|
* Please see {@link java.beans.XMLEncoder}. |
|
* |
|
* @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 committing 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 committing 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, false); |
|
} |
|
/** |
|
* 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, 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, true); |
|
} |
|
} |
|
/** |
|
* 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, 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, true); |
|
} catch (ParseException pe) { |
|
if (fb == JFormattedTextField.this.COMMIT_OR_REVERT) { |
|
JFormattedTextField.this.setValue( |
|
JFormattedTextField.this.getValue(), true, true); |
|
} |
|
} |
|
} |
|
else if (fb == JFormattedTextField.REVERT) { |
|
JFormattedTextField.this.setValue( |
|
JFormattedTextField.this.getValue(), true, 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. If <code>createFormat</code> is true, |
|
* this will also obtain a new <code>AbstractFormatter</code> from the |
|
* current factory. The property change event will be fired if |
|
* <code>firePC</code> is true. |
|
*/ |
|
private void setValue(Object value, boolean createFormat, boolean firePC) { |
|
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); |
|
if (firePC) { |
|
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(); |
|
((NumberFormatter)displayFormatter).setValueClass(type.getClass()); |
|
AbstractFormatter editFormatter = new NumberFormatter( |
|
new DecimalFormat("#.#")); |
|
((NumberFormatter)editFormatter).setValueClass(type.getClass()); |
|
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. |
|
* @since 1.4 |
|
*/ |
|
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 enforce 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. |
|
* @since 1.4 |
|
*/ |
|
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) { |
|
((JFormattedTextField)target).invalidEdit(); |
|
// value not committed, don't notify ActionListeners |
|
return; |
|
} |
|
} |
|
// Super behavior. |
|
super.actionPerformed(e); |
|
} |
|
public boolean isEnabled() { |
|
JTextComponent target = getFocusedComponent(); |
|
if (target instanceof JFormattedTextField) { |
|
JFormattedTextField ftf = (JFormattedTextField)target; |
|
if (!ftf.isEdited()) { |
|
return false; |
|
} |
|
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(); |
|
if (target instanceof JFormattedTextField) { |
|
JFormattedTextField ftf = (JFormattedTextField)target; |
|
if (!ftf.isEdited()) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
return super.isEnabled(); |
|
} |
|
} |
|
/** |
|
* 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) {} |
|
} |
|
} |