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