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