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