View Javadoc
1   /*
2    * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.swing;
27  
28  import java.awt.*;
29  import java.awt.event.*;
30  
31  import javax.swing.event.*;
32  import javax.swing.text.*;
33  import javax.swing.plaf.SpinnerUI;
34  
35  import java.util.*;
36  import java.beans.*;
37  import java.text.*;
38  import java.io.*;
39  import java.text.spi.DateFormatProvider;
40  import java.text.spi.NumberFormatProvider;
41  
42  import javax.accessibility.*;
43  import sun.util.locale.provider.LocaleProviderAdapter;
44  import sun.util.locale.provider.LocaleResources;
45  import sun.util.locale.provider.LocaleServiceProviderPool;
46  
47  
48  /**
49   * A single line input field that lets the user select a
50   * number or an object value from an ordered sequence. Spinners typically
51   * provide a pair of tiny arrow buttons for stepping through the elements
52   * of the sequence. The keyboard up/down arrow keys also cycle through the
53   * elements. The user may also be allowed to type a (legal) value directly
54   * into the spinner. Although combo boxes provide similar functionality,
55   * spinners are sometimes preferred because they don't require a drop down list
56   * that can obscure important data.
57   * <p>
58   * A <code>JSpinner</code>'s sequence value is defined by its
59   * <code>SpinnerModel</code>.
60   * The <code>model</code> can be specified as a constructor argument and
61   * changed with the <code>model</code> property.  <code>SpinnerModel</code>
62   * classes for some common types are provided: <code>SpinnerListModel</code>,
63   * <code>SpinnerNumberModel</code>, and <code>SpinnerDateModel</code>.
64   * <p>
65   * A <code>JSpinner</code> has a single child component that's
66   * responsible for displaying
67   * and potentially changing the current element or <i>value</i> of
68   * the model, which is called the <code>editor</code>.  The editor is created
69   * by the <code>JSpinner</code>'s constructor and can be changed with the
70   * <code>editor</code> property.  The <code>JSpinner</code>'s editor stays
71   * in sync with the model by listening for <code>ChangeEvent</code>s. If the
72   * user has changed the value displayed by the <code>editor</code> it is
73   * possible for the <code>model</code>'s value to differ from that of
74   * the <code>editor</code>. To make sure the <code>model</code> has the same
75   * value as the editor use the <code>commitEdit</code> method, eg:
76   * <pre>
77   *   try {
78   *       spinner.commitEdit();
79   *   }
80   *   catch (ParseException pe) {{
81   *       // Edited value is invalid, spinner.getValue() will return
82   *       // the last valid value, you could revert the spinner to show that:
83   *       JComponent editor = spinner.getEditor()
84   *       if (editor instanceof DefaultEditor) {
85   *           ((DefaultEditor)editor).getTextField().setValue(spinner.getValue();
86   *       }
87   *       // reset the value to some known value:
88   *       spinner.setValue(fallbackValue);
89   *       // or treat the last valid value as the current, in which
90   *       // case you don't need to do anything.
91   *   }
92   *   return spinner.getValue();
93   * </pre>
94   * <p>
95   * For information and examples of using spinner see
96   * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/spinner.html">How to Use Spinners</a>,
97   * a section in <em>The Java Tutorial.</em>
98   * <p>
99   * <strong>Warning:</strong> Swing is not thread safe. For more
100  * information see <a
101  * href="package-summary.html#threading">Swing's Threading
102  * Policy</a>.
103  * <p>
104  * <strong>Warning:</strong>
105  * Serialized objects of this class will not be compatible with
106  * future Swing releases. The current serialization support is
107  * appropriate for short term storage or RMI between applications running
108  * the same version of Swing.  As of 1.4, support for long term storage
109  * of all JavaBeans&trade;
110  * has been added to the <code>java.beans</code> package.
111  * Please see {@link java.beans.XMLEncoder}.
112  *
113  * @beaninfo
114  *   attribute: isContainer false
115  * description: A single line input field that lets the user select a
116  *     number or an object value from an ordered set.
117  *
118  * @see SpinnerModel
119  * @see AbstractSpinnerModel
120  * @see SpinnerListModel
121  * @see SpinnerNumberModel
122  * @see SpinnerDateModel
123  * @see JFormattedTextField
124  *
125  * @author Hans Muller
126  * @author Lynn Monsanto (accessibility)
127  * @since 1.4
128  */
129 public class JSpinner extends JComponent implements Accessible
130 {
131     /**
132      * @see #getUIClassID
133      * @see #readObject
134      */
135     private static final String uiClassID = "SpinnerUI";
136 
137     private static final Action DISABLED_ACTION = new DisabledAction();
138 
139     private SpinnerModel model;
140     private JComponent editor;
141     private ChangeListener modelListener;
142     private transient ChangeEvent changeEvent;
143     private boolean editorExplicitlySet = false;
144 
145 
146     /**
147      * Constructs a spinner for the given model. The spinner has
148      * a set of previous/next buttons, and an editor appropriate
149      * for the model.
150      *
151      * @throws NullPointerException if the model is {@code null}
152      */
153     public JSpinner(SpinnerModel model) {
154         if (model == null) {
155             throw new NullPointerException("model cannot be null");
156         }
157         this.model = model;
158         this.editor = createEditor(model);
159         setUIProperty("opaque",true);
160         updateUI();
161     }
162 
163 
164     /**
165      * Constructs a spinner with an <code>Integer SpinnerNumberModel</code>
166      * with initial value 0 and no minimum or maximum limits.
167      */
168     public JSpinner() {
169         this(new SpinnerNumberModel());
170     }
171 
172 
173     /**
174      * Returns the look and feel (L&amp;F) object that renders this component.
175      *
176      * @return the <code>SpinnerUI</code> object that renders this component
177      */
178     public SpinnerUI getUI() {
179         return (SpinnerUI)ui;
180     }
181 
182 
183     /**
184      * Sets the look and feel (L&amp;F) object that renders this component.
185      *
186      * @param ui  the <code>SpinnerUI</code> L&amp;F object
187      * @see UIDefaults#getUI
188      */
189     public void setUI(SpinnerUI ui) {
190         super.setUI(ui);
191     }
192 
193 
194     /**
195      * Returns the suffix used to construct the name of the look and feel
196      * (L&amp;F) class used to render this component.
197      *
198      * @return the string "SpinnerUI"
199      * @see JComponent#getUIClassID
200      * @see UIDefaults#getUI
201      */
202     public String getUIClassID() {
203         return uiClassID;
204     }
205 
206 
207 
208     /**
209      * Resets the UI property with the value from the current look and feel.
210      *
211      * @see UIManager#getUI
212      */
213     public void updateUI() {
214         setUI((SpinnerUI)UIManager.getUI(this));
215         invalidate();
216     }
217 
218 
219     /**
220      * This method is called by the constructors to create the
221      * <code>JComponent</code>
222      * that displays the current value of the sequence.  The editor may
223      * also allow the user to enter an element of the sequence directly.
224      * An editor must listen for <code>ChangeEvents</code> on the
225      * <code>model</code> and keep the value it displays
226      * in sync with the value of the model.
227      * <p>
228      * Subclasses may override this method to add support for new
229      * <code>SpinnerModel</code> classes.  Alternatively one can just
230      * replace the editor created here with the <code>setEditor</code>
231      * method.  The default mapping from model type to editor is:
232      * <ul>
233      * <li> <code>SpinnerNumberModel =&gt; JSpinner.NumberEditor</code>
234      * <li> <code>SpinnerDateModel =&gt; JSpinner.DateEditor</code>
235      * <li> <code>SpinnerListModel =&gt; JSpinner.ListEditor</code>
236      * <li> <i>all others</i> =&gt; <code>JSpinner.DefaultEditor</code>
237      * </ul>
238      *
239      * @return a component that displays the current value of the sequence
240      * @param model the value of getModel
241      * @see #getModel
242      * @see #setEditor
243      */
244     protected JComponent createEditor(SpinnerModel model) {
245         if (model instanceof SpinnerDateModel) {
246             return new DateEditor(this);
247         }
248         else if (model instanceof SpinnerListModel) {
249             return new ListEditor(this);
250         }
251         else if (model instanceof SpinnerNumberModel) {
252             return new NumberEditor(this);
253         }
254         else {
255             return new DefaultEditor(this);
256         }
257     }
258 
259 
260     /**
261      * Changes the model that represents the value of this spinner.
262      * If the editor property has not been explicitly set,
263      * the editor property is (implicitly) set after the <code>"model"</code>
264      * <code>PropertyChangeEvent</code> has been fired.  The editor
265      * property is set to the value returned by <code>createEditor</code>,
266      * as in:
267      * <pre>
268      * setEditor(createEditor(model));
269      * </pre>
270      *
271      * @param model the new <code>SpinnerModel</code>
272      * @see #getModel
273      * @see #getEditor
274      * @see #setEditor
275      * @throws IllegalArgumentException if model is <code>null</code>
276      *
277      * @beaninfo
278      *        bound: true
279      *    attribute: visualUpdate true
280      *  description: Model that represents the value of this spinner.
281      */
282     public void setModel(SpinnerModel model) {
283         if (model == null) {
284             throw new IllegalArgumentException("null model");
285         }
286         if (!model.equals(this.model)) {
287             SpinnerModel oldModel = this.model;
288             this.model = model;
289             if (modelListener != null) {
290                 oldModel.removeChangeListener(modelListener);
291                 this.model.addChangeListener(modelListener);
292             }
293             firePropertyChange("model", oldModel, model);
294             if (!editorExplicitlySet) {
295                 setEditor(createEditor(model)); // sets editorExplicitlySet true
296                 editorExplicitlySet = false;
297             }
298             repaint();
299             revalidate();
300         }
301     }
302 
303 
304     /**
305      * Returns the <code>SpinnerModel</code> that defines
306      * this spinners sequence of values.
307      *
308      * @return the value of the model property
309      * @see #setModel
310      */
311     public SpinnerModel getModel() {
312         return model;
313     }
314 
315 
316     /**
317      * Returns the current value of the model, typically
318      * this value is displayed by the <code>editor</code>. If the
319      * user has changed the value displayed by the <code>editor</code> it is
320      * possible for the <code>model</code>'s value to differ from that of
321      * the <code>editor</code>, refer to the class level javadoc for examples
322      * of how to deal with this.
323      * <p>
324      * This method simply delegates to the <code>model</code>.
325      * It is equivalent to:
326      * <pre>
327      * getModel().getValue()
328      * </pre>
329      *
330      * @see #setValue
331      * @see SpinnerModel#getValue
332      */
333     public Object getValue() {
334         return getModel().getValue();
335     }
336 
337 
338     /**
339      * Changes current value of the model, typically
340      * this value is displayed by the <code>editor</code>.
341      * If the <code>SpinnerModel</code> implementation
342      * doesn't support the specified value then an
343      * <code>IllegalArgumentException</code> is thrown.
344      * <p>
345      * This method simply delegates to the <code>model</code>.
346      * It is equivalent to:
347      * <pre>
348      * getModel().setValue(value)
349      * </pre>
350      *
351      * @throws IllegalArgumentException if <code>value</code> isn't allowed
352      * @see #getValue
353      * @see SpinnerModel#setValue
354      */
355     public void setValue(Object value) {
356         getModel().setValue(value);
357     }
358 
359 
360     /**
361      * Returns the object in the sequence that comes after the object returned
362      * by <code>getValue()</code>. If the end of the sequence has been reached
363      * then return <code>null</code>.
364      * Calling this method does not effect <code>value</code>.
365      * <p>
366      * This method simply delegates to the <code>model</code>.
367      * It is equivalent to:
368      * <pre>
369      * getModel().getNextValue()
370      * </pre>
371      *
372      * @return the next legal value or <code>null</code> if one doesn't exist
373      * @see #getValue
374      * @see #getPreviousValue
375      * @see SpinnerModel#getNextValue
376      */
377     public Object getNextValue() {
378         return getModel().getNextValue();
379     }
380 
381 
382     /**
383      * We pass <code>Change</code> events along to the listeners with the
384      * the slider (instead of the model itself) as the event source.
385      */
386     private class ModelListener implements ChangeListener, Serializable {
387         public void stateChanged(ChangeEvent e) {
388             fireStateChanged();
389         }
390     }
391 
392 
393     /**
394      * Adds a listener to the list that is notified each time a change
395      * to the model occurs.  The source of <code>ChangeEvents</code>
396      * delivered to <code>ChangeListeners</code> will be this
397      * <code>JSpinner</code>.  Note also that replacing the model
398      * will not affect listeners added directly to JSpinner.
399      * Applications can add listeners to  the model directly.  In that
400      * case is that the source of the event would be the
401      * <code>SpinnerModel</code>.
402      *
403      * @param listener the <code>ChangeListener</code> to add
404      * @see #removeChangeListener
405      * @see #getModel
406      */
407     public void addChangeListener(ChangeListener listener) {
408         if (modelListener == null) {
409             modelListener = new ModelListener();
410             getModel().addChangeListener(modelListener);
411         }
412         listenerList.add(ChangeListener.class, listener);
413     }
414 
415 
416 
417     /**
418      * Removes a <code>ChangeListener</code> from this spinner.
419      *
420      * @param listener the <code>ChangeListener</code> to remove
421      * @see #fireStateChanged
422      * @see #addChangeListener
423      */
424     public void removeChangeListener(ChangeListener listener) {
425         listenerList.remove(ChangeListener.class, listener);
426     }
427 
428 
429     /**
430      * Returns an array of all the <code>ChangeListener</code>s added
431      * to this JSpinner with addChangeListener().
432      *
433      * @return all of the <code>ChangeListener</code>s added or an empty
434      *         array if no listeners have been added
435      * @since 1.4
436      */
437     public ChangeListener[] getChangeListeners() {
438         return listenerList.getListeners(ChangeListener.class);
439     }
440 
441 
442     /**
443      * Sends a <code>ChangeEvent</code>, whose source is this
444      * <code>JSpinner</code>, to each <code>ChangeListener</code>.
445      * When a <code>ChangeListener</code> has been added
446      * to the spinner, this method method is called each time
447      * a <code>ChangeEvent</code> is received from the model.
448      *
449      * @see #addChangeListener
450      * @see #removeChangeListener
451      * @see EventListenerList
452      */
453     protected void fireStateChanged() {
454         Object[] listeners = listenerList.getListenerList();
455         for (int i = listeners.length - 2; i >= 0; i -= 2) {
456             if (listeners[i] == ChangeListener.class) {
457                 if (changeEvent == null) {
458                     changeEvent = new ChangeEvent(this);
459                 }
460                 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
461             }
462         }
463     }
464 
465 
466     /**
467      * Returns the object in the sequence that comes
468      * before the object returned by <code>getValue()</code>.
469      * If the end of the sequence has been reached then
470      * return <code>null</code>. Calling this method does
471      * not effect <code>value</code>.
472      * <p>
473      * This method simply delegates to the <code>model</code>.
474      * It is equivalent to:
475      * <pre>
476      * getModel().getPreviousValue()
477      * </pre>
478      *
479      * @return the previous legal value or <code>null</code>
480      *   if one doesn't exist
481      * @see #getValue
482      * @see #getNextValue
483      * @see SpinnerModel#getPreviousValue
484      */
485     public Object getPreviousValue() {
486         return getModel().getPreviousValue();
487     }
488 
489 
490     /**
491      * Changes the <code>JComponent</code> that displays the current value
492      * of the <code>SpinnerModel</code>.  It is the responsibility of this
493      * method to <i>disconnect</i> the old editor from the model and to
494      * connect the new editor.  This may mean removing the
495      * old editors <code>ChangeListener</code> from the model or the
496      * spinner itself and adding one for the new editor.
497      *
498      * @param editor the new editor
499      * @see #getEditor
500      * @see #createEditor
501      * @see #getModel
502      * @throws IllegalArgumentException if editor is <code>null</code>
503      *
504      * @beaninfo
505      *        bound: true
506      *    attribute: visualUpdate true
507      *  description: JComponent that displays the current value of the model
508      */
509     public void setEditor(JComponent editor) {
510         if (editor == null) {
511             throw new IllegalArgumentException("null editor");
512         }
513         if (!editor.equals(this.editor)) {
514             JComponent oldEditor = this.editor;
515             this.editor = editor;
516             if (oldEditor instanceof DefaultEditor) {
517                 ((DefaultEditor)oldEditor).dismiss(this);
518             }
519             editorExplicitlySet = true;
520             firePropertyChange("editor", oldEditor, editor);
521             revalidate();
522             repaint();
523         }
524     }
525 
526 
527     /**
528      * Returns the component that displays and potentially
529      * changes the model's value.
530      *
531      * @return the component that displays and potentially
532      *    changes the model's value
533      * @see #setEditor
534      * @see #createEditor
535      */
536     public JComponent getEditor() {
537         return editor;
538     }
539 
540 
541     /**
542      * Commits the currently edited value to the <code>SpinnerModel</code>.
543      * <p>
544      * If the editor is an instance of <code>DefaultEditor</code>, the
545      * call if forwarded to the editor, otherwise this does nothing.
546      *
547      * @throws ParseException if the currently edited value couldn't
548      *         be committed.
549      */
550     public void commitEdit() throws ParseException {
551         JComponent editor = getEditor();
552         if (editor instanceof DefaultEditor) {
553             ((DefaultEditor)editor).commitEdit();
554         }
555     }
556 
557 
558     /*
559      * See readObject and writeObject in JComponent for more
560      * information about serialization in Swing.
561      *
562      * @param s Stream to write to
563      */
564     private void writeObject(ObjectOutputStream s) throws IOException {
565         s.defaultWriteObject();
566         if (getUIClassID().equals(uiClassID)) {
567             byte count = JComponent.getWriteObjCounter(this);
568             JComponent.setWriteObjCounter(this, --count);
569             if (count == 0 && ui != null) {
570                 ui.installUI(this);
571             }
572         }
573     }
574 
575 
576     /**
577      * A simple base class for more specialized editors
578      * that displays a read-only view of the model's current
579      * value with a <code>JFormattedTextField</code>.  Subclasses
580      * can configure the <code>JFormattedTextField</code> to create
581      * an editor that's appropriate for the type of model they
582      * support and they may want to override
583      * the <code>stateChanged</code> and <code>propertyChanged</code>
584      * methods, which keep the model and the text field in sync.
585      * <p>
586      * This class defines a <code>dismiss</code> method that removes the
587      * editors <code>ChangeListener</code> from the <code>JSpinner</code>
588      * that it's part of.   The <code>setEditor</code> method knows about
589      * <code>DefaultEditor.dismiss</code>, so if the developer
590      * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
591      * its <code>ChangeListener</code> connection back to the
592      * <code>JSpinner</code> will be removed.  However after that,
593      * it's up to the developer to manage their editor listeners.
594      * Similarly, if a subclass overrides <code>createEditor</code>,
595      * it's up to the subclasser to deal with their editor
596      * subsequently being replaced (with <code>setEditor</code>).
597      * We expect that in most cases, and in editor installed
598      * with <code>setEditor</code> or created by a <code>createEditor</code>
599      * override, will not be replaced anyway.
600      * <p>
601      * This class is the <code>LayoutManager</code> for it's single
602      * <code>JFormattedTextField</code> child.   By default the
603      * child is just centered with the parents insets.
604      * @since 1.4
605      */
606     public static class DefaultEditor extends JPanel
607         implements ChangeListener, PropertyChangeListener, LayoutManager
608     {
609         /**
610          * Constructs an editor component for the specified <code>JSpinner</code>.
611          * This <code>DefaultEditor</code> is it's own layout manager and
612          * it is added to the spinner's <code>ChangeListener</code> list.
613          * The constructor creates a single <code>JFormattedTextField</code> child,
614          * initializes it's value to be the spinner model's current value
615          * and adds it to <code>this</code> <code>DefaultEditor</code>.
616          *
617          * @param spinner the spinner whose model <code>this</code> editor will monitor
618          * @see #getTextField
619          * @see JSpinner#addChangeListener
620          */
621         public DefaultEditor(JSpinner spinner) {
622             super(null);
623 
624             JFormattedTextField ftf = new JFormattedTextField();
625             ftf.setName("Spinner.formattedTextField");
626             ftf.setValue(spinner.getValue());
627             ftf.addPropertyChangeListener(this);
628             ftf.setEditable(false);
629             ftf.setInheritsPopupMenu(true);
630 
631             String toolTipText = spinner.getToolTipText();
632             if (toolTipText != null) {
633                 ftf.setToolTipText(toolTipText);
634             }
635 
636             add(ftf);
637 
638             setLayout(this);
639             spinner.addChangeListener(this);
640 
641             // We want the spinner's increment/decrement actions to be
642             // active vs those of the JFormattedTextField. As such we
643             // put disabled actions in the JFormattedTextField's actionmap.
644             // A binding to a disabled action is treated as a nonexistant
645             // binding.
646             ActionMap ftfMap = ftf.getActionMap();
647 
648             if (ftfMap != null) {
649                 ftfMap.put("increment", DISABLED_ACTION);
650                 ftfMap.put("decrement", DISABLED_ACTION);
651             }
652         }
653 
654 
655         /**
656          * Disconnect <code>this</code> editor from the specified
657          * <code>JSpinner</code>.  By default, this method removes
658          * itself from the spinners <code>ChangeListener</code> list.
659          *
660          * @param spinner the <code>JSpinner</code> to disconnect this
661          *    editor from; the same spinner as was passed to the constructor.
662          */
663         public void dismiss(JSpinner spinner) {
664             spinner.removeChangeListener(this);
665         }
666 
667 
668         /**
669          * Returns the <code>JSpinner</code> ancestor of this editor or
670          * <code>null</code> if none of the ancestors are a
671          * <code>JSpinner</code>.
672          * Typically the editor's parent is a <code>JSpinner</code> however
673          * subclasses of <code>JSpinner</code> may override the
674          * the <code>createEditor</code> method and insert one or more containers
675          * between the <code>JSpinner</code> and it's editor.
676          *
677          * @return <code>JSpinner</code> ancestor; <code>null</code>
678          *         if none of the ancestors are a <code>JSpinner</code>
679          *
680          * @see JSpinner#createEditor
681          */
682         public JSpinner getSpinner() {
683             for (Component c = this; c != null; c = c.getParent()) {
684                 if (c instanceof JSpinner) {
685                     return (JSpinner)c;
686                 }
687             }
688             return null;
689         }
690 
691 
692         /**
693          * Returns the <code>JFormattedTextField</code> child of this
694          * editor.  By default the text field is the first and only
695          * child of editor.
696          *
697          * @return the <code>JFormattedTextField</code> that gives the user
698          *     access to the <code>SpinnerDateModel's</code> value.
699          * @see #getSpinner
700          * @see #getModel
701          */
702         public JFormattedTextField getTextField() {
703             return (JFormattedTextField)getComponent(0);
704         }
705 
706 
707         /**
708          * This method is called when the spinner's model's state changes.
709          * It sets the <code>value</code> of the text field to the current
710          * value of the spinners model.
711          *
712          * @param e the <code>ChangeEvent</code> whose source is the
713          * <code>JSpinner</code> whose model has changed.
714          * @see #getTextField
715          * @see JSpinner#getValue
716          */
717         public void stateChanged(ChangeEvent e) {
718             JSpinner spinner = (JSpinner)(e.getSource());
719             getTextField().setValue(spinner.getValue());
720         }
721 
722 
723         /**
724          * Called by the <code>JFormattedTextField</code>
725          * <code>PropertyChangeListener</code>.  When the <code>"value"</code>
726          * property changes, which implies that the user has typed a new
727          * number, we set the value of the spinners model.
728          * <p>
729          * This class ignores <code>PropertyChangeEvents</code> whose
730          * source is not the <code>JFormattedTextField</code>, so subclasses
731          * may safely make <code>this</code> <code>DefaultEditor</code> a
732          * <code>PropertyChangeListener</code> on other objects.
733          *
734          * @param e the <code>PropertyChangeEvent</code> whose source is
735          *    the <code>JFormattedTextField</code> created by this class.
736          * @see #getTextField
737          */
738         public void propertyChange(PropertyChangeEvent e)
739         {
740             JSpinner spinner = getSpinner();
741 
742             if (spinner == null) {
743                 // Indicates we aren't installed anywhere.
744                 return;
745             }
746 
747             Object source = e.getSource();
748             String name = e.getPropertyName();
749             if ((source instanceof JFormattedTextField) && "value".equals(name)) {
750                 Object lastValue = spinner.getValue();
751 
752                 // Try to set the new value
753                 try {
754                     spinner.setValue(getTextField().getValue());
755                 } catch (IllegalArgumentException iae) {
756                     // SpinnerModel didn't like new value, reset
757                     try {
758                         ((JFormattedTextField)source).setValue(lastValue);
759                     } catch (IllegalArgumentException iae2) {
760                         // Still bogus, nothing else we can do, the
761                         // SpinnerModel and JFormattedTextField are now out
762                         // of sync.
763                     }
764                 }
765             }
766         }
767 
768 
769         /**
770          * This <code>LayoutManager</code> method does nothing.  We're
771          * only managing a single child and there's no support
772          * for layout constraints.
773          *
774          * @param name ignored
775          * @param child ignored
776          */
777         public void addLayoutComponent(String name, Component child) {
778         }
779 
780 
781         /**
782          * This <code>LayoutManager</code> method does nothing.  There
783          * isn't any per-child state.
784          *
785          * @param child ignored
786          */
787         public void removeLayoutComponent(Component child) {
788         }
789 
790 
791         /**
792          * Returns the size of the parents insets.
793          */
794         private Dimension insetSize(Container parent) {
795             Insets insets = parent.getInsets();
796             int w = insets.left + insets.right;
797             int h = insets.top + insets.bottom;
798             return new Dimension(w, h);
799         }
800 
801 
802         /**
803          * Returns the preferred size of first (and only) child plus the
804          * size of the parents insets.
805          *
806          * @param parent the Container that's managing the layout
807          * @return the preferred dimensions to lay out the subcomponents
808          *          of the specified container.
809          */
810         public Dimension preferredLayoutSize(Container parent) {
811             Dimension preferredSize = insetSize(parent);
812             if (parent.getComponentCount() > 0) {
813                 Dimension childSize = getComponent(0).getPreferredSize();
814                 preferredSize.width += childSize.width;
815                 preferredSize.height += childSize.height;
816             }
817             return preferredSize;
818         }
819 
820 
821         /**
822          * Returns the minimum size of first (and only) child plus the
823          * size of the parents insets.
824          *
825          * @param parent the Container that's managing the layout
826          * @return  the minimum dimensions needed to lay out the subcomponents
827          *          of the specified container.
828          */
829         public Dimension minimumLayoutSize(Container parent) {
830             Dimension minimumSize = insetSize(parent);
831             if (parent.getComponentCount() > 0) {
832                 Dimension childSize = getComponent(0).getMinimumSize();
833                 minimumSize.width += childSize.width;
834                 minimumSize.height += childSize.height;
835             }
836             return minimumSize;
837         }
838 
839 
840         /**
841          * Resize the one (and only) child to completely fill the area
842          * within the parents insets.
843          */
844         public void layoutContainer(Container parent) {
845             if (parent.getComponentCount() > 0) {
846                 Insets insets = parent.getInsets();
847                 int w = parent.getWidth() - (insets.left + insets.right);
848                 int h = parent.getHeight() - (insets.top + insets.bottom);
849                 getComponent(0).setBounds(insets.left, insets.top, w, h);
850             }
851         }
852 
853         /**
854          * Pushes the currently edited value to the <code>SpinnerModel</code>.
855          * <p>
856          * The default implementation invokes <code>commitEdit</code> on the
857          * <code>JFormattedTextField</code>.
858          *
859          * @throws ParseException if the edited value is not legal
860          */
861         public void commitEdit()  throws ParseException {
862             // If the value in the JFormattedTextField is legal, this will have
863             // the result of pushing the value to the SpinnerModel
864             // by way of the <code>propertyChange</code> method.
865             JFormattedTextField ftf = getTextField();
866 
867             ftf.commitEdit();
868         }
869 
870         /**
871          * Returns the baseline.
872          *
873          * @throws IllegalArgumentException {@inheritDoc}
874          * @see javax.swing.JComponent#getBaseline(int,int)
875          * @see javax.swing.JComponent#getBaselineResizeBehavior()
876          * @since 1.6
877          */
878         public int getBaseline(int width, int height) {
879             // check size.
880             super.getBaseline(width, height);
881             Insets insets = getInsets();
882             width = width - insets.left - insets.right;
883             height = height - insets.top - insets.bottom;
884             int baseline = getComponent(0).getBaseline(width, height);
885             if (baseline >= 0) {
886                 return baseline + insets.top;
887             }
888             return -1;
889         }
890 
891         /**
892          * Returns an enum indicating how the baseline of the component
893          * changes as the size changes.
894          *
895          * @throws NullPointerException {@inheritDoc}
896          * @see javax.swing.JComponent#getBaseline(int, int)
897          * @since 1.6
898          */
899         public BaselineResizeBehavior getBaselineResizeBehavior() {
900             return getComponent(0).getBaselineResizeBehavior();
901         }
902     }
903 
904 
905 
906 
907     /**
908      * This subclass of javax.swing.DateFormatter maps the minimum/maximum
909      * properties to te start/end properties of a SpinnerDateModel.
910      */
911     private static class DateEditorFormatter extends DateFormatter {
912         private final SpinnerDateModel model;
913 
914         DateEditorFormatter(SpinnerDateModel model, DateFormat format) {
915             super(format);
916             this.model = model;
917         }
918 
919         public void setMinimum(Comparable min) {
920             model.setStart(min);
921         }
922 
923         public Comparable getMinimum() {
924             return  model.getStart();
925         }
926 
927         public void setMaximum(Comparable max) {
928             model.setEnd(max);
929         }
930 
931         public Comparable getMaximum() {
932             return model.getEnd();
933         }
934     }
935 
936 
937     /**
938      * An editor for a <code>JSpinner</code> whose model is a
939      * <code>SpinnerDateModel</code>.  The value of the editor is
940      * displayed with a <code>JFormattedTextField</code> whose format
941      * is defined by a <code>DateFormatter</code> instance whose
942      * <code>minimum</code> and <code>maximum</code> properties
943      * are mapped to the <code>SpinnerDateModel</code>.
944      * @since 1.4
945      */
946     // PENDING(hmuller): more example javadoc
947     public static class DateEditor extends DefaultEditor
948     {
949         // This is here until SimpleDateFormat gets a constructor that
950         // takes a Locale: 4923525
951         private static String getDefaultPattern(Locale loc) {
952             LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DateFormatProvider.class, loc);
953             LocaleResources lr = adapter.getLocaleResources(loc);
954             if (lr == null) {
955                 lr = LocaleProviderAdapter.forJRE().getLocaleResources(loc);
956             }
957             return lr.getDateTimePattern(DateFormat.SHORT, DateFormat.SHORT, null);
958         }
959 
960         /**
961          * Construct a <code>JSpinner</code> editor that supports displaying
962          * and editing the value of a <code>SpinnerDateModel</code>
963          * with a <code>JFormattedTextField</code>.  <code>This</code>
964          * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
965          * on the spinners model and a <code>PropertyChangeListener</code>
966          * on the new <code>JFormattedTextField</code>.
967          *
968          * @param spinner the spinner whose model <code>this</code> editor will monitor
969          * @exception IllegalArgumentException if the spinners model is not
970          *     an instance of <code>SpinnerDateModel</code>
971          *
972          * @see #getModel
973          * @see #getFormat
974          * @see SpinnerDateModel
975          */
976         public DateEditor(JSpinner spinner) {
977             this(spinner, getDefaultPattern(spinner.getLocale()));
978         }
979 
980 
981         /**
982          * Construct a <code>JSpinner</code> editor that supports displaying
983          * and editing the value of a <code>SpinnerDateModel</code>
984          * with a <code>JFormattedTextField</code>.  <code>This</code>
985          * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
986          * on the spinner and a <code>PropertyChangeListener</code>
987          * on the new <code>JFormattedTextField</code>.
988          *
989          * @param spinner the spinner whose model <code>this</code> editor will monitor
990          * @param dateFormatPattern the initial pattern for the
991          *     <code>SimpleDateFormat</code> object that's used to display
992          *     and parse the value of the text field.
993          * @exception IllegalArgumentException if the spinners model is not
994          *     an instance of <code>SpinnerDateModel</code>
995          *
996          * @see #getModel
997          * @see #getFormat
998          * @see SpinnerDateModel
999          * @see java.text.SimpleDateFormat
1000          */
1001         public DateEditor(JSpinner spinner, String dateFormatPattern) {
1002             this(spinner, new SimpleDateFormat(dateFormatPattern,
1003                                                spinner.getLocale()));
1004         }
1005 
1006         /**
1007          * Construct a <code>JSpinner</code> editor that supports displaying
1008          * and editing the value of a <code>SpinnerDateModel</code>
1009          * with a <code>JFormattedTextField</code>.  <code>This</code>
1010          * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
1011          * on the spinner and a <code>PropertyChangeListener</code>
1012          * on the new <code>JFormattedTextField</code>.
1013          *
1014          * @param spinner the spinner whose model <code>this</code> editor
1015          *        will monitor
1016          * @param format <code>DateFormat</code> object that's used to display
1017          *     and parse the value of the text field.
1018          * @exception IllegalArgumentException if the spinners model is not
1019          *     an instance of <code>SpinnerDateModel</code>
1020          *
1021          * @see #getModel
1022          * @see #getFormat
1023          * @see SpinnerDateModel
1024          * @see java.text.SimpleDateFormat
1025          */
1026         private DateEditor(JSpinner spinner, DateFormat format) {
1027             super(spinner);
1028             if (!(spinner.getModel() instanceof SpinnerDateModel)) {
1029                 throw new IllegalArgumentException(
1030                                  "model not a SpinnerDateModel");
1031             }
1032 
1033             SpinnerDateModel model = (SpinnerDateModel)spinner.getModel();
1034             DateFormatter formatter = new DateEditorFormatter(model, format);
1035             DefaultFormatterFactory factory = new DefaultFormatterFactory(
1036                                                   formatter);
1037             JFormattedTextField ftf = getTextField();
1038             ftf.setEditable(true);
1039             ftf.setFormatterFactory(factory);
1040 
1041             /* TBD - initializing the column width of the text field
1042              * is imprecise and doing it here is tricky because
1043              * the developer may configure the formatter later.
1044              */
1045             try {
1046                 String maxString = formatter.valueToString(model.getStart());
1047                 String minString = formatter.valueToString(model.getEnd());
1048                 ftf.setColumns(Math.max(maxString.length(),
1049                                         minString.length()));
1050             }
1051             catch (ParseException e) {
1052                 // PENDING: hmuller
1053             }
1054         }
1055 
1056         /**
1057          * Returns the <code>java.text.SimpleDateFormat</code> object the
1058          * <code>JFormattedTextField</code> uses to parse and format
1059          * numbers.
1060          *
1061          * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1062          * @see #getTextField
1063          * @see java.text.SimpleDateFormat
1064          */
1065         public SimpleDateFormat getFormat() {
1066             return (SimpleDateFormat)((DateFormatter)(getTextField().getFormatter())).getFormat();
1067         }
1068 
1069 
1070         /**
1071          * Return our spinner ancestor's <code>SpinnerDateModel</code>.
1072          *
1073          * @return <code>getSpinner().getModel()</code>
1074          * @see #getSpinner
1075          * @see #getTextField
1076          */
1077         public SpinnerDateModel getModel() {
1078             return (SpinnerDateModel)(getSpinner().getModel());
1079         }
1080     }
1081 
1082 
1083     /**
1084      * This subclass of javax.swing.NumberFormatter maps the minimum/maximum
1085      * properties to a SpinnerNumberModel and initializes the valueClass
1086      * of the NumberFormatter to match the type of the initial models value.
1087      */
1088     private static class NumberEditorFormatter extends NumberFormatter {
1089         private final SpinnerNumberModel model;
1090 
1091         NumberEditorFormatter(SpinnerNumberModel model, NumberFormat format) {
1092             super(format);
1093             this.model = model;
1094             setValueClass(model.getValue().getClass());
1095         }
1096 
1097         public void setMinimum(Comparable min) {
1098             model.setMinimum(min);
1099         }
1100 
1101         public Comparable getMinimum() {
1102             return  model.getMinimum();
1103         }
1104 
1105         public void setMaximum(Comparable max) {
1106             model.setMaximum(max);
1107         }
1108 
1109         public Comparable getMaximum() {
1110             return model.getMaximum();
1111         }
1112     }
1113 
1114 
1115 
1116     /**
1117      * An editor for a <code>JSpinner</code> whose model is a
1118      * <code>SpinnerNumberModel</code>.  The value of the editor is
1119      * displayed with a <code>JFormattedTextField</code> whose format
1120      * is defined by a <code>NumberFormatter</code> instance whose
1121      * <code>minimum</code> and <code>maximum</code> properties
1122      * are mapped to the <code>SpinnerNumberModel</code>.
1123      * @since 1.4
1124      */
1125     // PENDING(hmuller): more example javadoc
1126     public static class NumberEditor extends DefaultEditor
1127     {
1128         // This is here until DecimalFormat gets a constructor that
1129         // takes a Locale: 4923525
1130         private static String getDefaultPattern(Locale locale) {
1131             // Get the pattern for the default locale.
1132             LocaleProviderAdapter adapter;
1133             adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
1134                                                        locale);
1135             LocaleResources lr = adapter.getLocaleResources(locale);
1136             if (lr == null) {
1137                 lr = LocaleProviderAdapter.forJRE().getLocaleResources(locale);
1138             }
1139             String[] all = lr.getNumberPatterns();
1140             return all[0];
1141         }
1142 
1143         /**
1144          * Construct a <code>JSpinner</code> editor that supports displaying
1145          * and editing the value of a <code>SpinnerNumberModel</code>
1146          * with a <code>JFormattedTextField</code>.  <code>This</code>
1147          * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1148          * on the spinner and a <code>PropertyChangeListener</code>
1149          * on the new <code>JFormattedTextField</code>.
1150          *
1151          * @param spinner the spinner whose model <code>this</code> editor will monitor
1152          * @exception IllegalArgumentException if the spinners model is not
1153          *     an instance of <code>SpinnerNumberModel</code>
1154          *
1155          * @see #getModel
1156          * @see #getFormat
1157          * @see SpinnerNumberModel
1158          */
1159         public NumberEditor(JSpinner spinner) {
1160             this(spinner, getDefaultPattern(spinner.getLocale()));
1161         }
1162 
1163         /**
1164          * Construct a <code>JSpinner</code> editor that supports displaying
1165          * and editing the value of a <code>SpinnerNumberModel</code>
1166          * with a <code>JFormattedTextField</code>.  <code>This</code>
1167          * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1168          * on the spinner and a <code>PropertyChangeListener</code>
1169          * on the new <code>JFormattedTextField</code>.
1170          *
1171          * @param spinner the spinner whose model <code>this</code> editor will monitor
1172          * @param decimalFormatPattern the initial pattern for the
1173          *     <code>DecimalFormat</code> object that's used to display
1174          *     and parse the value of the text field.
1175          * @exception IllegalArgumentException if the spinners model is not
1176          *     an instance of <code>SpinnerNumberModel</code> or if
1177          *     <code>decimalFormatPattern</code> is not a legal
1178          *     argument to <code>DecimalFormat</code>
1179          *
1180          * @see #getTextField
1181          * @see SpinnerNumberModel
1182          * @see java.text.DecimalFormat
1183          */
1184         public NumberEditor(JSpinner spinner, String decimalFormatPattern) {
1185             this(spinner, new DecimalFormat(decimalFormatPattern));
1186         }
1187 
1188 
1189         /**
1190          * Construct a <code>JSpinner</code> editor that supports displaying
1191          * and editing the value of a <code>SpinnerNumberModel</code>
1192          * with a <code>JFormattedTextField</code>.  <code>This</code>
1193          * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1194          * on the spinner and a <code>PropertyChangeListener</code>
1195          * on the new <code>JFormattedTextField</code>.
1196          *
1197          * @param spinner the spinner whose model <code>this</code> editor will monitor
1198          * @param decimalFormatPattern the initial pattern for the
1199          *     <code>DecimalFormat</code> object that's used to display
1200          *     and parse the value of the text field.
1201          * @exception IllegalArgumentException if the spinners model is not
1202          *     an instance of <code>SpinnerNumberModel</code>
1203          *
1204          * @see #getTextField
1205          * @see SpinnerNumberModel
1206          * @see java.text.DecimalFormat
1207          */
1208         private NumberEditor(JSpinner spinner, DecimalFormat format) {
1209             super(spinner);
1210             if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
1211                 throw new IllegalArgumentException(
1212                           "model not a SpinnerNumberModel");
1213             }
1214 
1215             SpinnerNumberModel model = (SpinnerNumberModel)spinner.getModel();
1216             NumberFormatter formatter = new NumberEditorFormatter(model,
1217                                                                   format);
1218             DefaultFormatterFactory factory = new DefaultFormatterFactory(
1219                                                   formatter);
1220             JFormattedTextField ftf = getTextField();
1221             ftf.setEditable(true);
1222             ftf.setFormatterFactory(factory);
1223             ftf.setHorizontalAlignment(JTextField.RIGHT);
1224 
1225             /* TBD - initializing the column width of the text field
1226              * is imprecise and doing it here is tricky because
1227              * the developer may configure the formatter later.
1228              */
1229             try {
1230                 String maxString = formatter.valueToString(model.getMinimum());
1231                 String minString = formatter.valueToString(model.getMaximum());
1232                 ftf.setColumns(Math.max(maxString.length(),
1233                                         minString.length()));
1234             }
1235             catch (ParseException e) {
1236                 // TBD should throw a chained error here
1237             }
1238 
1239         }
1240 
1241 
1242         /**
1243          * Returns the <code>java.text.DecimalFormat</code> object the
1244          * <code>JFormattedTextField</code> uses to parse and format
1245          * numbers.
1246          *
1247          * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1248          * @see #getTextField
1249          * @see java.text.DecimalFormat
1250          */
1251         public DecimalFormat getFormat() {
1252             return (DecimalFormat)((NumberFormatter)(getTextField().getFormatter())).getFormat();
1253         }
1254 
1255 
1256         /**
1257          * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1258          *
1259          * @return <code>getSpinner().getModel()</code>
1260          * @see #getSpinner
1261          * @see #getTextField
1262          */
1263         public SpinnerNumberModel getModel() {
1264             return (SpinnerNumberModel)(getSpinner().getModel());
1265         }
1266     }
1267 
1268 
1269     /**
1270      * An editor for a <code>JSpinner</code> whose model is a
1271      * <code>SpinnerListModel</code>.
1272      * @since 1.4
1273      */
1274     public static class ListEditor extends DefaultEditor
1275     {
1276         /**
1277          * Construct a <code>JSpinner</code> editor that supports displaying
1278          * and editing the value of a <code>SpinnerListModel</code>
1279          * with a <code>JFormattedTextField</code>.  <code>This</code>
1280          * <code>ListEditor</code> becomes both a <code>ChangeListener</code>
1281          * on the spinner and a <code>PropertyChangeListener</code>
1282          * on the new <code>JFormattedTextField</code>.
1283          *
1284          * @param spinner the spinner whose model <code>this</code> editor will monitor
1285          * @exception IllegalArgumentException if the spinners model is not
1286          *     an instance of <code>SpinnerListModel</code>
1287          *
1288          * @see #getModel
1289          * @see SpinnerListModel
1290          */
1291         public ListEditor(JSpinner spinner) {
1292             super(spinner);
1293             if (!(spinner.getModel() instanceof SpinnerListModel)) {
1294                 throw new IllegalArgumentException("model not a SpinnerListModel");
1295             }
1296             getTextField().setEditable(true);
1297             getTextField().setFormatterFactory(new
1298                               DefaultFormatterFactory(new ListFormatter()));
1299         }
1300 
1301         /**
1302          * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1303          *
1304          * @return <code>getSpinner().getModel()</code>
1305          * @see #getSpinner
1306          * @see #getTextField
1307          */
1308         public SpinnerListModel getModel() {
1309             return (SpinnerListModel)(getSpinner().getModel());
1310         }
1311 
1312 
1313         /**
1314          * ListFormatter provides completion while text is being input
1315          * into the JFormattedTextField. Completion is only done if the
1316          * user is inserting text at the end of the document. Completion
1317          * is done by way of the SpinnerListModel method findNextMatch.
1318          */
1319         private class ListFormatter extends
1320                           JFormattedTextField.AbstractFormatter {
1321             private DocumentFilter filter;
1322 
1323             public String valueToString(Object value) throws ParseException {
1324                 if (value == null) {
1325                     return "";
1326                 }
1327                 return value.toString();
1328             }
1329 
1330             public Object stringToValue(String string) throws ParseException {
1331                 return string;
1332             }
1333 
1334             protected DocumentFilter getDocumentFilter() {
1335                 if (filter == null) {
1336                     filter = new Filter();
1337                 }
1338                 return filter;
1339             }
1340 
1341 
1342             private class Filter extends DocumentFilter {
1343                 public void replace(FilterBypass fb, int offset, int length,
1344                                     String string, AttributeSet attrs) throws
1345                                            BadLocationException {
1346                     if (string != null && (offset + length) ==
1347                                           fb.getDocument().getLength()) {
1348                         Object next = getModel().findNextMatch(
1349                                          fb.getDocument().getText(0, offset) +
1350                                          string);
1351                         String value = (next != null) ? next.toString() : null;
1352 
1353                         if (value != null) {
1354                             fb.remove(0, offset + length);
1355                             fb.insertString(0, value, null);
1356                             getFormattedTextField().select(offset +
1357                                                            string.length(),
1358                                                            value.length());
1359                             return;
1360                         }
1361                     }
1362                     super.replace(fb, offset, length, string, attrs);
1363                 }
1364 
1365                 public void insertString(FilterBypass fb, int offset,
1366                                      String string, AttributeSet attr)
1367                        throws BadLocationException {
1368                     replace(fb, offset, 0, string, attr);
1369                 }
1370             }
1371         }
1372     }
1373 
1374 
1375     /**
1376      * An Action implementation that is always disabled.
1377      */
1378     private static class DisabledAction implements Action {
1379         public Object getValue(String key) {
1380             return null;
1381         }
1382         public void putValue(String key, Object value) {
1383         }
1384         public void setEnabled(boolean b) {
1385         }
1386         public boolean isEnabled() {
1387             return false;
1388         }
1389         public void addPropertyChangeListener(PropertyChangeListener l) {
1390         }
1391         public void removePropertyChangeListener(PropertyChangeListener l) {
1392         }
1393         public void actionPerformed(ActionEvent ae) {
1394         }
1395     }
1396 
1397     /////////////////
1398     // Accessibility support
1399     ////////////////
1400 
1401     /**
1402      * Gets the <code>AccessibleContext</code> for the <code>JSpinner</code>
1403      *
1404      * @return the <code>AccessibleContext</code> for the <code>JSpinner</code>
1405      * @since 1.5
1406      */
1407     public AccessibleContext getAccessibleContext() {
1408         if (accessibleContext == null) {
1409             accessibleContext = new AccessibleJSpinner();
1410         }
1411         return accessibleContext;
1412     }
1413 
1414     /**
1415      * <code>AccessibleJSpinner</code> implements accessibility
1416      * support for the <code>JSpinner</code> class.
1417      * @since 1.5
1418      */
1419     protected class AccessibleJSpinner extends AccessibleJComponent
1420         implements AccessibleValue, AccessibleAction, AccessibleText,
1421                    AccessibleEditableText, ChangeListener {
1422 
1423         private Object oldModelValue = null;
1424 
1425         /**
1426          * AccessibleJSpinner constructor
1427          */
1428         protected AccessibleJSpinner() {
1429             // model is guaranteed to be non-null
1430             oldModelValue = model.getValue();
1431             JSpinner.this.addChangeListener(this);
1432         }
1433 
1434         /**
1435          * Invoked when the target of the listener has changed its state.
1436          *
1437          * @param e  a <code>ChangeEvent</code> object. Must not be null.
1438          * @throws NullPointerException if the parameter is null.
1439          */
1440         public void stateChanged(ChangeEvent e) {
1441             if (e == null) {
1442                 throw new NullPointerException();
1443             }
1444             Object newModelValue = model.getValue();
1445             firePropertyChange(ACCESSIBLE_VALUE_PROPERTY,
1446                                oldModelValue,
1447                                newModelValue);
1448             firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
1449                                null,
1450                                0); // entire text may have changed
1451 
1452             oldModelValue = newModelValue;
1453         }
1454 
1455         /* ===== Begin AccessibleContext methods ===== */
1456 
1457         /**
1458          * Gets the role of this object.  The role of the object is the generic
1459          * purpose or use of the class of this object.  For example, the role
1460          * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1461          * AccessibleRole are provided so component developers can pick from
1462          * a set of predefined roles.  This enables assistive technologies to
1463          * provide a consistent interface to various tweaked subclasses of
1464          * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1465          * that act like a push button) as well as distinguish between subclasses
1466          * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1467          * and AccessibleRole.RADIO_BUTTON for radio buttons).
1468          * <p>Note that the AccessibleRole class is also extensible, so
1469          * custom component developers can define their own AccessibleRole's
1470          * if the set of predefined roles is inadequate.
1471          *
1472          * @return an instance of AccessibleRole describing the role of the object
1473          * @see AccessibleRole
1474          */
1475         public AccessibleRole getAccessibleRole() {
1476             return AccessibleRole.SPIN_BOX;
1477         }
1478 
1479         /**
1480          * Returns the number of accessible children of the object.
1481          *
1482          * @return the number of accessible children of the object.
1483          */
1484         public int getAccessibleChildrenCount() {
1485             // the JSpinner has one child, the editor
1486             if (editor.getAccessibleContext() != null) {
1487                 return 1;
1488             }
1489             return 0;
1490         }
1491 
1492         /**
1493          * Returns the specified Accessible child of the object.  The Accessible
1494          * children of an Accessible object are zero-based, so the first child
1495          * of an Accessible child is at index 0, the second child is at index 1,
1496          * and so on.
1497          *
1498          * @param i zero-based index of child
1499          * @return the Accessible child of the object
1500          * @see #getAccessibleChildrenCount
1501          */
1502         public Accessible getAccessibleChild(int i) {
1503             // the JSpinner has one child, the editor
1504             if (i != 0) {
1505                 return null;
1506             }
1507             if (editor.getAccessibleContext() != null) {
1508                 return (Accessible)editor;
1509             }
1510             return null;
1511         }
1512 
1513         /* ===== End AccessibleContext methods ===== */
1514 
1515         /**
1516          * Gets the AccessibleAction associated with this object that supports
1517          * one or more actions.
1518          *
1519          * @return AccessibleAction if supported by object; else return null
1520          * @see AccessibleAction
1521          */
1522         public AccessibleAction getAccessibleAction() {
1523             return this;
1524         }
1525 
1526         /**
1527          * Gets the AccessibleText associated with this object presenting
1528          * text on the display.
1529          *
1530          * @return AccessibleText if supported by object; else return null
1531          * @see AccessibleText
1532          */
1533         public AccessibleText getAccessibleText() {
1534             return this;
1535         }
1536 
1537         /*
1538          * Returns the AccessibleContext for the JSpinner editor
1539          */
1540         private AccessibleContext getEditorAccessibleContext() {
1541             if (editor instanceof DefaultEditor) {
1542                 JTextField textField = ((DefaultEditor)editor).getTextField();
1543                 if (textField != null) {
1544                     return textField.getAccessibleContext();
1545                 }
1546             } else if (editor instanceof Accessible) {
1547                 return editor.getAccessibleContext();
1548             }
1549             return null;
1550         }
1551 
1552         /*
1553          * Returns the AccessibleText for the JSpinner editor
1554          */
1555         private AccessibleText getEditorAccessibleText() {
1556             AccessibleContext ac = getEditorAccessibleContext();
1557             if (ac != null) {
1558                 return ac.getAccessibleText();
1559             }
1560             return null;
1561         }
1562 
1563         /*
1564          * Returns the AccessibleEditableText for the JSpinner editor
1565          */
1566         private AccessibleEditableText getEditorAccessibleEditableText() {
1567             AccessibleText at = getEditorAccessibleText();
1568             if (at instanceof AccessibleEditableText) {
1569                 return (AccessibleEditableText)at;
1570             }
1571             return null;
1572         }
1573 
1574         /**
1575          * Gets the AccessibleValue associated with this object.
1576          *
1577          * @return AccessibleValue if supported by object; else return null
1578          * @see AccessibleValue
1579          *
1580          */
1581         public AccessibleValue getAccessibleValue() {
1582             return this;
1583         }
1584 
1585         /* ===== Begin AccessibleValue impl ===== */
1586 
1587         /**
1588          * Get the value of this object as a Number.  If the value has not been
1589          * set, the return value will be null.
1590          *
1591          * @return value of the object
1592          * @see #setCurrentAccessibleValue
1593          */
1594         public Number getCurrentAccessibleValue() {
1595             Object o = model.getValue();
1596             if (o instanceof Number) {
1597                 return (Number)o;
1598             }
1599             return null;
1600         }
1601 
1602         /**
1603          * Set the value of this object as a Number.
1604          *
1605          * @param n the value to set for this object
1606          * @return true if the value was set; else False
1607          * @see #getCurrentAccessibleValue
1608          */
1609         public boolean setCurrentAccessibleValue(Number n) {
1610             // try to set the new value
1611             try {
1612                 model.setValue(n);
1613                 return true;
1614             } catch (IllegalArgumentException iae) {
1615                 // SpinnerModel didn't like new value
1616             }
1617             return false;
1618         }
1619 
1620         /**
1621          * Get the minimum value of this object as a Number.
1622          *
1623          * @return Minimum value of the object; null if this object does not
1624          * have a minimum value
1625          * @see #getMaximumAccessibleValue
1626          */
1627         public Number getMinimumAccessibleValue() {
1628             if (model instanceof SpinnerNumberModel) {
1629                 SpinnerNumberModel numberModel = (SpinnerNumberModel)model;
1630                 Object o = numberModel.getMinimum();
1631                 if (o instanceof Number) {
1632                     return (Number)o;
1633                 }
1634             }
1635             return null;
1636         }
1637 
1638         /**
1639          * Get the maximum value of this object as a Number.
1640          *
1641          * @return Maximum value of the object; null if this object does not
1642          * have a maximum value
1643          * @see #getMinimumAccessibleValue
1644          */
1645         public Number getMaximumAccessibleValue() {
1646             if (model instanceof SpinnerNumberModel) {
1647                 SpinnerNumberModel numberModel = (SpinnerNumberModel)model;
1648                 Object o = numberModel.getMaximum();
1649                 if (o instanceof Number) {
1650                     return (Number)o;
1651                 }
1652             }
1653             return null;
1654         }
1655 
1656         /* ===== End AccessibleValue impl ===== */
1657 
1658         /* ===== Begin AccessibleAction impl ===== */
1659 
1660         /**
1661          * Returns the number of accessible actions available in this object
1662          * If there are more than one, the first one is considered the "default"
1663          * action of the object.
1664          *
1665          * Two actions are supported: AccessibleAction.INCREMENT which
1666          * increments the spinner value and AccessibleAction.DECREMENT
1667          * which decrements the spinner value
1668          *
1669          * @return the zero-based number of Actions in this object
1670          */
1671         public int getAccessibleActionCount() {
1672             return 2;
1673         }
1674 
1675         /**
1676          * Returns a description of the specified action of the object.
1677          *
1678          * @param i zero-based index of the actions
1679          * @return a String description of the action
1680          * @see #getAccessibleActionCount
1681          */
1682         public String getAccessibleActionDescription(int i) {
1683             if (i == 0) {
1684                 return AccessibleAction.INCREMENT;
1685             } else if (i == 1) {
1686                 return AccessibleAction.DECREMENT;
1687             }
1688             return null;
1689         }
1690 
1691         /**
1692          * Performs the specified Action on the object
1693          *
1694          * @param i zero-based index of actions. The first action
1695          * (index 0) is AccessibleAction.INCREMENT and the second
1696          * action (index 1) is AccessibleAction.DECREMENT.
1697          * @return true if the action was performed; otherwise false.
1698          * @see #getAccessibleActionCount
1699          */
1700         public boolean doAccessibleAction(int i) {
1701             if (i < 0 || i > 1) {
1702                 return false;
1703             }
1704             Object o;
1705             if (i == 0) {
1706                 o = getNextValue(); // AccessibleAction.INCREMENT
1707             } else {
1708                 o = getPreviousValue(); // AccessibleAction.DECREMENT
1709             }
1710             // try to set the new value
1711             try {
1712                 model.setValue(o);
1713                 return true;
1714             } catch (IllegalArgumentException iae) {
1715                 // SpinnerModel didn't like new value
1716             }
1717             return false;
1718         }
1719 
1720         /* ===== End AccessibleAction impl ===== */
1721 
1722         /* ===== Begin AccessibleText impl ===== */
1723 
1724         /*
1725          * Returns whether source and destination components have the
1726          * same window ancestor
1727          */
1728         private boolean sameWindowAncestor(Component src, Component dest) {
1729             if (src == null || dest == null) {
1730                 return false;
1731             }
1732             return SwingUtilities.getWindowAncestor(src) ==
1733                 SwingUtilities.getWindowAncestor(dest);
1734         }
1735 
1736         /**
1737          * Given a point in local coordinates, return the zero-based index
1738          * of the character under that Point.  If the point is invalid,
1739          * this method returns -1.
1740          *
1741          * @param p the Point in local coordinates
1742          * @return the zero-based index of the character under Point p; if
1743          * Point is invalid return -1.
1744          */
1745         public int getIndexAtPoint(Point p) {
1746             AccessibleText at = getEditorAccessibleText();
1747             if (at != null && sameWindowAncestor(JSpinner.this, editor)) {
1748                 // convert point from the JSpinner bounds (source) to
1749                 // editor bounds (destination)
1750                 Point editorPoint = SwingUtilities.convertPoint(JSpinner.this,
1751                                                                 p,
1752                                                                 editor);
1753                 if (editorPoint != null) {
1754                     return at.getIndexAtPoint(editorPoint);
1755                 }
1756             }
1757             return -1;
1758         }
1759 
1760         /**
1761          * Determines the bounding box of the character at the given
1762          * index into the string.  The bounds are returned in local
1763          * coordinates.  If the index is invalid an empty rectangle is
1764          * returned.
1765          *
1766          * @param i the index into the String
1767          * @return the screen coordinates of the character's bounding box,
1768          * if index is invalid return an empty rectangle.
1769          */
1770         public Rectangle getCharacterBounds(int i) {
1771             AccessibleText at = getEditorAccessibleText();
1772             if (at != null ) {
1773                 Rectangle editorRect = at.getCharacterBounds(i);
1774                 if (editorRect != null &&
1775                     sameWindowAncestor(JSpinner.this, editor)) {
1776                     // return rectangle in the the JSpinner bounds
1777                     return SwingUtilities.convertRectangle(editor,
1778                                                            editorRect,
1779                                                            JSpinner.this);
1780                 }
1781             }
1782             return null;
1783         }
1784 
1785         /**
1786          * Returns the number of characters (valid indicies)
1787          *
1788          * @return the number of characters
1789          */
1790         public int getCharCount() {
1791             AccessibleText at = getEditorAccessibleText();
1792             if (at != null) {
1793                 return at.getCharCount();
1794             }
1795             return -1;
1796         }
1797 
1798         /**
1799          * Returns the zero-based offset of the caret.
1800          *
1801          * Note: That to the right of the caret will have the same index
1802          * value as the offset (the caret is between two characters).
1803          * @return the zero-based offset of the caret.
1804          */
1805         public int getCaretPosition() {
1806             AccessibleText at = getEditorAccessibleText();
1807             if (at != null) {
1808                 return at.getCaretPosition();
1809             }
1810             return -1;
1811         }
1812 
1813         /**
1814          * Returns the String at a given index.
1815          *
1816          * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1817          * @param index an index within the text
1818          * @return the letter, word, or sentence
1819          */
1820         public String getAtIndex(int part, int index) {
1821             AccessibleText at = getEditorAccessibleText();
1822             if (at != null) {
1823                 return at.getAtIndex(part, index);
1824             }
1825             return null;
1826         }
1827 
1828         /**
1829          * Returns the String after a given index.
1830          *
1831          * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1832          * @param index an index within the text
1833          * @return the letter, word, or sentence
1834          */
1835         public String getAfterIndex(int part, int index) {
1836             AccessibleText at = getEditorAccessibleText();
1837             if (at != null) {
1838                 return at.getAfterIndex(part, index);
1839             }
1840             return null;
1841         }
1842 
1843         /**
1844          * Returns the String before a given index.
1845          *
1846          * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1847          * @param index an index within the text
1848          * @return the letter, word, or sentence
1849          */
1850         public String getBeforeIndex(int part, int index) {
1851             AccessibleText at = getEditorAccessibleText();
1852             if (at != null) {
1853                 return at.getBeforeIndex(part, index);
1854             }
1855             return null;
1856         }
1857 
1858         /**
1859          * Returns the AttributeSet for a given character at a given index
1860          *
1861          * @param i the zero-based index into the text
1862          * @return the AttributeSet of the character
1863          */
1864         public AttributeSet getCharacterAttribute(int i) {
1865             AccessibleText at = getEditorAccessibleText();
1866             if (at != null) {
1867                 return at.getCharacterAttribute(i);
1868             }
1869             return null;
1870         }
1871 
1872         /**
1873          * Returns the start offset within the selected text.
1874          * If there is no selection, but there is
1875          * a caret, the start and end offsets will be the same.
1876          *
1877          * @return the index into the text of the start of the selection
1878          */
1879         public int getSelectionStart() {
1880             AccessibleText at = getEditorAccessibleText();
1881             if (at != null) {
1882                 return at.getSelectionStart();
1883             }
1884             return -1;
1885         }
1886 
1887         /**
1888          * Returns the end offset within the selected text.
1889          * If there is no selection, but there is
1890          * a caret, the start and end offsets will be the same.
1891          *
1892          * @return the index into the text of the end of the selection
1893          */
1894         public int getSelectionEnd() {
1895             AccessibleText at = getEditorAccessibleText();
1896             if (at != null) {
1897                 return at.getSelectionEnd();
1898             }
1899             return -1;
1900         }
1901 
1902         /**
1903          * Returns the portion of the text that is selected.
1904          *
1905          * @return the String portion of the text that is selected
1906          */
1907         public String getSelectedText() {
1908             AccessibleText at = getEditorAccessibleText();
1909             if (at != null) {
1910                 return at.getSelectedText();
1911             }
1912             return null;
1913         }
1914 
1915         /* ===== End AccessibleText impl ===== */
1916 
1917 
1918         /* ===== Begin AccessibleEditableText impl ===== */
1919 
1920         /**
1921          * Sets the text contents to the specified string.
1922          *
1923          * @param s the string to set the text contents
1924          */
1925         public void setTextContents(String s) {
1926             AccessibleEditableText at = getEditorAccessibleEditableText();
1927             if (at != null) {
1928                 at.setTextContents(s);
1929             }
1930         }
1931 
1932         /**
1933          * Inserts the specified string at the given index/
1934          *
1935          * @param index the index in the text where the string will
1936          * be inserted
1937          * @param s the string to insert in the text
1938          */
1939         public void insertTextAtIndex(int index, String s) {
1940             AccessibleEditableText at = getEditorAccessibleEditableText();
1941             if (at != null) {
1942                 at.insertTextAtIndex(index, s);
1943             }
1944         }
1945 
1946         /**
1947          * Returns the text string between two indices.
1948          *
1949          * @param startIndex the starting index in the text
1950          * @param endIndex the ending index in the text
1951          * @return the text string between the indices
1952          */
1953         public String getTextRange(int startIndex, int endIndex) {
1954             AccessibleEditableText at = getEditorAccessibleEditableText();
1955             if (at != null) {
1956                 return at.getTextRange(startIndex, endIndex);
1957             }
1958             return null;
1959         }
1960 
1961         /**
1962          * Deletes the text between two indices
1963          *
1964          * @param startIndex the starting index in the text
1965          * @param endIndex the ending index in the text
1966          */
1967         public void delete(int startIndex, int endIndex) {
1968             AccessibleEditableText at = getEditorAccessibleEditableText();
1969             if (at != null) {
1970                 at.delete(startIndex, endIndex);
1971             }
1972         }
1973 
1974         /**
1975          * Cuts the text between two indices into the system clipboard.
1976          *
1977          * @param startIndex the starting index in the text
1978          * @param endIndex the ending index in the text
1979          */
1980         public void cut(int startIndex, int endIndex) {
1981             AccessibleEditableText at = getEditorAccessibleEditableText();
1982             if (at != null) {
1983                 at.cut(startIndex, endIndex);
1984             }
1985         }
1986 
1987         /**
1988          * Pastes the text from the system clipboard into the text
1989          * starting at the specified index.
1990          *
1991          * @param startIndex the starting index in the text
1992          */
1993         public void paste(int startIndex) {
1994             AccessibleEditableText at = getEditorAccessibleEditableText();
1995             if (at != null) {
1996                 at.paste(startIndex);
1997             }
1998         }
1999 
2000         /**
2001          * Replaces the text between two indices with the specified
2002          * string.
2003          *
2004          * @param startIndex the starting index in the text
2005          * @param endIndex the ending index in the text
2006          * @param s the string to replace the text between two indices
2007          */
2008         public void replaceText(int startIndex, int endIndex, String s) {
2009             AccessibleEditableText at = getEditorAccessibleEditableText();
2010             if (at != null) {
2011                 at.replaceText(startIndex, endIndex, s);
2012             }
2013         }
2014 
2015         /**
2016          * Selects the text between two indices.
2017          *
2018          * @param startIndex the starting index in the text
2019          * @param endIndex the ending index in the text
2020          */
2021         public void selectText(int startIndex, int endIndex) {
2022             AccessibleEditableText at = getEditorAccessibleEditableText();
2023             if (at != null) {
2024                 at.selectText(startIndex, endIndex);
2025             }
2026         }
2027 
2028         /**
2029          * Sets attributes for the text between two indices.
2030          *
2031          * @param startIndex the starting index in the text
2032          * @param endIndex the ending index in the text
2033          * @param as the attribute set
2034          * @see AttributeSet
2035          */
2036         public void setAttributes(int startIndex, int endIndex, AttributeSet as) {
2037             AccessibleEditableText at = getEditorAccessibleEditableText();
2038             if (at != null) {
2039                 at.setAttributes(startIndex, endIndex, as);
2040             }
2041         }
2042     }  /* End AccessibleJSpinner */
2043 }