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          */

987         try {
988         String JavaDoc maxString = formatter.valueToString(model.getStart());
989         String JavaDoc minString = formatter.valueToString(model.getEnd());
990         ftf.setColumns(Math.max(maxString.length(),
991                                         minString.length()));
992         }
993         catch (ParseException e) {
994                 // PENDING: hmuller
995
}
996         }
997
998     /**
999      * Returns the <code>java.text.SimpleDateFormat</code> object the
1000     * <code>JFormattedTextField</code> uses to parse and format
1001     * numbers.
1002     *
1003     * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1004     * @see #getTextField
1005         * @see java.text.SimpleDateFormat
1006     */

1007    public SimpleDateFormat getFormat() {
1008        return (SimpleDateFormat)((DateFormatter)(getTextField().getFormatter())).getFormat();
1009    }
1010
1011
1012    /**
1013     * Return our spinner ancestor's <code>SpinnerDateModel</code>.
1014     *
1015     * @return <code>getSpinner().getModel()</code>
1016     * @see #getSpinner
1017     * @see #getTextField
1018     */

1019    public SpinnerDateModel JavaDoc getModel() {
1020        return (SpinnerDateModel JavaDoc)(getSpinner().getModel());
1021    }
1022    }
1023
1024
1025    /**
1026     * This subclass of javax.swing.NumberFormatter maps the minimum/maximum
1027     * properties to a SpinnerNumberModel and initializes the valueClass
1028     * of the NumberFormatter to match the type of the initial models value.
1029     */

1030    private static class NumberEditorFormatter extends NumberFormatter {
1031    private final SpinnerNumberModel JavaDoc model;
1032
1033    NumberEditorFormatter(SpinnerNumberModel JavaDoc model, NumberFormat format) {
1034        super(format);
1035        this.model = model;
1036        setValueClass(model.getValue().getClass());
1037    }
1038
1039    public void setMinimum(Comparable JavaDoc min) {
1040        model.setMinimum(min);
1041    }
1042
1043    public Comparable JavaDoc getMinimum() {
1044        return model.getMinimum();
1045    }
1046
1047    public void setMaximum(Comparable JavaDoc max) {
1048        model.setMaximum(max);
1049    }
1050
1051    public Comparable JavaDoc getMaximum() {
1052        return model.getMaximum();
1053    }
1054    }
1055
1056
1057
1058    /**
1059     * An editor for a <code>JSpinner</code> whose model is a
1060     * <code>SpinnerNumberModel</code>. The value of the editor is
1061     * displayed with a <code>JFormattedTextField</code> whose format
1062     * is defined by a <code>NumberFormatter</code> instance whose
1063     * <code>minimum</code> and <code>maximum</code> properties
1064     * are mapped to the <code>SpinnerNumberModel</code>.
1065     */

