Use Tree Navigation
public static class

JSpinner.DefaultEditor

extends JPanel
implements LayoutManager PropertyChangeListener ChangeListener
/*
 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package javax.swing;

import java.awt.*;
import java.awt.event.*;

import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.plaf.SpinnerUI;

import java.util.*;
import java.beans.*;
import java.text.*;
import java.io.*;
import java.util.HashMap;
import sun.util.resources.LocaleData;

import javax.accessibility.*;


/**
 * A single line input field that lets the user select a
 * number or an object value from an ordered sequence. Spinners typically
 * provide a pair of tiny arrow buttons for stepping through the elements
 * of the sequence. The keyboard up/down arrow keys also cycle through the
 * elements. The user may also be allowed to type a (legal) value directly
 * into the spinner. Although combo boxes provide similar functionality,
 * spinners are sometimes preferred because they don't require a drop down list
 * that can obscure important data.
 * <p>
 * A <code>JSpinner</code>'s sequence value is defined by its
 * <code>SpinnerModel</code>.
 * The <code>model</code> can be specified as a constructor argument and
 * changed with the <code>model</code> property.  <code>SpinnerModel</code>
 * classes for some common types are provided: <code>SpinnerListModel</code>,
 * <code>SpinnerNumberModel</code>, and <code>SpinnerDateModel</code>.
 * <p>
 * A <code>JSpinner</code> has a single child component that's
 * responsible for displaying
 * and potentially changing the current element or <i>value</i> of
 * the model, which is called the <code>editor</code>.  The editor is created
 * by the <code>JSpinner</code>'s constructor and can be changed with the
 * <code>editor</code> property.  The <code>JSpinner</code>'s editor stays
 * in sync with the model by listening for <code>ChangeEvent</code>s. If the
 * user has changed the value displayed by the <code>editor</code> it is
 * possible for the <code>model</code>'s value to differ from that of
 * the <code>editor</code>. To make sure the <code>model</code> has the same
 * value as the editor use the <code>commitEdit</code> method, eg:
 * <pre>
 *   try {
 *       spinner.commitEdit();
 *   }
 *   catch (ParseException pe) {{
 *       // Edited value is invalid, spinner.getValue() will return
 *       // the last valid value, you could revert the spinner to show that:
 *       JComponent editor = spinner.getEditor()
 *       if (editor instanceof DefaultEditor) {
 *           ((DefaultEditor)editor).getTextField().setValue(spinner.getValue();
 *       }
 *       // reset the value to some known value:
 *       spinner.setValue(fallbackValue);
 *       // or treat the last valid value as the current, in which
 *       // case you don't need to do anything.
 *   }
 *   return spinner.getValue();
 * </pre>
 * <p>
 * For information and examples of using spinner see
 * <a href="http://java.sun.com/doc/books/tutorial/uiswing/components/spinner.html">How to Use Spinners</a>,
 * a section in <em>The Java Tutorial.</em>
 * <p>
 * <strong>Warning:</strong> Swing is not thread safe. For more
 * information see <a
 * href="package-summary.html#threading">Swing's Threading
 * Policy</a>.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans<sup><font size="-2">TM</font></sup>
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @beaninfo
 *   attribute: isContainer false
 * description: A single line input field that lets the user select a
 *     number or an object value from an ordered set.
 *
 * @see SpinnerModel
 * @see AbstractSpinnerModel
 * @see SpinnerListModel
 * @see SpinnerNumberModel
 * @see SpinnerDateModel
 * @see JFormattedTextField
 *
 * @author Hans Muller
 * @author Lynn Monsanto (accessibility)
 * @since 1.4
 */

