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