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