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 }