1066    // PENDING(hmuller): more example javadoc
1067
public static class NumberEditor extends DefaultEditor
1068    {
1069        // This is here until DecimalFormat gets a constructor that
1070
// takes a Locale: 4923525
1071
private static String JavaDoc getDefaultPattern(Locale locale) {
1072            // Get the pattern for the default locale.
1073
ResourceBundle rb = LocaleData.getLocaleElements(locale);
1074            String JavaDoc[] all = rb.getStringArray("NumberPatterns");
1075            return all[0];
1076        }
1077
1078    /**
1079     * Construct a <code>JSpinner</code> editor that supports displaying
1080     * and editing the value of a <code>SpinnerNumberModel</code>
1081     * with a <code>JFormattedTextField</code>. <code>This</code>
1082     * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1083     * on the spinner and a <code>PropertyChangeListener</code>
1084     * on the new <code>JFormattedTextField</code>.
1085     *
1086     * @param spinner the spinner whose model <code>this</code> editor will monitor
1087     * @exception IllegalArgumentException if the spinners model is not
1088     * an instance of <code>SpinnerNumberModel</code>
1089     *
1090     * @see #getModel
1091     * @see #getFormat
1092     * @see SpinnerNumberModel
1093     */

1094    public NumberEditor(JSpinner JavaDoc spinner) {
1095            this(spinner, getDefaultPattern(spinner.getLocale()));
1096        }
1097
1098    /**
1099     * Construct a <code>JSpinner</code> editor that supports displaying
1100     * and editing the value of a <code>SpinnerNumberModel</code>
1101     * with a <code>JFormattedTextField</code>. <code>This</code>
1102     * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1103     * on the spinner and a <code>PropertyChangeListener</code>
1104     * on the new <code>JFormattedTextField</code>.
1105     *
1106     * @param spinner the spinner whose model <code>this</code> editor will monitor
1107     * @param decimalFormatPattern the initial pattern for the
1108     * <code>DecimalFormat</code> object that's used to display
1109     * and parse the value of the text field.
1110     * @exception IllegalArgumentException if the spinners model is not
1111     * an instance of <code>SpinnerNumberModel</code> or if
1112         * <code>decimalFormatPattern</code> is not a legal
1113         * argument to <code>DecimalFormat</code>
1114     *
1115     * @see #getTextField
1116     * @see SpinnerNumberModel
1117         * @see java.text.DecimalFormat
1118     */

1119    public NumberEditor(JSpinner JavaDoc spinner, String JavaDoc decimalFormatPattern) {
1120        this(spinner, new DecimalFormat(decimalFormatPattern));
1121    }
1122
1123
1124    /**
1125     * Construct a <code>JSpinner</code> editor that supports displaying
1126     * and editing the value of a <code>SpinnerNumberModel</code>
1127     * with a <code>JFormattedTextField</code>. <code>This</code>
1128     * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1129     * on the spinner and a <code>PropertyChangeListener</code>
1130     * on the new <code>JFormattedTextField</code>.
1131     *
1132     * @param spinner the spinner whose model <code>this</code> editor will monitor
1133     * @param decimalFormatPattern the initial pattern for the
1134     * <code>DecimalFormat</code> object that's used to display
1135     * and parse the value of the text field.
1136     * @exception IllegalArgumentException if the spinners model is not
1137     * an instance of <code>SpinnerNumberModel</code>
1138     *
1139     * @see #getTextField
1140     * @see SpinnerNumberModel
1141         * @see java.text.DecimalFormat
1142     */

1143    private NumberEditor(JSpinner JavaDoc spinner, DecimalFormat format) {
1144        super(spinner);
1145        if (!(spinner.getModel() instanceof SpinnerNumberModel JavaDoc)) {
1146        throw new IllegalArgumentException JavaDoc(
1147                          "model not a SpinnerNumberModel");
1148        }
1149
1150        SpinnerNumberModel JavaDoc model = (SpinnerNumberModel JavaDoc)spinner.getModel();
1151        NumberFormatter formatter = new NumberEditorFormatter(model,
1152                                                                  format);
1153        DefaultFormatterFactory factory = new DefaultFormatterFactory(
1154                                                  formatter);
1155        JFormattedTextField JavaDoc ftf = getTextField();
1156        ftf.setEditable(true);
1157        ftf.setFormatterFactory(factory);
1158            ftf.setHorizontalAlignment(JTextField.RIGHT);
1159
1160        /* TBD - initializing the column width of the text field
1161         * is imprecise and doing it here is tricky because
1162         * the developer may configure the formatter later.
1163         */

1164        try {
1165        String JavaDoc maxString = formatter.valueToString(model.getMinimum());
1166        String JavaDoc minString = formatter.valueToString(model.getMaximum());
1167        ftf.setColumns(Math.max(maxString.length(),
1168                                        minString.length()));
1169        }
1170        catch (ParseException e) {
1171        // TBD should throw a chained error here
1172
}
1173
1174    }
1175
1176
1177    /**
1178     * Returns the <code>java.text.DecimalFormat</code> object the
1179     * <code>JFormattedTextField</code> uses to parse and format
1180     * numbers.
1181     *
1182     * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1183     * @see #getTextField
1184         * @see java.text.DecimalFormat
1185     */

1186    public DecimalFormat getFormat() {
1187        return (DecimalFormat)((NumberFormatter)(getTextField().getFormatter())).getFormat();
1188    }
1189
1190
1191    /**
1192     * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1193     *
1194     * @return <code>getSpinner().getModel()</code>
1195     * @see #getSpinner
1196     * @see #getTextField
1197     */

1198    public SpinnerNumberModel JavaDoc getModel() {
1199        return (SpinnerNumberModel JavaDoc)(getSpinner().getModel());
1200    }
1201    }
1202
1203
1204    /**
1205     * An editor for a <code>JSpinner</code> whose model is a
1206     * <code>SpinnerListModel</code>.
1207     */

