Frames | No Frames |
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: }