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