1208    public static class ListEditor extends DefaultEditor
1209    {
1210    /**
1211     * Construct a <code>JSpinner</code> editor that supports displaying
1212     * and editing the value of a <code>SpinnerListModel</code>
1213     * with a <code>JFormattedTextField</code>. <code>This</code>
1214     * <code>ListEditor</code> becomes both a <code>ChangeListener</code>
1215     * on the spinner and a <code>PropertyChangeListener</code>
1216     * on the new <code>JFormattedTextField</code>.
1217     *
1218     * @param spinner the spinner whose model <code>this</code> editor will monitor
1219     * @exception IllegalArgumentException if the spinners model is not
1220     * an instance of <code>SpinnerListModel</code>
1221     *
1222     * @see #getModel
1223     * @see SpinnerListModel
1224     */

1225    public ListEditor(JSpinner JavaDoc spinner) {
1226        super(spinner);
1227        if (!(spinner.getModel() instanceof SpinnerListModel JavaDoc)) {
1228        throw new IllegalArgumentException JavaDoc("model not a SpinnerListModel");
1229        }
1230        getTextField().setEditable(true);
1231            getTextField().setFormatterFactory(new
1232                              DefaultFormatterFactory(new ListFormatter()));
1233    }
1234
1235    /**
1236     * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1237     *
1238     * @return <code>getSpinner().getModel()</code>
1239     * @see #getSpinner
1240     * @see #getTextField
1241     */

1242    public SpinnerListModel JavaDoc getModel() {
1243        return (SpinnerListModel JavaDoc)(getSpinner().getModel());
1244    }
1245
1246
1247        /**
1248         * ListFormatter provides completion while text is being input
1249         * into the JFormattedTextField. Completion is only done if the
1250         * user is inserting text at the end of the document. Completion
1251         * is done by way of the SpinnerListModel method findNextMatch.
1252         */

1253        private class ListFormatter extends
1254                          JFormattedTextField.AbstractFormatter JavaDoc {
1255            private DocumentFilter filter;
1256
1257            public String JavaDoc valueToString(Object JavaDoc value) throws ParseException {
1258                if (value == null) {
1259                    return "";
1260                }
1261                return value.toString();
1262            }
1263
1264            public Object JavaDoc stringToValue(String JavaDoc string) throws ParseException {
1265                return string;
1266            }
1267
1268            protected DocumentFilter getDocumentFilter() {
1269                if (filter == null) {
1270                    filter = new Filter();
1271                }
1272                return filter;
1273            }
1274
1275
1276            private class Filter extends DocumentFilter {
1277                public void replace(FilterBypass fb, int offset, int length,
1278                                    String JavaDoc string, AttributeSet attrs) throws
1279                                           BadLocationException {
1280                    if (string != null && (offset + length) ==
1281                                          fb.getDocument().getLength()) {
1282                        Object JavaDoc next = getModel().findNextMatch(
1283                                         fb.getDocument().getText(0, offset) +
1284                                         string);
1285                        String JavaDoc value = (next != null) ? next.toString() : null;
1286
1287                        if (value != null) {
1288                            fb.remove(0, offset + length);
1289                            fb.insertString(0, value, null);
1290                            getFormattedTextField().select(offset +
1291                                                           string.length(),
1292                                                           value.length());
1293                            return;
1294                        }
1295                    }
1296                    super.replace(fb, offset, length, string, attrs);
1297                }
1298
1299                public void insertString(FilterBypass fb, int offset,
1300                                     String JavaDoc string, AttributeSet attr)
1301                       throws BadLocationException {
1302                    replace(fb, offset, 0, string, attr);
1303                }
1304            }
1305        }
1306    }
1307
1308
1309    /**
1310     * An Action implementation that is always disabled.
1311     */

