This is the last big part of JFormattedTextField I was working on. It implements all the parts that were throwing InternalError ("not implemented") and fixes a lot of bugs with how we were generating formatters and formatter factories. It also implements missing parts of JFormattedTextField.AbstractFormatter.
I added 3 more tests to gnu.testlet.javax.swing.JFormattedTextField.JFormattedTextFieldTests that this patch makes us pass. 2005-12-02 Anthony Balkissoon <[EMAIL PROTECTED]> * javax/swing/JFormattedTextField.java: Added docs all over. (AbstractFormatter.clone): Implemented. (AbstractFormatter.getActions): Implemented. (AbstractFormatter.getDocumentFilter): Implemented. (AbstractFormatter.getNavigationFilter): Implemented. (AbstractFormatter.install): Install the DocumentFilter and NavigationFilter. Properly catch ParseException. Added FIXME to add custom Actions to the JFormattedTextField. (AbstractFormatter.uninstall): Remove the DocumentFilter and NavigationFilter. Added FIXME to remove the custom Actions. (JFormattedTextField(AbstractFormatter)): Call the single argument constructor that takes in an AbstractFormatterFactory. This avoids a call to setValue that shouldn't occur. (JFormattedTextField(AbstractFormatterFactory): Call setFormatterFactory instead of calling the 2-argument constructor which would also make an unwanted call to setValue. (JFormattedTextField(AbstractFormatterFactory, Object)): Switch the order of the calls to setValue and setFormatterFactory. This ensures that the passed in factory is actually the one used, not one generated by setValue. (commitEdit): Implemented. (setFormatter): Removed incorrect early escape if the parameter is the same as the current formatter. (setFormatterFactory): If formatterFactory is null set the formatter to null as well. (setValue): Don't set the text here, this is done when we call setFormatter and it calls AbstractFormatter.install. Create a formatter factory if one doesn't exist already. Call setFormatter to get an appropriate formatter from the factory. (createFormatter): Changed this to createFormatterFactory because we should return a factory, not just a formatter. (createFormatterFactory): New method adapted from createFormatter. --Tony
Index: javax/swing/JFormattedTextField.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/JFormattedTextField.java,v retrieving revision 1.18 diff -u -r1.18 JFormattedTextField.java --- javax/swing/JFormattedTextField.java 30 Nov 2005 23:02:37 -0000 1.18 +++ javax/swing/JFormattedTextField.java 2 Dec 2005 16:11:22 -0000 @@ -46,6 +46,7 @@ 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; @@ -67,6 +68,7 @@ * formatting of the value of the JFormattedTextField. * * @author Michael Koch + * @author Anthony Balkissoon abalkiss at redhat dot com * * @since 1.4 */ @@ -90,70 +92,184 @@ //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 [EMAIL PROTECTED] 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 [EMAIL PROTECTED] 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(); 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) { - // FIXME: Not sure what to do here. + // 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; } @@ -172,45 +288,93 @@ 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 [EMAIL PROTECTED] 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), 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) - { + { + setFormatterFactory(factory); setValue(value); - setFormatterFactory(factory); } + /** + * 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); @@ -236,28 +400,65 @@ 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 () { 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; @@ -268,21 +469,43 @@ 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); @@ -290,9 +513,17 @@ // 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) @@ -301,6 +532,16 @@ 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 @@ -312,15 +553,20 @@ 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 == formatter) - return; - if (oldFormatter != null) oldFormatter.uninstall(); @@ -332,6 +578,13 @@ 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) @@ -340,9 +593,19 @@ AbstractFormatterFactory oldFactory = formatterFactory; formatterFactory = factory; firePropertyChange("formatterFactory", oldFactory, factory); - setFormatter(formatterFactory.getFormatter(this)); + + // 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) @@ -351,47 +614,35 @@ Object oldValue = value; value = newValue; - // format value - formatter = createFormatter(newValue); - try - { - setText(formatter.valueToString(newValue)); - } - catch (ParseException ex) - { - // TODO: what should we do with this? - } + // 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>. - * - * 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>. + * 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 + * @param value an object which should be formatted by the formatter factory. * - * @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); + if (value instanceof Date) + formatter = new DateFormatter(); + else if (value instanceof Number) + formatter = new NumberFormatter(); else - { - if (value instanceof Date) - formatter = new DateFormatter(); - else if (value instanceof Number) - formatter = new NumberFormatter(); - else - formatter = new DefaultFormatter(); - formatterFactory = new DefaultFormatterFactory (formatter); - } - return formatter; + formatter = new DefaultFormatter(); + return new DefaultFormatterFactory(formatter); } }
_______________________________________________ Classpath-patches mailing list Classpath-patches@gnu.org http://lists.gnu.org/mailman/listinfo/classpath-patches