KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > JSpinner


1 /*
2  * @(#)JSpinner.java 1.38 04/05/12
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing;
9
10 import java.awt.*;
11 import java.awt.event.*;
12
13 import javax.swing.*;
14 import javax.swing.event.*;
15 import javax.swing.text.*;
16 import javax.swing.plaf.SpinnerUI JavaDoc;
17
18 import java.util.*;
19 import java.beans.*;
20 import java.text.*;
21 import java.io.*;
22 import java.util.HashMap JavaDoc;
23 import sun.text.resources.LocaleData;
24
25 import javax.accessibility.*;
26
27
28 /**
29  * A single line input field that lets the user select a
30  * number or an object value from an ordered sequence. Spinners typically
31  * provide a pair of tiny arrow buttons for stepping through the elements
32  * of the sequence. The keyboard up/down arrow keys also cycle through the
33  * elements. The user may also be allowed to type a (legal) value directly
34  * into the spinner. Although combo boxes provide similar functionality,
35  * spinners are sometimes preferred because they don't require a drop down list
36  * that can obscure important data.
37  * <p>
38  * A <code>JSpinner</code>'s sequence value is defined by its
39  * <code>SpinnerModel</code>.
40  * The <code>model</code> can be specified as a constructor argument and
41  * changed with the <code>model</code> property. <code>SpinnerModel</code>
42  * classes for some common types are provided: <code>SpinnerListModel</code>,
43  * <code>SpinnerNumberModel</code>, and <code>SpinnerDateModel</code>.
44  * <p>
45  * A <code>JSpinner</code> has a single child component that's
46  * responsible for displaying
47  * and potentially changing the current element or <i>value</i> of
48  * the model, which is called the <code>editor</code>. The editor is created
49  * by the <code>JSpinner</code>'s constructor and can be changed with the
50  * <code>editor</code> property. The <code>JSpinner</code>'s editor stays
51  * in sync with the model by listening for <code>ChangeEvent</code>s. If the
52  * user has changed the value displayed by the <code>editor</code> it is
53  * possible for the <code>model</code>'s value to differ from that of
54  * the <code>editor</code>. To make sure the <code>model</code> has the same
55  * value as the editor use the <code>commitEdit</code> method, eg:
56  * <pre>
57  * try {
58  * spinner.commitEdit();
59  * }
60  * catch (ParseException pe) {{
61  * // Edited value is invalid, spinner.getValue() will return
62  * // the last valid value, you could revert the spinner to show that:
63  * JComponent editor = spinner.getEditor()
64  * if (editor instanceof DefaultEditor) {
65  * ((DefaultEditor)editor).getTextField().setValue(spinner.getValue();
66  * }
67  * // reset the value to some known value:
68  * spinner.setValue(fallbackValue);
69  * // or treat the last valid value as the current, in which
70  * // case you don't need to do anything.
71  * }
72  * return spinner.getValue();
73  * </pre>
74  * <p>
75  * <strong>Warning:</strong>
76  * Serialized objects of this class will not be compatible with
77  * future Swing releases. The current serialization support is
78  * appropriate for short term storage or RMI between applications running
79  * the same version of Swing. As of 1.4, support for long term storage
80  * of all JavaBeans<sup><font size="-2">TM</font></sup>
81  * has been added to the <code>java.beans</code> package.
82  * Please see {@link java.beans.XMLEncoder}.
83  *
84  * @beaninfo
85  * attribute: isContainer false
86  * description: A single line input field that lets the user select a
87  * number or an object value from an ordered set.
88  *
89  * @see SpinnerModel
90  * @see AbstractSpinnerModel
91  * @see SpinnerListModel
92  * @see SpinnerNumberModel
93  * @see SpinnerDateModel
94  * @see JFormattedTextField
95  *
96  * @version 1.38 05/12/04
97  * @author Hans Muller
98  * @author Lynn Monsanto (accessibility)
99  * @since 1.4
100  */

