001    /* JFormattedTextField.java --
002       Copyright (C) 2003, 2004 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import java.awt.event.FocusEvent;
042    import java.io.Serializable;
043    import java.text.DateFormat;
044    import java.text.Format;
045    import java.text.NumberFormat;
046    import java.text.ParseException;
047    import java.util.Date;
048    
049    import javax.swing.text.AbstractDocument;
050    import javax.swing.text.DateFormatter;
051    import javax.swing.text.DefaultFormatter;
052    import javax.swing.text.DefaultFormatterFactory;
053    import javax.swing.text.Document;
054    import javax.swing.text.DocumentFilter;
055    import javax.swing.text.InternationalFormatter;
056    import javax.swing.text.NavigationFilter;
057    import 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     */
075    public 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    }