1312    private static class DisabledAction implements Action JavaDoc {
1313        public Object JavaDoc getValue(String JavaDoc key) {
1314            return null;
1315        }
1316        public void putValue(String JavaDoc key, Object JavaDoc value) {
1317        }
1318        public void setEnabled(boolean b) {
1319        }
1320        public boolean isEnabled() {
1321            return false;
1322        }
1323        public void addPropertyChangeListener(PropertyChangeListener l) {
1324        }
1325        public void removePropertyChangeListener(PropertyChangeListener l) {
1326        }
1327        public void actionPerformed(ActionEvent ae) {
1328        }
1329    }
1330
1331    /////////////////
1332
// Accessibility support
1333
////////////////
1334

1335    /**
1336     * Gets the <code>AccessibleContext<code> for the <code>JSpinner</code>
1337     *
1338     * @return the <code>AccessibleContext</code> for the <code>JSpinner</code>
1339     * @since 1.5
1340     */

1341    public AccessibleContext getAccessibleContext() {
1342        if (accessibleContext == null) {
1343            accessibleContext = new AccessibleJSpinner();
1344        }
1345        return accessibleContext;
1346    }
1347    
1348    /**
1349     * <code>AccessibleJSpinner</code> implements accessibility
1350     * support for the <code>JSpinner</code> class.
1351     * @since 1.5
1352     */

1353    protected class AccessibleJSpinner extends AccessibleJComponent
1354        implements AccessibleValue, AccessibleAction, AccessibleText,
1355           AccessibleEditableText, ChangeListener {
1356
1357    private Object JavaDoc oldModelValue = null;
1358
1359    /**
1360     * AccessibleJSpinner constructor
1361     */

1362    protected AccessibleJSpinner() {
1363        // model is guaranteed to be non-null
1364
oldModelValue = model.getValue();
1365        JSpinner.this.addChangeListener(this);
1366    }
1367
1368    /**
1369         * Invoked when the target of the listener has changed its state.
1370         *
1371         * @param e a <code>ChangeEvent</code> object. Must not be null.
1372     * @throws NullPointerException if the parameter is null.
1373     */

1374    public void stateChanged(ChangeEvent e) {
1375        if (e == null) {
1376        throw new NullPointerException JavaDoc();
1377        }
1378        Object JavaDoc newModelValue = model.getValue();
1379        firePropertyChange(ACCESSIBLE_VALUE_PROPERTY,
1380                   oldModelValue,
1381                   newModelValue);
1382        firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
1383                   null,
1384                   0); // entire text may have changed
1385

1386        oldModelValue = newModelValue;
1387    }
1388
1389    /* ===== Begin AccessibleContext methods ===== */
1390
1391    /**
1392     * Gets the role of this object. The role of the object is the generic
1393     * purpose or use of the class of this object. For example, the role
1394     * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
1395     * AccessibleRole are provided so component developers can pick from
1396     * a set of predefined roles. This enables assistive technologies to
1397     * provide a consistent interface to various tweaked subclasses of
1398     * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1399     * that act like a push button) as well as distinguish between sublasses
1400     * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1401     * and AccessibleRole.RADIO_BUTTON for radio buttons).
1402     * <p>Note that the AccessibleRole class is also extensible, so
1403     * custom component developers can define their own AccessibleRole's
1404     * if the set of predefined roles is inadequate.
1405     *
1406     * @return an instance of AccessibleRole describing the role of the object
1407     * @see AccessibleRole
1408     */

1409    public AccessibleRole getAccessibleRole() {
1410        return AccessibleRole.SPIN_BOX;
1411    }
1412    
1413    /**
1414     * Returns the number of accessible children of the object.
1415     *
1416     * @return the number of accessible children of the object.
1417     */

1418    public int getAccessibleChildrenCount() {
1419        // the JSpinner has one child, the editor
1420
if (editor.getAccessibleContext() != null) {
1421        return 1;
1422        }
1423        return 0;
1424    }
1425
1426    /**
1427     * Returns the specified Accessible child of the object. The Accessible
1428     * children of an Accessible object are zero-based, so the first child
1429     * of an Accessible child is at index 0, the second child is at index 1,
1430     * and so on.
1431     *
1432     * @param i zero-based index of child
1433     * @return the Accessible child of the object
1434     * @see #getAccessibleChildrenCount
1435     */

