001/* JFormattedTextField.java --
002   Copyright (C) 2003, 2004 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import java.awt.event.FocusEvent;
042import java.io.Serializable;
043import java.text.DateFormat;
044import java.text.Format;
045import java.text.NumberFormat;
046import java.text.ParseException;
047import java.util.Date;
048
049import javax.swing.text.AbstractDocument;
050import javax.swing.text.DateFormatter;
051import javax.swing.text.DefaultFormatter;
052import javax.swing.text.DefaultFormatterFactory;
053import javax.swing.text.Document;
054import javax.swing.text.DocumentFilter;
055import javax.swing.text.InternationalFormatter;
056import javax.swing.text.NavigationFilter;
057import javax.swing.text.NumberFormatter;
058
059/**
060 * A text field that makes use of a formatter to display and edit a specific
061 * type of data. The value that is displayed can be an arbitrary object. The
062 * formatter is responsible for displaying the value in a textual form and
063 * it may allow editing of the value.
064 *
065 * Formatters are usually obtained using an instance of
066 * {@link AbstractFormatterFactory}. This factory is responsible for providing
067 * an instance of {@link AbstractFormatter} that is able to handle the
068 * formatting of the value of the JFormattedTextField.
069 *
070 * @author Michael Koch
071 * @author Anthony Balkissoon abalkiss at redhat dot com
072 *
073 * @since 1.4
074 */
075public class JFormattedTextField extends JTextField
076{
077  private static final long serialVersionUID = 5464657870110180632L;
078
079  /**
080   * An abstract base implementation for a formatter that can be used by
081   * a JTextField. A formatter can display a specific type of object and
082   * may provide a way to edit this value.
083   */
084  public abstract static class AbstractFormatter implements Serializable
085  {
086    private static final long serialVersionUID = -5193212041738979680L;
087    
088    private JFormattedTextField textField;
089    
090    public AbstractFormatter ()
091    {
092      //Do nothing here.
093    }
094
095    /**
096     * Clones the AbstractFormatter and removes the association to any 
097     * particular JFormattedTextField.
098     * 
099     * @return a clone of this formatter with no association to any particular
100     * JFormattedTextField
101     * @throws CloneNotSupportedException if the Object's class doesn't support
102     * the {@link Cloneable} interface
103     */
104    protected Object clone()
105      throws CloneNotSupportedException
106    {
107      // Clone this formatter.
108      AbstractFormatter newFormatter = (AbstractFormatter) super.clone();
109      
110      // And remove the association to the JFormattedTextField.
111      newFormatter.textField = null;
112      return newFormatter;
113    }
114
115    /**
116     * Returns a custom set of Actions that this formatter supports.  Should
117     * be subclassed by formatters that have a custom set of Actions.
118     * 
119     * @return <code>null</code>.  Should be subclassed by formatters that want
120     * to install custom Actions on the JFormattedTextField.
121     */
122    protected Action[] getActions()
123    {
124      return null;
125    }
126
127    /**
128     * Gets the DocumentFilter for this formatter.  Should be subclassed
129     * by formatters wishing to install a filter that oversees Document
130     * mutations.
131     * 
132     * @return <code>null</code>.  Should be subclassed by formatters
133     * that want to restrict Document mutations.
134     */
135    protected DocumentFilter getDocumentFilter()
136    {
137      // Subclasses should override this if they want to install a 
138      // DocumentFilter.
139      return null;
140    }
141
142    /**
143     * Returns the JFormattedTextField on which this formatter is
144     * currently installed.
145     * 
146     * @return the JFormattedTextField on which this formatter is currently
147     * installed
148     */
149    protected JFormattedTextField getFormattedTextField()
150    {
151      return textField;
152    }
153
154    /**
155     * Gets the NavigationFilter for this formatter.  Should be subclassed
156     * by formatters (such as {@link DefaultFormatter}) that wish to 
157     * restrict where the cursor can be placed within the text field.
158     * 
159     * @return <code>null</code>.  Subclassed by formatters that want to restrict
160     * cursor location within the JFormattedTextField.
161     */
162    protected NavigationFilter getNavigationFilter()
163    {
164      // This should be subclassed if the formatter wants to install 
165      // a NavigationFilter on the JFormattedTextField.
166      return null;
167    }
168
169    /**
170     * Installs this formatter on the specified JFormattedTextField.  This 
171     * converts the current value to a displayable String and displays it, 
172     * and installs formatter specific Actions from <code>getActions</code>.
173     * It also installs a DocumentFilter and NavigationFilter on the 
174     * JFormattedTextField.  
175     * <p>
176     * If there is a <code>ParseException</code> this sets the text to an 
177     * empty String and marks the text field in an invalid state.
178     * 
179     * @param textField the JFormattedTextField on which to install this
180     * formatter
181     */
182    public void install(JFormattedTextField textField)
183    {
184      // Uninstall the current textfield.
185      if (this.textField != null)
186        uninstall();
187      
188      this.textField = textField;
189      
190      // Install some state on the text field, including display text, 
191      // DocumentFilter, NavigationFilter, and formatter specific Actions.
192      if (textField != null)
193        {
194          try
195          {
196            // Set the text of the field.
197            textField.setText(valueToString(textField.getValue()));
198            Document doc = textField.getDocument();
199            
200            // Set the DocumentFilter for the field's Document.
201            if (doc instanceof AbstractDocument)
202              ((AbstractDocument) doc).setDocumentFilter(getDocumentFilter());
203            
204            // Set the NavigationFilter.
205            textField.setNavigationFilter(getNavigationFilter());
206            
207            // Set the Formatter Actions
208            // FIXME: Have to add the actions from getActions()            
209          }
210          catch (ParseException pe)
211          {
212            // Set the text to an empty String and mark the field as invalid.
213            textField.setText("");
214            setEditValid(false);
215          }
216        }
217    }
218
219    /**
220     * Clears the state installed on the JFormattedTextField by the formatter.
221     * This resets the DocumentFilter, NavigationFilter, and any additional 
222     * Actions (returned by <code>getActions()</code>).     
223     */
224    public void uninstall()
225    {
226      // Set the DocumentFilter for the field's Document.
227      Document doc = textField.getDocument();
228      if (doc instanceof AbstractDocument)
229        ((AbstractDocument) doc).setDocumentFilter(null);
230      textField.setNavigationFilter(null);
231      // FIXME: Have to remove the Actions from getActions()
232      this.textField = null;
233    }
234
235    /**
236     * Invoke this method when invalid values are entered.  This forwards the
237     * call to the JFormattedTextField.     
238     */
239    protected void invalidEdit()
240    {
241      textField.invalidEdit();
242    }
243
244    /**
245     * This method updates the <code>editValid</code> property of 
246     * JFormattedTextField.
247     * 
248     * @param valid the new state for the <code>editValid</code> property
249     */
250    protected void setEditValid(boolean valid)
251    {
252      textField.editValid = valid;
253    }
254
255    /**
256     * Parses <code>text</code> to return a corresponding Object.
257     * 
258     * @param text the String to parse
259     * @return an Object that <code>text</code> represented
260     * @throws ParseException if there is an error in the conversion
261     */
262    public abstract Object stringToValue(String text)
263      throws ParseException;
264
265    /**
266     * Returns a String to be displayed, based on the Object
267     * <code>value</code>.
268     * 
269     * @param value the Object from which to generate a String
270     * @return a String to be displayed
271     * @throws ParseException if there is an error in the conversion
272     */
273    public abstract String valueToString(Object value)
274      throws ParseException;
275  }
276
277  /**
278   * Delivers instances of an {@link AbstractFormatter} for
279   * a specific value type for a JFormattedTextField. 
280   */
281  public abstract static class AbstractFormatterFactory
282  {
283    public AbstractFormatterFactory()
284    {
285      // Do nothing here.
286    }
287
288    public abstract AbstractFormatter getFormatter(JFormattedTextField tf);
289  }
290
291  /** The possible focusLostBehavior options **/
292  public static final int COMMIT = 0;
293  public static final int COMMIT_OR_REVERT = 1;
294  public static final int REVERT = 2;
295  public static final int PERSIST = 3;
296
297  /** The most recent valid and committed value **/
298  private Object value;
299  
300  /** The behaviour for when this text field loses focus **/
301  private int focusLostBehavior = COMMIT_OR_REVERT;
302  
303  /** The formatter factory currently being used **/
304  private AbstractFormatterFactory formatterFactory;
305  
306  /** The formatter currently being used **/
307  private AbstractFormatter formatter;
308  
309  // Package-private to avoid an accessor method.
310  boolean editValid = true;
311  
312  /**
313   * Creates a JFormattedTextField with no formatter factory.  
314   * <code>setValue</code> or <code>setFormatterFactory</code> will 
315   * properly configure this text field to edit a particular type
316   * of value.
317   */
318  public JFormattedTextField()
319  {
320    this((AbstractFormatterFactory) null, null);
321  }
322
323  /**
324   * Creates a JFormattedTextField that can handle the specified Format.  
325   * An appopriate AbstractFormatter and AbstractFormatterFactory will 
326   * be created for the specified Format.
327   * 
328   * @param format the Format that this JFormattedTextField should be able
329   * to handle
330   */
331  public JFormattedTextField(Format format)
332  {
333    this ();
334    setFormatterFactory(getAppropriateFormatterFactory(format));
335  }
336
337  /**
338   * Creates a JFormattedTextField with the specified formatter.  This will 
339   * create a {@link DefaultFormatterFactory} with this formatter as the default
340   * formatter.
341   * 
342   * @param formatter the formatter to use for this JFormattedTextField
343   */
344  public JFormattedTextField(AbstractFormatter formatter)
345  {
346    this(new DefaultFormatterFactory(formatter));
347  }
348
349  /**
350   * Creates a JFormattedTextField with the specified formatter factory.
351   * 
352   * @param factory the formatter factory to use for this JFormattedTextField
353   */
354  public JFormattedTextField(AbstractFormatterFactory factory)
355  {
356    setFormatterFactory(factory);
357  }
358
359  /**
360   * Creates a JFormattedTextField with the specified formatter factory and
361   * initial value.
362   * 
363   * @param factory the initial formatter factory for this JFormattedTextField
364   * @param value the initial value for the text field
365   */
366  public JFormattedTextField(AbstractFormatterFactory factory, Object value)
367  {    
368    setFormatterFactory(factory);
369    setValue(value);
370  }
371
372  /**
373   * Creates a JFormattedTextField with the specified value.  This creates a
374   * formatter and formatterFactory that are appropriate for the value.
375   * 
376   * @param value the initial value for this JFormattedTextField
377   */
378  public JFormattedTextField(Object value)
379  {
380    setValue(value);
381  }
382  
383  /**
384   * Returns an AbstractFormatterFactory that will give an appropriate
385   * AbstractFormatter for the given Format.
386   * @param format the Format to match with an AbstractFormatter.
387   * @return a DefaultFormatterFactory whose defaultFormatter is appropriate
388   * for the given Format.
389   */
390  private AbstractFormatterFactory getAppropriateFormatterFactory(Format format)
391  {
392    AbstractFormatter newFormatter;
393    if (format instanceof DateFormat)
394      newFormatter = new DateFormatter((DateFormat) format);
395    else if (format instanceof NumberFormat)
396      newFormatter = new NumberFormatter ((NumberFormat) format);
397    else
398      newFormatter = new InternationalFormatter(format);
399    
400    return new DefaultFormatterFactory(newFormatter);
401  }
402
403  /**
404   * Forces the current value from the editor to be set as the current
405   * value.  If there is no current formatted this has no effect.
406   * 
407   * @throws ParseException if the formatter cannot format the current value
408   */
409  public void commitEdit()
410    throws ParseException
411  {
412    if (formatter == null)
413      return;
414    // Note: this code is a lot like setValue except that we don't want
415    // to create a new formatter.
416    Object oldValue = this.value;
417    
418    this.value = formatter.stringToValue(getText());
419    editValid = true;
420    
421    firePropertyChange("value", oldValue, this.value); 
422  }
423
424  /**
425   * Gets the command list supplied by the UI augmented by the specific
426   * Actions for JFormattedTextField.
427   * 
428   * @return an array of Actions that this text field supports
429   */
430  public Action[] getActions()
431  {
432    // FIXME: Add JFormattedTextField specific actions
433    // These are related to committing or cancelling edits.
434    return super.getActions();
435  }
436
437  /**
438   * Returns the behaviour of this JFormattedTextField upon losing focus.  This
439   * is one of <code>COMMIT</code>, <code>COMMIT_OR_REVERT</code>, 
440   * <code>PERSIST</code>, or <code>REVERT</code>.  
441   * @return the behaviour upon losing focus
442   */
443  public int getFocusLostBehavior()
444  {
445    return focusLostBehavior;
446  }
447
448  /**
449   * Returns the current formatter used for this JFormattedTextField.
450   * @return the current formatter used for this JFormattedTextField
451   */
452  public AbstractFormatter getFormatter()
453  {
454    return formatter;
455  }
456  
457  /**
458   * Returns the factory currently used to generate formatters for this
459   * JFormattedTextField.
460   * @return the factory currently used to generate formatters
461   */
462  public AbstractFormatterFactory getFormatterFactory()
463  {
464    return formatterFactory;
465  }
466
467  public String getUIClassID()
468  {
469    return "FormattedTextFieldUI";
470  }
471
472  /**
473   * Returns the last valid value.  This may not be the value currently shown 
474   * in the text field depending on whether or not the formatter commits on 
475   * valid edits and allows invalid input to be temporarily displayed.  
476   * @return the last committed valid value
477   */
478  public Object getValue()
479  {
480    return value;
481  }
482
483  /**
484   * This method is used to provide feedback to the user when an invalid value
485   * is input during editing.   
486   */
487  protected void invalidEdit()
488  {
489    UIManager.getLookAndFeel().provideErrorFeedback(this);
490  }
491
492  /**
493   * Returns true if the current value being edited is valid.  This property is
494   * managed by the current formatted.
495   * @return true if the value being edited is valid.
496   */
497  public boolean isEditValid()
498  {
499    return editValid;
500  }
501
502  /**
503   * Processes focus events.  This is overridden because we may want to 
504   * change the formatted depending on whether or not this field has 
505   * focus.
506   * 
507   * @param evt the FocusEvent
508   */
509  protected void processFocusEvent(FocusEvent evt)
510  {
511    super.processFocusEvent(evt);
512    // Let the formatterFactory change the formatter for this text field
513    // based on whether or not it has focus.
514    setFormatter (formatterFactory.getFormatter(this));
515  }
516  
517  /**
518   * Associates this JFormattedTextField with a Document and propagates
519   * a PropertyChange event to each listener.
520   * 
521   * @param newDocument the Document to associate with this text field
522   */
523  public void setDocument(Document newDocument)
524  {
525    // FIXME: This method should do more than this.  Must do some handling
526    // of the DocumentListeners.
527    Document oldDocument = getDocument();
528
529    if (oldDocument == newDocument)
530      return;
531    
532    super.setDocument(newDocument);
533  }
534
535  /**
536   * Sets the behaviour of this JFormattedTextField upon losing focus.
537   * This must be <code>COMMIT</code>, <code>COMMIT_OR_REVERT</code>, 
538   * <code>PERSIST</code>, or <code>REVERT</code> or an 
539   * IllegalArgumentException will be thrown.
540   * 
541   * @param behavior
542   * @throws IllegalArgumentException if <code>behaviour</code> is not 
543   * one of the above
544   */
545  public void setFocusLostBehavior(int behavior)
546  {
547    if (behavior != COMMIT
548        && behavior != COMMIT_OR_REVERT
549        && behavior != PERSIST
550        && behavior != REVERT)
551      throw new IllegalArgumentException("invalid behavior");
552
553    this.focusLostBehavior = behavior;
554  }
555
556  /**
557   * Sets the formatter for this JFormattedTextField.  Normally the formatter
558   * factory will take care of this, or calls to setValue will also make sure
559   * that the formatter is set appropriately.  
560   * 
561   * @param formatter the AbstractFormatter to use for formatting the value for
562   * this JFormattedTextField
563   */
564  protected void setFormatter(AbstractFormatter formatter)
565  {
566    AbstractFormatter oldFormatter = null;
567    
568    oldFormatter = this.formatter;
569
570    if (oldFormatter != null)
571      oldFormatter.uninstall();
572    
573    this.formatter = formatter;
574    
575    if (formatter != null)
576      formatter.install(this);
577
578    firePropertyChange("formatter", oldFormatter, formatter);
579  }
580
581  /**
582   * Sets the factory from which this JFormattedTextField should obtain 
583   * its formatters.  
584   * 
585   * @param factory the AbstractFormatterFactory that will be used to generate
586   * formatters for this JFormattedTextField
587   */
588  public void setFormatterFactory(AbstractFormatterFactory factory)
589  {
590    if (formatterFactory == factory)
591      return;
592    
593    AbstractFormatterFactory oldFactory = formatterFactory;
594    formatterFactory = factory;
595    firePropertyChange("formatterFactory", oldFactory, factory);
596    
597    // Now set the formatter according to our new factory.
598    if (formatterFactory != null)
599      setFormatter(formatterFactory.getFormatter(this));
600    else
601      setFormatter(null);
602  }
603
604  /**
605   * Sets the value that will be formatted and displayed.
606   *   
607   * @param newValue the value to be formatted and displayed
608   */
609  public void setValue(Object newValue)
610  {
611    if (value == newValue)
612      return;
613
614    Object oldValue = value;
615    value = newValue;
616    
617    // If there is no formatterFactory then make one.
618    if (formatterFactory == null)
619      setFormatterFactory(createFormatterFactory(newValue));
620    
621    // Set the formatter appropriately.  This is because there may be a new
622    // formatterFactory from the line above, or we may want a new formatter
623    // depending on the type of newValue (or if newValue is null).
624    setFormatter (formatterFactory.getFormatter(this));
625    firePropertyChange("value", oldValue, newValue);
626  }
627
628  /**
629   * A helper method that attempts to create a formatter factory that is 
630   * suitable to format objects of the type like <code>value</code>.
631   *
632   * @param value an object which should be formatted by the formatter factory.
633   *
634   * @return a formatter factory able to format objects of the class of
635   *     <code>value</code>
636   */
637  AbstractFormatterFactory createFormatterFactory(Object value)
638  {
639    AbstractFormatter formatter = null;
640    if (value instanceof Date)
641      formatter = new DateFormatter();
642    else if (value instanceof Number)
643      formatter = new NumberFormatter();
644    else
645      formatter = new DefaultFormatter();        
646    return new DefaultFormatterFactory(formatter);
647  }
648}