| /* JFormattedTextField.java -- |
| Copyright (C) 2003, 2004 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing; |
| |
| import java.awt.event.FocusEvent; |
| import java.io.Serializable; |
| import java.text.DateFormat; |
| import java.text.Format; |
| import java.text.NumberFormat; |
| import java.text.ParseException; |
| import java.util.Date; |
| |
| import javax.swing.text.AbstractDocument; |
| import javax.swing.text.DateFormatter; |
| import javax.swing.text.DefaultFormatter; |
| import javax.swing.text.DefaultFormatterFactory; |
| import javax.swing.text.Document; |
| import javax.swing.text.DocumentFilter; |
| import javax.swing.text.InternationalFormatter; |
| import javax.swing.text.NavigationFilter; |
| import javax.swing.text.NumberFormatter; |
| |
| /** |
| * A text field that makes use of a formatter to display and edit a specific |
| * type of data. The value that is displayed can be an arbitrary object. The |
| * formatter is responsible for displaying the value in a textual form and |
| * it may allow editing of the value. |
| * |
| * Formatters are usually obtained using an instance of |
| * {@link AbstractFormatterFactory}. This factory is responsible for providing |
| * an instance of {@link AbstractFormatter} that is able to handle the |
| * formatting of the value of the JFormattedTextField. |
| * |
| * @author Michael Koch |
| * @author Anthony Balkissoon abalkiss at redhat dot com |
| * |
| * @since 1.4 |
| */ |
| public class JFormattedTextField extends JTextField |
| { |
| private static final long serialVersionUID = 5464657870110180632L; |
| |
| /** |
| * An abstract base implementation for a formatter that can be used by |
| * a JTextField. A formatter can display a specific type of object and |
| * may provide a way to edit this value. |
| */ |
| public abstract static class AbstractFormatter implements Serializable |
| { |
| private static final long serialVersionUID = -5193212041738979680L; |
| |
| private JFormattedTextField textField; |
| |
| public AbstractFormatter () |
| { |
| //Do nothing here. |
| } |
| |
| /** |
| * Clones the AbstractFormatter and removes the association to any |
| * particular JFormattedTextField. |
| * |
| * @return a clone of this formatter with no association to any particular |
| * JFormattedTextField |
| * @throws CloneNotSupportedException if the Object's class doesn't support |
| * the {@link Cloneable} interface |
| */ |
| protected Object clone() |
| throws CloneNotSupportedException |
| { |
| // Clone this formatter. |
| AbstractFormatter newFormatter = (AbstractFormatter) super.clone(); |
| |
| // And remove the association to the JFormattedTextField. |
| newFormatter.textField = null; |
| return newFormatter; |
| } |
| |
| /** |
| * Returns a custom set of Actions that this formatter supports. Should |
| * be subclassed by formatters that have a custom set of Actions. |
| * |
| * @return <code>null</code>. Should be subclassed by formatters that want |
| * to install custom Actions on the JFormattedTextField. |
| */ |
| protected Action[] getActions() |
| { |
| return null; |
| } |
| |
| /** |
| * Gets the DocumentFilter for this formatter. Should be subclassed |
| * by formatters wishing to install a filter that oversees Document |
| * mutations. |
| * |
| * @return <code>null</code>. Should be subclassed by formatters |
| * that want to restrict Document mutations. |
| */ |
| protected DocumentFilter getDocumentFilter() |
| { |
| // Subclasses should override this if they want to install a |
| // DocumentFilter. |
| return null; |
| } |
| |
| /** |
| * Returns the JFormattedTextField on which this formatter is |
| * currently installed. |
| * |
| * @return the JFormattedTextField on which this formatter is currently |
| * installed |
| */ |
| protected JFormattedTextField getFormattedTextField() |
| { |
| return textField; |
| } |
| |
| /** |
| * Gets the NavigationFilter for this formatter. Should be subclassed |
| * by formatters (such as {@link DefaultFormatter}) that wish to |
| * restrict where the cursor can be placed within the text field. |
| * |
| * @return <code>null</code>. Subclassed by formatters that want to restrict |
| * cursor location within the JFormattedTextField. |
| */ |
| protected NavigationFilter getNavigationFilter() |
| { |
| // This should be subclassed if the formatter wants to install |
| // a NavigationFilter on the JFormattedTextField. |
| return null; |
| } |
| |
| /** |
| * Installs this formatter on the specified JFormattedTextField. This |
| * converts the current value to a displayable String and displays it, |
| * and installs formatter specific Actions from <code>getActions</code>. |
| * It also installs a DocumentFilter and NavigationFilter on the |
| * JFormattedTextField. |
| * <p> |
| * If there is a <code>ParseException</code> this sets the text to an |
| * empty String and marks the text field in an invalid state. |
| * |
| * @param textField the JFormattedTextField on which to install this |
| * formatter |
| */ |
| public void install(JFormattedTextField textField) |
| { |
| // Uninstall the current textfield. |
| if (this.textField != null) |
| uninstall(); |
| |
| this.textField = textField; |
| |
| // Install some state on the text field, including display text, |
| // DocumentFilter, NavigationFilter, and formatter specific Actions. |
| if (textField != null) |
| { |
| try |
| { |
| // Set the text of the field. |
| textField.setText(valueToString(textField.getValue())); |
| Document doc = textField.getDocument(); |
| |
| // Set the DocumentFilter for the field's Document. |
| if (doc instanceof AbstractDocument) |
| ((AbstractDocument) doc).setDocumentFilter(getDocumentFilter()); |
| |
| // Set the NavigationFilter. |
| textField.setNavigationFilter(getNavigationFilter()); |
| |
| // Set the Formatter Actions |
| // FIXME: Have to add the actions from getActions() |
| } |
| catch (ParseException pe) |
| { |
| // Set the text to an empty String and mark the field as invalid. |
| textField.setText(""); |
| setEditValid(false); |
| } |
| } |
| } |
| |
| /** |
| * Clears the state installed on the JFormattedTextField by the formatter. |
| * This resets the DocumentFilter, NavigationFilter, and any additional |
| * Actions (returned by <code>getActions()</code>). |
| */ |
| public void uninstall() |
| { |
| // Set the DocumentFilter for the field's Document. |
| Document doc = textField.getDocument(); |
| if (doc instanceof AbstractDocument) |
| ((AbstractDocument) doc).setDocumentFilter(null); |
| textField.setNavigationFilter(null); |
| // FIXME: Have to remove the Actions from getActions() |
| this.textField = null; |
| } |
| |
| /** |
| * Invoke this method when invalid values are entered. This forwards the |
| * call to the JFormattedTextField. |
| */ |
| protected void invalidEdit() |
| { |
| textField.invalidEdit(); |
| } |
| |
| /** |
| * This method updates the <code>editValid</code> property of |
| * JFormattedTextField. |
| * |
| * @param valid the new state for the <code>editValid</code> property |
| */ |
| protected void setEditValid(boolean valid) |
| { |
| textField.editValid = valid; |
| } |
| |
| /** |
| * Parses <code>text</code> to return a corresponding Object. |
| * |
| * @param text the String to parse |
| * @return an Object that <code>text</code> represented |
| * @throws ParseException if there is an error in the conversion |
| */ |
| public abstract Object stringToValue(String text) |
| throws ParseException; |
| |
| /** |
| * Returns a String to be displayed, based on the Object |
| * <code>value</code>. |
| * |
| * @param value the Object from which to generate a String |
| * @return a String to be displayed |
| * @throws ParseException if there is an error in the conversion |
| */ |
| public abstract String valueToString(Object value) |
| throws ParseException; |
| } |
| |
| /** |
| * Delivers instances of an {@link AbstractFormatter} for |
| * a specific value type for a JFormattedTextField. |
| */ |
| public abstract static class AbstractFormatterFactory |
| { |
| public AbstractFormatterFactory() |
| { |
| // Do nothing here. |
| } |
| |
| public abstract AbstractFormatter getFormatter(JFormattedTextField tf); |
| } |
| |
| /** The possible focusLostBehavior options **/ |
| public static final int COMMIT = 0; |
| public static final int COMMIT_OR_REVERT = 1; |
| public static final int REVERT = 2; |
| public static final int PERSIST = 3; |
| |
| /** The most recent valid and committed value **/ |
| private Object value; |
| |
| /** The behaviour for when this text field loses focus **/ |
| private int focusLostBehavior = COMMIT_OR_REVERT; |
| |
| /** The formatter factory currently being used **/ |
| private AbstractFormatterFactory formatterFactory; |
| |
| /** The formatter currently being used **/ |
| private AbstractFormatter formatter; |
| |
| // Package-private to avoid an accessor method. |
| boolean editValid = true; |
| |
| /** |
| * Creates a JFormattedTextField with no formatter factory. |
| * <code>setValue</code> or <code>setFormatterFactory</code> will |
| * properly configure this text field to edit a particular type |
| * of value. |
| */ |
| public JFormattedTextField() |
| { |
| this((AbstractFormatterFactory) null, null); |
| } |
| |
| /** |
| * Creates a JFormattedTextField that can handle the specified Format. |
| * An appopriate AbstractFormatter and AbstractFormatterFactory will |
| * be created for the specified Format. |
| * |
| * @param format the Format that this JFormattedTextField should be able |
| * to handle |
| */ |
| public JFormattedTextField(Format format) |
| { |
| this (); |
| setFormatterFactory(getAppropriateFormatterFactory(format)); |
| } |
| |
| /** |
| * Creates a JFormattedTextField with the specified formatter. This will |
| * create a {@link DefaultFormatterFactory} with this formatter as the default |
| * formatter. |
| * |
| * @param formatter the formatter to use for this JFormattedTextField |
| */ |
| public JFormattedTextField(AbstractFormatter formatter) |
| { |
| this(new DefaultFormatterFactory(formatter)); |
| } |
| |
| /** |
| * Creates a JFormattedTextField with the specified formatter factory. |
| * |
| * @param factory the formatter factory to use for this JFormattedTextField |
| */ |
| public JFormattedTextField(AbstractFormatterFactory factory) |
| { |
| setFormatterFactory(factory); |
| } |
| |
| /** |
| * Creates a JFormattedTextField with the specified formatter factory and |
| * initial value. |
| * |
| * @param factory the initial formatter factory for this JFormattedTextField |
| * @param value the initial value for the text field |
| */ |
| public JFormattedTextField(AbstractFormatterFactory factory, Object value) |
| { |
| setFormatterFactory(factory); |
| setValue(value); |
| } |
| |
| /** |
| * Creates a JFormattedTextField with the specified value. This creates a |
| * formatter and formatterFactory that are appropriate for the value. |
| * |
| * @param value the initial value for this JFormattedTextField |
| */ |
| public JFormattedTextField(Object value) |
| { |
| setValue(value); |
| } |
| |
| /** |
| * Returns an AbstractFormatterFactory that will give an appropriate |
| * AbstractFormatter for the given Format. |
| * @param format the Format to match with an AbstractFormatter. |
| * @return a DefaultFormatterFactory whose defaultFormatter is appropriate |
| * for the given Format. |
| */ |
| private AbstractFormatterFactory getAppropriateFormatterFactory(Format format) |
| { |
| AbstractFormatter newFormatter; |
| if (format instanceof DateFormat) |
| newFormatter = new DateFormatter((DateFormat) format); |
| else if (format instanceof NumberFormat) |
| newFormatter = new NumberFormatter ((NumberFormat) format); |
| else |
| newFormatter = new InternationalFormatter(format); |
| |
| return new DefaultFormatterFactory(newFormatter); |
| } |
| |
| /** |
| * Forces the current value from the editor to be set as the current |
| * value. If there is no current formatted this has no effect. |
| * |
| * @throws ParseException if the formatter cannot format the current value |
| */ |
| public void commitEdit() |
| throws ParseException |
| { |
| if (formatter == null) |
| return; |
| // Note: this code is a lot like setValue except that we don't want |
| // to create a new formatter. |
| Object oldValue = this.value; |
| |
| this.value = formatter.stringToValue(getText());; |
| editValid = true; |
| |
| firePropertyChange("value", oldValue, this.value); |
| } |
| |
| /** |
| * Gets the command list supplied by the UI augmented by the specific |
| * Actions for JFormattedTextField. |
| * |
| * @return an array of Actions that this text field supports |
| */ |
| public Action[] getActions() |
| { |
| // FIXME: Add JFormattedTextField specific actions |
| // These are related to committing or cancelling edits. |
| return super.getActions(); |
| } |
| |
| /** |
| * Returns the behaviour of this JFormattedTextField upon losing focus. This |
| * is one of <code>COMMIT</code>, <code>COMMIT_OR_REVERT</code>, |
| * <code>PERSIST</code>, or <code>REVERT</code>. |
| * @return the behaviour upon losing focus |
| */ |
| public int getFocusLostBehavior() |
| { |
| return focusLostBehavior; |
| } |
| |
| /** |
| * Returns the current formatter used for this JFormattedTextField. |
| * @return the current formatter used for this JFormattedTextField |
| */ |
| public AbstractFormatter getFormatter() |
| { |
| return formatter; |
| } |
| |
| /** |
| * Returns the factory currently used to generate formatters for this |
| * JFormattedTextField. |
| * @return the factory currently used to generate formatters |
| */ |
| public AbstractFormatterFactory getFormatterFactory() |
| { |
| return formatterFactory; |
| } |
| |
| public String getUIClassID() |
| { |
| return "FormattedTextFieldUI"; |
| } |
| |
| /** |
| * Returns the last valid value. This may not be the value currently shown |
| * in the text field depending on whether or not the formatter commits on |
| * valid edits and allows invalid input to be temporarily displayed. |
| * @return the last committed valid value |
| */ |
| public Object getValue() |
| { |
| return value; |
| } |
| |
| /** |
| * This method is used to provide feedback to the user when an invalid value |
| * is input during editing. |
| */ |
| protected void invalidEdit() |
| { |
| UIManager.getLookAndFeel().provideErrorFeedback(this); |
| } |
| |
| /** |
| * Returns true if the current value being edited is valid. This property is |
| * managed by the current formatted. |
| * @return true if the value being edited is valid. |
| */ |
| public boolean isEditValid() |
| { |
| return editValid; |
| } |
| |
| /** |
| * Processes focus events. This is overridden because we may want to |
| * change the formatted depending on whether or not this field has |
| * focus. |
| * |
| * @param evt the FocusEvent |
| */ |
| protected void processFocusEvent(FocusEvent evt) |
| { |
| super.processFocusEvent(evt); |
| // Let the formatterFactory change the formatter for this text field |
| // based on whether or not it has focus. |
| setFormatter (formatterFactory.getFormatter(this)); |
| } |
| |
| /** |
| * Associates this JFormattedTextField with a Document and propagates |
| * a PropertyChange event to each listener. |
| * |
| * @param newDocument the Document to associate with this text field |
| */ |
| public void setDocument(Document newDocument) |
| { |
| // FIXME: This method should do more than this. Must do some handling |
| // of the DocumentListeners. |
| Document oldDocument = getDocument(); |
| |
| if (oldDocument == newDocument) |
| return; |
| |
| super.setDocument(newDocument); |
| } |
| |
| /** |
| * Sets the behaviour of this JFormattedTextField upon losing focus. |
| * This must be <code>COMMIT</code>, <code>COMMIT_OR_REVERT</code>, |
| * <code>PERSIST</code>, or <code>REVERT</code> or an |
| * IllegalArgumentException will be thrown. |
| * |
| * @param behavior |
| * @throws IllegalArgumentException if <code>behaviour</code> is not |
| * one of the above |
| */ |
| public void setFocusLostBehavior(int behavior) |
| { |
| if (behavior != COMMIT |
| && behavior != COMMIT_OR_REVERT |
| && behavior != PERSIST |
| && behavior != REVERT) |
| throw new IllegalArgumentException("invalid behavior"); |
| |
| this.focusLostBehavior = behavior; |
| } |
| |
| /** |
| * Sets the formatter for this JFormattedTextField. Normally the formatter |
| * factory will take care of this, or calls to setValue will also make sure |
| * that the formatter is set appropriately. |
| * |
| * @param formatter the AbstractFormatter to use for formatting the value for |
| * this JFormattedTextField |
| */ |
| protected void setFormatter(AbstractFormatter formatter) |
| { |
| AbstractFormatter oldFormatter = null; |
| |
| oldFormatter = this.formatter; |
| |
| if (oldFormatter != null) |
| oldFormatter.uninstall(); |
| |
| this.formatter = formatter; |
| |
| if (formatter != null) |
| formatter.install(this); |
| |
| firePropertyChange("formatter", oldFormatter, formatter); |
| } |
| |
| /** |
| * Sets the factory from which this JFormattedTextField should obtain |
| * its formatters. |
| * |
| * @param factory the AbstractFormatterFactory that will be used to generate |
| * formatters for this JFormattedTextField |
| */ |
| public void setFormatterFactory(AbstractFormatterFactory factory) |
| { |
| if (formatterFactory == factory) |
| return; |
| |
| AbstractFormatterFactory oldFactory = formatterFactory; |
| formatterFactory = factory; |
| firePropertyChange("formatterFactory", oldFactory, factory); |
| |
| // Now set the formatter according to our new factory. |
| if (formatterFactory != null) |
| setFormatter(formatterFactory.getFormatter(this)); |
| else |
| setFormatter(null); |
| } |
| |
| /** |
| * Sets the value that will be formatted and displayed. |
| * |
| * @param newValue the value to be formatted and displayed |
| */ |
| public void setValue(Object newValue) |
| { |
| if (value == newValue) |
| return; |
| |
| Object oldValue = value; |
| value = newValue; |
| |
| // If there is no formatterFactory then make one. |
| if (formatterFactory == null) |
| setFormatterFactory(createFormatterFactory(newValue)); |
| |
| // Set the formatter appropriately. This is because there may be a new |
| // formatterFactory from the line above, or we may want a new formatter |
| // depending on the type of newValue (or if newValue is null). |
| setFormatter (formatterFactory.getFormatter(this)); |
| firePropertyChange("value", oldValue, newValue); |
| } |
| |
| /** |
| * A helper method that attempts to create a formatter factory that is |
| * suitable to format objects of the type like <code>value</code>. |
| * |
| * @param value an object which should be formatted by the formatter factory. |
| * |
| * @return a formatter factory able to format objects of the class of |
| * <code>value</code> |
| */ |
| AbstractFormatterFactory createFormatterFactory(Object value) |
| { |
| AbstractFormatter formatter = null; |
| if (value instanceof Date) |
| formatter = new DateFormatter(); |
| else if (value instanceof Number) |
| formatter = new NumberFormatter(); |
| else |
| formatter = new DefaultFormatter(); |
| return new DefaultFormatterFactory(formatter); |
| } |
| } |