1436    public Accessible getAccessibleChild(int i) {
1437        // the JSpinner has one child, the editor
1438
if (i != 0) {
1439        return null;
1440        }
1441        if (editor.getAccessibleContext() != null) {
1442        return (Accessible)editor;
1443        }
1444        return null;
1445    }
1446
1447    /* ===== End AccessibleContext methods ===== */
1448
1449    /**
1450     * Gets the AccessibleAction associated with this object that supports
1451     * one or more actions.
1452     *
1453     * @return AccessibleAction if supported by object; else return null
1454     * @see AccessibleAction
1455     */

1456    public AccessibleAction getAccessibleAction() {
1457        return this;
1458    }
1459    
1460    /**
1461     * Gets the AccessibleText associated with this object presenting
1462     * text on the display.
1463     *
1464     * @return AccessibleText if supported by object; else return null
1465     * @see AccessibleText
1466     */

1467    public AccessibleText getAccessibleText() {
1468        return this;
1469    }
1470
1471    /*
1472     * Returns the AccessibleContext for the JSpinner editor
1473     */

1474    private AccessibleContext getEditorAccessibleContext() {
1475        if (editor instanceof DefaultEditor) {
1476        JTextField JavaDoc textField = ((DefaultEditor)editor).getTextField();
1477        if (textField != null) {
1478            return textField.getAccessibleContext();
1479        }
1480        } else if (editor instanceof Accessible) {
1481        return ((Accessible)editor).getAccessibleContext();
1482        }
1483        return null;
1484    }
1485
1486    /*
1487     * Returns the AccessibleText for the JSpinner editor
1488     */

1489    private AccessibleText getEditorAccessibleText() {
1490        AccessibleContext ac = getEditorAccessibleContext();
1491        if (ac != null) {
1492        return ac.getAccessibleText();
1493        }
1494        return null;
1495    }
1496
1497    /*
1498     * Returns the AccessibleExtendedText for the JSpinner editor
1499     */

1500    private AccessibleEditableText getEditorAccessibleEditableText() {
1501        AccessibleText at = getEditorAccessibleText();
1502        if (at instanceof AccessibleEditableText) {
1503        return (AccessibleEditableText)at;
1504        }
1505        return null;
1506    }
1507
1508    /**
1509     * Gets the AccessibleValue associated with this object.
1510     *
1511     * @return AccessibleValue if supported by object; else return null
1512     * @see AccessibleValue
1513     *
1514     */

1515    public AccessibleValue getAccessibleValue() {
1516        return this;
1517    }
1518
1519    /* ===== Begin AccessibleValue impl ===== */
1520
1521    /**
1522     * Get the value of this object as a Number. If the value has not been
1523     * set, the return value will be null.
1524     *
1525     * @return value of the object
1526     * @see #setCurrentAccessibleValue
1527     */

1528    public Number JavaDoc getCurrentAccessibleValue() {
1529        Object JavaDoc o = model.getValue();
1530        if (o instanceof Number JavaDoc) {
1531        return (Number JavaDoc)o;
1532        }
1533        return null;
1534    }
1535    
1536    /**
1537     * Set the value of this object as a Number.
1538     *
1539     * @param n the value to set for this object
1540     * @return true if the value was set; else False
1541     * @see #getCurrentAccessibleValue
1542     */

1543    public boolean setCurrentAccessibleValue(Number JavaDoc n) {
1544        // try to set the new value
1545
try {
1546        model.setValue(n);
1547        return true;
1548        } catch (IllegalArgumentException JavaDoc iae) {
1549        // SpinnerModel didn't like new value
1550
}
1551        return false;
1552    }
1553    
1554    /**
1555     * Get the minimum value of this object as a Number.
1556     *
1557     * @return Minimum value of the object; null if this object does not
1558     * have a minimum value
1559     * @see #getMaximumAccessibleValue
1560     */

1561    public Number JavaDoc getMinimumAccessibleValue() {
1562        if (model instanceof SpinnerNumberModel JavaDoc) {
1563        SpinnerNumberModel JavaDoc numberModel = (SpinnerNumberModel JavaDoc)model;
1564        Object JavaDoc o = numberModel.getMinimum();
1565        if (o instanceof Number JavaDoc) {
1566            return (Number JavaDoc)o;
1567        }
1568        }
1569        return null;
1570    }
1571    
1572    /**
1573     * Get the maximum value of this object as a Number.
1574     *
1575     * @return Maximum value of the object; null if this object does not
1576     * have a maximum value
1577     * @see #getMinimumAccessibleValue
1578     */

