Source for javax.swing.JFormattedTextField

   1: /* JFormattedTextField.java --
   2:    Copyright (C) 2003, 2004 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing;
  40: 
  41: import java.awt.event.FocusEvent;
  42: import java.io.Serializable;
  43: import java.text.DateFormat;
  44: import java.text.Format;
  45: import java.text.NumberFormat;
  46: import java.text.ParseException;
  47: import java.util.Date;
  48: 
  49: import javax.swing.text.AbstractDocument;
  50: import javax.swing.text.DateFormatter;
  51: import javax.swing.text.DefaultFormatter;
  52: import javax.swing.text.DefaultFormatterFactory;
  53: import javax.swing.text.Document;
  54: import javax.swing.text.DocumentFilter;
  55: import javax.swing.text.InternationalFormatter;
  56: import javax.swing.text.NavigationFilter;
  57: import javax.swing.text.NumberFormatter;
  58: 
  59: /**
  60:  * A text field that makes use of a formatter to display and edit a specific
  61:  * type of data. The value that is displayed can be an arbitrary object. The
  62:  * formatter is responsible for displaying the value in a textual form and
  63:  * it may allow editing of the value.
  64:  *
  65:  * Formatters are usually obtained using an instance of
  66:  * {@link AbstractFormatterFactory}. This factory is responsible for providing
  67:  * an instance of {@link AbstractFormatter} that is able to handle the
  68:  * formatting of the value of the JFormattedTextField.
  69:  *
  70:  * @author Michael Koch
  71:  * @author Anthony Balkissoon abalkiss at redhat dot com
  72:  *
  73:  * @since 1.4
  74:  */
  75: public class JFormattedTextField extends JTextField
  76: {
  77:   private static final long serialVersionUID = 5464657870110180632L;
  78: 
  79:   /**
  80:    * An abstract base implementation for a formatter that can be used by
  81:    * a JTextField. A formatter can display a specific type of object and
  82:    * may provide a way to edit this value.
  83:    */
  84:   public abstract static class AbstractFormatter implements Serializable
  85:   {
  86:     private static final long serialVersionUID = -5193212041738979680L;
  87:     
  88:     private JFormattedTextField textField;
  89:     
  90:     public AbstractFormatter ()
  91:     {
  92:       //Do nothing here.
  93:     }
  94: 
  95:     /**
  96:      * Clones the AbstractFormatter and removes the association to any 
  97:      * particular JFormattedTextField.
  98:      * 
  99:      * @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: }