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

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

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