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

Reply via email to