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

Save This Page
Home » openjdk-7 » javax » swing » [javadoc | source]