public class JSpinner extends JComponent implements Accessible
{
   
/**
     * @see #getUIClassID
     * @see #readObject
     */

   
private static final String uiClassID = "SpinnerUI";

   
private static final Action DISABLED_ACTION = new DisabledAction();

   
private transient SpinnerModel model;
   
private JComponent editor;
   
private ChangeListener modelListener;
   
private transient ChangeEvent changeEvent;
   
private boolean editorExplicitlySet = false;


   
/**
     * Constructs a complete spinner with pair of next/previous buttons
     * and an editor for the <code>SpinnerModel</code>.
     */

   
public JSpinner(SpinnerModel model) {
       
this.model = model;
       
this.editor = createEditor(model);
        setUIProperty
("opaque",true);
        updateUI
();
   
}


   
/**
     * Constructs a spinner with an <code>Integer SpinnerNumberModel</code>
     * with initial value 0 and no minimum or maximum limits.
     */

   
public JSpinner() {
       
this(new SpinnerNumberModel());
   
}


   
/**
     * Returns the look and feel (L&F) object that renders this component.
     *
     * @return the <code>SpinnerUI</code> object that renders this component
     */

   
public SpinnerUI getUI() {
       
return (SpinnerUI)ui;
   
}


   
/**
     * Sets the look and feel (L&F) object that renders this component.
     *
     * @param ui  the <code>SpinnerUI</code> L&F object
     * @see UIDefaults#getUI
     */

   
public void setUI(SpinnerUI ui) {
       
super.setUI(ui);
   
}


   
/**
     * Returns the suffix used to construct the name of the look and feel
     * (L&F) class used to render this component.
     *
     * @return the string "SpinnerUI"
     * @see JComponent#getUIClassID
     * @see UIDefaults#getUI
     */

   
public String getUIClassID() {
       
return uiClassID;
   
}



   
/**
     * Resets the UI property with the value from the current look and feel.
     *
     * @see UIManager#getUI
     */

   
public void updateUI() {
        setUI
((SpinnerUI)UIManager.getUI(this));
        invalidate
();
   
}


   
/**
     * This method is called by the constructors to create the
     * <code>JComponent</code>
     * that displays the current value of the sequence.  The editor may
     * also allow the user to enter an element of the sequence directly.
     * An editor must listen for <code>ChangeEvents</code> on the
     * <code>model</code> and keep the value it displays
     * in sync with the value of the model.
     * <p>
     * Subclasses may override this method to add support for new
     * <code>SpinnerModel</code> classes.  Alternatively one can just
     * replace the editor created here with the <code>setEditor</code>
     * method.  The default mapping from model type to editor is:
     * <ul>
     * <li> <code>SpinnerNumberModel =&gt; JSpinner.NumberEditor</code>
     * <li> <code>SpinnerDateModel =&gt; JSpinner.DateEditor</code>
     * <li> <code>SpinnerListModel =&gt; JSpinner.ListEditor</code>
     * <li> <i>all others</i> =&gt; <code>JSpinner.DefaultEditor</code>
     * </ul>
     *
     * @return a component that displays the current value of the sequence
     * @param model the value of getModel
     * @see #getModel
     * @see #setEditor
     */

   
protected JComponent createEditor(SpinnerModel model) {
       
if (model instanceof SpinnerDateModel) {
           
return new DateEditor(this);
       
}
       
else if (model instanceof SpinnerListModel) {
           
return new ListEditor(this);
       
}
       
else if (model instanceof SpinnerNumberModel) {
           
return new NumberEditor(this);
       
}
       
else {
           
return new DefaultEditor(this);
       
}
   
}


   
/**
     * Changes the model that represents the value of this spinner.
     * If the editor property has not been explicitly set,
     * the editor property is (implicitly) set after the <code>"model"</code>
     * <code>PropertyChangeEvent</code> has been fired.  The editor
     * property is set to the value returned by <code>createEditor</code>,
     * as in:
     * <pre>
     * setEditor(createEditor(model));
     * </pre>
     *
     * @param model the new <code>SpinnerModel</code>
     * @see #getModel
     * @see #getEditor
     * @see #setEditor
     * @throws IllegalArgumentException if model is <code>null</code>
     *
     * @beaninfo
     *        bound: true
     *    attribute: visualUpdate true
     *  description: Model that represents the value of this spinner.
     */

   
public void setModel(SpinnerModel model) {
       
if (model == null) {
           
throw new IllegalArgumentException("null model");
       
}
       
if (!model.equals(this.model)) {
           
SpinnerModel oldModel = this.model;
           
this.model = model;
           
if (modelListener != null) {
                oldModel
.removeChangeListener(modelListener);
               
this.model.addChangeListener(modelListener);
           
}
            firePropertyChange
("model", oldModel, model);
           
if (!editorExplicitlySet) {
                setEditor
(createEditor(model)); // sets editorExplicitlySet true
                editorExplicitlySet
= false;
           
}
            repaint
();
            revalidate
();
       
}
   
}


   
/**
     * Returns the <code>SpinnerModel</code> that defines
     * this spinners sequence of values.
     *
     * @return the value of the model property
     * @see #setModel
     */

   
public SpinnerModel getModel() {
       
return model;
   
}


   
/**
     * Returns the current value of the model, typically
     * this value is displayed by the <code>editor</code>. If the
     * user has changed the value displayed by the <code>editor</code> it is
     * possible for the <code>model</code>'s value to differ from that of
     * the <code>editor</code>, refer to the class level javadoc for examples
     * of how to deal with this.
     * <p>
     * This method simply delegates to the <code>model</code>.
     * It is equivalent to:
     * <pre>
     * getModel().getValue()
     * </pre>
     *
     * @see #setValue
     * @see SpinnerModel#getValue
     */

   
public Object getValue() {
       
return getModel().getValue();
   
}


   
/**
     * Changes current value of the model, typically
     * this value is displayed by the <code>editor</code>.
     * If the <code>SpinnerModel</code> implementation
     * doesn't support the specified value then an
     * <code>IllegalArgumentException</code> is thrown.
     * <p>
     * This method simply delegates to the <code>model</code>.
     * It is equivalent to:
     * <pre>
     * getModel().setValue(value)
     * </pre>
     *
     * @throws IllegalArgumentException if <code>value</code> isn't allowed
     * @see #getValue
     * @see SpinnerModel#setValue
     */

   
public void setValue(Object value) {
        getModel
().setValue(value);
   
}


   
/**
     * Returns the object in the sequence that comes after the object returned
     * by <code>getValue()</code>. If the end of the sequence has been reached
     * then return <code>null</code>.
     * Calling this method does not effect <code>value</code>.
     * <p>
     * This method simply delegates to the <code>model</code>.
     * It is equivalent to:
     * <pre>
     * getModel().getNextValue()
     * </pre>
     *
     * @return the next legal value or <code>null</code> if one doesn't exist
     * @see #getValue
     * @see #getPreviousValue
     * @see SpinnerModel#getNextValue
     */

   
public Object getNextValue() {
       
return getModel().getNextValue();
   
}


   
/**
     * We pass <code>Change</code> events along to the listeners with the
     * the slider (instead of the model itself) as the event source.
     */

   
private class ModelListener implements ChangeListener, Serializable {
       
public void stateChanged(ChangeEvent e) {
            fireStateChanged
();
       
}
   
}


   
/**
     * Adds a listener to the list that is notified each time a change
     * to the model occurs.  The source of <code>ChangeEvents</code>
     * delivered to <code>ChangeListeners</code> will be this
     * <code>JSpinner</code>.  Note also that replacing the model
     * will not affect listeners added directly to JSpinner.
     * Applications can add listeners to  the model directly.  In that
     * case is that the source of the event would be the
     * <code>SpinnerModel</code>.
     *
     * @param listener the <code>ChangeListener</code> to add
     * @see #removeChangeListener
     * @see #getModel
     */

   
public void addChangeListener(ChangeListener listener) {
       
if (modelListener == null) {
            modelListener
= new ModelListener();
            getModel
().addChangeListener(modelListener);
       
}
        listenerList
.add(ChangeListener.class, listener);
   
}



   
/**
     * Removes a <code>ChangeListener</code> from this spinner.
     *
     * @param listener the <code>ChangeListener</code> to remove
     * @see #fireStateChanged
     * @see #addChangeListener
     */

   
public void removeChangeListener(ChangeListener listener) {
        listenerList
.remove(ChangeListener.class, listener);
   
}


   
/**
     * Returns an array of all the <code>ChangeListener</code>s added
     * to this JSpinner with addChangeListener().
     *
     * @return all of the <code>ChangeListener</code>s added or an empty
     *         array if no listeners have been added
     * @since 1.4
     */

   
public ChangeListener[] getChangeListeners() {
       
return (ChangeListener[])listenerList.getListeners(
               
ChangeListener.class);
   
}


   
/**
     * Sends a <code>ChangeEvent</code>, whose source is this
     * <code>JSpinner</code>, to each <code>ChangeListener</code>.
     * When a <code>ChangeListener</code> has been added
     * to the spinner, this method method is called each time
     * a <code>ChangeEvent</code> is received from the model.
     *
     * @see #addChangeListener
     * @see #removeChangeListener
     * @see EventListenerList
     */

   
protected void fireStateChanged() {
       
Object[] listeners = listenerList.getListenerList();
       
for (int i = listeners.length - 2; i >= 0; i -= 2) {
           
if (listeners[i] == ChangeListener.class) {
               
if (changeEvent == null) {
                    changeEvent
= new ChangeEvent(this);
               
}
               
((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
           
}
       
}
   
}


   
/**
     * Returns the object in the sequence that comes
     * before the object returned by <code>getValue()</code>.
     * If the end of the sequence has been reached then
     * return <code>null</code>. Calling this method does
     * not effect <code>value</code>.
     * <p>
     * This method simply delegates to the <code>model</code>.
     * It is equivalent to:
     * <pre>
     * getModel().getPreviousValue()
     * </pre>
     *
     * @return the previous legal value or <code>null</code>
     *   if one doesn't exist
     * @see #getValue
     * @see #getNextValue
     * @see SpinnerModel#getPreviousValue
     */

   
public Object getPreviousValue() {
       
return getModel().getPreviousValue();
   
}


   
/**
     * Changes the <code>JComponent</code> that displays the current value
     * of the <code>SpinnerModel</code>.  It is the responsibility of this
     * method to <i>disconnect</i> the old editor from the model and to
     * connect the new editor.  This may mean removing the
     * old editors <code>ChangeListener</code> from the model or the
     * spinner itself and adding one for the new editor.
     *
     * @param editor the new editor
     * @see #getEditor
     * @see #createEditor
     * @see #getModel
     * @throws IllegalArgumentException if editor is <code>null</code>
     *
     * @beaninfo
     *        bound: true
     *    attribute: visualUpdate true
     *  description: JComponent that displays the current value of the model
     */

   
public void setEditor(JComponent editor) {
       
if (editor == null) {
           
throw new IllegalArgumentException("null editor");
       
}
       
if (!editor.equals(this.editor)) {
           
JComponent oldEditor = this.editor;
           
this.editor = editor;
           
if (oldEditor instanceof DefaultEditor) {
               
((DefaultEditor)oldEditor).dismiss(this);
           
}
            editorExplicitlySet
= true;
            firePropertyChange
("editor", oldEditor, editor);
            revalidate
();
            repaint
();
       
}
   
}


   
/**
     * Returns the component that displays and potentially
     * changes the model's value.
     *
     * @return the component that displays and potentially
     *    changes the model's value
     * @see #setEditor
     * @see #createEditor
     */

   
public JComponent getEditor() {
       
return editor;
   
}


   
/**
     * Commits the currently edited value to the <code>SpinnerModel</code>.
     * <p>
     * If the editor is an instance of <code>DefaultEditor</code>, the
     * call if forwarded to the editor, otherwise this does nothing.
     *
     * @throws ParseException if the currently edited value couldn't
     *         be commited.
     */

   
public void commitEdit() throws ParseException {
       
JComponent editor = getEditor();
       
if (editor instanceof DefaultEditor) {
           
((DefaultEditor)editor).commitEdit();
       
}
   
}


   
/*
     * See readObject and writeObject in JComponent for more
     * information about serialization in Swing.
     *
     * @param s Stream to write to
     */

   
private void writeObject(ObjectOutputStream s) throws IOException {
        s
.defaultWriteObject();
       
HashMap additionalValues = new HashMap(1);
       
SpinnerModel model = getModel();

       
if (model instanceof Serializable) {
            additionalValues
.put("model", model);
       
}
        s
.writeObject(additionalValues);

       
if (getUIClassID().equals(uiClassID)) {
           
byte count = JComponent.getWriteObjCounter(this);
           
JComponent.setWriteObjCounter(this, --count);
           
if (count == 0 && ui != null) {
                ui
.installUI(this);
           
}
       
}
   
}

   
private void readObject(ObjectInputStream s)
       
throws IOException, ClassNotFoundException {
        s
.defaultReadObject();

       
Map additionalValues = (Map)s.readObject();

        model
= (SpinnerModel)additionalValues.get("model");
   
}


   
/**
     * A simple base class for more specialized editors
     * that displays a read-only view of the model's current
     * value with a <code>JFormattedTextField</code>.  Subclasses
     * can configure the <code>JFormattedTextField</code> to create
     * an editor that's appropriate for the type of model they
     * support and they may want to override
     * the <code>stateChanged</code> and <code>propertyChanged</code>
     * methods, which keep the model and the text field in sync.
     * <p>
     * This class defines a <code>dismiss</code> method that removes the
     * editors <code>ChangeListener</code> from the <code>JSpinner</code>
     * that it's part of.   The <code>setEditor</code> method knows about
     * <code>DefaultEditor.dismiss</code>, so if the developer
     * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
     * its <code>ChangeListener</code> connection back to the
     * <code>JSpinner</code> will be removed.  However after that,
     * it's up to the developer to manage their editor listeners.
     * Similarly, if a subclass overrides <code>createEditor</code>,
     * it's up to the subclasser to deal with their editor
     * subsequently being replaced (with <code>setEditor</code>).
     * We expect that in most cases, and in editor installed
     * with <code>setEditor</code> or created by a <code>createEditor</code>
     * override, will not be replaced anyway.
     * <p>
     * This class is the <code>LayoutManager</code> for it's single
     * <code>JFormattedTextField</code> child.   By default the
     * child is just centered with the parents insets.
     * @since 1.4
     */

   
public static class DefaultEditor extends JPanel
       
implements ChangeListener, PropertyChangeListener, LayoutManager
   
{
       
/**
         * Constructs an editor component for the specified <code>JSpinner</code>.
         * This <code>DefaultEditor</code> is it's own layout manager and
         * it is added to the spinner's <code>ChangeListener</code> list.
         * The constructor creates a single <code>JFormattedTextField</code> child,
         * initializes it's value to be the spinner model's current value
         * and adds it to <code>this</code> <code>DefaultEditor</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @see #getTextField
         * @see JSpinner#addChangeListener
         */

       
public DefaultEditor(JSpinner spinner) {
           
super(null);

           
JFormattedTextField ftf = new JFormattedTextField();
            ftf
.setName("Spinner.formattedTextField");
            ftf
.setValue(spinner.getValue());
            ftf
.addPropertyChangeListener(this);
            ftf
.setEditable(false);
            ftf
.setInheritsPopupMenu(true);

           
String toolTipText = spinner.getToolTipText();
           
if (toolTipText != null) {
                ftf
.setToolTipText(toolTipText);
           
}

            add
(ftf);

            setLayout
(this);
            spinner
.addChangeListener(this);

           
// We want the spinner's increment/decrement actions to be
           
// active vs those of the JFormattedTextField. As such we
           
// put disabled actions in the JFormattedTextField's actionmap.
           
// A binding to a disabled action is treated as a nonexistant
           
// binding.
           
ActionMap ftfMap = ftf.getActionMap();

           
if (ftfMap != null) {
                ftfMap
.put("increment", DISABLED_ACTION);
                ftfMap
.put("decrement", DISABLED_ACTION);
           
}
       
}


       
/**
         * Disconnect <code>this</code> editor from the specified
         * <code>JSpinner</code>.  By default, this method removes
         * itself from the spinners <code>ChangeListener</code> list.
         *
         * @param spinner the <code>JSpinner</code> to disconnect this
         *    editor from; the same spinner as was passed to the constructor.
         */

       
public void dismiss(JSpinner spinner) {
            spinner
.removeChangeListener(this);
       
}


       
/**
         * Returns the <code>JSpinner</code> ancestor of this editor or
         * <code>null</code> if none of the ancestors are a
         * <code>JSpinner</code>.
         * Typically the editor's parent is a <code>JSpinner</code> however
         * subclasses of <code>JSpinner</code> may override the
         * the <code>createEditor</code> method and insert one or more containers
         * between the <code>JSpinner</code> and it's editor.
         *
         * @return <code>JSpinner</code> ancestor; <code>null</code>
         *         if none of the ancestors are a <code>JSpinner</code>
         *
         * @see JSpinner#createEditor
         */

       
public JSpinner getSpinner() {
           
for (Component c = this; c != null; c = c.getParent()) {
               
if (c instanceof JSpinner) {
                   
return (JSpinner)c;
               
}
           
}
           
return null;
       
}


       
/**
         * Returns the <code>JFormattedTextField</code> child of this
         * editor.  By default the text field is the first and only
         * child of editor.
         *
         * @return the <code>JFormattedTextField</code> that gives the user
         *     access to the <code>SpinnerDateModel's</code> value.
         * @see #getSpinner
         * @see #getModel
         */

       
public JFormattedTextField getTextField() {
           
return (JFormattedTextField)getComponent(0);
       
}


       
/**
         * This method is called when the spinner's model's state changes.
         * It sets the <code>value</code> of the text field to the current
         * value of the spinners model.
         *
         * @param e the <code>ChangeEvent</code> whose source is the
         * <code>JSpinner</code> whose model has changed.
         * @see #getTextField
         * @see JSpinner#getValue
         */

       
public void stateChanged(ChangeEvent e) {
           
JSpinner spinner = (JSpinner)(e.getSource());
            getTextField
().setValue(spinner.getValue());
       
}


       
/**
         * Called by the <code>JFormattedTextField</code>
         * <code>PropertyChangeListener</code>.  When the <code>"value"</code>
         * property changes, which implies that the user has typed a new
         * number, we set the value of the spinners model.
         * <p>
         * This class ignores <code>PropertyChangeEvents</code> whose
         * source is not the <code>JFormattedTextField</code>, so subclasses
         * may safely make <code>this</code> <code>DefaultEditor</code> a
         * <code>PropertyChangeListener</code> on other objects.
         *
         * @param e the <code>PropertyChangeEvent</code> whose source is
         *    the <code>JFormattedTextField</code> created by this class.
         * @see #getTextField
         */

       
public void propertyChange(PropertyChangeEvent e)
       
{
           
JSpinner spinner = getSpinner();

           
if (spinner == null) {
               
// Indicates we aren't installed anywhere.
               
return;
           
}

           
Object source = e.getSource();
           
String name = e.getPropertyName();
           
if ((source instanceof JFormattedTextField) && "value".equals(name)) {
               
Object lastValue = spinner.getValue();

               
// Try to set the new value
               
try {
                    spinner
.setValue(getTextField().getValue());
               
} catch (IllegalArgumentException iae) {
                   
// SpinnerModel didn't like new value, reset
                   
try {
                       
((JFormattedTextField)source).setValue(lastValue);
                   
} catch (IllegalArgumentException iae2) {
                       
// Still bogus, nothing else we can do, the
                       
// SpinnerModel and JFormattedTextField are now out
                       
// of sync.
                   
}
               
}
           
}
       
}


       
/**
         * This <code>LayoutManager</code> method does nothing.  We're
         * only managing a single child and there's no support
         * for layout constraints.
         *
         * @param name ignored
         * @param child ignored
         */

       
public void addLayoutComponent(String name, Component child) {
       
}


       
/**
         * This <code>LayoutManager</code> method does nothing.  There
         * isn't any per-child state.
         *
         * @param child ignored
         */

       
public void removeLayoutComponent(Component child) {
       
}


       
/**
         * Returns the size of the parents insets.
         */

       
private Dimension insetSize(Container parent) {
           
Insets insets = parent.getInsets();
           
int w = insets.left + insets.right;
           
int h = insets.top + insets.bottom;
           
return new Dimension(w, h);
       
}


       
/**
         * Returns the preferred size of first (and only) child plus the
         * size of the parents insets.
         *
         * @param parent the Container that's managing the layout
         * @return the preferred dimensions to lay out the subcomponents
         *          of the specified container.
         */

       
public Dimension preferredLayoutSize(Container parent) {
           
Dimension preferredSize = insetSize(parent);
           
if (parent.getComponentCount() > 0) {
               
Dimension childSize = getComponent(0).getPreferredSize();
                preferredSize
.width += childSize.width;
                preferredSize
.height += childSize.height;
           
}
           
return preferredSize;
       
}


       
/**
         * Returns the minimum size of first (and only) child plus the
         * size of the parents insets.
         *
         * @param parent the Container that's managing the layout
         * @return  the minimum dimensions needed to lay out the subcomponents
         *          of the specified container.
         */

       
public Dimension minimumLayoutSize(Container parent) {
           
Dimension minimumSize = insetSize(parent);
           
if (parent.getComponentCount() > 0) {
               
Dimension childSize = getComponent(0).getMinimumSize();
                minimumSize
.width += childSize.width;
                minimumSize
.height += childSize.height;
           
}
           
return minimumSize;
       
}


       
/**
         * Resize the one (and only) child to completely fill the area
         * within the parents insets.
         */

       
public void layoutContainer(Container parent) {
           
if (parent.getComponentCount() > 0) {
               
Insets insets = parent.getInsets();
               
int w = parent.getWidth() - (insets.left + insets.right);
               
int h = parent.getHeight() - (insets.top + insets.bottom);
                getComponent
(0).setBounds(insets.left, insets.top, w, h);
           
}
       
}

       
/**
         * Pushes the currently edited value to the <code>SpinnerModel</code>.
         * <p>
         * The default implementation invokes <code>commitEdit</code> on the
         * <code>JFormattedTextField</code>.
         *
         * @throws ParseException if the edited value is not legal
         */

       
public void commitEdit()  throws ParseException {
           
// If the value in the JFormattedTextField is legal, this will have
           
// the result of pushing the value to the SpinnerModel
           
// by way of the <code>propertyChange</code> method.
           
JFormattedTextField ftf = getTextField();

            ftf
.commitEdit();
       
}

       
/**
         * Returns the baseline.
         *
         * @throws IllegalArgumentException {@inheritDoc}
         * @see javax.swing.JComponent#getBaseline(int,int)
         * @see javax.swing.JComponent#getBaselineResizeBehavior()
         * @since 1.6
         */

       
public int getBaseline(int width, int height) {
           
// check size.
           
super.getBaseline(width, height);
           
Insets insets = getInsets();
            width
= width - insets.left - insets.right;
            height
= height - insets.top - insets.bottom;
           
int baseline = getComponent(0).getBaseline(width, height);
           
if (baseline >= 0) {
               
return baseline + insets.top;
           
}
           
return -1;
       
}

       
/**
         * Returns an enum indicating how the baseline of the component
         * changes as the size changes.
         *
         * @throws NullPointerException {@inheritDoc}
         * @see javax.swing.JComponent#getBaseline(int, int)
         * @since 1.6
         */

       
public BaselineResizeBehavior getBaselineResizeBehavior() {
           
return getComponent(0).getBaselineResizeBehavior();
       
}
   
}




   
/**
     * This subclass of javax.swing.DateFormatter maps the minimum/maximum
     * properties to te start/end properties of a SpinnerDateModel.
     */

   
private static class DateEditorFormatter extends DateFormatter {
       
private final SpinnerDateModel model;

       
DateEditorFormatter(SpinnerDateModel model, DateFormat format) {
           
super(format);
           
this.model = model;
       
}

       
public void setMinimum(Comparable min) {
            model
.setStart(min);
       
}

       
public Comparable getMinimum() {
           
return  model.getStart();
       
}

       
public void setMaximum(Comparable max) {
            model
.setEnd(max);
       
}

       
public Comparable getMaximum() {
           
return model.getEnd();
       
}
   
}


   
/**
     * An editor for a <code>JSpinner</code> whose model is a
     * <code>SpinnerDateModel</code>.  The value of the editor is
     * displayed with a <code>JFormattedTextField</code> whose format
     * is defined by a <code>DateFormatter</code> instance whose
     * <code>minimum</code> and <code>maximum</code> properties
     * are mapped to the <code>SpinnerDateModel</code>.
     * @since 1.4
     */

   
// PENDING(hmuller): more example javadoc
   
public static class DateEditor extends DefaultEditor
   
{
       
// This is here until SimpleDateFormat gets a constructor that
       
// takes a Locale: 4923525
       
private static String getDefaultPattern(Locale loc) {
           
ResourceBundle r = LocaleData.getDateFormatData(loc);
           
String[] dateTimePatterns = r.getStringArray("DateTimePatterns");
           
Object[] dateTimeArgs = {dateTimePatterns[DateFormat.SHORT],
                                     dateTimePatterns
[DateFormat.SHORT + 4]};
           
return MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
       
}

       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerDateModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinners model and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerDateModel</code>
         *
         * @see #getModel
         * @see #getFormat
         * @see SpinnerDateModel
         */

       
public DateEditor(JSpinner spinner) {
           
this(spinner, getDefaultPattern(spinner.getLocale()));
       
}


       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerDateModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinner and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @param dateFormatPattern the initial pattern for the
         *     <code>SimpleDateFormat</code> object that's used to display
         *     and parse the value of the text field.
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerDateModel</code>
         *
         * @see #getModel
         * @see #getFormat
         * @see SpinnerDateModel
         * @see java.text.SimpleDateFormat
         */

       
public DateEditor(JSpinner spinner, String dateFormatPattern) {
           
this(spinner, new SimpleDateFormat(dateFormatPattern,
                                               spinner
.getLocale()));
       
}

       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerDateModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinner and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor
         *        will monitor
         * @param format <code>DateFormat</code> object that's used to display
         *     and parse the value of the text field.
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerDateModel</code>
         *
         * @see #getModel
         * @see #getFormat
         * @see SpinnerDateModel
         * @see java.text.SimpleDateFormat
         */

       
private DateEditor(JSpinner spinner, DateFormat format) {
           
super(spinner);
           
if (!(spinner.getModel() instanceof SpinnerDateModel)) {
               
throw new IllegalArgumentException(
                                 
"model not a SpinnerDateModel");
           
}

           
SpinnerDateModel model = (SpinnerDateModel)spinner.getModel();
           
DateFormatter formatter = new DateEditorFormatter(model, format);
           
DefaultFormatterFactory factory = new DefaultFormatterFactory(
                                                  formatter
);
           
JFormattedTextField ftf = getTextField();
            ftf
.setEditable(true);
            ftf
.setFormatterFactory(factory);

           
/* TBD - initializing the column width of the text field
             * is imprecise and doing it here is tricky because
             * the developer may configure the formatter later.
             */

           
try {
               
String maxString = formatter.valueToString(model.getStart());
               
String minString = formatter.valueToString(model.getEnd());
                ftf
.setColumns(Math.max(maxString.length(),
                                        minString
.length()));
           
}
           
catch (ParseException e) {
               
// PENDING: hmuller
           
}
       
}

       
/**
         * Returns the <code>java.text.SimpleDateFormat</code> object the
         * <code>JFormattedTextField</code> uses to parse and format
         * numbers.
         *
         * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
         * @see #getTextField
         * @see java.text.SimpleDateFormat
         */

       
public SimpleDateFormat getFormat() {
           
return (SimpleDateFormat)((DateFormatter)(getTextField().getFormatter())).getFormat();
       
}


       
/**
         * Return our spinner ancestor's <code>SpinnerDateModel</code>.
         *
         * @return <code>getSpinner().getModel()</code>
         * @see #getSpinner
         * @see #getTextField
         */

       
public SpinnerDateModel getModel() {
           
return (SpinnerDateModel)(getSpinner().getModel());
       
}
   
}


   
/**
     * This subclass of javax.swing.NumberFormatter maps the minimum/maximum
     * properties to a SpinnerNumberModel and initializes the valueClass
     * of the NumberFormatter to match the type of the initial models value.
     */

   
private static class NumberEditorFormatter extends NumberFormatter {
       
private final SpinnerNumberModel model;

       
NumberEditorFormatter(SpinnerNumberModel model, NumberFormat format) {
           
super(format);
           
this.model = model;
            setValueClass
(model.getValue().getClass());
       
}

       
public void setMinimum(Comparable min) {
            model
.setMinimum(min);
       
}

       
public Comparable getMinimum() {
           
return  model.getMinimum();
       
}

       
public void setMaximum(Comparable max) {
            model
.setMaximum(max);
       
}

       
public Comparable getMaximum() {
           
return model.getMaximum();
       
}
   
}



   
/**
     * An editor for a <code>JSpinner</code> whose model is a
     * <code>SpinnerNumberModel</code>.  The value of the editor is
     * displayed with a <code>JFormattedTextField</code> whose format
     * is defined by a <code>NumberFormatter</code> instance whose
     * <code>minimum</code> and <code>maximum</code> properties
     * are mapped to the <code>SpinnerNumberModel</code>.
     * @since 1.4
     */

   
// PENDING(hmuller): more example javadoc
   
public static class NumberEditor extends DefaultEditor
   
{
       
// This is here until DecimalFormat gets a constructor that
       
// takes a Locale: 4923525
       
private static String getDefaultPattern(Locale locale) {
           
// Get the pattern for the default locale.
           
ResourceBundle rb = LocaleData.getNumberFormatData(locale);
           
String[] all = rb.getStringArray("NumberPatterns");
           
return all[0];
       
}

       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerNumberModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinner and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerNumberModel</code>
         *
         * @see #getModel
         * @see #getFormat
         * @see SpinnerNumberModel
         */

       
public NumberEditor(JSpinner spinner) {
           
this(spinner, getDefaultPattern(spinner.getLocale()));
       
}

       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerNumberModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinner and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @param decimalFormatPattern the initial pattern for the
         *     <code>DecimalFormat</code> object that's used to display
         *     and parse the value of the text field.
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerNumberModel</code> or if
         *     <code>decimalFormatPattern</code> is not a legal
         *     argument to <code>DecimalFormat</code>
         *
         * @see #getTextField
         * @see SpinnerNumberModel
         * @see java.text.DecimalFormat
         */

       
public NumberEditor(JSpinner spinner, String decimalFormatPattern) {
           
this(spinner, new DecimalFormat(decimalFormatPattern));
       
}


       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerNumberModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinner and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @param decimalFormatPattern the initial pattern for the
         *     <code>DecimalFormat</code> object that's used to display
         *     and parse the value of the text field.
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerNumberModel</code>
         *
         * @see #getTextField
         * @see SpinnerNumberModel
         * @see java.text.DecimalFormat
         */

       
private NumberEditor(JSpinner spinner, DecimalFormat format) {
           
super(spinner);
           
if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
               
throw new IllegalArgumentException(
                         
"model not a SpinnerNumberModel");
           
}

           
SpinnerNumberModel model = (SpinnerNumberModel)spinner.getModel();
           
NumberFormatter formatter = new NumberEditorFormatter(model,
                                                                  format
);
           
DefaultFormatterFactory factory = new DefaultFormatterFactory(
                                                  formatter
);
           
JFormattedTextField ftf = getTextField();
            ftf
.setEditable(true);
            ftf
.setFormatterFactory(factory);
            ftf
.setHorizontalAlignment(JTextField.RIGHT);

           
/* TBD - initializing the column width of the text field
             * is imprecise and doing it here is tricky because
             * the developer may configure the formatter later.
             */

           
try {
               
String maxString = formatter.valueToString(model.getMinimum());
               
String minString = formatter.valueToString(model.getMaximum());
                ftf
.setColumns(Math.max(maxString.length(),
                                        minString
.length()));
           
}
           
catch (ParseException e) {
               
// TBD should throw a chained error here
           
}

       
}


       
/**
         * Returns the <code>java.text.DecimalFormat</code> object the
         * <code>JFormattedTextField</code> uses to parse and format
         * numbers.
         *
         * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
         * @see #getTextField
         * @see java.text.DecimalFormat
         */

       
public DecimalFormat getFormat() {
           
return (DecimalFormat)((NumberFormatter)(getTextField().getFormatter())).getFormat();
       
}


       
/**
         * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
         *
         * @return <code>getSpinner().getModel()</code>
         * @see #getSpinner
         * @see #getTextField
         */

       
public SpinnerNumberModel getModel() {
           
return (SpinnerNumberModel)(getSpinner().getModel());
       
}
   
}


   
/**
     * An editor for a <code>JSpinner</code> whose model is a
     * <code>SpinnerListModel</code>.
     * @since 1.4
     */

   
public static class ListEditor extends DefaultEditor
   
{
       
/**
         * Construct a <code>JSpinner</code> editor that supports displaying
         * and editing the value of a <code>SpinnerListModel</code>
         * with a <code>JFormattedTextField</code>.  <code>This</code>
         * <code>ListEditor</code> becomes both a <code>ChangeListener</code>
         * on the spinner and a <code>PropertyChangeListener</code>
         * on the new <code>JFormattedTextField</code>.
         *
         * @param spinner the spinner whose model <code>this</code> editor will monitor
         * @exception IllegalArgumentException if the spinners model is not
         *     an instance of <code>SpinnerListModel</code>
         *
         * @see #getModel
         * @see SpinnerListModel
         */

       
public ListEditor(JSpinner spinner) {
           
super(spinner);
           
if (!(spinner.getModel() instanceof SpinnerListModel)) {
               
throw new IllegalArgumentException("model not a SpinnerListModel");
           
}
            getTextField
().setEditable(true);
            getTextField
().setFormatterFactory(new
                             
DefaultFormatterFactory(new ListFormatter()));
       
}

       
/**
         * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
         *
         * @return <code>getSpinner().getModel()</code>
         * @see #getSpinner
         * @see #getTextField
         */

       
public SpinnerListModel getModel() {
           
return (SpinnerListModel)(getSpinner().getModel());
       
}


       
/**
         * ListFormatter provides completion while text is being input
         * into the JFormattedTextField. Completion is only done if the
         * user is inserting text at the end of the document. Completion
         * is done by way of the SpinnerListModel method findNextMatch.
         */

       
private class ListFormatter extends
                         
JFormattedTextField.AbstractFormatter {
           
private DocumentFilter filter;

           
public String valueToString(Object value) throws ParseException {
               
if (value == null) {
                   
return "";
               
}
               
return value.toString();
           
}

           
public Object stringToValue(String string) throws ParseException {
               
return string;
           
}

           
protected DocumentFilter getDocumentFilter() {
               
if (filter == null) {
                    filter
= new Filter();
               
}
               
return filter;
           
}


           
private class Filter extends DocumentFilter {
               
public void replace(FilterBypass fb, int offset, int length,
                                   
String string, AttributeSet attrs) throws
                                           
BadLocationException {
                   
if (string != null && (offset + length) ==
                                          fb
.getDocument().getLength()) {
                       
Object next = getModel().findNextMatch(
                                         fb
.getDocument().getText(0, offset) +
                                         
string);
                       
String value = (next != null) ? next.toString() : null;

                       
if (value != null) {
                            fb
.remove(0, offset + length);
                            fb
.insertString(0, value, null);
                            getFormattedTextField
().select(offset +
                                                           
string.length(),
                                                           value
.length());
                           
return;
                       
}
                   
}
                   
super.replace(fb, offset, length, string, attrs);
               
}

               
public void insertString(FilterBypass fb, int offset,
                                     
String string, AttributeSet attr)
                       
throws BadLocationException {
                    replace
(fb, offset, 0, string, attr);
               
}
           
}
       
}
   
}


   
/**
     * An Action implementation that is always disabled.
     */

   
private static class DisabledAction implements Action {
       
public Object getValue(String key) {
           
return null;
       
}
       
public void putValue(String key, Object value) {
       
}
       
public void setEnabled(boolean b) {
       
}
       
public boolean isEnabled() {
           
return false;
       
}
       
public void addPropertyChangeListener(PropertyChangeListener l) {
       
}
       
public void removePropertyChangeListener(PropertyChangeListener l) {
       
}
       
public void actionPerformed(ActionEvent ae) {
       
}
   
}

   
/////////////////
   
// Accessibility support
   
////////////////

   
/**
     * Gets the <code>AccessibleContext</code> for the <code>JSpinner</code>
     *
     * @return the <code>AccessibleContext</code> for the <code>JSpinner</code>
     * @since 1.5
     */

   
public AccessibleContext getAccessibleContext() {
       
if (accessibleContext == null) {
            accessibleContext
= new AccessibleJSpinner();
       
}
       
return accessibleContext;
   
}

   
/**
     * <code>AccessibleJSpinner</code> implements accessibility
     * support for the <code>JSpinner</code> class.
     * @since 1.5
     */

   
protected class AccessibleJSpinner extends AccessibleJComponent
       
implements AccessibleValue, AccessibleAction, AccessibleText,
                   
AccessibleEditableText, ChangeListener {

       
private Object oldModelValue = null;

       
/**
         * AccessibleJSpinner constructor
         */

       
protected AccessibleJSpinner() {
           
// model is guaranteed to be non-null
            oldModelValue
= model.getValue();
           
JSpinner.this.addChangeListener(this);
       
}

       
/**
         * Invoked when the target of the listener has changed its state.
         *
         * @param e  a <code>ChangeEvent</code> object. Must not be null.
         * @throws NullPointerException if the parameter is null.
         */

       
public void stateChanged(ChangeEvent e) {
           
if (e == null) {
               
throw new NullPointerException();
           
}
           
Object newModelValue = model.getValue();
            firePropertyChange
(ACCESSIBLE_VALUE_PROPERTY,
                               oldModelValue
,
                               newModelValue
);
            firePropertyChange
(ACCESSIBLE_TEXT_PROPERTY,
                               
null,
                               
0); // entire text may have changed

            oldModelValue
= newModelValue;
       
}

       
/* ===== Begin AccessibleContext methods ===== */

       
/**
         * Gets the role of this object.  The role of the object is the generic
         * purpose or use of the class of this object.  For example, the role
         * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
         * AccessibleRole are provided so component developers can pick from
         * a set of predefined roles.  This enables assistive technologies to
         * provide a consistent interface to various tweaked subclasses of
         * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
         * that act like a push button) as well as distinguish between sublasses
         * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
         * and AccessibleRole.RADIO_BUTTON for radio buttons).
         * <p>Note that the AccessibleRole class is also extensible, so
         * custom component developers can define their own AccessibleRole's
         * if the set of predefined roles is inadequate.
         *
         * @return an instance of AccessibleRole describing the role of the object
         * @see AccessibleRole
         */

       
public AccessibleRole getAccessibleRole() {
           
return AccessibleRole.SPIN_BOX;
       
}

       
/**
         * Returns the number of accessible children of the object.
         *
         * @return the number of accessible children of the object.
         */

       
public int getAccessibleChildrenCount() {
           
// the JSpinner has one child, the editor
           
if (editor.getAccessibleContext() != null) {
               
return 1;
           
}
           
return 0;
       
}

       
/**
         * Returns the specified Accessible child of the object.  The Accessible
         * children of an Accessible object are zero-based, so the first child
         * of an Accessible child is at index 0, the second child is at index 1,
         * and so on.
         *
         * @param i zero-based index of child
         * @return the Accessible child of the object
         * @see #getAccessibleChildrenCount
         */

       
public Accessible getAccessibleChild(int i) {
           
// the JSpinner has one child, the editor
           
if (i != 0) {
               
return null;
           
}
           
if (editor.getAccessibleContext() != null) {
               
return (Accessible)editor;
           
}
           
return null;
       
}

       
/* ===== End AccessibleContext methods ===== */

       
/**
         * Gets the AccessibleAction associated with this object that supports
         * one or more actions.
         *
         * @return AccessibleAction if supported by object; else return null
         * @see AccessibleAction
         */

       
public AccessibleAction getAccessibleAction() {
           
return this;
       
}

       
/**
         * Gets the AccessibleText associated with this object presenting
         * text on the display.
         *
         * @return AccessibleText if supported by object; else return null
         * @see AccessibleText
         */

       
public AccessibleText getAccessibleText() {
           
return this;
       
}

       
/*
         * Returns the AccessibleContext for the JSpinner editor
         */

       
private AccessibleContext getEditorAccessibleContext() {
           
if (editor instanceof DefaultEditor) {
               
JTextField textField = ((DefaultEditor)editor).getTextField();
               
if (textField != null) {
                   
return textField.getAccessibleContext();
               
}
           
} else if (editor instanceof Accessible) {
               
return ((Accessible)editor).getAccessibleContext();
           
}
           
return null;
       
}

       
/*
         * Returns the AccessibleText for the JSpinner editor
         */

       
private AccessibleText getEditorAccessibleText() {
           
AccessibleContext ac = getEditorAccessibleContext();
           
if (ac != null) {
               
return ac.getAccessibleText();
           
}
           
return null;
       
}

       
/*
         * Returns the AccessibleEditableText for the JSpinner editor
         */

       
private AccessibleEditableText getEditorAccessibleEditableText() {
           
AccessibleText at = getEditorAccessibleText();
           
if (at instanceof AccessibleEditableText) {
               
return (AccessibleEditableText)at;
           
}
           
return null;
       
}

       
/**
         * Gets the AccessibleValue associated with this object.
         *
         * @return AccessibleValue if supported by object; else return null
         * @see AccessibleValue
         *
         */

       
public AccessibleValue getAccessibleValue() {
           
return this;
       
}

       
/* ===== Begin AccessibleValue impl ===== */

       
/**
         * Get the value of this object as a Number.  If the value has not been
         * set, the return value will be null.
         *
         * @return value of the object
         * @see #setCurrentAccessibleValue
         */

       
public Number getCurrentAccessibleValue() {
           
Object o = model.getValue();
           
if (o instanceof Number) {
               
return (Number)o;
           
}
           
return null;
       
}

       
/**
         * Set the value of this object as a Number.
         *
         * @param n the value to set for this object
         * @return true if the value was set; else False
         * @see #getCurrentAccessibleValue
         */

       
public boolean setCurrentAccessibleValue(Number n) {
           
// try to set the new value
           
try {
                model
.setValue(n);
               
return true;
           
} catch (IllegalArgumentException iae) {
               
// SpinnerModel didn't like new value
           
}
           
return false;
       
}

       
/**
         * Get the minimum value of this object as a Number.
         *
         * @return Minimum value of the object; null if this object does not
         * have a minimum value
         * @see #getMaximumAccessibleValue
         */

       
public Number getMinimumAccessibleValue() {
           
if (model instanceof SpinnerNumberModel) {
               
SpinnerNumberModel numberModel = (SpinnerNumberModel)model;
               
Object o = numberModel.getMinimum();
               
if (o instanceof Number) {
                   
return (Number)o;
               
}
           
}
           
return null;
       
}

       
/**
         * Get the maximum value of this object as a Number.
         *
         * @return Maximum value of the object; null if this object does not
         * have a maximum value
         * @see #getMinimumAccessibleValue
         */

       
public Number getMaximumAccessibleValue() {
           
if (model instanceof SpinnerNumberModel) {
               
SpinnerNumberModel numberModel = (SpinnerNumberModel)model;
               
Object o = numberModel.getMaximum();
               
if (o instanceof Number) {
                   
return (Number)o;
               
}
           
}
           
return null;
       
}

       
/* ===== End AccessibleValue impl ===== */

       
/* ===== Begin AccessibleAction impl ===== */

       
/**
         * Returns the number of accessible actions available in this object
         * If there are more than one, the first one is considered the "default"
         * action of the object.
         *
         * Two actions are supported: AccessibleAction.INCREMENT which
         * increments the spinner value and AccessibleAction.DECREMENT
         * which decrements the spinner value
         *
         * @return the zero-based number of Actions in this object
         */

       
public int getAccessibleActionCount() {
           
return 2;
       
}

       
/**
         * Returns a description of the specified action of the object.
         *
         * @param i zero-based index of the actions
         * @return a String description of the action
         * @see #getAccessibleActionCount
         */

       
public String getAccessibleActionDescription(int i) {
           
if (i == 0) {
               
return AccessibleAction.INCREMENT;
           
} else if (i == 1) {
               
return AccessibleAction.DECREMENT;
           
}
           
return null;
       
}

       
/**
         * Performs the specified Action on the object
         *
         * @param i zero-based index of actions. The first action
         * (index 0) is AccessibleAction.INCREMENT and the second
         * action (index 1) is AccessibleAction.DECREMENT.
         * @return true if the action was performed; otherwise false.
         * @see #getAccessibleActionCount
         */

       
public boolean doAccessibleAction(int i) {
           
if (i < 0 || i > 1) {
               
return false;
           
}
           
Object o = null;
           
if (i == 0) {
                o
= getNextValue(); // AccessibleAction.INCREMENT
           
} else {
                o
= getPreviousValue(); // AccessibleAction.DECREMENT
           
}
           
// try to set the new value
           
try {
                model
.setValue(o);
               
return true;
           
} catch (IllegalArgumentException iae) {
               
// SpinnerModel didn't like new value
           
}
           
return false;
       
}

       
/* ===== End AccessibleAction impl ===== */

       
/* ===== Begin AccessibleText impl ===== */

       
/*
         * Returns whether source and destination components have the
         * same window ancestor
         */

       
private boolean sameWindowAncestor(Component src, Component dest) {
           
if (src == null || dest == null) {
               
return false;
           
}
           
return SwingUtilities.getWindowAncestor(src) ==
               
SwingUtilities.getWindowAncestor(dest);
       
}

       
/**
         * Given a point in local coordinates, return the zero-based index
         * of the character under that Point.  If the point is invalid,
         * this method returns -1.
         *
         * @param p the Point in local coordinates
         * @return the zero-based index of the character under Point p; if
         * Point is invalid return -1.
         */

       
public int getIndexAtPoint(Point p) {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null && sameWindowAncestor(JSpinner.this, editor)) {
               
// convert point from the JSpinner bounds (source) to
               
// editor bounds (destination)
               
Point editorPoint = SwingUtilities.convertPoint(JSpinner.this,
                                                                p
,
                                                                editor
);
               
if (editorPoint != null) {
                   
return at.getIndexAtPoint(editorPoint);
               
}
           
}
           
return -1;
       
}

       
/**
         * Determines the bounding box of the character at the given
         * index into the string.  The bounds are returned in local
         * coordinates.  If the index is invalid an empty rectangle is
         * returned.
         *
         * @param i the index into the String
         * @return the screen coordinates of the character's bounding box,
         * if index is invalid return an empty rectangle.
         */

       
public Rectangle getCharacterBounds(int i) {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null ) {
               
Rectangle editorRect = at.getCharacterBounds(i);
               
if (editorRect != null &&
                    sameWindowAncestor
(JSpinner.this, editor)) {
                   
// return rectangle in the the JSpinner bounds
                   
return SwingUtilities.convertRectangle(editor,
                                                           editorRect
,
                                                           
JSpinner.this);
               
}
           
}
           
return null;
       
}

       
/**
         * Returns the number of characters (valid indicies)
         *
         * @return the number of characters
         */

       
public int getCharCount() {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getCharCount();
           
}
           
return -1;
       
}

       
/**
         * Returns the zero-based offset of the caret.
         *
         * Note: That to the right of the caret will have the same index
         * value as the offset (the caret is between two characters).
         * @return the zero-based offset of the caret.
         */

       