1579    public Number JavaDoc getMaximumAccessibleValue() {
1580        if (model instanceof SpinnerNumberModel JavaDoc) {
1581        SpinnerNumberModel JavaDoc numberModel = (SpinnerNumberModel JavaDoc)model;
1582        Object JavaDoc o = numberModel.getMaximum();
1583        if (o instanceof Number JavaDoc) {
1584            return (Number JavaDoc)o;
1585        }
1586        }
1587        return null;
1588    }
1589    
1590    /* ===== End AccessibleValue impl ===== */
1591
1592    /* ===== Begin AccessibleAction impl ===== */
1593
1594    /**
1595     * Returns the number of accessible actions available in this object
1596     * If there are more than one, the first one is considered the "default"
1597     * action of the object.
1598     *
1599     * Two actions are supported: AccessibleAction.INCREMENT which
1600     * increments the spinner value and AccessibleAction.DECREMENT
1601     * which decrements the spinner value
1602     *
1603     * @return the zero-based number of Actions in this object
1604     */

1605    public int getAccessibleActionCount() {
1606        return 2;
1607    }
1608    
1609    /**
1610     * Returns a description of the specified action of the object.
1611     *
1612     * @param i zero-based index of the actions
1613     * @return a String description of the action
1614     * @see #getAccessibleActionCount
1615     */

1616    public String JavaDoc getAccessibleActionDescription(int i) {
1617        if (i == 0) {
1618        return AccessibleAction.INCREMENT;
1619        } else if (i == 1) {
1620        return AccessibleAction.DECREMENT;
1621        }
1622        return null;
1623    }
1624    
1625    /**
1626     * Performs the specified Action on the object
1627     *
1628     * @param i zero-based index of actions. The first action
1629     * (index 0) is AccessibleAction.INCREMENT and the second
1630     * action (index 1) is AccessibleAction.DECREMENT.
1631     * @return true if the action was performed; otherwise false.
1632     * @see #getAccessibleActionCount
1633     */

1634    public boolean doAccessibleAction(int i) {
1635        if (i < 0 || i > 1) {
1636        return false;
1637        }
1638        Object JavaDoc o = null;
1639        if (i == 0) {
1640        o = getNextValue(); // AccessibleAction.INCREMENT
1641
} else {
1642        o = getPreviousValue(); // AccessibleAction.DECREMENT
1643
}
1644        // try to set the new value
1645
try {
1646        model.setValue(o);
1647        return true;
1648        } catch (IllegalArgumentException JavaDoc iae) {
1649        // SpinnerModel didn't like new value
1650
}
1651        return false;
1652    }
1653
1654    /* ===== End AccessibleAction impl ===== */
1655
1656    /* ===== Begin AccessibleText impl ===== */
1657
1658    /*
1659     * Returns whether source and destination components have the
1660     * same window ancestor
1661     */

1662    private boolean sameWindowAncestor(Component src, Component dest) {
1663        if (src == null || dest == null) {
1664        return false;
1665        }
1666        return SwingUtilities.getWindowAncestor(src) ==
1667        SwingUtilities.getWindowAncestor(dest);
1668    }
1669
1670    /**
1671     * Given a point in local coordinates, return the zero-based index
1672     * of the character under that Point. If the point is invalid,
1673     * this method returns -1.
1674     *
1675     * @param p the Point in local coordinates
1676     * @return the zero-based index of the character under Point p; if
1677     * Point is invalid return -1.
1678     */

1679    public int getIndexAtPoint(Point p) {
1680        AccessibleText at = getEditorAccessibleText();
1681        if (at != null && sameWindowAncestor(JSpinner.this, editor)) {
1682        // convert point from the JSpinner bounds (source) to
1683
// editor bounds (destination)
1684
Point editorPoint = SwingUtilities.convertPoint(JSpinner.this,
1685                                p,
1686                                editor);
1687        if (editorPoint != null) {
1688            return at.getIndexAtPoint(editorPoint);
1689        }
1690        }
1691        return -1;
1692    }
1693
1694    /**
1695     * Determines the bounding box of the character at the given
1696     * index into the string. The bounds are returned in local
1697     * coordinates. If the index is invalid an empty rectangle is
1698     * returned.
1699     *
1700     * @param i the index into the String
1701     * @return the screen coordinates of the character's bounding box,
1702     * if index is invalid return an empty rectangle.
1703     */

