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}