diff options
Diffstat (limited to 'libjava/classpath/javax/swing/JFormattedTextField.java')
-rw-r--r-- | libjava/classpath/javax/swing/JFormattedTextField.java | 427 |
1 files changed, 354 insertions, 73 deletions
diff --git a/libjava/classpath/javax/swing/JFormattedTextField.java b/libjava/classpath/javax/swing/JFormattedTextField.java index 9890df20087..761955d6dd9 100644 --- a/libjava/classpath/javax/swing/JFormattedTextField.java +++ b/libjava/classpath/javax/swing/JFormattedTextField.java @@ -40,15 +40,21 @@ 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 @@ -62,6 +68,7 @@ import javax.swing.text.NavigationFilter; * formatting of the value of the JFormattedTextField. * * @author Michael Koch + * @author Anthony Balkissoon abalkiss at redhat dot com * * @since 1.4 */ @@ -85,58 +92,184 @@ public class JFormattedTextField extends JTextField //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 { - throw new InternalError ("not implemented"); + // 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 textField.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 () { - throw new InternalError ("not implemented"); + // 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 () { - return textField.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(); + 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; } @@ -155,88 +288,177 @@ public class JFormattedTextField extends JTextField public abstract AbstractFormatter getFormatter (JFormattedTextField tf); } - static class FormatterFactoryWrapper extends AbstractFormatterFactory - { - AbstractFormatter formatter; - - public FormatterFactoryWrapper(AbstractFormatter formatter) - { - this.formatter = formatter; - } - - public AbstractFormatter getFormatter(JFormattedTextField tf) - { - return formatter; - } - } - + /** 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) { - throw new InternalError ("not implemented"); + 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 FormatterFactoryWrapper(formatter), null); + 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) { - this(factory, null); + 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) - { - this.formatterFactory = factory; - this.value = 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) { - this.value = 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 { - throw new InternalError ("not implemented"); + 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 () { - if (formatterFactory == null) - return null; - - return formatterFactory.getFormatter(this); + 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; @@ -247,31 +469,61 @@ public class JFormattedTextField extends JTextField 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) { - // it's safe to simply call super for now, until it gets clear - // what this method is supposed to do - // throw new InternalError ("not implemented"); 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) @@ -280,6 +532,16 @@ public class JFormattedTextField extends JTextField 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 @@ -291,20 +553,38 @@ public class JFormattedTextField extends JTextField 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; - if (formatterFactory != null) - oldFormatter = formatterFactory.getFormatter(this); + oldFormatter = this.formatter; - if (oldFormatter == formatter) - return; + if (oldFormatter != null) + oldFormatter.uninstall(); + + this.formatter = formatter; + + if (formatter != null) + formatter.install(this); - setFormatterFactory(new FormatterFactoryWrapper(formatter)); 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) @@ -313,55 +593,56 @@ public class JFormattedTextField extends JTextField 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; - // format value - AbstractFormatter formatter = createFormatter(newValue); - try - { - setText(formatter.valueToString(newValue)); - } - catch (ParseException ex) - { - // TODO: what should we do with this? - } - 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 that is suitable - * to format objects of the type like <code>value</code>. + * A helper method that attempts to create a formatter factory that is + * suitable to format objects of the type like <code>value</code>. * - * If <code>formatterFactory</code> is not null and the returned formatter - * is also not <code>null</code> then this formatter is used. Otherwise we - * try to create one based on the type of <code>value</code>. + * @param value an object which should be formatted by the formatter factory. * - * @param value an object which should be formatted by the formatter - * - * @return a formatter able to format objects of the class of + * @return a formatter factory able to format objects of the class of * <code>value</code> */ - AbstractFormatter createFormatter(Object value) + AbstractFormatterFactory createFormatterFactory(Object value) { AbstractFormatter formatter = null; - if (formatterFactory != null - && formatterFactory.getFormatter(this) != null) - formatter = formatterFactory.getFormatter(this); - else - { - if (value instanceof Date) - formatter = new DateFormatter(); - else - formatter = new DefaultFormatter(); - } - return formatter; + if (value instanceof Date) + formatter = new DateFormatter(); + else if (value instanceof Number) + formatter = new NumberFormatter(); + else + formatter = new DefaultFormatter(); + return new DefaultFormatterFactory(formatter); } } |