public int getCaretPosition() {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getCaretPosition();
           
}
           
return -1;
       
}

       
/**
         * Returns the String at a given index.
         *
         * @param part the CHARACTER, WORD, or SENTENCE to retrieve
         * @param index an index within the text
         * @return the letter, word, or sentence
         */

       
public String getAtIndex(int part, int index) {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getAtIndex(part, index);
           
}
           
return null;
       
}

       
/**
         * Returns the String after a given index.
         *
         * @param part the CHARACTER, WORD, or SENTENCE to retrieve
         * @param index an index within the text
         * @return the letter, word, or sentence
         */

       
public String getAfterIndex(int part, int index) {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getAfterIndex(part, index);
           
}
           
return null;
       
}

       
/**
         * Returns the String before a given index.
         *
         * @param part the CHARACTER, WORD, or SENTENCE to retrieve
         * @param index an index within the text
         * @return the letter, word, or sentence
         */

       
public String getBeforeIndex(int part, int index) {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getBeforeIndex(part, index);
           
}
           
return null;
       
}

       
/**
         * Returns the AttributeSet for a given character at a given index
         *
         * @param i the zero-based index into the text
         * @return the AttributeSet of the character
         */

       
public AttributeSet getCharacterAttribute(int i) {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getCharacterAttribute(i);
           
}
           
