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