1704    public Rectangle getCharacterBounds(int i) {
1705        AccessibleText at = getEditorAccessibleText();
1706        if (at != null ) {
1707        Rectangle editorRect = at.getCharacterBounds(i);
1708        if (editorRect != null &&
1709            sameWindowAncestor(JSpinner.this, editor)) {
1710            // return rectangle in the the JSpinner bounds
1711
return SwingUtilities.convertRectangle(editor,
1712                               editorRect,
1713                               JSpinner.this);
1714        }
1715        }
1716        return null;
1717    }
1718
1719    /**
1720     * Returns the number of characters (valid indicies)
1721     *
1722     * @return the number of characters
1723     */

1724    public int getCharCount() {
1725        AccessibleText at = getEditorAccessibleText();
1726        if (at != null) {
1727        return at.getCharCount();
1728        }
1729        return -1;
1730    }
1731
1732    /**
1733     * Returns the zero-based offset of the caret.
1734     *
1735     * Note: That to the right of the caret will have the same index
1736     * value as the offset (the caret is between two characters).
1737     * @return the zero-based offset of the caret.
1738     */

1739    public int getCaretPosition() {
1740        AccessibleText at = getEditorAccessibleText();
1741        if (at != null) {
1742        return at.getCaretPosition();
1743        }
1744        return -1;
1745    }
1746
1747    /**
1748     * Returns the String at a given index.
1749     *
1750     * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1751     * @param index an index within the text
1752     * @return the letter, word, or sentence
1753     */

1754    public String JavaDoc getAtIndex(int part, int index) {
1755        AccessibleText at = getEditorAccessibleText();
1756        if (at != null) {
1757        return at.getAtIndex(part, index);
1758        }
1759        return null;
1760    }
1761
1762    /**
1763     * Returns the String after a given index.
1764     *
1765     * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1766     * @param index an index within the text
1767     * @return the letter, word, or sentence
1768     */

1769    public String JavaDoc getAfterIndex(int part, int index) {
1770        AccessibleText at = getEditorAccessibleText();
1771        if (at != null) {
1772        return at.getAfterIndex(part, index);
1773        }
1774        return null;
1775    }
1776
1777    /**
1778     * Returns the String before a given index.
1779     *
1780     * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1781     * @param index an index within the text
1782     * @return the letter, word, or sentence
1783     */

1784    public String JavaDoc getBeforeIndex(int part, int index) {
1785        AccessibleText at = getEditorAccessibleText();
1786        if (at != null) {
1787        return at.getBeforeIndex(part, index);
1788        }
1789        return null;
1790    }
1791
1792    /**
1793     * Returns the AttributeSet for a given character at a given index
1794     *
1795     * @param i the zero-based index into the text
1796     * @return the AttributeSet of the character
1797     */

1798    public AttributeSet getCharacterAttribute(int i) {
1799        AccessibleText at = getEditorAccessibleText();
1800        if (at != null) {
1801        return at.getCharacterAttribute(i);
1802        }
1803        return null;
1804    }
1805
1806    /**
1807     * Returns the start offset within the selected text.
1808     * If there is no selection, but there is
1809     * a caret, the start and end offsets will be the same.
1810     *
1811     * @return the index into the text of the start of the selection
1812     */

1813    public int getSelectionStart() {
1814        AccessibleText at = getEditorAccessibleText();
1815        if (at != null) {
1816        return at.getSelectionStart();
1817        }
1818        return -1;
1819    }
1820
1821    /**
1822     * Returns the end offset within the selected text.
1823     * If there is no selection, but there is
1824     * a caret, the start and end offsets will be the same.
1825     *
1826     * @return the index into teh text of the end of the selection
1827     */

1828    public int getSelectionEnd() {
1829        AccessibleText at = getEditorAccessibleText();
1830        if (at != null) {
1831        return at.getSelectionEnd();
1832        }
1833        return -1;
1834    }
1835
1836    /**
1837     * Returns the portion of the text that is selected.
1838     *
1839     * @return the String portion of the text that is selected
1840     */

