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