return null;
       
}

       
/**
         * Returns the start offset within the selected text.
         * If there is no selection, but there is
         * a caret, the start and end offsets will be the same.
         *
         * @return the index into the text of the start of the selection
         */

       
public int getSelectionStart() {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getSelectionStart();
           
}
           
return -1;
       
}

       
/**
         * Returns the end offset within the selected text.
         * If there is no selection, but there is
         * a caret, the start and end offsets will be the same.
         *
         * @return the index into teh text of the end of the selection
         */

       
public int getSelectionEnd() {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getSelectionEnd();
           
}
           
return -1;
       
}

       
/**
         * Returns the portion of the text that is selected.
         *
         * @return the String portion of the text that is selected
         */

       
public String getSelectedText() {
           
AccessibleText at = getEditorAccessibleText();
           
if (at != null) {
               
return at.getSelectedText();
           
}
           
return null;
       
}

       
/* ===== End AccessibleText impl ===== */


       
/* ===== Begin AccessibleEditableText impl ===== */

       
/**
         * Sets the text contents to the specified string.
         *
         * @param s the string to set the text contents
         */

       
public void setTextContents(String s) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.setTextContents(s);
           
}
       
}

       
/**
         * Inserts the specified string at the given index/
         *
         * @param index the index in the text where the string will
         * be inserted
         * @param s the string to insert in the text
         */

       
