< prev index next >

src/java.desktop/share/classes/javax/swing/JSpinner.java

Print this page




  36 import java.beans.*;
  37 import java.text.*;
  38 import java.io.*;
  39 import java.text.spi.DateFormatProvider;
  40 import java.text.spi.NumberFormatProvider;
  41 
  42 import javax.accessibility.*;
  43 import sun.util.locale.provider.LocaleProviderAdapter;
  44 import sun.util.locale.provider.LocaleResources;
  45 
  46 /**
  47  * A single line input field that lets the user select a
  48  * number or an object value from an ordered sequence. Spinners typically
  49  * provide a pair of tiny arrow buttons for stepping through the elements
  50  * of the sequence. The keyboard up/down arrow keys also cycle through the
  51  * elements. The user may also be allowed to type a (legal) value directly
  52  * into the spinner. Although combo boxes provide similar functionality,
  53  * spinners are sometimes preferred because they don't require a drop down list
  54  * that can obscure important data.
  55  * <p>
  56  * A <code>JSpinner</code>'s sequence value is defined by its
  57  * <code>SpinnerModel</code>.
  58  * The <code>model</code> can be specified as a constructor argument and
  59  * changed with the <code>model</code> property.  <code>SpinnerModel</code>
  60  * classes for some common types are provided: <code>SpinnerListModel</code>,
  61  * <code>SpinnerNumberModel</code>, and <code>SpinnerDateModel</code>.
  62  * <p>
  63  * A <code>JSpinner</code> has a single child component that's
  64  * responsible for displaying
  65  * and potentially changing the current element or <i>value</i> of
  66  * the model, which is called the <code>editor</code>.  The editor is created
  67  * by the <code>JSpinner</code>'s constructor and can be changed with the
  68  * <code>editor</code> property.  The <code>JSpinner</code>'s editor stays
  69  * in sync with the model by listening for <code>ChangeEvent</code>s. If the
  70  * user has changed the value displayed by the <code>editor</code> it is
  71  * possible for the <code>model</code>'s value to differ from that of
  72  * the <code>editor</code>. To make sure the <code>model</code> has the same
  73  * value as the editor use the <code>commitEdit</code> method, eg:
  74  * <pre>
  75  *   try {
  76  *       spinner.commitEdit();
  77  *   }
  78  *   catch (ParseException pe) {
  79  *       // Edited value is invalid, spinner.getValue() will return
  80  *       // the last valid value, you could revert the spinner to show that:
  81  *       JComponent editor = spinner.getEditor();
  82  *       if (editor instanceof DefaultEditor) {
  83  *           ((DefaultEditor)editor).getTextField().setValue(spinner.getValue());
  84  *       }
  85  *       // reset the value to some known value:
  86  *       spinner.setValue(fallbackValue);
  87  *       // or treat the last valid value as the current, in which
  88  *       // case you don't need to do anything.
  89  *   }
  90  *   return spinner.getValue();
  91  * </pre>
  92  * <p>
  93  * For information and examples of using spinner see
  94  * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/spinner.html">How to Use Spinners</a>,
  95  * a section in <em>The Java Tutorial.</em>
  96  * <p>
  97  * <strong>Warning:</strong> Swing is not thread safe. For more
  98  * information see <a
  99  * href="package-summary.html#threading">Swing's Threading
 100  * Policy</a>.
 101  * <p>
 102  * <strong>Warning:</strong>
 103  * Serialized objects of this class will not be compatible with
 104  * future Swing releases. The current serialization support is
 105  * appropriate for short term storage or RMI between applications running
 106  * the same version of Swing.  As of 1.4, support for long term storage
 107  * of all JavaBeans&trade;
 108  * has been added to the <code>java.beans</code> package.
 109  * Please see {@link java.beans.XMLEncoder}.
 110  *
 111  * @beaninfo
 112  *   attribute: isContainer false
 113  * description: A single line input field that lets the user select a
 114  *     number or an object value from an ordered set.
 115  *
 116  * @see SpinnerModel
 117  * @see AbstractSpinnerModel
 118  * @see SpinnerListModel
 119  * @see SpinnerNumberModel
 120  * @see SpinnerDateModel
 121  * @see JFormattedTextField
 122  *
 123  * @author Hans Muller
 124  * @author Lynn Monsanto (accessibility)
 125  * @since 1.4
 126  */
 127 @SuppressWarnings("serial") // Same-version serialization only
 128 public class JSpinner extends JComponent implements Accessible


 145     /**
 146      * Constructs a spinner for the given model. The spinner has
 147      * a set of previous/next buttons, and an editor appropriate
 148      * for the model.
 149      *
 150      * @param model  a model for the new spinner
 151      * @throws NullPointerException if the model is {@code null}
 152      */
 153     public JSpinner(SpinnerModel model) {
 154         if (model == null) {
 155             throw new NullPointerException("model cannot be null");
 156         }
 157         this.model = model;
 158         this.editor = createEditor(model);
 159         setUIProperty("opaque",true);
 160         updateUI();
 161     }
 162 
 163 
 164     /**
 165      * Constructs a spinner with an <code>Integer SpinnerNumberModel</code>
 166      * with initial value 0 and no minimum or maximum limits.
 167      */
 168     public JSpinner() {
 169         this(new SpinnerNumberModel());
 170     }
 171 
 172 
 173     /**
 174      * Returns the look and feel (L&amp;F) object that renders this component.
 175      *
 176      * @return the <code>SpinnerUI</code> object that renders this component
 177      */
 178     public SpinnerUI getUI() {
 179         return (SpinnerUI)ui;
 180     }
 181 
 182 
 183     /**
 184      * Sets the look and feel (L&amp;F) object that renders this component.
 185      *
 186      * @param ui  the <code>SpinnerUI</code> L&amp;F object
 187      * @see UIDefaults#getUI
 188      */
 189     public void setUI(SpinnerUI ui) {
 190         super.setUI(ui);
 191     }
 192 
 193 
 194     /**
 195      * Returns the suffix used to construct the name of the look and feel
 196      * (L&amp;F) class used to render this component.
 197      *
 198      * @return the string "SpinnerUI"
 199      * @see JComponent#getUIClassID
 200      * @see UIDefaults#getUI
 201      */
 202     public String getUIClassID() {
 203         return uiClassID;
 204     }
 205 
 206 
 207 
 208     /**
 209      * Resets the UI property with the value from the current look and feel.
 210      *
 211      * @see UIManager#getUI
 212      */
 213     public void updateUI() {
 214         setUI((SpinnerUI)UIManager.getUI(this));
 215         invalidate();
 216     }
 217 
 218 
 219     /**
 220      * This method is called by the constructors to create the
 221      * <code>JComponent</code>
 222      * that displays the current value of the sequence.  The editor may
 223      * also allow the user to enter an element of the sequence directly.
 224      * An editor must listen for <code>ChangeEvents</code> on the
 225      * <code>model</code> and keep the value it displays
 226      * in sync with the value of the model.
 227      * <p>
 228      * Subclasses may override this method to add support for new
 229      * <code>SpinnerModel</code> classes.  Alternatively one can just
 230      * replace the editor created here with the <code>setEditor</code>
 231      * method.  The default mapping from model type to editor is:
 232      * <ul>
 233      * <li> <code>SpinnerNumberModel =&gt; JSpinner.NumberEditor</code>
 234      * <li> <code>SpinnerDateModel =&gt; JSpinner.DateEditor</code>
 235      * <li> <code>SpinnerListModel =&gt; JSpinner.ListEditor</code>
 236      * <li> <i>all others</i> =&gt; <code>JSpinner.DefaultEditor</code>
 237      * </ul>
 238      *
 239      * @return a component that displays the current value of the sequence
 240      * @param model the value of getModel
 241      * @see #getModel
 242      * @see #setEditor
 243      */
 244     protected JComponent createEditor(SpinnerModel model) {
 245         if (model instanceof SpinnerDateModel) {
 246             return new DateEditor(this);
 247         }
 248         else if (model instanceof SpinnerListModel) {
 249             return new ListEditor(this);
 250         }
 251         else if (model instanceof SpinnerNumberModel) {
 252             return new NumberEditor(this);
 253         }
 254         else {
 255             return new DefaultEditor(this);
 256         }
 257     }
 258 
 259 
 260     /**
 261      * Changes the model that represents the value of this spinner.
 262      * If the editor property has not been explicitly set,
 263      * the editor property is (implicitly) set after the <code>"model"</code>
 264      * <code>PropertyChangeEvent</code> has been fired.  The editor
 265      * property is set to the value returned by <code>createEditor</code>,
 266      * as in:
 267      * <pre>
 268      * setEditor(createEditor(model));
 269      * </pre>
 270      *
 271      * @param model the new <code>SpinnerModel</code>
 272      * @see #getModel
 273      * @see #getEditor
 274      * @see #setEditor
 275      * @throws IllegalArgumentException if model is <code>null</code>
 276      *
 277      * @beaninfo
 278      *        bound: true
 279      *    attribute: visualUpdate true
 280      *  description: Model that represents the value of this spinner.
 281      */
 282     public void setModel(SpinnerModel model) {
 283         if (model == null) {
 284             throw new IllegalArgumentException("null model");
 285         }
 286         if (!model.equals(this.model)) {
 287             SpinnerModel oldModel = this.model;
 288             this.model = model;
 289             if (modelListener != null) {
 290                 oldModel.removeChangeListener(modelListener);
 291                 this.model.addChangeListener(modelListener);
 292             }
 293             firePropertyChange("model", oldModel, model);
 294             if (!editorExplicitlySet) {
 295                 setEditor(createEditor(model)); // sets editorExplicitlySet true
 296                 editorExplicitlySet = false;
 297             }
 298             repaint();
 299             revalidate();
 300         }
 301     }
 302 
 303 
 304     /**
 305      * Returns the <code>SpinnerModel</code> that defines
 306      * this spinners sequence of values.
 307      *
 308      * @return the value of the model property
 309      * @see #setModel
 310      */
 311     public SpinnerModel getModel() {
 312         return model;
 313     }
 314 
 315 
 316     /**
 317      * Returns the current value of the model, typically
 318      * this value is displayed by the <code>editor</code>. If the
 319      * user has changed the value displayed by the <code>editor</code> it is
 320      * possible for the <code>model</code>'s value to differ from that of
 321      * the <code>editor</code>, refer to the class level javadoc for examples
 322      * of how to deal with this.
 323      * <p>
 324      * This method simply delegates to the <code>model</code>.
 325      * It is equivalent to:
 326      * <pre>
 327      * getModel().getValue()
 328      * </pre>
 329      *
 330      * @return the current value of the model
 331      * @see #setValue
 332      * @see SpinnerModel#getValue
 333      */
 334     public Object getValue() {
 335         return getModel().getValue();
 336     }
 337 
 338 
 339     /**
 340      * Changes current value of the model, typically
 341      * this value is displayed by the <code>editor</code>.
 342      * If the <code>SpinnerModel</code> implementation
 343      * doesn't support the specified value then an
 344      * <code>IllegalArgumentException</code> is thrown.
 345      * <p>
 346      * This method simply delegates to the <code>model</code>.
 347      * It is equivalent to:
 348      * <pre>
 349      * getModel().setValue(value)
 350      * </pre>
 351      *
 352      * @param value  new value for the spinner
 353      * @throws IllegalArgumentException if <code>value</code> isn't allowed
 354      * @see #getValue
 355      * @see SpinnerModel#setValue
 356      */
 357     public void setValue(Object value) {
 358         getModel().setValue(value);
 359     }
 360 
 361 
 362     /**
 363      * Returns the object in the sequence that comes after the object returned
 364      * by <code>getValue()</code>. If the end of the sequence has been reached
 365      * then return <code>null</code>.
 366      * Calling this method does not effect <code>value</code>.
 367      * <p>
 368      * This method simply delegates to the <code>model</code>.
 369      * It is equivalent to:
 370      * <pre>
 371      * getModel().getNextValue()
 372      * </pre>
 373      *
 374      * @return the next legal value or <code>null</code> if one doesn't exist
 375      * @see #getValue
 376      * @see #getPreviousValue
 377      * @see SpinnerModel#getNextValue
 378      */
 379     public Object getNextValue() {
 380         return getModel().getNextValue();
 381     }
 382 
 383 
 384     /**
 385      * We pass <code>Change</code> events along to the listeners with the
 386      * the slider (instead of the model itself) as the event source.
 387      */
 388     private class ModelListener implements ChangeListener, Serializable {
 389         public void stateChanged(ChangeEvent e) {
 390             fireStateChanged();
 391         }
 392     }
 393 
 394 
 395     /**
 396      * Adds a listener to the list that is notified each time a change
 397      * to the model occurs.  The source of <code>ChangeEvents</code>
 398      * delivered to <code>ChangeListeners</code> will be this
 399      * <code>JSpinner</code>.  Note also that replacing the model
 400      * will not affect listeners added directly to JSpinner.
 401      * Applications can add listeners to  the model directly.  In that
 402      * case is that the source of the event would be the
 403      * <code>SpinnerModel</code>.
 404      *
 405      * @param listener the <code>ChangeListener</code> to add
 406      * @see #removeChangeListener
 407      * @see #getModel
 408      */
 409     public void addChangeListener(ChangeListener listener) {
 410         if (modelListener == null) {
 411             modelListener = new ModelListener();
 412             getModel().addChangeListener(modelListener);
 413         }
 414         listenerList.add(ChangeListener.class, listener);
 415     }
 416 
 417 
 418 
 419     /**
 420      * Removes a <code>ChangeListener</code> from this spinner.
 421      *
 422      * @param listener the <code>ChangeListener</code> to remove
 423      * @see #fireStateChanged
 424      * @see #addChangeListener
 425      */
 426     public void removeChangeListener(ChangeListener listener) {
 427         listenerList.remove(ChangeListener.class, listener);
 428     }
 429 
 430 
 431     /**
 432      * Returns an array of all the <code>ChangeListener</code>s added
 433      * to this JSpinner with addChangeListener().
 434      *
 435      * @return all of the <code>ChangeListener</code>s added or an empty
 436      *         array if no listeners have been added
 437      * @since 1.4
 438      */
 439     public ChangeListener[] getChangeListeners() {
 440         return listenerList.getListeners(ChangeListener.class);
 441     }
 442 
 443 
 444     /**
 445      * Sends a <code>ChangeEvent</code>, whose source is this
 446      * <code>JSpinner</code>, to each <code>ChangeListener</code>.
 447      * When a <code>ChangeListener</code> has been added
 448      * to the spinner, this method is called each time
 449      * a <code>ChangeEvent</code> is received from the model.
 450      *
 451      * @see #addChangeListener
 452      * @see #removeChangeListener
 453      * @see EventListenerList
 454      */
 455     protected void fireStateChanged() {
 456         Object[] listeners = listenerList.getListenerList();
 457         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 458             if (listeners[i] == ChangeListener.class) {
 459                 if (changeEvent == null) {
 460                     changeEvent = new ChangeEvent(this);
 461                 }
 462                 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
 463             }
 464         }
 465     }
 466 
 467 
 468     /**
 469      * Returns the object in the sequence that comes
 470      * before the object returned by <code>getValue()</code>.
 471      * If the end of the sequence has been reached then
 472      * return <code>null</code>. Calling this method does
 473      * not effect <code>value</code>.
 474      * <p>
 475      * This method simply delegates to the <code>model</code>.
 476      * It is equivalent to:
 477      * <pre>
 478      * getModel().getPreviousValue()
 479      * </pre>
 480      *
 481      * @return the previous legal value or <code>null</code>
 482      *   if one doesn't exist
 483      * @see #getValue
 484      * @see #getNextValue
 485      * @see SpinnerModel#getPreviousValue
 486      */
 487     public Object getPreviousValue() {
 488         return getModel().getPreviousValue();
 489     }
 490 
 491 
 492     /**
 493      * Changes the <code>JComponent</code> that displays the current value
 494      * of the <code>SpinnerModel</code>.  It is the responsibility of this
 495      * method to <i>disconnect</i> the old editor from the model and to
 496      * connect the new editor.  This may mean removing the
 497      * old editors <code>ChangeListener</code> from the model or the
 498      * spinner itself and adding one for the new editor.
 499      *
 500      * @param editor the new editor
 501      * @see #getEditor
 502      * @see #createEditor
 503      * @see #getModel
 504      * @throws IllegalArgumentException if editor is <code>null</code>
 505      *
 506      * @beaninfo
 507      *        bound: true
 508      *    attribute: visualUpdate true
 509      *  description: JComponent that displays the current value of the model
 510      */
 511     public void setEditor(JComponent editor) {
 512         if (editor == null) {
 513             throw new IllegalArgumentException("null editor");
 514         }
 515         if (!editor.equals(this.editor)) {
 516             JComponent oldEditor = this.editor;
 517             this.editor = editor;
 518             if (oldEditor instanceof DefaultEditor) {
 519                 ((DefaultEditor)oldEditor).dismiss(this);
 520             }
 521             editorExplicitlySet = true;
 522             firePropertyChange("editor", oldEditor, editor);
 523             revalidate();
 524             repaint();
 525         }
 526     }
 527 
 528 
 529     /**
 530      * Returns the component that displays and potentially
 531      * changes the model's value.
 532      *
 533      * @return the component that displays and potentially
 534      *    changes the model's value
 535      * @see #setEditor
 536      * @see #createEditor
 537      */
 538     public JComponent getEditor() {
 539         return editor;
 540     }
 541 
 542 
 543     /**
 544      * Commits the currently edited value to the <code>SpinnerModel</code>.
 545      * <p>
 546      * If the editor is an instance of <code>DefaultEditor</code>, the
 547      * call if forwarded to the editor, otherwise this does nothing.
 548      *
 549      * @throws ParseException if the currently edited value couldn't
 550      *         be committed.
 551      */
 552     public void commitEdit() throws ParseException {
 553         JComponent editor = getEditor();
 554         if (editor instanceof DefaultEditor) {
 555             ((DefaultEditor)editor).commitEdit();
 556         }
 557     }
 558 
 559 
 560     /*
 561      * See readObject and writeObject in JComponent for more
 562      * information about serialization in Swing.
 563      *
 564      * @param s Stream to write to
 565      */
 566     private void writeObject(ObjectOutputStream s) throws IOException {
 567         s.defaultWriteObject();
 568         if (getUIClassID().equals(uiClassID)) {
 569             byte count = JComponent.getWriteObjCounter(this);
 570             JComponent.setWriteObjCounter(this, --count);
 571             if (count == 0 && ui != null) {
 572                 ui.installUI(this);
 573             }
 574         }
 575     }
 576 
 577 
 578     /**
 579      * A simple base class for more specialized editors
 580      * that displays a read-only view of the model's current
 581      * value with a <code>JFormattedTextField</code>.  Subclasses
 582      * can configure the <code>JFormattedTextField</code> to create
 583      * an editor that's appropriate for the type of model they
 584      * support and they may want to override
 585      * the <code>stateChanged</code> and <code>propertyChanged</code>
 586      * methods, which keep the model and the text field in sync.
 587      * <p>
 588      * This class defines a <code>dismiss</code> method that removes the
 589      * editors <code>ChangeListener</code> from the <code>JSpinner</code>
 590      * that it's part of.   The <code>setEditor</code> method knows about
 591      * <code>DefaultEditor.dismiss</code>, so if the developer
 592      * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
 593      * its <code>ChangeListener</code> connection back to the
 594      * <code>JSpinner</code> will be removed.  However after that,
 595      * it's up to the developer to manage their editor listeners.
 596      * Similarly, if a subclass overrides <code>createEditor</code>,
 597      * it's up to the subclasser to deal with their editor
 598      * subsequently being replaced (with <code>setEditor</code>).
 599      * We expect that in most cases, and in editor installed
 600      * with <code>setEditor</code> or created by a <code>createEditor</code>
 601      * override, will not be replaced anyway.
 602      * <p>
 603      * This class is the <code>LayoutManager</code> for it's single
 604      * <code>JFormattedTextField</code> child.   By default the
 605      * child is just centered with the parents insets.
 606      * @since 1.4
 607      */
 608     public static class DefaultEditor extends JPanel
 609         implements ChangeListener, PropertyChangeListener, LayoutManager
 610     {
 611         /**
 612          * Constructs an editor component for the specified <code>JSpinner</code>.
 613          * This <code>DefaultEditor</code> is it's own layout manager and
 614          * it is added to the spinner's <code>ChangeListener</code> list.
 615          * The constructor creates a single <code>JFormattedTextField</code> child,
 616          * initializes it's value to be the spinner model's current value
 617          * and adds it to <code>this</code> <code>DefaultEditor</code>.
 618          *
 619          * @param spinner the spinner whose model <code>this</code> editor will monitor
 620          * @see #getTextField
 621          * @see JSpinner#addChangeListener
 622          */
 623         public DefaultEditor(JSpinner spinner) {
 624             super(null);
 625 
 626             JFormattedTextField ftf = new JFormattedTextField();
 627             ftf.setName("Spinner.formattedTextField");
 628             ftf.setValue(spinner.getValue());
 629             ftf.addPropertyChangeListener(this);
 630             ftf.setEditable(false);
 631             ftf.setInheritsPopupMenu(true);
 632 
 633             String toolTipText = spinner.getToolTipText();
 634             if (toolTipText != null) {
 635                 ftf.setToolTipText(toolTipText);
 636             }
 637 
 638             add(ftf);
 639 
 640             setLayout(this);
 641             spinner.addChangeListener(this);
 642 
 643             // We want the spinner's increment/decrement actions to be
 644             // active vs those of the JFormattedTextField. As such we
 645             // put disabled actions in the JFormattedTextField's actionmap.
 646             // A binding to a disabled action is treated as a nonexistant
 647             // binding.
 648             ActionMap ftfMap = ftf.getActionMap();
 649 
 650             if (ftfMap != null) {
 651                 ftfMap.put("increment", DISABLED_ACTION);
 652                 ftfMap.put("decrement", DISABLED_ACTION);
 653             }
 654         }
 655 
 656 
 657         /**
 658          * Disconnect <code>this</code> editor from the specified
 659          * <code>JSpinner</code>.  By default, this method removes
 660          * itself from the spinners <code>ChangeListener</code> list.
 661          *
 662          * @param spinner the <code>JSpinner</code> to disconnect this
 663          *    editor from; the same spinner as was passed to the constructor.
 664          */
 665         public void dismiss(JSpinner spinner) {
 666             spinner.removeChangeListener(this);
 667         }
 668 
 669 
 670         /**
 671          * Returns the <code>JSpinner</code> ancestor of this editor or
 672          * <code>null</code> if none of the ancestors are a
 673          * <code>JSpinner</code>.
 674          * Typically the editor's parent is a <code>JSpinner</code> however
 675          * subclasses of <code>JSpinner</code> may override the
 676          * the <code>createEditor</code> method and insert one or more containers
 677          * between the <code>JSpinner</code> and it's editor.
 678          *
 679          * @return <code>JSpinner</code> ancestor; <code>null</code>
 680          *         if none of the ancestors are a <code>JSpinner</code>
 681          *
 682          * @see JSpinner#createEditor
 683          */
 684         public JSpinner getSpinner() {
 685             for (Component c = this; c != null; c = c.getParent()) {
 686                 if (c instanceof JSpinner) {
 687                     return (JSpinner)c;
 688                 }
 689             }
 690             return null;
 691         }
 692 
 693 
 694         /**
 695          * Returns the <code>JFormattedTextField</code> child of this
 696          * editor.  By default the text field is the first and only
 697          * child of editor.
 698          *
 699          * @return the <code>JFormattedTextField</code> that gives the user
 700          *     access to the <code>SpinnerDateModel's</code> value.
 701          * @see #getSpinner
 702          * @see #getModel
 703          */
 704         public JFormattedTextField getTextField() {
 705             return (JFormattedTextField)getComponent(0);
 706         }
 707 
 708 
 709         /**
 710          * This method is called when the spinner's model's state changes.
 711          * It sets the <code>value</code> of the text field to the current
 712          * value of the spinners model.
 713          *
 714          * @param e the <code>ChangeEvent</code> whose source is the
 715          * <code>JSpinner</code> whose model has changed.
 716          * @see #getTextField
 717          * @see JSpinner#getValue
 718          */
 719         public void stateChanged(ChangeEvent e) {
 720             JSpinner spinner = (JSpinner)(e.getSource());
 721             getTextField().setValue(spinner.getValue());
 722         }
 723 
 724 
 725         /**
 726          * Called by the <code>JFormattedTextField</code>
 727          * <code>PropertyChangeListener</code>.  When the <code>"value"</code>
 728          * property changes, which implies that the user has typed a new
 729          * number, we set the value of the spinners model.
 730          * <p>
 731          * This class ignores <code>PropertyChangeEvents</code> whose
 732          * source is not the <code>JFormattedTextField</code>, so subclasses
 733          * may safely make <code>this</code> <code>DefaultEditor</code> a
 734          * <code>PropertyChangeListener</code> on other objects.
 735          *
 736          * @param e the <code>PropertyChangeEvent</code> whose source is
 737          *    the <code>JFormattedTextField</code> created by this class.
 738          * @see #getTextField
 739          */
 740         public void propertyChange(PropertyChangeEvent e)
 741         {
 742             JSpinner spinner = getSpinner();
 743 
 744             if (spinner == null) {
 745                 // Indicates we aren't installed anywhere.
 746                 return;
 747             }
 748 
 749             Object source = e.getSource();
 750             String name = e.getPropertyName();
 751             if ((source instanceof JFormattedTextField) && "value".equals(name)) {
 752                 Object lastValue = spinner.getValue();
 753 
 754                 // Try to set the new value
 755                 try {
 756                     spinner.setValue(getTextField().getValue());
 757                 } catch (IllegalArgumentException iae) {
 758                     // SpinnerModel didn't like new value, reset
 759                     try {
 760                         ((JFormattedTextField)source).setValue(lastValue);
 761                     } catch (IllegalArgumentException iae2) {
 762                         // Still bogus, nothing else we can do, the
 763                         // SpinnerModel and JFormattedTextField are now out
 764                         // of sync.
 765                     }
 766                 }
 767             }
 768         }
 769 
 770 
 771         /**
 772          * This <code>LayoutManager</code> method does nothing.  We're
 773          * only managing a single child and there's no support
 774          * for layout constraints.
 775          *
 776          * @param name ignored
 777          * @param child ignored
 778          */
 779         public void addLayoutComponent(String name, Component child) {
 780         }
 781 
 782 
 783         /**
 784          * This <code>LayoutManager</code> method does nothing.  There
 785          * isn't any per-child state.
 786          *
 787          * @param child ignored
 788          */
 789         public void removeLayoutComponent(Component child) {
 790         }
 791 
 792 
 793         /**
 794          * Returns the size of the parents insets.
 795          */
 796         private Dimension insetSize(Container parent) {
 797             Insets insets = parent.getInsets();
 798             int w = insets.left + insets.right;
 799             int h = insets.top + insets.bottom;
 800             return new Dimension(w, h);
 801         }
 802 
 803 
 804         /**


 836                 minimumSize.height += childSize.height;
 837             }
 838             return minimumSize;
 839         }
 840 
 841 
 842         /**
 843          * Resize the one (and only) child to completely fill the area
 844          * within the parents insets.
 845          */
 846         public void layoutContainer(Container parent) {
 847             if (parent.getComponentCount() > 0) {
 848                 Insets insets = parent.getInsets();
 849                 int w = parent.getWidth() - (insets.left + insets.right);
 850                 int h = parent.getHeight() - (insets.top + insets.bottom);
 851                 getComponent(0).setBounds(insets.left, insets.top, w, h);
 852             }
 853         }
 854 
 855         /**
 856          * Pushes the currently edited value to the <code>SpinnerModel</code>.
 857          * <p>
 858          * The default implementation invokes <code>commitEdit</code> on the
 859          * <code>JFormattedTextField</code>.
 860          *
 861          * @throws ParseException if the edited value is not legal
 862          */
 863         public void commitEdit()  throws ParseException {
 864             // If the value in the JFormattedTextField is legal, this will have
 865             // the result of pushing the value to the SpinnerModel
 866             // by way of the <code>propertyChange</code> method.
 867             JFormattedTextField ftf = getTextField();
 868 
 869             ftf.commitEdit();
 870         }
 871 
 872         /**
 873          * Returns the baseline.
 874          *
 875          * @throws IllegalArgumentException {@inheritDoc}
 876          * @see javax.swing.JComponent#getBaseline(int,int)
 877          * @see javax.swing.JComponent#getBaselineResizeBehavior()
 878          * @since 1.6
 879          */


 926 
 927         @Override
 928         public Comparable<Date> getMinimum() {
 929             return  model.getStart();
 930         }
 931 
 932         @Override
 933         @SuppressWarnings("unchecked")
 934         public void setMaximum(Comparable<?> max) {
 935             model.setEnd((Comparable<Date>)max);
 936         }
 937 
 938         @Override
 939         public Comparable<Date> getMaximum() {
 940             return model.getEnd();
 941         }
 942     }
 943 
 944 
 945     /**
 946      * An editor for a <code>JSpinner</code> whose model is a
 947      * <code>SpinnerDateModel</code>.  The value of the editor is
 948      * displayed with a <code>JFormattedTextField</code> whose format
 949      * is defined by a <code>DateFormatter</code> instance whose
 950      * <code>minimum</code> and <code>maximum</code> properties
 951      * are mapped to the <code>SpinnerDateModel</code>.
 952      * @since 1.4
 953      */
 954     // PENDING(hmuller): more example javadoc
 955     public static class DateEditor extends DefaultEditor
 956     {
 957         // This is here until SimpleDateFormat gets a constructor that
 958         // takes a Locale: 4923525
 959         private static String getDefaultPattern(Locale loc) {
 960             LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DateFormatProvider.class, loc);
 961             LocaleResources lr = adapter.getLocaleResources(loc);
 962             if (lr == null) {
 963                 lr = LocaleProviderAdapter.forJRE().getLocaleResources(loc);
 964             }
 965             return lr.getDateTimePattern(DateFormat.SHORT, DateFormat.SHORT, null);
 966         }
 967 
 968         /**
 969          * Construct a <code>JSpinner</code> editor that supports displaying
 970          * and editing the value of a <code>SpinnerDateModel</code>
 971          * with a <code>JFormattedTextField</code>.  <code>This</code>
 972          * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
 973          * on the spinner and a <code>PropertyChangeListener</code>
 974          * on the new <code>JFormattedTextField</code>.
 975          *
 976          * @param spinner the spinner whose model <code>this</code> editor will monitor
 977          * @exception IllegalArgumentException if the spinners model is not
 978          *     an instance of <code>SpinnerDateModel</code>
 979          *
 980          * @see #getModel
 981          * @see #getFormat
 982          * @see SpinnerDateModel
 983          */
 984         public DateEditor(JSpinner spinner) {
 985             this(spinner, getDefaultPattern(spinner.getLocale()));
 986         }
 987 
 988 
 989         /**
 990          * Construct a <code>JSpinner</code> editor that supports displaying
 991          * and editing the value of a <code>SpinnerDateModel</code>
 992          * with a <code>JFormattedTextField</code>.  <code>This</code>
 993          * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
 994          * on the spinner and a <code>PropertyChangeListener</code>
 995          * on the new <code>JFormattedTextField</code>.
 996          *
 997          * @param spinner the spinner whose model <code>this</code> editor will monitor
 998          * @param dateFormatPattern the initial pattern for the
 999          *     <code>SimpleDateFormat</code> object that's used to display
1000          *     and parse the value of the text field.
1001          * @exception IllegalArgumentException if the spinners model is not
1002          *     an instance of <code>SpinnerDateModel</code>
1003          *
1004          * @see #getModel
1005          * @see #getFormat
1006          * @see SpinnerDateModel
1007          * @see java.text.SimpleDateFormat
1008          */
1009         public DateEditor(JSpinner spinner, String dateFormatPattern) {
1010             this(spinner, new SimpleDateFormat(dateFormatPattern,
1011                                                spinner.getLocale()));
1012         }
1013 
1014         /**
1015          * Construct a <code>JSpinner</code> editor that supports displaying
1016          * and editing the value of a <code>SpinnerDateModel</code>
1017          * with a <code>JFormattedTextField</code>.  <code>This</code>
1018          * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
1019          * on the spinner and a <code>PropertyChangeListener</code>
1020          * on the new <code>JFormattedTextField</code>.
1021          *
1022          * @param spinner the spinner whose model <code>this</code> editor
1023          *        will monitor
1024          * @param format <code>DateFormat</code> object that's used to display
1025          *     and parse the value of the text field.
1026          * @exception IllegalArgumentException if the spinners model is not
1027          *     an instance of <code>SpinnerDateModel</code>
1028          *
1029          * @see #getModel
1030          * @see #getFormat
1031          * @see SpinnerDateModel
1032          * @see java.text.SimpleDateFormat
1033          */
1034         private DateEditor(JSpinner spinner, DateFormat format) {
1035             super(spinner);
1036             if (!(spinner.getModel() instanceof SpinnerDateModel)) {
1037                 throw new IllegalArgumentException(
1038                                  "model not a SpinnerDateModel");
1039             }
1040 
1041             SpinnerDateModel model = (SpinnerDateModel)spinner.getModel();
1042             DateFormatter formatter = new DateEditorFormatter(model, format);
1043             DefaultFormatterFactory factory = new DefaultFormatterFactory(
1044                                                   formatter);
1045             JFormattedTextField ftf = getTextField();
1046             ftf.setEditable(true);
1047             ftf.setFormatterFactory(factory);
1048 
1049             /* TBD - initializing the column width of the text field
1050              * is imprecise and doing it here is tricky because
1051              * the developer may configure the formatter later.
1052              */
1053             try {
1054                 String maxString = formatter.valueToString(model.getStart());
1055                 String minString = formatter.valueToString(model.getEnd());
1056                 ftf.setColumns(Math.max(maxString.length(),
1057                                         minString.length()));
1058             }
1059             catch (ParseException e) {
1060                 // PENDING: hmuller
1061             }
1062         }
1063 
1064         /**
1065          * Returns the <code>java.text.SimpleDateFormat</code> object the
1066          * <code>JFormattedTextField</code> uses to parse and format
1067          * numbers.
1068          *
1069          * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1070          * @see #getTextField
1071          * @see java.text.SimpleDateFormat
1072          */
1073         public SimpleDateFormat getFormat() {
1074             return (SimpleDateFormat)((DateFormatter)(getTextField().getFormatter())).getFormat();
1075         }
1076 
1077 
1078         /**
1079          * Return our spinner ancestor's <code>SpinnerDateModel</code>.
1080          *
1081          * @return <code>getSpinner().getModel()</code>
1082          * @see #getSpinner
1083          * @see #getTextField
1084          */
1085         public SpinnerDateModel getModel() {
1086             return (SpinnerDateModel)(getSpinner().getModel());
1087         }
1088     }
1089 
1090 
1091     /**
1092      * This subclass of javax.swing.NumberFormatter maps the minimum/maximum
1093      * properties to a SpinnerNumberModel and initializes the valueClass
1094      * of the NumberFormatter to match the type of the initial models value.
1095      */
1096     private static class NumberEditorFormatter extends NumberFormatter {
1097         private final SpinnerNumberModel model;
1098 
1099         NumberEditorFormatter(SpinnerNumberModel model, NumberFormat format) {
1100             super(format);
1101             this.model = model;


1109 
1110         @Override
1111         public Comparable<?> getMinimum() {
1112             return  model.getMinimum();
1113         }
1114 
1115         @Override
1116         public void setMaximum(Comparable<?> max) {
1117             model.setMaximum(max);
1118         }
1119 
1120         @Override
1121         public Comparable<?> getMaximum() {
1122             return model.getMaximum();
1123         }
1124     }
1125 
1126 
1127 
1128     /**
1129      * An editor for a <code>JSpinner</code> whose model is a
1130      * <code>SpinnerNumberModel</code>.  The value of the editor is
1131      * displayed with a <code>JFormattedTextField</code> whose format
1132      * is defined by a <code>NumberFormatter</code> instance whose
1133      * <code>minimum</code> and <code>maximum</code> properties
1134      * are mapped to the <code>SpinnerNumberModel</code>.
1135      * @since 1.4
1136      */
1137     // PENDING(hmuller): more example javadoc
1138     public static class NumberEditor extends DefaultEditor
1139     {
1140         // This is here until DecimalFormat gets a constructor that
1141         // takes a Locale: 4923525
1142         private static String getDefaultPattern(Locale locale) {
1143             // Get the pattern for the default locale.
1144             LocaleProviderAdapter adapter;
1145             adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
1146                                                        locale);
1147             LocaleResources lr = adapter.getLocaleResources(locale);
1148             if (lr == null) {
1149                 lr = LocaleProviderAdapter.forJRE().getLocaleResources(locale);
1150             }
1151             String[] all = lr.getNumberPatterns();
1152             return all[0];
1153         }
1154 
1155         /**
1156          * Construct a <code>JSpinner</code> editor that supports displaying
1157          * and editing the value of a <code>SpinnerNumberModel</code>
1158          * with a <code>JFormattedTextField</code>.  <code>This</code>
1159          * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1160          * on the spinner and a <code>PropertyChangeListener</code>
1161          * on the new <code>JFormattedTextField</code>.
1162          *
1163          * @param spinner the spinner whose model <code>this</code> editor will monitor
1164          * @exception IllegalArgumentException if the spinners model is not
1165          *     an instance of <code>SpinnerNumberModel</code>
1166          *
1167          * @see #getModel
1168          * @see #getFormat
1169          * @see SpinnerNumberModel
1170          */
1171         public NumberEditor(JSpinner spinner) {
1172             this(spinner, getDefaultPattern(spinner.getLocale()));
1173         }
1174 
1175         /**
1176          * Construct a <code>JSpinner</code> editor that supports displaying
1177          * and editing the value of a <code>SpinnerNumberModel</code>
1178          * with a <code>JFormattedTextField</code>.  <code>This</code>
1179          * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1180          * on the spinner and a <code>PropertyChangeListener</code>
1181          * on the new <code>JFormattedTextField</code>.
1182          *
1183          * @param spinner the spinner whose model <code>this</code> editor will monitor
1184          * @param decimalFormatPattern the initial pattern for the
1185          *     <code>DecimalFormat</code> object that's used to display
1186          *     and parse the value of the text field.
1187          * @exception IllegalArgumentException if the spinners model is not
1188          *     an instance of <code>SpinnerNumberModel</code> or if
1189          *     <code>decimalFormatPattern</code> is not a legal
1190          *     argument to <code>DecimalFormat</code>
1191          *
1192          * @see #getTextField
1193          * @see SpinnerNumberModel
1194          * @see java.text.DecimalFormat
1195          */
1196         public NumberEditor(JSpinner spinner, String decimalFormatPattern) {
1197             this(spinner, new DecimalFormat(decimalFormatPattern));
1198         }
1199 
1200 
1201         /**
1202          * Construct a <code>JSpinner</code> editor that supports displaying
1203          * and editing the value of a <code>SpinnerNumberModel</code>
1204          * with a <code>JFormattedTextField</code>.  <code>This</code>
1205          * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1206          * on the spinner and a <code>PropertyChangeListener</code>
1207          * on the new <code>JFormattedTextField</code>.
1208          *
1209          * @param spinner the spinner whose model <code>this</code> editor will monitor
1210          * @param decimalFormatPattern the initial pattern for the
1211          *     <code>DecimalFormat</code> object that's used to display
1212          *     and parse the value of the text field.
1213          * @exception IllegalArgumentException if the spinners model is not
1214          *     an instance of <code>SpinnerNumberModel</code>
1215          *
1216          * @see #getTextField
1217          * @see SpinnerNumberModel
1218          * @see java.text.DecimalFormat
1219          */
1220         private NumberEditor(JSpinner spinner, DecimalFormat format) {
1221             super(spinner);
1222             if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
1223                 throw new IllegalArgumentException(
1224                           "model not a SpinnerNumberModel");
1225             }
1226 
1227             SpinnerNumberModel model = (SpinnerNumberModel)spinner.getModel();
1228             NumberFormatter formatter = new NumberEditorFormatter(model,
1229                                                                   format);
1230             DefaultFormatterFactory factory = new DefaultFormatterFactory(
1231                                                   formatter);
1232             JFormattedTextField ftf = getTextField();
1233             ftf.setEditable(true);
1234             ftf.setFormatterFactory(factory);


1236             ftf.setHorizontalAlignment(JTextField.RIGHT);
1237 
1238             /* TBD - initializing the column width of the text field
1239              * is imprecise and doing it here is tricky because
1240              * the developer may configure the formatter later.
1241              */
1242             try {
1243                 String maxString = formatter.valueToString(model.getMinimum());
1244                 String minString = formatter.valueToString(model.getMaximum());
1245                 ftf.setColumns(Math.max(maxString.length(),
1246                                         minString.length()));
1247             }
1248             catch (ParseException e) {
1249                 // TBD should throw a chained error here
1250             }
1251 
1252         }
1253 
1254 
1255         /**
1256          * Returns the <code>java.text.DecimalFormat</code> object the
1257          * <code>JFormattedTextField</code> uses to parse and format
1258          * numbers.
1259          *
1260          * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1261          * @see #getTextField
1262          * @see java.text.DecimalFormat
1263          */
1264         public DecimalFormat getFormat() {
1265             return (DecimalFormat)((NumberFormatter)(getTextField().getFormatter())).getFormat();
1266         }
1267 
1268 
1269         /**
1270          * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1271          *
1272          * @return <code>getSpinner().getModel()</code>
1273          * @see #getSpinner
1274          * @see #getTextField
1275          */
1276         public SpinnerNumberModel getModel() {
1277             return (SpinnerNumberModel)(getSpinner().getModel());
1278         }
1279 
1280         /**
1281          * {@inheritDoc}
1282          */
1283         @Override
1284         public void setComponentOrientation(ComponentOrientation o) {
1285             super.setComponentOrientation(o);
1286             getTextField().setHorizontalAlignment(
1287                     o.isLeftToRight() ? JTextField.RIGHT : JTextField.LEFT);
1288         }
1289     }
1290 
1291 
1292     /**
1293      * An editor for a <code>JSpinner</code> whose model is a
1294      * <code>SpinnerListModel</code>.
1295      * @since 1.4
1296      */
1297     public static class ListEditor extends DefaultEditor
1298     {
1299         /**
1300          * Construct a <code>JSpinner</code> editor that supports displaying
1301          * and editing the value of a <code>SpinnerListModel</code>
1302          * with a <code>JFormattedTextField</code>.  <code>This</code>
1303          * <code>ListEditor</code> becomes both a <code>ChangeListener</code>
1304          * on the spinner and a <code>PropertyChangeListener</code>
1305          * on the new <code>JFormattedTextField</code>.
1306          *
1307          * @param spinner the spinner whose model <code>this</code> editor will monitor
1308          * @exception IllegalArgumentException if the spinners model is not
1309          *     an instance of <code>SpinnerListModel</code>
1310          *
1311          * @see #getModel
1312          * @see SpinnerListModel
1313          */
1314         public ListEditor(JSpinner spinner) {
1315             super(spinner);
1316             if (!(spinner.getModel() instanceof SpinnerListModel)) {
1317                 throw new IllegalArgumentException("model not a SpinnerListModel");
1318             }
1319             getTextField().setEditable(true);
1320             getTextField().setFormatterFactory(new
1321                               DefaultFormatterFactory(new ListFormatter()));
1322         }
1323 
1324         /**
1325          * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1326          *
1327          * @return <code>getSpinner().getModel()</code>
1328          * @see #getSpinner
1329          * @see #getTextField
1330          */
1331         public SpinnerListModel getModel() {
1332             return (SpinnerListModel)(getSpinner().getModel());
1333         }
1334 
1335 
1336         /**
1337          * ListFormatter provides completion while text is being input
1338          * into the JFormattedTextField. Completion is only done if the
1339          * user is inserting text at the end of the document. Completion
1340          * is done by way of the SpinnerListModel method findNextMatch.
1341          */
1342         private class ListFormatter extends
1343                           JFormattedTextField.AbstractFormatter {
1344             private DocumentFilter filter;
1345 
1346             public String valueToString(Object value) throws ParseException {
1347                 if (value == null) {


1405         public void putValue(String key, Object value) {
1406         }
1407         public void setEnabled(boolean b) {
1408         }
1409         public boolean isEnabled() {
1410             return false;
1411         }
1412         public void addPropertyChangeListener(PropertyChangeListener l) {
1413         }
1414         public void removePropertyChangeListener(PropertyChangeListener l) {
1415         }
1416         public void actionPerformed(ActionEvent ae) {
1417         }
1418     }
1419 
1420     /////////////////
1421     // Accessibility support
1422     ////////////////
1423 
1424     /**
1425      * Gets the <code>AccessibleContext</code> for the <code>JSpinner</code>
1426      *
1427      * @return the <code>AccessibleContext</code> for the <code>JSpinner</code>
1428      * @since 1.5
1429      */
1430     public AccessibleContext getAccessibleContext() {
1431         if (accessibleContext == null) {
1432             accessibleContext = new AccessibleJSpinner();
1433         }
1434         return accessibleContext;
1435     }
1436 
1437     /**
1438      * <code>AccessibleJSpinner</code> implements accessibility
1439      * support for the <code>JSpinner</code> class.
1440      * @since 1.5
1441      */
1442     protected class AccessibleJSpinner extends AccessibleJComponent
1443         implements AccessibleValue, AccessibleAction, AccessibleText,
1444                    AccessibleEditableText, ChangeListener {
1445 
1446         private Object oldModelValue = null;
1447 
1448         /**
1449          * AccessibleJSpinner constructor
1450          */
1451         protected AccessibleJSpinner() {
1452             // model is guaranteed to be non-null
1453             oldModelValue = model.getValue();
1454             JSpinner.this.addChangeListener(this);
1455         }
1456 
1457         /**
1458          * Invoked when the target of the listener has changed its state.
1459          *
1460          * @param e  a <code>ChangeEvent</code> object. Must not be null.
1461          * @throws NullPointerException if the parameter is null.
1462          */
1463         public void stateChanged(ChangeEvent e) {
1464             if (e == null) {
1465                 throw new NullPointerException();
1466             }
1467             Object newModelValue = model.getValue();
1468             firePropertyChange(ACCESSIBLE_VALUE_PROPERTY,
1469                                oldModelValue,
1470                                newModelValue);
1471             firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
1472                                null,
1473                                0); // entire text may have changed
1474 
1475             oldModelValue = newModelValue;
1476         }
1477 
1478         /* ===== Begin AccessibleContext methods ===== */
1479 
1480         /**




  36 import java.beans.*;
  37 import java.text.*;
  38 import java.io.*;
  39 import java.text.spi.DateFormatProvider;
  40 import java.text.spi.NumberFormatProvider;
  41 
  42 import javax.accessibility.*;
  43 import sun.util.locale.provider.LocaleProviderAdapter;
  44 import sun.util.locale.provider.LocaleResources;
  45 
  46 /**
  47  * A single line input field that lets the user select a
  48  * number or an object value from an ordered sequence. Spinners typically
  49  * provide a pair of tiny arrow buttons for stepping through the elements
  50  * of the sequence. The keyboard up/down arrow keys also cycle through the
  51  * elements. The user may also be allowed to type a (legal) value directly
  52  * into the spinner. Although combo boxes provide similar functionality,
  53  * spinners are sometimes preferred because they don't require a drop down list
  54  * that can obscure important data.
  55  * <p>
  56  * A {@code JSpinner}'s sequence value is defined by its
  57  * {@code SpinnerModel}.
  58  * The {@code model} can be specified as a constructor argument and
  59  * changed with the {@code model} property.  {@code SpinnerModel}
  60  * classes for some common types are provided: {@code SpinnerListModel},
  61  * {@code SpinnerNumberModel}, and {@code SpinnerDateModel}.
  62  * <p>
  63  * A {@code JSpinner} has a single child component that's
  64  * responsible for displaying
  65  * and potentially changing the current element or <i>value</i> of
  66  * the model, which is called the {@code editor}.  The editor is created
  67  * by the {@code JSpinner}'s constructor and can be changed with the
  68  * {@code editor} property.  The {@code JSpinner}'s editor stays
  69  * in sync with the model by listening for {@code ChangeEvent}s. If the
  70  * user has changed the value displayed by the {@code editor} it is
  71  * possible for the {@code model}'s value to differ from that of
  72  * the {@code editor}. To make sure the {@code model} has the same
  73  * value as the editor use the {@code commitEdit} method, eg:
  74  * <pre>
  75  *   try {
  76  *       spinner.commitEdit();
  77  *   }
  78  *   catch (ParseException pe) {
  79  *       // Edited value is invalid, spinner.getValue() will return
  80  *       // the last valid value, you could revert the spinner to show that:
  81  *       JComponent editor = spinner.getEditor();
  82  *       if (editor instanceof DefaultEditor) {
  83  *           ((DefaultEditor)editor).getTextField().setValue(spinner.getValue());
  84  *       }
  85  *       // reset the value to some known value:
  86  *       spinner.setValue(fallbackValue);
  87  *       // or treat the last valid value as the current, in which
  88  *       // case you don't need to do anything.
  89  *   }
  90  *   return spinner.getValue();
  91  * </pre>
  92  * <p>
  93  * For information and examples of using spinner see
  94  * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/spinner.html">How to Use Spinners</a>,
  95  * a section in <em>The Java Tutorial.</em>
  96  * <p>
  97  * <strong>Warning:</strong> Swing is not thread safe. For more
  98  * information see <a
  99  * href="package-summary.html#threading">Swing's Threading
 100  * Policy</a>.
 101  * <p>
 102  * <strong>Warning:</strong>
 103  * Serialized objects of this class will not be compatible with
 104  * future Swing releases. The current serialization support is
 105  * appropriate for short term storage or RMI between applications running
 106  * the same version of Swing.  As of 1.4, support for long term storage
 107  * of all JavaBeans&trade;
 108  * has been added to the {@code java.beans} package.
 109  * Please see {@link java.beans.XMLEncoder}.
 110  *
 111  * @beaninfo
 112  *   attribute: isContainer false
 113  * description: A single line input field that lets the user select a
 114  *     number or an object value from an ordered set.
 115  *
 116  * @see SpinnerModel
 117  * @see AbstractSpinnerModel
 118  * @see SpinnerListModel
 119  * @see SpinnerNumberModel
 120  * @see SpinnerDateModel
 121  * @see JFormattedTextField
 122  *
 123  * @author Hans Muller
 124  * @author Lynn Monsanto (accessibility)
 125  * @since 1.4
 126  */
 127 @SuppressWarnings("serial") // Same-version serialization only
 128 public class JSpinner extends JComponent implements Accessible


 145     /**
 146      * Constructs a spinner for the given model. The spinner has
 147      * a set of previous/next buttons, and an editor appropriate
 148      * for the model.
 149      *
 150      * @param model  a model for the new spinner
 151      * @throws NullPointerException if the model is {@code null}
 152      */
 153     public JSpinner(SpinnerModel model) {
 154         if (model == null) {
 155             throw new NullPointerException("model cannot be null");
 156         }
 157         this.model = model;
 158         this.editor = createEditor(model);
 159         setUIProperty("opaque",true);
 160         updateUI();
 161     }
 162 
 163 
 164     /**
 165      * Constructs a spinner with an {@code Integer SpinnerNumberModel}
 166      * with initial value 0 and no minimum or maximum limits.
 167      */
 168     public JSpinner() {
 169         this(new SpinnerNumberModel());
 170     }
 171 
 172 
 173     /**
 174      * Returns the look and feel (L&amp;F) object that renders this component.
 175      *
 176      * @return the {@code SpinnerUI} object that renders this component
 177      */
 178     public SpinnerUI getUI() {
 179         return (SpinnerUI)ui;
 180     }
 181 
 182 
 183     /**
 184      * Sets the look and feel (L&amp;F) object that renders this component.
 185      *
 186      * @param ui  the {@code SpinnerUI} L&amp;F object
 187      * @see UIDefaults#getUI
 188      */
 189     public void setUI(SpinnerUI ui) {
 190         super.setUI(ui);
 191     }
 192 
 193 
 194     /**
 195      * Returns the suffix used to construct the name of the look and feel
 196      * (L&amp;F) class used to render this component.
 197      *
 198      * @return the string "SpinnerUI"
 199      * @see JComponent#getUIClassID
 200      * @see UIDefaults#getUI
 201      */
 202     public String getUIClassID() {
 203         return uiClassID;
 204     }
 205 
 206 
 207 
 208     /**
 209      * Resets the UI property with the value from the current look and feel.
 210      *
 211      * @see UIManager#getUI
 212      */
 213     public void updateUI() {
 214         setUI((SpinnerUI)UIManager.getUI(this));
 215         invalidate();
 216     }
 217 
 218 
 219     /**
 220      * This method is called by the constructors to create the
 221      * {@code JComponent}
 222      * that displays the current value of the sequence.  The editor may
 223      * also allow the user to enter an element of the sequence directly.
 224      * An editor must listen for {@code ChangeEvents} on the
 225      * {@code model} and keep the value it displays
 226      * in sync with the value of the model.
 227      * <p>
 228      * Subclasses may override this method to add support for new
 229      * {@code SpinnerModel} classes.  Alternatively one can just
 230      * replace the editor created here with the {@code setEditor}
 231      * method.  The default mapping from model type to editor is:
 232      * <ul>
 233      * <li> {@code SpinnerNumberModel => JSpinner.NumberEditor}
 234      * <li> {@code SpinnerDateModel => JSpinner.DateEditor}
 235      * <li> {@code SpinnerListModel => JSpinner.ListEditor}
 236      * <li> <i>all others</i> {@code => JSpinner.DefaultEditor}
 237      * </ul>
 238      *
 239      * @return a component that displays the current value of the sequence
 240      * @param model the value of getModel
 241      * @see #getModel
 242      * @see #setEditor
 243      */
 244     protected JComponent createEditor(SpinnerModel model) {
 245         if (model instanceof SpinnerDateModel) {
 246             return new DateEditor(this);
 247         }
 248         else if (model instanceof SpinnerListModel) {
 249             return new ListEditor(this);
 250         }
 251         else if (model instanceof SpinnerNumberModel) {
 252             return new NumberEditor(this);
 253         }
 254         else {
 255             return new DefaultEditor(this);
 256         }
 257     }
 258 
 259 
 260     /**
 261      * Changes the model that represents the value of this spinner.
 262      * If the editor property has not been explicitly set,
 263      * the editor property is (implicitly) set after the {@code "model"}
 264      * {@code PropertyChangeEvent} has been fired.  The editor
 265      * property is set to the value returned by {@code createEditor},
 266      * as in:
 267      * <pre>
 268      * setEditor(createEditor(model));
 269      * </pre>
 270      *
 271      * @param model the new {@code SpinnerModel}
 272      * @see #getModel
 273      * @see #getEditor
 274      * @see #setEditor
 275      * @throws IllegalArgumentException if model is {@code null}
 276      *
 277      * @beaninfo
 278      *        bound: true
 279      *    attribute: visualUpdate true
 280      *  description: Model that represents the value of this spinner.
 281      */
 282     public void setModel(SpinnerModel model) {
 283         if (model == null) {
 284             throw new IllegalArgumentException("null model");
 285         }
 286         if (!model.equals(this.model)) {
 287             SpinnerModel oldModel = this.model;
 288             this.model = model;
 289             if (modelListener != null) {
 290                 oldModel.removeChangeListener(modelListener);
 291                 this.model.addChangeListener(modelListener);
 292             }
 293             firePropertyChange("model", oldModel, model);
 294             if (!editorExplicitlySet) {
 295                 setEditor(createEditor(model)); // sets editorExplicitlySet true
 296                 editorExplicitlySet = false;
 297             }
 298             repaint();
 299             revalidate();
 300         }
 301     }
 302 
 303 
 304     /**
 305      * Returns the {@code SpinnerModel} that defines
 306      * this spinners sequence of values.
 307      *
 308      * @return the value of the model property
 309      * @see #setModel
 310      */
 311     public SpinnerModel getModel() {
 312         return model;
 313     }
 314 
 315 
 316     /**
 317      * Returns the current value of the model, typically
 318      * this value is displayed by the {@code editor}. If the
 319      * user has changed the value displayed by the {@code editor} it is
 320      * possible for the {@code model}'s value to differ from that of
 321      * the {@code editor}, refer to the class level javadoc for examples
 322      * of how to deal with this.
 323      * <p>
 324      * This method simply delegates to the {@code model}.
 325      * It is equivalent to:
 326      * <pre>
 327      * getModel().getValue()
 328      * </pre>
 329      *
 330      * @return the current value of the model
 331      * @see #setValue
 332      * @see SpinnerModel#getValue
 333      */
 334     public Object getValue() {
 335         return getModel().getValue();
 336     }
 337 
 338 
 339     /**
 340      * Changes current value of the model, typically
 341      * this value is displayed by the {@code editor}.
 342      * If the {@code SpinnerModel} implementation
 343      * doesn't support the specified value then an
 344      * {@code IllegalArgumentException} is thrown.
 345      * <p>
 346      * This method simply delegates to the {@code model}.
 347      * It is equivalent to:
 348      * <pre>
 349      * getModel().setValue(value)
 350      * </pre>
 351      *
 352      * @param value  new value for the spinner
 353      * @throws IllegalArgumentException if {@code value} isn't allowed
 354      * @see #getValue
 355      * @see SpinnerModel#setValue
 356      */
 357     public void setValue(Object value) {
 358         getModel().setValue(value);
 359     }
 360 
 361 
 362     /**
 363      * Returns the object in the sequence that comes after the object returned
 364      * by {@code getValue()}. If the end of the sequence has been reached
 365      * then return {@code null}.
 366      * Calling this method does not effect {@code value}.
 367      * <p>
 368      * This method simply delegates to the {@code model}.
 369      * It is equivalent to:
 370      * <pre>
 371      * getModel().getNextValue()
 372      * </pre>
 373      *
 374      * @return the next legal value or {@code null} if one doesn't exist
 375      * @see #getValue
 376      * @see #getPreviousValue
 377      * @see SpinnerModel#getNextValue
 378      */
 379     public Object getNextValue() {
 380         return getModel().getNextValue();
 381     }
 382 
 383 
 384     /**
 385      * We pass {@code Change} events along to the listeners with the
 386      * the slider (instead of the model itself) as the event source.
 387      */
 388     private class ModelListener implements ChangeListener, Serializable {
 389         public void stateChanged(ChangeEvent e) {
 390             fireStateChanged();
 391         }
 392     }
 393 
 394 
 395     /**
 396      * Adds a listener to the list that is notified each time a change
 397      * to the model occurs.  The source of {@code ChangeEvents}
 398      * delivered to {@code ChangeListeners} will be this
 399      * {@code JSpinner}.  Note also that replacing the model
 400      * will not affect listeners added directly to JSpinner.
 401      * Applications can add listeners to  the model directly.  In that
 402      * case is that the source of the event would be the
 403      * {@code SpinnerModel}.
 404      *
 405      * @param listener the {@code ChangeListener} to add
 406      * @see #removeChangeListener
 407      * @see #getModel
 408      */
 409     public void addChangeListener(ChangeListener listener) {
 410         if (modelListener == null) {
 411             modelListener = new ModelListener();
 412             getModel().addChangeListener(modelListener);
 413         }
 414         listenerList.add(ChangeListener.class, listener);
 415     }
 416 
 417 
 418 
 419     /**
 420      * Removes a {@code ChangeListener} from this spinner.
 421      *
 422      * @param listener the {@code ChangeListener} to remove
 423      * @see #fireStateChanged
 424      * @see #addChangeListener
 425      */
 426     public void removeChangeListener(ChangeListener listener) {
 427         listenerList.remove(ChangeListener.class, listener);
 428     }
 429 
 430 
 431     /**
 432      * Returns an array of all the {@code ChangeListener}s added
 433      * to this JSpinner with addChangeListener().
 434      *
 435      * @return all of the {@code ChangeListener}s added or an empty
 436      *         array if no listeners have been added
 437      * @since 1.4
 438      */
 439     public ChangeListener[] getChangeListeners() {
 440         return listenerList.getListeners(ChangeListener.class);
 441     }
 442 
 443 
 444     /**
 445      * Sends a {@code ChangeEvent}, whose source is this
 446      * {@code JSpinner}, to each {@code ChangeListener}.
 447      * When a {@code ChangeListener} has been added
 448      * to the spinner, this method is called each time
 449      * a {@code ChangeEvent} is received from the model.
 450      *
 451      * @see #addChangeListener
 452      * @see #removeChangeListener
 453      * @see EventListenerList
 454      */
 455     protected void fireStateChanged() {
 456         Object[] listeners = listenerList.getListenerList();
 457         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 458             if (listeners[i] == ChangeListener.class) {
 459                 if (changeEvent == null) {
 460                     changeEvent = new ChangeEvent(this);
 461                 }
 462                 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
 463             }
 464         }
 465     }
 466 
 467 
 468     /**
 469      * Returns the object in the sequence that comes
 470      * before the object returned by {@code getValue()}.
 471      * If the end of the sequence has been reached then
 472      * return {@code null}. Calling this method does
 473      * not effect {@code value}.
 474      * <p>
 475      * This method simply delegates to the {@code model}.
 476      * It is equivalent to:
 477      * <pre>
 478      * getModel().getPreviousValue()
 479      * </pre>
 480      *
 481      * @return the previous legal value or {@code null}
 482      *   if one doesn't exist
 483      * @see #getValue
 484      * @see #getNextValue
 485      * @see SpinnerModel#getPreviousValue
 486      */
 487     public Object getPreviousValue() {
 488         return getModel().getPreviousValue();
 489     }
 490 
 491 
 492     /**
 493      * Changes the {@code JComponent} that displays the current value
 494      * of the {@code SpinnerModel}.  It is the responsibility of this
 495      * method to <i>disconnect</i> the old editor from the model and to
 496      * connect the new editor.  This may mean removing the
 497      * old editors {@code ChangeListener} from the model or the
 498      * spinner itself and adding one for the new editor.
 499      *
 500      * @param editor the new editor
 501      * @see #getEditor
 502      * @see #createEditor
 503      * @see #getModel
 504      * @throws IllegalArgumentException if editor is {@code null}
 505      *
 506      * @beaninfo
 507      *        bound: true
 508      *    attribute: visualUpdate true
 509      *  description: JComponent that displays the current value of the model
 510      */
 511     public void setEditor(JComponent editor) {
 512         if (editor == null) {
 513             throw new IllegalArgumentException("null editor");
 514         }
 515         if (!editor.equals(this.editor)) {
 516             JComponent oldEditor = this.editor;
 517             this.editor = editor;
 518             if (oldEditor instanceof DefaultEditor) {
 519                 ((DefaultEditor)oldEditor).dismiss(this);
 520             }
 521             editorExplicitlySet = true;
 522             firePropertyChange("editor", oldEditor, editor);
 523             revalidate();
 524             repaint();
 525         }
 526     }
 527 
 528 
 529     /**
 530      * Returns the component that displays and potentially
 531      * changes the model's value.
 532      *
 533      * @return the component that displays and potentially
 534      *    changes the model's value
 535      * @see #setEditor
 536      * @see #createEditor
 537      */
 538     public JComponent getEditor() {
 539         return editor;
 540     }
 541 
 542 
 543     /**
 544      * Commits the currently edited value to the {@code SpinnerModel}.
 545      * <p>
 546      * If the editor is an instance of {@code DefaultEditor}, the
 547      * call if forwarded to the editor, otherwise this does nothing.
 548      *
 549      * @throws ParseException if the currently edited value couldn't
 550      *         be committed.
 551      */
 552     public void commitEdit() throws ParseException {
 553         JComponent editor = getEditor();
 554         if (editor instanceof DefaultEditor) {
 555             ((DefaultEditor)editor).commitEdit();
 556         }
 557     }
 558 
 559 
 560     /*
 561      * See readObject and writeObject in JComponent for more
 562      * information about serialization in Swing.
 563      *
 564      * @param s Stream to write to
 565      */
 566     private void writeObject(ObjectOutputStream s) throws IOException {
 567         s.defaultWriteObject();
 568         if (getUIClassID().equals(uiClassID)) {
 569             byte count = JComponent.getWriteObjCounter(this);
 570             JComponent.setWriteObjCounter(this, --count);
 571             if (count == 0 && ui != null) {
 572                 ui.installUI(this);
 573             }
 574         }
 575     }
 576 
 577 
 578     /**
 579      * A simple base class for more specialized editors
 580      * that displays a read-only view of the model's current
 581      * value with a {@code JFormattedTextField}.  Subclasses
 582      * can configure the {@code JFormattedTextField} to create
 583      * an editor that's appropriate for the type of model they
 584      * support and they may want to override
 585      * the {@code stateChanged} and {@code propertyChanged}
 586      * methods, which keep the model and the text field in sync.
 587      * <p>
 588      * This class defines a {@code dismiss} method that removes the
 589      * editors {@code ChangeListener} from the {@code JSpinner}
 590      * that it's part of.   The {@code setEditor} method knows about
 591      * {@code DefaultEditor.dismiss}, so if the developer
 592      * replaces an editor that's derived from {@code JSpinner.DefaultEditor}
 593      * its {@code ChangeListener} connection back to the
 594      * {@code JSpinner} will be removed.  However after that,
 595      * it's up to the developer to manage their editor listeners.
 596      * Similarly, if a subclass overrides {@code createEditor},
 597      * it's up to the subclasser to deal with their editor
 598      * subsequently being replaced (with {@code setEditor}).
 599      * We expect that in most cases, and in editor installed
 600      * with {@code setEditor} or created by a {@code createEditor}
 601      * override, will not be replaced anyway.
 602      * <p>
 603      * This class is the {@code LayoutManager} for it's single
 604      * {@code JFormattedTextField} child.   By default the
 605      * child is just centered with the parents insets.
 606      * @since 1.4
 607      */
 608     public static class DefaultEditor extends JPanel
 609         implements ChangeListener, PropertyChangeListener, LayoutManager
 610     {
 611         /**
 612          * Constructs an editor component for the specified {@code JSpinner}.
 613          * This {@code DefaultEditor} is it's own layout manager and
 614          * it is added to the spinner's {@code ChangeListener} list.
 615          * The constructor creates a single {@code JFormattedTextField} child,
 616          * initializes it's value to be the spinner model's current value
 617          * and adds it to {@code this DefaultEditor}.
 618          *
 619          * @param spinner the spinner whose model {@code this} editor will monitor
 620          * @see #getTextField
 621          * @see JSpinner#addChangeListener
 622          */
 623         public DefaultEditor(JSpinner spinner) {
 624             super(null);
 625 
 626             JFormattedTextField ftf = new JFormattedTextField();
 627             ftf.setName("Spinner.formattedTextField");
 628             ftf.setValue(spinner.getValue());
 629             ftf.addPropertyChangeListener(this);
 630             ftf.setEditable(false);
 631             ftf.setInheritsPopupMenu(true);
 632 
 633             String toolTipText = spinner.getToolTipText();
 634             if (toolTipText != null) {
 635                 ftf.setToolTipText(toolTipText);
 636             }
 637 
 638             add(ftf);
 639 
 640             setLayout(this);
 641             spinner.addChangeListener(this);
 642 
 643             // We want the spinner's increment/decrement actions to be
 644             // active vs those of the JFormattedTextField. As such we
 645             // put disabled actions in the JFormattedTextField's actionmap.
 646             // A binding to a disabled action is treated as a nonexistant
 647             // binding.
 648             ActionMap ftfMap = ftf.getActionMap();
 649 
 650             if (ftfMap != null) {
 651                 ftfMap.put("increment", DISABLED_ACTION);
 652                 ftfMap.put("decrement", DISABLED_ACTION);
 653             }
 654         }
 655 
 656 
 657         /**
 658          * Disconnect {@code this} editor from the specified
 659          * {@code JSpinner}.  By default, this method removes
 660          * itself from the spinners {@code ChangeListener} list.
 661          *
 662          * @param spinner the {@code JSpinner} to disconnect this
 663          *    editor from; the same spinner as was passed to the constructor.
 664          */
 665         public void dismiss(JSpinner spinner) {
 666             spinner.removeChangeListener(this);
 667         }
 668 
 669 
 670         /**
 671          * Returns the {@code JSpinner} ancestor of this editor or
 672          * {@code null} if none of the ancestors are a
 673          * {@code JSpinner}.
 674          * Typically the editor's parent is a {@code JSpinner} however
 675          * subclasses of {@code JSpinner} may override the
 676          * the {@code createEditor} method and insert one or more containers
 677          * between the {@code JSpinner} and it's editor.
 678          *
 679          * @return {@code JSpinner} ancestor; {@code null}
 680          *         if none of the ancestors are a {@code JSpinner}
 681          *
 682          * @see JSpinner#createEditor
 683          */
 684         public JSpinner getSpinner() {
 685             for (Component c = this; c != null; c = c.getParent()) {
 686                 if (c instanceof JSpinner) {
 687                     return (JSpinner)c;
 688                 }
 689             }
 690             return null;
 691         }
 692 
 693 
 694         /**
 695          * Returns the {@code JFormattedTextField} child of this
 696          * editor.  By default the text field is the first and only
 697          * child of editor.
 698          *
 699          * @return the {@code JFormattedTextField} that gives the user
 700          *     access to the {@code SpinnerDateModel's} value.
 701          * @see #getSpinner
 702          * @see #getModel
 703          */
 704         public JFormattedTextField getTextField() {
 705             return (JFormattedTextField)getComponent(0);
 706         }
 707 
 708 
 709         /**
 710          * This method is called when the spinner's model's state changes.
 711          * It sets the {@code value} of the text field to the current
 712          * value of the spinners model.
 713          *
 714          * @param e the {@code ChangeEvent} whose source is the
 715          * {@code JSpinner} whose model has changed.
 716          * @see #getTextField
 717          * @see JSpinner#getValue
 718          */
 719         public void stateChanged(ChangeEvent e) {
 720             JSpinner spinner = (JSpinner)(e.getSource());
 721             getTextField().setValue(spinner.getValue());
 722         }
 723 
 724 
 725         /**
 726          * Called by the {@code JFormattedTextField}
 727          * {@code PropertyChangeListener}.  When the {@code "value"}
 728          * property changes, which implies that the user has typed a new
 729          * number, we set the value of the spinners model.
 730          * <p>
 731          * This class ignores {@code PropertyChangeEvents} whose
 732          * source is not the {@code JFormattedTextField}, so subclasses
 733          * may safely make {@code this DefaultEditor} a
 734          * {@code PropertyChangeListener} on other objects.
 735          *
 736          * @param e the {@code PropertyChangeEvent} whose source is
 737          *    the {@code JFormattedTextField} created by this class.
 738          * @see #getTextField
 739          */
 740         public void propertyChange(PropertyChangeEvent e)
 741         {
 742             JSpinner spinner = getSpinner();
 743 
 744             if (spinner == null) {
 745                 // Indicates we aren't installed anywhere.
 746                 return;
 747             }
 748 
 749             Object source = e.getSource();
 750             String name = e.getPropertyName();
 751             if ((source instanceof JFormattedTextField) && "value".equals(name)) {
 752                 Object lastValue = spinner.getValue();
 753 
 754                 // Try to set the new value
 755                 try {
 756                     spinner.setValue(getTextField().getValue());
 757                 } catch (IllegalArgumentException iae) {
 758                     // SpinnerModel didn't like new value, reset
 759                     try {
 760                         ((JFormattedTextField)source).setValue(lastValue);
 761                     } catch (IllegalArgumentException iae2) {
 762                         // Still bogus, nothing else we can do, the
 763                         // SpinnerModel and JFormattedTextField are now out
 764                         // of sync.
 765                     }
 766                 }
 767             }
 768         }
 769 
 770 
 771         /**
 772          * This {@code LayoutManager} method does nothing.  We're
 773          * only managing a single child and there's no support
 774          * for layout constraints.
 775          *
 776          * @param name ignored
 777          * @param child ignored
 778          */
 779         public void addLayoutComponent(String name, Component child) {
 780         }
 781 
 782 
 783         /**
 784          * This {@code LayoutManager} method does nothing.  There
 785          * isn't any per-child state.
 786          *
 787          * @param child ignored
 788          */
 789         public void removeLayoutComponent(Component child) {
 790         }
 791 
 792 
 793         /**
 794          * Returns the size of the parents insets.
 795          */
 796         private Dimension insetSize(Container parent) {
 797             Insets insets = parent.getInsets();
 798             int w = insets.left + insets.right;
 799             int h = insets.top + insets.bottom;
 800             return new Dimension(w, h);
 801         }
 802 
 803 
 804         /**


 836                 minimumSize.height += childSize.height;
 837             }
 838             return minimumSize;
 839         }
 840 
 841 
 842         /**
 843          * Resize the one (and only) child to completely fill the area
 844          * within the parents insets.
 845          */
 846         public void layoutContainer(Container parent) {
 847             if (parent.getComponentCount() > 0) {
 848                 Insets insets = parent.getInsets();
 849                 int w = parent.getWidth() - (insets.left + insets.right);
 850                 int h = parent.getHeight() - (insets.top + insets.bottom);
 851                 getComponent(0).setBounds(insets.left, insets.top, w, h);
 852             }
 853         }
 854 
 855         /**
 856          * Pushes the currently edited value to the {@code SpinnerModel}.
 857          * <p>
 858          * The default implementation invokes {@code commitEdit} on the
 859          * {@code JFormattedTextField}.
 860          *
 861          * @throws ParseException if the edited value is not legal
 862          */
 863         public void commitEdit()  throws ParseException {
 864             // If the value in the JFormattedTextField is legal, this will have
 865             // the result of pushing the value to the SpinnerModel
 866             // by way of the <code>propertyChange</code> method.
 867             JFormattedTextField ftf = getTextField();
 868 
 869             ftf.commitEdit();
 870         }
 871 
 872         /**
 873          * Returns the baseline.
 874          *
 875          * @throws IllegalArgumentException {@inheritDoc}
 876          * @see javax.swing.JComponent#getBaseline(int,int)
 877          * @see javax.swing.JComponent#getBaselineResizeBehavior()
 878          * @since 1.6
 879          */


 926 
 927         @Override
 928         public Comparable<Date> getMinimum() {
 929             return  model.getStart();
 930         }
 931 
 932         @Override
 933         @SuppressWarnings("unchecked")
 934         public void setMaximum(Comparable<?> max) {
 935             model.setEnd((Comparable<Date>)max);
 936         }
 937 
 938         @Override
 939         public Comparable<Date> getMaximum() {
 940             return model.getEnd();
 941         }
 942     }
 943 
 944 
 945     /**
 946      * An editor for a {@code JSpinner} whose model is a
 947      * {@code SpinnerDateModel}.  The value of the editor is
 948      * displayed with a {@code JFormattedTextField} whose format
 949      * is defined by a {@code DateFormatter} instance whose
 950      * {@code minimum} and {@code maximum} properties
 951      * are mapped to the {@code SpinnerDateModel}.
 952      * @since 1.4
 953      */
 954     // PENDING(hmuller): more example javadoc
 955     public static class DateEditor extends DefaultEditor
 956     {
 957         // This is here until SimpleDateFormat gets a constructor that
 958         // takes a Locale: 4923525
 959         private static String getDefaultPattern(Locale loc) {
 960             LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DateFormatProvider.class, loc);
 961             LocaleResources lr = adapter.getLocaleResources(loc);
 962             if (lr == null) {
 963                 lr = LocaleProviderAdapter.forJRE().getLocaleResources(loc);
 964             }
 965             return lr.getDateTimePattern(DateFormat.SHORT, DateFormat.SHORT, null);
 966         }
 967 
 968         /**
 969          * Construct a {@code JSpinner} editor that supports displaying
 970          * and editing the value of a {@code SpinnerDateModel}
 971          * with a {@code JFormattedTextField}.  {@code This}
 972          * {@code DateEditor} becomes both a {@code ChangeListener}
 973          * on the spinner and a {@code PropertyChangeListener}
 974          * on the new {@code JFormattedTextField}.
 975          *
 976          * @param spinner the spinner whose model {@code this} editor will monitor
 977          * @exception IllegalArgumentException if the spinners model is not
 978          *     an instance of {@code SpinnerDateModel}
 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} editor that supports displaying
 991          * and editing the value of a {@code SpinnerDateModel}
 992          * with a {@code JFormattedTextField}.  {@code This}
 993          * {@code DateEditor} becomes both a {@code ChangeListener}
 994          * on the spinner and a {@code PropertyChangeListener}
 995          * on the new {@code JFormattedTextField}.
 996          *
 997          * @param spinner the spinner whose model {@code this} editor will monitor
 998          * @param dateFormatPattern the initial pattern for the
 999          *     {@code SimpleDateFormat} 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}
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} editor that supports displaying
1016          * and editing the value of a {@code SpinnerDateModel}
1017          * with a {@code JFormattedTextField}.  {@code This}
1018          * {@code DateEditor} becomes both a {@code ChangeListener}
1019          * on the spinner and a {@code PropertyChangeListener}
1020          * on the new {@code JFormattedTextField}.
1021          *
1022          * @param spinner the spinner whose model {@code this} editor
1023          *        will monitor
1024          * @param format {@code DateFormat} 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}
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} object the
1066          * {@code JFormattedTextField} uses to parse and format
1067          * numbers.
1068          *
1069          * @return the value of {@code getTextField().getFormatter().getFormat()}.
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}.
1080          *
1081          * @return {@code getSpinner().getModel()}
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;


1109 
1110         @Override
1111         public Comparable<?> getMinimum() {
1112             return  model.getMinimum();
1113         }
1114 
1115         @Override
1116         public void setMaximum(Comparable<?> max) {
1117             model.setMaximum(max);
1118         }
1119 
1120         @Override
1121         public Comparable<?> getMaximum() {
1122             return model.getMaximum();
1123         }
1124     }
1125 
1126 
1127 
1128     /**
1129      * An editor for a {@code JSpinner} whose model is a
1130      * {@code SpinnerNumberModel}.  The value of the editor is
1131      * displayed with a {@code JFormattedTextField} whose format
1132      * is defined by a {@code NumberFormatter} instance whose
1133      * {@code minimum} and {@code maximum} properties
1134      * are mapped to the {@code SpinnerNumberModel}.
1135      * @since 1.4
1136      */
1137     // PENDING(hmuller): more example javadoc
1138     public static class NumberEditor extends DefaultEditor
1139     {
1140         // This is here until DecimalFormat gets a constructor that
1141         // takes a Locale: 4923525
1142         private static String getDefaultPattern(Locale locale) {
1143             // Get the pattern for the default locale.
1144             LocaleProviderAdapter adapter;
1145             adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
1146                                                        locale);
1147             LocaleResources lr = adapter.getLocaleResources(locale);
1148             if (lr == null) {
1149                 lr = LocaleProviderAdapter.forJRE().getLocaleResources(locale);
1150             }
1151             String[] all = lr.getNumberPatterns();
1152             return all[0];
1153         }
1154 
1155         /**
1156          * Construct a {@code JSpinner} editor that supports displaying
1157          * and editing the value of a {@code SpinnerNumberModel}
1158          * with a {@code JFormattedTextField}.  {@code This}
1159          * {@code NumberEditor} becomes both a {@code ChangeListener}
1160          * on the spinner and a {@code PropertyChangeListener}
1161          * on the new {@code JFormattedTextField}.
1162          *
1163          * @param spinner the spinner whose model {@code this} editor will monitor
1164          * @exception IllegalArgumentException if the spinners model is not
1165          *     an instance of {@code SpinnerNumberModel}
1166          *
1167          * @see #getModel
1168          * @see #getFormat
1169          * @see SpinnerNumberModel
1170          */
1171         public NumberEditor(JSpinner spinner) {
1172             this(spinner, getDefaultPattern(spinner.getLocale()));
1173         }
1174 
1175         /**
1176          * Construct a {@code JSpinner} editor that supports displaying
1177          * and editing the value of a {@code SpinnerNumberModel}
1178          * with a {@code JFormattedTextField}.  {@code This}
1179          * {@code NumberEditor} becomes both a {@code ChangeListener}
1180          * on the spinner and a {@code PropertyChangeListener}
1181          * on the new {@code JFormattedTextField}.
1182          *
1183          * @param spinner the spinner whose model {@code this} editor will monitor
1184          * @param decimalFormatPattern the initial pattern for the
1185          *     {@code DecimalFormat} object that's used to display
1186          *     and parse the value of the text field.
1187          * @exception IllegalArgumentException if the spinners model is not
1188          *     an instance of {@code SpinnerNumberModel} or if
1189          *     {@code decimalFormatPattern} is not a legal
1190          *     argument to {@code DecimalFormat}
1191          *
1192          * @see #getTextField
1193          * @see SpinnerNumberModel
1194          * @see java.text.DecimalFormat
1195          */
1196         public NumberEditor(JSpinner spinner, String decimalFormatPattern) {
1197             this(spinner, new DecimalFormat(decimalFormatPattern));
1198         }
1199 
1200 
1201         /**
1202          * Construct a {@code JSpinner} editor that supports displaying
1203          * and editing the value of a {@code SpinnerNumberModel}
1204          * with a {@code JFormattedTextField}.  {@code This}
1205          * {@code NumberEditor} becomes both a {@code ChangeListener}
1206          * on the spinner and a {@code PropertyChangeListener}
1207          * on the new {@code JFormattedTextField}.
1208          *
1209          * @param spinner the spinner whose model {@code this} editor will monitor
1210          * @param decimalFormatPattern the initial pattern for the
1211          *     {@code DecimalFormat} object that's used to display
1212          *     and parse the value of the text field.
1213          * @exception IllegalArgumentException if the spinners model is not
1214          *     an instance of {@code SpinnerNumberModel}
1215          *
1216          * @see #getTextField
1217          * @see SpinnerNumberModel
1218          * @see java.text.DecimalFormat
1219          */
1220         private NumberEditor(JSpinner spinner, DecimalFormat format) {
1221             super(spinner);
1222             if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
1223                 throw new IllegalArgumentException(
1224                           "model not a SpinnerNumberModel");
1225             }
1226 
1227             SpinnerNumberModel model = (SpinnerNumberModel)spinner.getModel();
1228             NumberFormatter formatter = new NumberEditorFormatter(model,
1229                                                                   format);
1230             DefaultFormatterFactory factory = new DefaultFormatterFactory(
1231                                                   formatter);
1232             JFormattedTextField ftf = getTextField();
1233             ftf.setEditable(true);
1234             ftf.setFormatterFactory(factory);


1236             ftf.setHorizontalAlignment(JTextField.RIGHT);
1237 
1238             /* TBD - initializing the column width of the text field
1239              * is imprecise and doing it here is tricky because
1240              * the developer may configure the formatter later.
1241              */
1242             try {
1243                 String maxString = formatter.valueToString(model.getMinimum());
1244                 String minString = formatter.valueToString(model.getMaximum());
1245                 ftf.setColumns(Math.max(maxString.length(),
1246                                         minString.length()));
1247             }
1248             catch (ParseException e) {
1249                 // TBD should throw a chained error here
1250             }
1251 
1252         }
1253 
1254 
1255         /**
1256          * Returns the {@code java.text.DecimalFormat} object the
1257          * {@code JFormattedTextField} uses to parse and format
1258          * numbers.
1259          *
1260          * @return the value of {@code getTextField().getFormatter().getFormat()}.
1261          * @see #getTextField
1262          * @see java.text.DecimalFormat
1263          */
1264         public DecimalFormat getFormat() {
1265             return (DecimalFormat)((NumberFormatter)(getTextField().getFormatter())).getFormat();
1266         }
1267 
1268 
1269         /**
1270          * Return our spinner ancestor's {@code SpinnerNumberModel}.
1271          *
1272          * @return {@code getSpinner().getModel()}
1273          * @see #getSpinner
1274          * @see #getTextField
1275          */
1276         public SpinnerNumberModel getModel() {
1277             return (SpinnerNumberModel)(getSpinner().getModel());
1278         }
1279 
1280         /**
1281          * {@inheritDoc}
1282          */
1283         @Override
1284         public void setComponentOrientation(ComponentOrientation o) {
1285             super.setComponentOrientation(o);
1286             getTextField().setHorizontalAlignment(
1287                     o.isLeftToRight() ? JTextField.RIGHT : JTextField.LEFT);
1288         }
1289     }
1290 
1291 
1292     /**
1293      * An editor for a {@code JSpinner} whose model is a
1294      * {@code SpinnerListModel}.
1295      * @since 1.4
1296      */
1297     public static class ListEditor extends DefaultEditor
1298     {
1299         /**
1300          * Construct a {@code JSpinner} editor that supports displaying
1301          * and editing the value of a {@code SpinnerListModel}
1302          * with a {@code JFormattedTextField}.  {@code This}
1303          * {@code ListEditor} becomes both a {@code ChangeListener}
1304          * on the spinner and a {@code PropertyChangeListener}
1305          * on the new {@code JFormattedTextField}.
1306          *
1307          * @param spinner the spinner whose model {@code this} editor will monitor
1308          * @exception IllegalArgumentException if the spinners model is not
1309          *     an instance of {@code SpinnerListModel}
1310          *
1311          * @see #getModel
1312          * @see SpinnerListModel
1313          */
1314         public ListEditor(JSpinner spinner) {
1315             super(spinner);
1316             if (!(spinner.getModel() instanceof SpinnerListModel)) {
1317                 throw new IllegalArgumentException("model not a SpinnerListModel");
1318             }
1319             getTextField().setEditable(true);
1320             getTextField().setFormatterFactory(new
1321                               DefaultFormatterFactory(new ListFormatter()));
1322         }
1323 
1324         /**
1325          * Return our spinner ancestor's {@code SpinnerNumberModel}.
1326          *
1327          * @return {@code getSpinner().getModel()}
1328          * @see #getSpinner
1329          * @see #getTextField
1330          */
1331         public SpinnerListModel getModel() {
1332             return (SpinnerListModel)(getSpinner().getModel());
1333         }
1334 
1335 
1336         /**
1337          * ListFormatter provides completion while text is being input
1338          * into the JFormattedTextField. Completion is only done if the
1339          * user is inserting text at the end of the document. Completion
1340          * is done by way of the SpinnerListModel method findNextMatch.
1341          */
1342         private class ListFormatter extends
1343                           JFormattedTextField.AbstractFormatter {
1344             private DocumentFilter filter;
1345 
1346             public String valueToString(Object value) throws ParseException {
1347                 if (value == null) {


1405         public void putValue(String key, Object value) {
1406         }
1407         public void setEnabled(boolean b) {
1408         }
1409         public boolean isEnabled() {
1410             return false;
1411         }
1412         public void addPropertyChangeListener(PropertyChangeListener l) {
1413         }
1414         public void removePropertyChangeListener(PropertyChangeListener l) {
1415         }
1416         public void actionPerformed(ActionEvent ae) {
1417         }
1418     }
1419 
1420     /////////////////
1421     // Accessibility support
1422     ////////////////
1423 
1424     /**
1425      * Gets the {@code AccessibleContext} for the {@code JSpinner}
1426      *
1427      * @return the {@code AccessibleContext} for the {@code JSpinner}
1428      * @since 1.5
1429      */
1430     public AccessibleContext getAccessibleContext() {
1431         if (accessibleContext == null) {
1432             accessibleContext = new AccessibleJSpinner();
1433         }
1434         return accessibleContext;
1435     }
1436 
1437     /**
1438      * {@code AccessibleJSpinner} implements accessibility
1439      * support for the {@code JSpinner} class.
1440      * @since 1.5
1441      */
1442     protected class AccessibleJSpinner extends AccessibleJComponent
1443         implements AccessibleValue, AccessibleAction, AccessibleText,
1444                    AccessibleEditableText, ChangeListener {
1445 
1446         private Object oldModelValue = null;
1447 
1448         /**
1449          * AccessibleJSpinner constructor
1450          */
1451         protected AccessibleJSpinner() {
1452             // model is guaranteed to be non-null
1453             oldModelValue = model.getValue();
1454             JSpinner.this.addChangeListener(this);
1455         }
1456 
1457         /**
1458          * Invoked when the target of the listener has changed its state.
1459          *
1460          * @param e  a {@code ChangeEvent} object. Must not be null.
1461          * @throws NullPointerException if the parameter is null.
1462          */
1463         public void stateChanged(ChangeEvent e) {
1464             if (e == null) {
1465                 throw new NullPointerException();
1466             }
1467             Object newModelValue = model.getValue();
1468             firePropertyChange(ACCESSIBLE_VALUE_PROPERTY,
1469                                oldModelValue,
1470                                newModelValue);
1471             firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
1472                                null,
1473                                0); // entire text may have changed
1474 
1475             oldModelValue = newModelValue;
1476         }
1477 
1478         /* ===== Begin AccessibleContext methods ===== */
1479 
1480         /**


< prev index next >