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