public void insertTextAtIndex(int index, String s) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.insertTextAtIndex(index, s);
           
}
       
}

       
/**
         * Returns the text string between two indices.
         *
         * @param startIndex the starting index in the text
         * @param endIndex the ending index in the text
         * @return the text string between the indices
         */

       
public String getTextRange(int startIndex, int endIndex) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
               
return at.getTextRange(startIndex, endIndex);
           
}
           
return null;
       
}

       
/**
         * Deletes the text between two indices
         *
         * @param startIndex the starting index in the text
         * @param endIndex the ending index in the text
         */

       
public void delete(int startIndex, int endIndex) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.delete(startIndex, endIndex);
           
}
       
}

       
/**
         * Cuts the text between two indices into the system clipboard.
         *
         * @param startIndex the starting index in the text
         * @param endIndex the ending index in the text
         */

       
public void cut(int startIndex, int endIndex) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.cut(startIndex, endIndex);
           
}
       
}

       
/**
         * Pastes the text from the system clipboard into the text
         * starting at the specified index.
         *
         * @param startIndex the starting index in the text
         */

       
public void paste(int startIndex) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.paste(startIndex);
           
}
       
}

       
/**
         * Replaces the text between two indices with the specified
         * string.
         *
         * @param startIndex the starting index in the text
         * @param endIndex the ending index in the text
         * @param s the string to replace the text between two indices
         */

       
public void replaceText(int startIndex, int endIndex, String s) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.replaceText(startIndex, endIndex, s);
           
}
       
}

       
/**
         * Selects the text between two indices.
         *
         * @param startIndex the starting index in the text
         * @param endIndex the ending index in the text
         */

       
public void selectText(int startIndex, int endIndex) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.selectText(startIndex, endIndex);
           
}
       
}

       
/**
         * Sets attributes for the text between two indices.
         *
         * @param startIndex the starting index in the text
         * @param endIndex the ending index in the text
         * @param as the attribute set
         * @see AttributeSet
         */

       
public void setAttributes(int startIndex, int endIndex, AttributeSet as) {
           
AccessibleEditableText at = getEditorAccessibleEditableText();
           
if (at != null) {
                at
.setAttributes(startIndex, endIndex, as);
           
}
       
}
   
}  /* End AccessibleJSpinner */
}