101 public class JSpinner extends JComponent JavaDoc implements Accessible
102 {
103     /**
104      * @see #getUIClassID
105      * @see #readObject
106      */

107     private static final String JavaDoc uiClassID = "SpinnerUI";
108
109     private static final Action JavaDoc DISABLED_ACTION = new DisabledAction();
110
111     private transient SpinnerModel JavaDoc model;
112     private JComponent JavaDoc editor;
113     private ChangeListener modelListener;
114     private transient ChangeEvent changeEvent;
115     private boolean editorExplicitlySet = false;
116
117
118     /**
119      * Constructs a complete spinner with pair of next/previous buttons
120      * and an editor for the <code>SpinnerModel</code>.
121      */

122     public JSpinner(SpinnerModel JavaDoc model) {
123     this.model = model;
124     this.editor = createEditor(model);
125     setOpaque(true);
126         updateUI();
127     }
128
129
130     /**
131      * Constructs a spinner with an <code>Integer SpinnerNumberModel</code>
132      * with initial value 0 and no minimum or maximum limits.
133      */

134     public JSpinner() {
135     this(new SpinnerNumberModel JavaDoc());
136     }
137
138
139     /**
140      * Returns the look and feel (L&F) object that renders this component.
141      *
142      * @return the <code>SpinnerUI</code> object that renders this component
143      */

144     public SpinnerUI JavaDoc getUI() {
145         return (SpinnerUI JavaDoc)ui;
146     }
147
148
149     /**
150      * Sets the look and feel (L&F) object that renders this component.
151      *
152      * @param ui the <code>SpinnerUI</code> L&F object
153      * @see UIDefaults#getUI
154      */

155     public void setUI(SpinnerUI JavaDoc ui) {
156         super.setUI(ui);
157     }
158
159
160     /**
161      * Returns the suffix used to construct the name of the look and feel
162      * (L&F) class used to render this component.
163      *
164      * @return the string "SpinnerUI"
165      * @see JComponent#getUIClassID
166      * @see UIDefaults#getUI
167      */

168     public String JavaDoc getUIClassID() {
169         return uiClassID;
170     }
171
172
173
174     /**
175      * Resets the UI property with the value from the current look and feel.
176      *
177      * @see UIManager#getUI
178      */

179     public void updateUI() {
180         setUI((SpinnerUI JavaDoc)UIManager.getUI(this));
181         invalidate();
182     }
183
184
185     /**
186      * This method is called by the constructors to create the
187      * <code>JComponent</code>
188      * that displays the current value of the sequence. The editor may
189      * also allow the user to enter an element of the sequence directly.
190      * An editor must listen for <code>ChangeEvents</code> on the
191      * <code>model</code> and keep the value it displays
192      * in sync with the value of the model.
193      * <p>
194      * Subclasses may override this method to add support for new
195      * <code>SpinnerModel</code> classes. Alternatively one can just
196      * replace the editor created here with the <code>setEditor</code>
197      * method. The default mapping from model type to editor is:
198      * <ul>
199      * <li> <code>SpinnerNumberModel =&gt; JSpinner.NumberEditor</code>
200      * <li> <code>SpinnerDateModel =&gt; JSpinner.DateEditor</code>
201      * <li> <code>SpinnerListModel =&gt; JSpinner.ListEditor</code>
202      * <li> <i>all others</i> =&gt; <code>JSpinner.DefaultEditor</code>
203      * </ul>
204      *
205      * @return a component that displays the current value of the sequence
206      * @param model the value of getModel
207      * @see #getModel
208      * @see #setEditor
209      */

210     protected JComponent JavaDoc createEditor(SpinnerModel JavaDoc model) {
211     if (model instanceof SpinnerDateModel JavaDoc) {
212         return new DateEditor(this);
213     }
214     else if (model instanceof SpinnerListModel JavaDoc) {
215         return new ListEditor(this);
216     }
217     else if (model instanceof SpinnerNumberModel JavaDoc) {
218         return new NumberEditor(this);
219     }
220     else {
221         return new DefaultEditor(this);
222     }
223     }
224
225
226     /**
227      * Changes the model that represents the value of this spinner.
228      * If the editor property has not been explicitly set,
229      * the editor property is (implicitly) set after the <code>"model"</code>
230      * <code>PropertyChangeEvent</code> has been fired. The editor
231      * property is set to the value returned by <code>createEditor</code>,
232      * as in:
233      * <pre>
234      * setEditor(createEditor(model));
235      * </pre>
236      *
237      * @param model the new <code>SpinnerModel</code>
238      * @see #getModel
239      * @see #getEditor
240      * @see #setEditor
241      * @throws IllegalArgumentException if model is <code>null</code>
242      *
243      * @beaninfo
244      * bound: true
245      * attribute: visualUpdate true
246      * description: Model that represents the value of this spinner.
247      */

248     public void setModel(SpinnerModel JavaDoc model) {
249     if (model == null) {
250         throw new IllegalArgumentException JavaDoc("null model");
251     }
252     if (!model.equals(this.model)) {
253         SpinnerModel JavaDoc oldModel = this.model;
254         this.model = model;
255         if (modelListener != null) {
256         this.model.addChangeListener(modelListener);
257         }
258         firePropertyChange("model", oldModel, model);
259         if (!editorExplicitlySet) {
260         setEditor(createEditor(model)); // sets editorExplicitlySet true
261
editorExplicitlySet = false;
262         }
263         repaint();
264         revalidate();
265     }
266     }
267
268
269     /**
270      * Returns the <code>SpinnerModel</code> that defines
271      * this spinners sequence of values.
272      *
273      * @return the value of the model property
274      * @see #setModel
275      */

276     public SpinnerModel JavaDoc getModel() {
277     return model;
278     }
279
280
281     /**
282      * Returns the current value of the model, typically
283      * this value is displayed by the <code>editor</code>. If the
284      * user has changed the value displayed by the <code>editor</code> it is
285      * possible for the <code>model</code>'s value to differ from that of
286      * the <code>editor</code>, refer to the class level javadoc for examples
287      * of how to deal with this.
288      * <p>
289      * This method simply delegates to the <code>model</code>.
290      * It is equivalent to:
291      * <pre>
292      * getModel().getValue()
293      * </pre>
294      *
295      * @see #setValue
296      * @see SpinnerModel#getValue
297      */

298     public Object JavaDoc getValue() {
299     return getModel().getValue();
300     }
301
302
303     /**
304      * Changes current value of the model, typically
305      * this value is displayed by the <code>editor</code>.
306      * If the <code>SpinnerModel</code> implementation
307      * doesn't support the specified value then an
308      * <code>IllegalArgumentException</code> is thrown.
309      * <p>
310      * This method simply delegates to the <code>model</code>.
311      * It is equivalent to:
312      * <pre>
313      * getModel().setValue(value)
314      * </pre>
315      *
316      * @throws IllegalArgumentException if <code>value</code> isn't allowed
317      * @see #getValue
318      * @see SpinnerModel#setValue
319      */

320     public void setValue(Object JavaDoc value) {
321     getModel().setValue(value);
322     }
323
324
325     /**
326      * Returns the object in the sequence that comes after the object returned
327      * by <code>getValue()</code>. If the end of the sequence has been reached
328      * then return <code>null</code>.
329      * Calling this method does not effect <code>value</code>.
330      * <p>
331      * This method simply delegates to the <code>model</code>.
332      * It is equivalent to:
333      * <pre>
334      * getModel().getNextValue()
335      * </pre>
336      *
337      * @return the next legal value or <code>null</code> if one doesn't exist
338      * @see #getValue
339      * @see #getPreviousValue
340      * @see SpinnerModel#getNextValue
341      */

342     public Object JavaDoc getNextValue() {
343     return getModel().getNextValue();
344     }
345
346
347     /**
348      * We pass <code>Change</code> events along to the listeners with the
349      * the slider (instead of the model itself) as the event source.
350      */

351     private class ModelListener implements ChangeListener, Serializable {
352         public void stateChanged(ChangeEvent e) {
353             fireStateChanged();
354         }
355     }
356
357
358     /**
359      * Adds a listener to the list that is notified each time a change
360      * to the model occurs. The source of <code>ChangeEvents</code>
361      * delivered to <code>ChangeListeners</code> will be this
362      * <code>JSpinner</code>. Note also that replacing the model
363      * will not affect listeners added directly to JSpinner.
364      * Applications can add listeners to the model directly. In that
365      * case is that the source of the event would be the
366      * <code>SpinnerModel</code>.
367      *
368      * @param listener the <code>ChangeListener</code> to add
369      * @see #removeChangeListener
370      * @see #getModel
371      */

372     public void addChangeListener(ChangeListener listener) {
373         if (modelListener == null) {
374             modelListener = new ModelListener();
375             getModel().addChangeListener(modelListener);
376         }
377         listenerList.add(ChangeListener.class, listener);
378     }
379
380
381
382     /**
383      * Removes a <code>ChangeListener</code> from this spinner.
384      *
385      * @param listener the <code>ChangeListener</code> to remove
386      * @see #fireStateChanged
387      * @see #addChangeListener
388      */

389     public void removeChangeListener(ChangeListener listener) {
390         listenerList.remove(ChangeListener.class, listener);
391     }
392
393
394     /**
395      * Returns an array of all the <code>ChangeListener</code>s added
396      * to this JSpinner with addChangeListener().
397      *
398      * @return all of the <code>ChangeListener</code>s added or an empty
399      * array if no listeners have been added
400      * @since 1.4
401      */

402     public ChangeListener[] getChangeListeners() {
403         return (ChangeListener[])listenerList.getListeners(
404                 ChangeListener.class);
405     }
406
407
408     /**
409      * Sends a <code>ChangeEvent</code>, whose source is this
410      * <code>JSpinner</code>, to each <code>ChangeListener</code>.
411      * When a <code>ChangeListener</code> has been added
412      * to the spinner, this method method is called each time
413      * a <code>ChangeEvent</code> is received from the model.
414      *
415      * @see #addChangeListener
416      * @see #removeChangeListener
417      * @see EventListenerList
418      */

419     protected void fireStateChanged() {
420         Object JavaDoc[] listeners = listenerList.getListenerList();
421         for (int i = listeners.length - 2; i >= 0; i -= 2) {
422             if (listeners[i] == ChangeListener.class) {
423                 if (changeEvent == null) {
424                     changeEvent = new ChangeEvent(this);
425                 }
426                 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
427             }
428         }
429     }
430
431
432     /**
433      * Returns the object in the sequence that comes
434      * before the object returned by <code>getValue()</code>.
435      * If the end of the sequence has been reached then
436      * return <code>null</code>. Calling this method does
437      * not effect <code>value</code>.
438      * <p>
439      * This method simply delegates to the <code>model</code>.
440      * It is equivalent to:
441      * <pre>
442      * getModel().getPreviousValue()
443      * </pre>
444      *
445      * @return the previous legal value or <code>null</code>
446      * if one doesn't exist
447      * @see #getValue
448      * @see #getNextValue
449      * @see SpinnerModel#getPreviousValue
450      */

451     public Object JavaDoc getPreviousValue() {
452     return getModel().getPreviousValue();
453     }
454
455
456     /**
457      * Changes the <code>JComponent</code> that displays the current value
458      * of the <code>SpinnerModel</code>. It is the responsibility of this
459      * method to <i>disconnect</i> the old editor from the model and to
460      * connect the new editor. This may mean removing the
461      * old editors <code>ChangeListener</code> from the model or the
462      * spinner itself and adding one for the new editor.
463      *
464      * @param editor the new editor
465      * @see #getEditor
466      * @see #createEditor
467      * @see #getModel
468      * @throws IllegalArgumentException if editor is <code>null</code>
469      *
470      * @beaninfo
471      * bound: true
472      * attribute: visualUpdate true
473      * description: JComponent that displays the current value of the model
474      */

475     public void setEditor(JComponent JavaDoc editor) {
476     if (editor == null) {
477         throw new IllegalArgumentException JavaDoc("null editor");
478     }
479     if (!editor.equals(this.editor)) {
480         JComponent JavaDoc oldEditor = this.editor;
481         this.editor = editor;
482         if (oldEditor instanceof DefaultEditor) {
483         ((DefaultEditor)oldEditor).dismiss(this);
484         }
485         editorExplicitlySet = true;
486         firePropertyChange("editor", oldEditor, editor);
487         revalidate();
488         repaint();
489     }
490     }
491
492
493     /**
494      * Returns the component that displays and potentially
495      * changes the model's value.
496      *
497      * @return the component that displays and potentially
498      * changes the model's value
499      * @see #setEditor
500      * @see #createEditor
501      */

502     public JComponent JavaDoc getEditor() {
503     return editor;
504     }
505
506
507     /**
508      * Commits the currently edited value to the <code>SpinnerModel</code>.
509      * <p>
510      * If the editor is an instance of <code>DefaultEditor</code>, the
511      * call if forwarded to the editor, otherwise this does nothing.
512      *
513      * @throws ParseException if the currently edited value couldn't
514      * be commited.
515      */

516     public void commitEdit() throws ParseException {
517         JComponent JavaDoc editor = getEditor();
518         if (editor instanceof DefaultEditor) {
519             ((DefaultEditor)editor).commitEdit();
520         }
521     }
522
523
524     /*
525      * See readObject and writeObject in JComponent for more
526      * information about serialization in Swing.
527      *
528      * @param s Stream to write to
529      */

530     private void writeObject(ObjectOutputStream s) throws IOException {
531         s.defaultWriteObject();
532         HashMap JavaDoc additionalValues = new HashMap JavaDoc(1);
533         SpinnerModel JavaDoc model = getModel();
534
535         if (model instanceof Serializable) {
536             additionalValues.put("model", model);
537         }
538         s.writeObject(additionalValues);
539
540         if (getUIClassID().equals(uiClassID)) {
541             byte count = JComponent.getWriteObjCounter(this);
542             JComponent.setWriteObjCounter(this, --count);
543             if (count == 0 && ui != null) {
544                 ui.installUI(this);
545             }
546         }
547     }
548
549
550     private void readObject(ObjectInputStream s)
551         throws IOException, ClassNotFoundException JavaDoc {
552         s.defaultReadObject();
553
554         Map JavaDoc additionalValues = (Map JavaDoc)s.readObject();
555
556         model = (SpinnerModel JavaDoc)additionalValues.get("model");
557     }
558
559
560     /**
561      * A simple base class for more specialized editors
562      * that displays a read-only view of the model's current
563      * value with a <code>JFormattedTextField<code>. Subclasses
564      * can configure the <code>JFormattedTextField<code> to create
565      * an editor that's appropriate for the type of model they
566      * support and they may want to override
567      * the <code>stateChanged</code> and <code>propertyChanged</code>
568      * methods, which keep the model and the text field in sync.
569      * <p>
570      * This class defines a <code>dismiss</code> method that removes the
571      * editors <code>ChangeListener</code> from the <code>JSpinner</code>
572      * that it's part of. The <code>setEditor</code> method knows about
573      * <code>DefaultEditor.dismiss</code>, so if the developer
574      * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
575      * its <code>ChangeListener</code> connection back to the
576      * <code>JSpinner</code> will be removed. However after that,
577      * it's up to the developer to manage their editor listeners.
578      * Similarly, if a subclass overrides <code>createEditor</code>,
579      * it's up to the subclasser to deal with their editor
580      * subsequently being replaced (with <code>setEditor</code>).
581      * We expect that in most cases, and in editor installed
582      * with <code>setEditor</code> or created by a <code>createEditor</code>
583      * override, will not be replaced anyway.
584      * <p>
585      * This class is the <code>LayoutManager<code> for it's single
586      * <code>JFormattedTextField</code> child. By default the
587      * child is just centered with the parents insets.
588      */

589     public static class DefaultEditor extends JPanel JavaDoc
590     implements ChangeListener, PropertyChangeListener, LayoutManager
591     {
592     /**
593      * Constructs an editor component for the specified <code>JSpinner</code>.
594      * This <code>DefaultEditor</code> is it's own layout manager and
595      * it is added to the spinner's <code>ChangeListener</code> list.
596      * The constructor creates a single <code>JFormattedTextField<code> child,
597      * initializes it's value to be the spinner model's current value
598      * and adds it to <code>this</code> <code>DefaultEditor</code>.
599      *
600      * @param spinner the spinner whose model <code>this</code> editor will monitor
601      * @see #getTextField
602      * @see JSpinner#addChangeListener
603      */

604     public DefaultEditor(JSpinner JavaDoc spinner) {
605         super(null);
606
607         JFormattedTextField JavaDoc ftf = new JFormattedTextField JavaDoc();
608             ftf.setName("Spinner.formattedTextField");
609         ftf.setValue(spinner.getValue());
610         ftf.addPropertyChangeListener(this);
611         ftf.setEditable(false);
612
613         String JavaDoc toolTipText = spinner.getToolTipText();
614         if (toolTipText != null) {
615         ftf.setToolTipText(toolTipText);
616         }
617
618         add(ftf);
619         
620         setLayout(this);
621         spinner.addChangeListener(this);
622
623             // We want the spinner's increment/decrement actions to be
624
// active vs those of the JFormattedTextField. As such we
625
// put disabled actions in the JFormattedTextField's actionmap.
626
// A binding to a disabled action is treated as a nonexistant
627
// binding.
628
ActionMap JavaDoc ftfMap = ftf.getActionMap();
629
630             if (ftfMap != null) {
631                 ftfMap.put("increment", DISABLED_ACTION);
632                 ftfMap.put("decrement", DISABLED_ACTION);
633             }
634     }
635
636
637     /**
638      * Disconnect <code>this</code> editor from the specified
639      * <code>JSpinner</code>. By default, this method removes
640      * itself from the spinners <code>ChangeListener</code> list.
641      *
642      * @param spinner the <code>JSpinner</code> to disconnect this
643      * editor from; the same spinner as was passed to the constructor.
644      */

645     public void dismiss(JSpinner JavaDoc spinner) {
646         spinner.removeChangeListener(this);
647     }
648
649     
650     /**
651      * Returns the <code>JSpinner</code> ancestor of this editor or null.
652      * Typically the editor's parent is a <code>JSpinner</code> however
653      * subclasses of <codeJSpinner</code> may override the
654      * the <code>createEditor</code> method and insert one or more containers
655      * between the <code>JSpinner</code> and it's editor.
656      *
657          * @return <code>JSpinner</code> ancestor
658      * @see JSpinner#createEditor
659      */

660     public JSpinner JavaDoc getSpinner() {
661         for (Component c = this; c != null; c = c.getParent()) {
662         if (c instanceof JSpinner JavaDoc) {
663             return (JSpinner JavaDoc)c;
664         }
665         }
666         return null;
667     }
668     
669
670     /**
671      * Returns the <code>JFormattedTextField</code> child of this
672      * editor. By default the text field is the first and only
673      * child of editor.
674      *
675      * @return the <code>JFormattedTextField</code> that gives the user
676      * access to the <code>SpinnerDateModel's</code> value.
677      * @see #getSpinner
678      * @see #getModel
679      */

680     public JFormattedTextField JavaDoc getTextField() {
681         return (JFormattedTextField JavaDoc)getComponent(0);
682     }
683
684
685     /**
686      * This method is called when the spinner's model's state changes.
687      * It sets the <code>value</code> of the text field to the current
688      * value of the spinners model.
689      *
690      * @param e not used
691      * @see #getTextField
692      * @see JSpinner#getValue
693      */

694     public void stateChanged(ChangeEvent e) {
695         JSpinner JavaDoc spinner = (JSpinner JavaDoc)(e.getSource());
696         getTextField().setValue(spinner.getValue());
697     }
698
699
700     /**
701      * Called by the <code>JFormattedTextField</code>
702      * <code>PropertyChangeListener</code>. When the <code>"value"</code>
703      * property changes, which implies that the user has typed a new
704      * number, we set the value of the spinners model.
705      * <p>
706      * This class ignores <code>PropertyChangeEvents</code> whose
707      * source is not the <code>JFormattedTextField</code>, so subclasses
708      * may safely make <code>this</code> <code>DefaultEditor</code> a
709      * <code>PropertyChangeListener</code> on other objects.
710      *
711      * @param e the <code>PropertyChangeEvent</code> whose source is
712      * the <code>JFormattedTextField</code> created by this class.
713      * @see #getTextField
714      */

715         public void propertyChange(PropertyChangeEvent e)
716         {
717             JSpinner JavaDoc spinner = getSpinner();
718
719             if (spinner == null) {
720                 // Indicates we aren't installed anywhere.
721
return;
722             }
723
724         Object JavaDoc source = e.getSource();
725         String JavaDoc name = e.getPropertyName();
726         if ((source instanceof JFormattedTextField JavaDoc) && "value".equals(name)) {
727                 Object JavaDoc lastValue = spinner.getValue();
728
729                 // Try to set the new value
730
try {
731                     spinner.setValue(getTextField().getValue());
732                 } catch (IllegalArgumentException JavaDoc iae) {
733                     // SpinnerModel didn't like new value, reset
734
try {
735                         ((JFormattedTextField JavaDoc)source).setValue(lastValue);
736                     } catch (IllegalArgumentException JavaDoc iae2) {
737                         // Still bogus, nothing else we can do, the
738
// SpinnerModel and JFormattedTextField are now out
739
// of sync.
740
}
741                 }
742         }
743     }
744
745
746     /**
747      * This <code>LayoutManager</code> method does nothing. We're
748      * only managing a single child and there's no support
749      * for layout constraints.
750      *
751      * @param name ignored
752      * @param child ignored
753      */

754     public void addLayoutComponent(String JavaDoc name, Component child) {
755     }
756
757
758     /**
759      * This <code>LayoutManager</code> method does nothing. There
760      * isn't any per-child state.
761      *
762      * @param child ignored
763      */

764     public void removeLayoutComponent(Component child) {
765     }
766
767
768     /**
769      * Returns the size of the parents insets.
770      */

771     private Dimension insetSize(Container parent) {
772         Insets insets = parent.getInsets();
773         int w = insets.left + insets.right;
774         int h = insets.top + insets.bottom;
775         return new Dimension(w, h);
776     }
777
778
779     /**
780      * Returns the preferred size of first (and only) child plus the
781      * size of the parents insets.
782      *
783      * @param parent the Container that's managing the layout
784          * @return the preferred dimensions to lay out the subcomponents
785          * of the specified container.
786      */

787     public Dimension preferredLayoutSize(Container parent) {
788         Dimension preferredSize = insetSize(parent);
789         if (parent.getComponentCount() > 0) {
790         Dimension childSize = getComponent(0).getPreferredSize();
791         preferredSize.width += childSize.width;
792         preferredSize.height += childSize.height;
793         }
794         return preferredSize;
795     }
796
797
798     /**
799      * Returns the minimum size of first (and only) child plus the
800      * size of the parents insets.
801      *
802      * @param parent the Container that's managing the layout
803          * @return the minimum dimensions needed to lay out the subcomponents
804          * of the specified container.
805      */

806     public Dimension minimumLayoutSize(Container parent) {
807         Dimension minimumSize = insetSize(parent);
808         if (parent.getComponentCount() > 0) {
809         Dimension childSize = getComponent(0).getMinimumSize();
810         minimumSize.width += childSize.width;
811         minimumSize.height += childSize.height;
812         }
813         return minimumSize;
814     }
815
816
817     /**
818      * Resize the one (and only) child to completely fill the area
819      * within the parents insets.
820      */

821     public void layoutContainer(Container parent) {
822         if (parent.getComponentCount() > 0) {
823         Insets insets = parent.getInsets();
824         int w = parent.getWidth() - (insets.left + insets.right);
825         int h = parent.getHeight() - (insets.top + insets.bottom);
826         getComponent(0).setBounds(insets.left, insets.top, w, h);
827         }
828     }
829
830         /**
831          * Pushes the currently edited value to the <code>SpinnerModel</code>.
832          * <p>
833          * The default implementation invokes <code>commitEdit</code> on the
834          * <code>JFormattedTextField</code>.
835          *
836          * @throws ParseException if the edited value is not legal
837          */

838         public void commitEdit() throws ParseException {
839             // If the value in the JFormattedTextField is legal, this will have
840
// the result of pushing the value to the SpinnerModel
841
// by way of the <code>propertyChange</code> method.
842
JFormattedTextField JavaDoc ftf = getTextField();
843
844             ftf.commitEdit();
845         }
846     }
847
848
849
850
851     /**
852      * This subclass of javax.swing.DateFormatter maps the minimum/maximum
853      * properties to te start/end properties of a SpinnerDateModel.
854      */

855     private static class DateEditorFormatter extends DateFormatter {
856     private final SpinnerDateModel JavaDoc model;
857
858     DateEditorFormatter(SpinnerDateModel JavaDoc model, DateFormat format) {
859         super(format);
860         this.model = model;
861     }
862
863     public void setMinimum(Comparable JavaDoc min) {
864         model.setStart(min);
865     }
866
867     public Comparable JavaDoc getMinimum() {
868         return model.getStart();
869     }
870
871     public void setMaximum(Comparable JavaDoc max) {
872         model.setEnd(max);
873     }
874
875     public Comparable JavaDoc getMaximum() {
876         return model.getEnd();
877     }
878     }
879
880
881     /**
882      * An editor for a <code>JSpinner</code> whose model is a
883      * <code>SpinnerDateModel</code>. The value of the editor is
884      * displayed with a <code>JFormattedTextField</code> whose format
885      * is defined by a <code>DateFormatter</code> instance whose
886      * <code>minimum</code> and <code>maximum</code> properties
887      * are mapped to the <code>SpinnerDateModel</code>.
888      */

889     // PENDING(hmuller): more example javadoc
890
public static class DateEditor extends DefaultEditor
891     {
892         // This is here until SimpleDateFormat gets a constructor that
893
// takes a Locale: 4923525
894
private static String JavaDoc getDefaultPattern(Locale loc) {
895             ResourceBundle r = LocaleData.getLocaleElements(loc);
896             String JavaDoc[] dateTimePatterns = r.getStringArray("DateTimePatterns");
897         Object JavaDoc[] dateTimeArgs = {dateTimePatterns[DateFormat.SHORT],
898                      dateTimePatterns[DateFormat.SHORT + 4]};
899             return MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
900         }
901
902     /**
903      * Construct a <code>JSpinner</code> editor that supports displaying
904      * and editing the value of a <code>SpinnerDateModel</code>
905      * with a <code>JFormattedTextField</code>. <code>This</code>
906      * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
907      * on the spinners model and a <code>PropertyChangeListener</code>
908      * on the new <code>JFormattedTextField</code>.
909      *
910      * @param spinner the spinner whose model <code>this</code> editor will monitor
911      * @exception IllegalArgumentException if the spinners model is not
912      * an instance of <code>SpinnerDateModel</code>
913      *
914      * @see #getModel
915      * @see #getFormat
916      * @see SpinnerDateModel
917      */

918     public DateEditor(JSpinner JavaDoc spinner) {
919             this(spinner, getDefaultPattern(spinner.getLocale()));
920     }
921
922
923     /**
924      * Construct a <code>JSpinner</code> editor that supports displaying
925      * and editing the value of a <code>SpinnerDateModel</code>
926      * with a <code>JFormattedTextField</code>. <code>This</code>
927      * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
928      * on the spinner and a <code>PropertyChangeListener</code>
929      * on the new <code>JFormattedTextField</code>.
930      *
931      * @param spinner the spinner whose model <code>this</code> editor will monitor
932      * @param dateFormatPattern the initial pattern for the
933      * <code>SimpleDateFormat</code> object that's used to display
934      * and parse the value of the text field.
935      * @exception IllegalArgumentException if the spinners model is not
936      * an instance of <code>SpinnerDateModel</code>
937      *
938      * @see #getModel
939      * @see #getFormat
940      * @see SpinnerDateModel
941          * @see java.text.SimpleDateFormat
942      */

943     public DateEditor(JSpinner JavaDoc spinner, String JavaDoc dateFormatPattern) {
944         this(spinner, new SimpleDateFormat(dateFormatPattern,
945                                                spinner.getLocale()));
946     }
947
948     /**
949      * Construct a <code>JSpinner</code> editor that supports displaying
950      * and editing the value of a <code>SpinnerDateModel</code>
951      * with a <code>JFormattedTextField</code>. <code>This</code>
952      * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
953      * on the spinner and a <code>PropertyChangeListener</code>
954      * on the new <code>JFormattedTextField</code>.
955      *
956      * @param spinner the spinner whose model <code>this</code> editor
957          * will monitor
958      * @param format <code>DateFormat</code> object that's used to display
959      * and parse the value of the text field.
960      * @exception IllegalArgumentException if the spinners model is not
961      * an instance of <code>SpinnerDateModel</code>
962      *
963      * @see #getModel
964      * @see #getFormat
965      * @see SpinnerDateModel
966          * @see java.text.SimpleDateFormat
967      */

968     private DateEditor(JSpinner JavaDoc spinner, DateFormat format) {
969         super(spinner);
970         if (!(spinner.getModel() instanceof SpinnerDateModel JavaDoc)) {
971         throw new IllegalArgumentException JavaDoc(
972                                  "model not a SpinnerDateModel");
973         }
974
975         SpinnerDateModel JavaDoc model = (SpinnerDateModel JavaDoc)spinner.getModel();
976         DateFormatter formatter = new DateEditorFormatter(model, format);
977         DefaultFormatterFactory factory = new DefaultFormatterFactory(
978                                                   formatter);
979         JFormattedTextField JavaDoc ftf = getTextField();
980         ftf.setEditable(true);
981         ftf.setFormatterFactory(factory);
982
983         /* TBD - initializing the column width of the text field
984          * is imprecise and doing it here is tricky because
985          * the developer may configure the formatter later.
986          */

9