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
[email protected]
http://lists.gnu.org/mailman/listinfo/classpath-patches