1841    public String JavaDoc getSelectedText() {
1842        AccessibleText at = getEditorAccessibleText();
1843        if (at != null) {
1844        return at.getSelectedText();
1845        }
1846        return null;
1847    }
1848
1849    /* ===== End AccessibleText impl ===== */
1850
1851
1852    /* ===== Begin AccessibleEditableText impl ===== */
1853
1854    /**
1855     * Sets the text contents to the specified string.
1856     *
1857     * @param s the string to set the text contents
1858     */

1859    public void setTextContents(String JavaDoc s) {
1860        AccessibleEditableText at = getEditorAccessibleEditableText();
1861        if (at != null) {
1862        at.setTextContents(s);
1863        }
1864    }
1865
1866    /**
1867     * Inserts the specified string at the given index/
1868     *
1869     * @param index the index in the text where the string will
1870     * be inserted
1871     * @param s the string to insert in the text
1872     */

1873    public void insertTextAtIndex(int index, String JavaDoc s) {
1874        AccessibleEditableText at = getEditorAccessibleEditableText();
1875        if (at != null) {
1876        at.insertTextAtIndex(index, s);
1877        }
1878    }
1879
1880    /**
1881     * Returns the text string between two indices.
1882     *
1883     * @param startIndex the starting index in the text
1884     * @param endIndex the ending index in the text
1885     * @return the text string between the indices
1886     */

1887    public String JavaDoc getTextRange(int startIndex, int endIndex) {
1888        AccessibleEditableText at = getEditorAccessibleEditableText();
1889        if (at != null) {
1890        return at.getTextRange(startIndex, endIndex);
1891        }
1892        return null;
1893    }
1894
1895    /**
1896     * Deletes the text between two indices
1897     *
1898     * @param startIndex the starting index in the text
1899     * @param endIndex the ending index in the text
1900     */

1901    public void delete(int startIndex, int endIndex) {
1902        AccessibleEditableText at = getEditorAccessibleEditableText();
1903        if (at != null) {
1904        at.delete(startIndex, endIndex);
1905        }
1906    }
1907
1908    /**
1909     * Cuts the text between two indices into the system clipboard.
1910     *
1911     * @param startIndex the starting index in the text
1912     * @param endIndex the ending index in the text
1913     */

1914    public void cut(int startIndex, int endIndex) {
1915        AccessibleEditableText at = getEditorAccessibleEditableText();
1916        if (at != null) {
1917        at.cut(startIndex, endIndex);
1918        }
1919    }
1920
1921    /**
1922     * Pastes the text from the system clipboard into the text
1923     * starting at the specified index.
1924     *
1925     * @param startIndex the starting index in the text
1926     */

1927    public void paste(int startIndex) {
1928        AccessibleEditableText at = getEditorAccessibleEditableText();
1929        if (at != null) {
1930        at.paste(startIndex);
1931        }
1932    }
1933
1934    /**
1935     * Replaces the text between two indices with the specified
1936     * string.
1937     *
1938     * @param startIndex the starting index in the text
1939     * @param endIndex the ending index in the text
1940     * @param s the string to replace the text between two indices
1941     */

1942    public void replaceText(int startIndex, int endIndex, String JavaDoc s) {
1943        AccessibleEditableText at = getEditorAccessibleEditableText();
1944        if (at != null) {
1945        at.replaceText(startIndex, endIndex, s);
1946        }
1947    }
1948
1949    /**
1950     * Selects the text between two indices.
1951     *
1952     * @param startIndex the starting index in the text
1953     * @param endIndex the ending index in the text
1954     */

1955    public void selectText(int startIndex, int endIndex) {
1956        AccessibleEditableText at = getEditorAccessibleEditableText();
1957        if (at != null) {
1958        at.selectText(startIndex, endIndex);
1959        }
1960    }
1961
1962    /**
1963     * Sets attributes for the text between two indices.
1964     *
1965     * @param startIndex the starting index in the text
1966     * @param endIndex the ending index in the text
1967     * @param as the attribute set
1968     * @see AttributeSet
1969     */

1970    public void setAttributes(int startIndex, int endIndex, AttributeSet as) {
1971        AccessibleEditableText at = getEditorAccessibleEditableText();
1972        if (at != null) {
1973        at.setAttributes(startIndex, endIndex, as);
1974        }
1975    }
1976    } /* End AccessibleJSpinner */
1977}
1978
Popular Tags