JSpinner.java revision 11099:678faa7d1a6a
150276Speter/*
2178866Srafan * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
350276Speter * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
450276Speter *
550276Speter * This code is free software; you can redistribute it and/or modify it
650276Speter * under the terms of the GNU General Public License version 2 only, as
750276Speter * published by the Free Software Foundation.  Oracle designates this
850276Speter * particular file as subject to the "Classpath" exception as provided
950276Speter * by Oracle in the LICENSE file that accompanied this code.
1050276Speter *
1150276Speter * This code is distributed in the hope that it will be useful, but WITHOUT
1250276Speter * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1350276Speter * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1450276Speter * version 2 for more details (a copy is included in the LICENSE file that
1550276Speter * accompanied this code).
1650276Speter *
1750276Speter * You should have received a copy of the GNU General Public License version
1850276Speter * 2 along with this work; if not, write to the Free Software Foundation,
1950276Speter * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2050276Speter *
2150276Speter * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2250276Speter * or visit www.oracle.com if you need additional information or have any
2350276Speter * questions.
2450276Speter */
2550276Speter
2650276Speterpackage javax.swing;
2750276Speter
2850276Speterimport java.awt.*;
2950276Speterimport java.awt.event.*;
3050276Speter
3150276Speterimport javax.swing.event.*;
32166124Srafanimport javax.swing.text.*;
3350276Speterimport javax.swing.plaf.SpinnerUI;
3450276Speter
3550276Speterimport java.util.*;
3650276Speterimport java.beans.*;
3750276Speterimport java.text.*;
3850276Speterimport java.io.*;
3950276Speterimport java.text.spi.DateFormatProvider;
4050276Speterimport java.text.spi.NumberFormatProvider;
4150276Speter
4250276Speterimport javax.accessibility.*;
4350276Speterimport sun.util.locale.provider.LocaleProviderAdapter;
4462449Speterimport sun.util.locale.provider.LocaleResources;
4550276Speterimport sun.util.locale.provider.LocaleServiceProviderPool;
46184989Srafan
4750276Speter
48184989Srafan/**
49184989Srafan * A single line input field that lets the user select a
50184989Srafan * number or an object value from an ordered sequence. Spinners typically
5176726Speter * provide a pair of tiny arrow buttons for stepping through the elements
5262449Speter * of the sequence. The keyboard up/down arrow keys also cycle through the
5350276Speter * elements. The user may also be allowed to type a (legal) value directly
5462449Speter * into the spinner. Although combo boxes provide similar functionality,
5550276Speter * spinners are sometimes preferred because they don't require a drop down list
5662449Speter * that can obscure important data.
5762449Speter * <p>
5862449Speter * A <code>JSpinner</code>'s sequence value is defined by its
5962449Speter * <code>SpinnerModel</code>.
6062449Speter * The <code>model</code> can be specified as a constructor argument and
6150276Speter * changed with the <code>model</code> property.  <code>SpinnerModel</code>
6250276Speter * classes for some common types are provided: <code>SpinnerListModel</code>,
6376726Speter * <code>SpinnerNumberModel</code>, and <code>SpinnerDateModel</code>.
6462449Speter * <p>
6550276Speter * A <code>JSpinner</code> has a single child component that's
6662449Speter * responsible for displaying
6750276Speter * and potentially changing the current element or <i>value</i> of
6862449Speter * the model, which is called the <code>editor</code>.  The editor is created
6962449Speter * by the <code>JSpinner</code>'s constructor and can be changed with the
7050276Speter * <code>editor</code> property.  The <code>JSpinner</code>'s editor stays
7162449Speter * in sync with the model by listening for <code>ChangeEvent</code>s. If the
7250276Speter * user has changed the value displayed by the <code>editor</code> it is
7350276Speter * possible for the <code>model</code>'s value to differ from that of
7476726Speter * the <code>editor</code>. To make sure the <code>model</code> has the same
7562449Speter * value as the editor use the <code>commitEdit</code> method, eg:
7650276Speter * <pre>
7762449Speter *   try {
7850276Speter *       spinner.commitEdit();
79166124Srafan *   }
8062449Speter *   catch (ParseException pe) {{
8150276Speter *       // Edited value is invalid, spinner.getValue() will return
8262449Speter *       // the last valid value, you could revert the spinner to show that:
8362449Speter *       JComponent editor = spinner.getEditor()
8462449Speter *       if (editor instanceof DefaultEditor) {
8550276Speter *           ((DefaultEditor)editor).getTextField().setValue(spinner.getValue();
8650276Speter *       }
8776726Speter *       // reset the value to some known value:
8862449Speter *       spinner.setValue(fallbackValue);
8950276Speter *       // or treat the last valid value as the current, in which
9062449Speter *       // case you don't need to do anything.
9150276Speter *   }
9262449Speter *   return spinner.getValue();
9362449Speter * </pre>
9450276Speter * <p>
9550276Speter * For information and examples of using spinner see
9662449Speter * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/spinner.html">How to Use Spinners</a>,
9762449Speter * a section in <em>The Java Tutorial.</em>
9862449Speter * <p>
9962449Speter * <strong>Warning:</strong> Swing is not thread safe. For more
10050276Speter * information see <a
10150276Speter * href="package-summary.html#threading">Swing's Threading
10276726Speter * Policy</a>.
10362449Speter * <p>
10450276Speter * <strong>Warning:</strong>
105166124Srafan * Serialized objects of this class will not be compatible with
10650276Speter * future Swing releases. The current serialization support is
10762449Speter * appropriate for short term storage or RMI between applications running
10862449Speter * the same version of Swing.  As of 1.4, support for long term storage
10962449Speter * of all JavaBeans&trade;
11062449Speter * has been added to the <code>java.beans</code> package.
11162449Speter * Please see {@link java.beans.XMLEncoder}.
11250276Speter *
11350276Speter * @beaninfo
11476726Speter *   attribute: isContainer false
11562449Speter * description: A single line input field that lets the user select a
11650276Speter *     number or an object value from an ordered set.
11762449Speter *
11850276Speter * @see SpinnerModel
11962449Speter * @see AbstractSpinnerModel
12062449Speter * @see SpinnerListModel
12162449Speter * @see SpinnerNumberModel
12297049Speter * @see SpinnerDateModel
12350276Speter * @see JFormattedTextField
12450276Speter *
12576726Speter * @author Hans Muller
12662449Speter * @author Lynn Monsanto (accessibility)
12750276Speter * @since 1.4
12862449Speter */
12950276Speter@SuppressWarnings("serial") // Same-version serialization only
13062449Speterpublic class JSpinner extends JComponent implements Accessible
13162449Speter{
132178866Srafan    /**
13362449Speter     * @see #getUIClassID
13462449Speter     * @see #readObject
13550276Speter     */
13650276Speter    private static final String uiClassID = "SpinnerUI";
13776726Speter
13862449Speter    private static final Action DISABLED_ACTION = new DisabledAction();
13950276Speter
140184989Srafan    private SpinnerModel model;
141166124Srafan    private JComponent editor;
14262449Speter    private ChangeListener modelListener;
14362449Speter    private transient ChangeEvent changeEvent;
144184989Srafan    private boolean editorExplicitlySet = false;
145166124Srafan
14650276Speter
14750276Speter    /**
14850276Speter     * Constructs a spinner for the given model. The spinner has
14950276Speter     * a set of previous/next buttons, and an editor appropriate
15076726Speter     * for the model.
15162449Speter     *
15250276Speter     * @param model  a model for the new spinner
153184989Srafan     * @throws NullPointerException if the model is {@code null}
15450276Speter     */
15562449Speter    public JSpinner(SpinnerModel model) {
156184989Srafan        if (model == null) {
157166124Srafan            throw new NullPointerException("model cannot be null");
15850276Speter        }
15950276Speter        this.model = model;
16076726Speter        this.editor = createEditor(model);
16162449Speter        setUIProperty("opaque",true);
16250276Speter        updateUI();
16362449Speter    }
164166124Srafan
165166124Srafan
166166124Srafan    /**
167166124Srafan     * Constructs a spinner with an <code>Integer SpinnerNumberModel</code>
168166124Srafan     * with initial value 0 and no minimum or maximum limits.
169166124Srafan     */
17050276Speter    public JSpinner() {
17150276Speter        this(new SpinnerNumberModel());
17250276Speter    }
17350276Speter
17450276Speter
17550276Speter    /**
17650276Speter     * Returns the look and feel (L&amp;F) object that renders this component.
17750276Speter     *
17850276Speter     * @return the <code>SpinnerUI</code> object that renders this component
17966963Speter     */
18062449Speter    public SpinnerUI getUI() {
181178866Srafan        return (SpinnerUI)ui;
18250276Speter    }
18350276Speter
18462449Speter
18550276Speter    /**
18662449Speter     * Sets the look and feel (L&amp;F) object that renders this component.
18750276Speter     *
18862449Speter     * @param ui  the <code>SpinnerUI</code> L&amp;F object
18976726Speter     * @see UIDefaults#getUI
19050276Speter     */
19150276Speter    public void setUI(SpinnerUI ui) {
19276726Speter        super.setUI(ui);
19362449Speter    }
19450276Speter
19550276Speter
196166124Srafan    /**
19750276Speter     * Returns the suffix used to construct the name of the look and feel
19850276Speter     * (L&amp;F) class used to render this component.
19950276Speter     *
200184989Srafan     * @return the string "SpinnerUI"
201184989Srafan     * @see JComponent#getUIClassID
202184989Srafan     * @see UIDefaults#getUI
203184989Srafan     */
204184989Srafan    public String getUIClassID() {
205184989Srafan        return uiClassID;
206184989Srafan    }
207184989Srafan
208184989Srafan
209184989Srafan
210184989Srafan    /**
211184989Srafan     * Resets the UI property with the value from the current look and feel.
212184989Srafan     *
213184989Srafan     * @see UIManager#getUI
214184989Srafan     */
215184989Srafan    public void updateUI() {
216184989Srafan        setUI((SpinnerUI)UIManager.getUI(this));
217184989Srafan        invalidate();
218184989Srafan    }
219184989Srafan
220184989Srafan
221184989Srafan    /**
222184989Srafan     * This method is called by the constructors to create the
223184989Srafan     * <code>JComponent</code>
224184989Srafan     * that displays the current value of the sequence.  The editor may
225184989Srafan     * also allow the user to enter an element of the sequence directly.
226184989Srafan     * An editor must listen for <code>ChangeEvents</code> on the
227184989Srafan     * <code>model</code> and keep the value it displays
228184989Srafan     * in sync with the value of the model.
22950276Speter     * <p>
23050276Speter     * Subclasses may override this method to add support for new
23150276Speter     * <code>SpinnerModel</code> classes.  Alternatively one can just
23250276Speter     * replace the editor created here with the <code>setEditor</code>
23350276Speter     * method.  The default mapping from model type to editor is:
23450276Speter     * <ul>
23550276Speter     * <li> <code>SpinnerNumberModel =&gt; JSpinner.NumberEditor</code>
23676726Speter     * <li> <code>SpinnerDateModel =&gt; JSpinner.DateEditor</code>
237178866Srafan     * <li> <code>SpinnerListModel =&gt; JSpinner.ListEditor</code>
23850276Speter     * <li> <i>all others</i> =&gt; <code>JSpinner.DefaultEditor</code>
239184989Srafan     * </ul>
24050276Speter     *
241178866Srafan     * @return a component that displays the current value of the sequence
242184989Srafan     * @param model the value of getModel
243184989Srafan     * @see #getModel
244184989Srafan     * @see #setEditor
245184989Srafan     */
246184989Srafan    protected JComponent createEditor(SpinnerModel model) {
247184989Srafan        if (model instanceof SpinnerDateModel) {
248184989Srafan            return new DateEditor(this);
249184989Srafan        }
250184989Srafan        else if (model instanceof SpinnerListModel) {
251184989Srafan            return new ListEditor(this);
252184989Srafan        }
253184989Srafan        else if (model instanceof SpinnerNumberModel) {
254184989Srafan            return new NumberEditor(this);
255184989Srafan        }
256184989Srafan        else {
257184989Srafan            return new DefaultEditor(this);
258184989Srafan        }
259184989Srafan    }
260184989Srafan
261184989Srafan
262184989Srafan    /**
263184989Srafan     * Changes the model that represents the value of this spinner.
264184989Srafan     * If the editor property has not been explicitly set,
265184989Srafan     * the editor property is (implicitly) set after the <code>"model"</code>
266184989Srafan     * <code>PropertyChangeEvent</code> has been fired.  The editor
267184989Srafan     * property is set to the value returned by <code>createEditor</code>,
268184989Srafan     * as in:
269184989Srafan     * <pre>
270184989Srafan     * setEditor(createEditor(model));
271184989Srafan     * </pre>
272184989Srafan     *
273166124Srafan     * @param model the new <code>SpinnerModel</code>
27462449Speter     * @see #getModel
275184989Srafan     * @see #getEditor
27650276Speter     * @see #setEditor
277184989Srafan     * @throws IllegalArgumentException if model is <code>null</code>
278184989Srafan     *
279184989Srafan     * @beaninfo
280184989Srafan     *        bound: true
281184989Srafan     *    attribute: visualUpdate true
282184989Srafan     *  description: Model that represents the value of this spinner.
283184989Srafan     */
284184989Srafan    public void setModel(SpinnerModel model) {
285184989Srafan        if (model == null) {
286184989Srafan            throw new IllegalArgumentException("null model");
287184989Srafan        }
288184989Srafan        if (!model.equals(this.model)) {
289184989Srafan            SpinnerModel oldModel = this.model;
290184989Srafan            this.model = model;
291184989Srafan            if (modelListener != null) {
292184989Srafan                oldModel.removeChangeListener(modelListener);
293184989Srafan                this.model.addChangeListener(modelListener);
294184989Srafan            }
295184989Srafan            firePropertyChange("model", oldModel, model);
296184989Srafan            if (!editorExplicitlySet) {
297184989Srafan                setEditor(createEditor(model)); // sets editorExplicitlySet true
298184989Srafan                editorExplicitlySet = false;
299184989Srafan            }
300184989Srafan            repaint();
301184989Srafan            revalidate();
302184989Srafan        }
303184989Srafan    }
304184989Srafan
305184989Srafan
306184989Srafan    /**
307184989Srafan     * Returns the <code>SpinnerModel</code> that defines
308184989Srafan     * this spinners sequence of values.
309184989Srafan     *
310184989Srafan     * @return the value of the model property
311184989Srafan     * @see #setModel
312184989Srafan     */
313184989Srafan    public SpinnerModel getModel() {
314184989Srafan        return model;
315184989Srafan    }
316184989Srafan
317184989Srafan
318184989Srafan    /**
319184989Srafan     * Returns the current value of the model, typically
320184989Srafan     * this value is displayed by the <code>editor</code>. If the
321184989Srafan     * user has changed the value displayed by the <code>editor</code> it is
322184989Srafan     * possible for the <code>model</code>'s value to differ from that of
323184989Srafan     * the <code>editor</code>, refer to the class level javadoc for examples
324184989Srafan     * of how to deal with this.
325184989Srafan     * <p>
326184989Srafan     * This method simply delegates to the <code>model</code>.
327184989Srafan     * It is equivalent to:
328     * <pre>
329     * getModel().getValue()
330     * </pre>
331     *
332     * @return the current value of the model
333     * @see #setValue
334     * @see SpinnerModel#getValue
335     */
336    public Object getValue() {
337        return getModel().getValue();
338    }
339
340
341    /**
342     * Changes current value of the model, typically
343     * this value is displayed by the <code>editor</code>.
344     * If the <code>SpinnerModel</code> implementation
345     * doesn't support the specified value then an
346     * <code>IllegalArgumentException</code> is thrown.
347     * <p>
348     * This method simply delegates to the <code>model</code>.
349     * It is equivalent to:
350     * <pre>
351     * getModel().setValue(value)
352     * </pre>
353     *
354     * @param value  new value for the spinner
355     * @throws IllegalArgumentException if <code>value</code> isn't allowed
356     * @see #getValue
357     * @see SpinnerModel#setValue
358     */
359    public void setValue(Object value) {
360        getModel().setValue(value);
361    }
362
363
364    /**
365     * Returns the object in the sequence that comes after the object returned
366     * by <code>getValue()</code>. If the end of the sequence has been reached
367     * then return <code>null</code>.
368     * Calling this method does not effect <code>value</code>.
369     * <p>
370     * This method simply delegates to the <code>model</code>.
371     * It is equivalent to:
372     * <pre>
373     * getModel().getNextValue()
374     * </pre>
375     *
376     * @return the next legal value or <code>null</code> if one doesn't exist
377     * @see #getValue
378     * @see #getPreviousValue
379     * @see SpinnerModel#getNextValue
380     */
381    public Object getNextValue() {
382        return getModel().getNextValue();
383    }
384
385
386    /**
387     * We pass <code>Change</code> events along to the listeners with the
388     * the slider (instead of the model itself) as the event source.
389     */
390    private class ModelListener implements ChangeListener, Serializable {
391        public void stateChanged(ChangeEvent e) {
392            fireStateChanged();
393        }
394    }
395
396
397    /**
398     * Adds a listener to the list that is notified each time a change
399     * to the model occurs.  The source of <code>ChangeEvents</code>
400     * delivered to <code>ChangeListeners</code> will be this
401     * <code>JSpinner</code>.  Note also that replacing the model
402     * will not affect listeners added directly to JSpinner.
403     * Applications can add listeners to  the model directly.  In that
404     * case is that the source of the event would be the
405     * <code>SpinnerModel</code>.
406     *
407     * @param listener the <code>ChangeListener</code> to add
408     * @see #removeChangeListener
409     * @see #getModel
410     */
411    public void addChangeListener(ChangeListener listener) {
412        if (modelListener == null) {
413            modelListener = new ModelListener();
414            getModel().addChangeListener(modelListener);
415        }
416        listenerList.add(ChangeListener.class, listener);
417    }
418
419
420
421    /**
422     * Removes a <code>ChangeListener</code> from this spinner.
423     *
424     * @param listener the <code>ChangeListener</code> to remove
425     * @see #fireStateChanged
426     * @see #addChangeListener
427     */
428    public void removeChangeListener(ChangeListener listener) {
429        listenerList.remove(ChangeListener.class, listener);
430    }
431
432
433    /**
434     * Returns an array of all the <code>ChangeListener</code>s added
435     * to this JSpinner with addChangeListener().
436     *
437     * @return all of the <code>ChangeListener</code>s added or an empty
438     *         array if no listeners have been added
439     * @since 1.4
440     */
441    public ChangeListener[] getChangeListeners() {
442        return listenerList.getListeners(ChangeListener.class);
443    }
444
445
446    /**
447     * Sends a <code>ChangeEvent</code>, whose source is this
448     * <code>JSpinner</code>, to each <code>ChangeListener</code>.
449     * When a <code>ChangeListener</code> has been added
450     * to the spinner, this method is called each time
451     * a <code>ChangeEvent</code> is received from the model.
452     *
453     * @see #addChangeListener
454     * @see #removeChangeListener
455     * @see EventListenerList
456     */
457    protected void fireStateChanged() {
458        Object[] listeners = listenerList.getListenerList();
459        for (int i = listeners.length - 2; i >= 0; i -= 2) {
460            if (listeners[i] == ChangeListener.class) {
461                if (changeEvent == null) {
462                    changeEvent = new ChangeEvent(this);
463                }
464                ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
465            }
466        }
467    }
468
469
470    /**
471     * Returns the object in the sequence that comes
472     * before the object returned by <code>getValue()</code>.
473     * If the end of the sequence has been reached then
474     * return <code>null</code>. Calling this method does
475     * not effect <code>value</code>.
476     * <p>
477     * This method simply delegates to the <code>model</code>.
478     * It is equivalent to:
479     * <pre>
480     * getModel().getPreviousValue()
481     * </pre>
482     *
483     * @return the previous legal value or <code>null</code>
484     *   if one doesn't exist
485     * @see #getValue
486     * @see #getNextValue
487     * @see SpinnerModel#getPreviousValue
488     */
489    public Object getPreviousValue() {
490        return getModel().getPreviousValue();
491    }
492
493
494    /**
495     * Changes the <code>JComponent</code> that displays the current value
496     * of the <code>SpinnerModel</code>.  It is the responsibility of this
497     * method to <i>disconnect</i> the old editor from the model and to
498     * connect the new editor.  This may mean removing the
499     * old editors <code>ChangeListener</code> from the model or the
500     * spinner itself and adding one for the new editor.
501     *
502     * @param editor the new editor
503     * @see #getEditor
504     * @see #createEditor
505     * @see #getModel
506     * @throws IllegalArgumentException if editor is <code>null</code>
507     *
508     * @beaninfo
509     *        bound: true
510     *    attribute: visualUpdate true
511     *  description: JComponent that displays the current value of the model
512     */
513    public void setEditor(JComponent editor) {
514        if (editor == null) {
515            throw new IllegalArgumentException("null editor");
516        }
517        if (!editor.equals(this.editor)) {
518            JComponent oldEditor = this.editor;
519            this.editor = editor;
520            if (oldEditor instanceof DefaultEditor) {
521                ((DefaultEditor)oldEditor).dismiss(this);
522            }
523            editorExplicitlySet = true;
524            firePropertyChange("editor", oldEditor, editor);
525            revalidate();
526            repaint();
527        }
528    }
529
530
531    /**
532     * Returns the component that displays and potentially
533     * changes the model's value.
534     *
535     * @return the component that displays and potentially
536     *    changes the model's value
537     * @see #setEditor
538     * @see #createEditor
539     */
540    public JComponent getEditor() {
541        return editor;
542    }
543
544
545    /**
546     * Commits the currently edited value to the <code>SpinnerModel</code>.
547     * <p>
548     * If the editor is an instance of <code>DefaultEditor</code>, the
549     * call if forwarded to the editor, otherwise this does nothing.
550     *
551     * @throws ParseException if the currently edited value couldn't
552     *         be committed.
553     */
554    public void commitEdit() throws ParseException {
555        JComponent editor = getEditor();
556        if (editor instanceof DefaultEditor) {
557            ((DefaultEditor)editor).commitEdit();
558        }
559    }
560
561
562    /*
563     * See readObject and writeObject in JComponent for more
564     * information about serialization in Swing.
565     *
566     * @param s Stream to write to
567     */
568    private void writeObject(ObjectOutputStream s) throws IOException {
569        s.defaultWriteObject();
570        if (getUIClassID().equals(uiClassID)) {
571            byte count = JComponent.getWriteObjCounter(this);
572            JComponent.setWriteObjCounter(this, --count);
573            if (count == 0 && ui != null) {
574                ui.installUI(this);
575            }
576        }
577    }
578
579
580    /**
581     * A simple base class for more specialized editors
582     * that displays a read-only view of the model's current
583     * value with a <code>JFormattedTextField</code>.  Subclasses
584     * can configure the <code>JFormattedTextField</code> to create
585     * an editor that's appropriate for the type of model they
586     * support and they may want to override
587     * the <code>stateChanged</code> and <code>propertyChanged</code>
588     * methods, which keep the model and the text field in sync.
589     * <p>
590     * This class defines a <code>dismiss</code> method that removes the
591     * editors <code>ChangeListener</code> from the <code>JSpinner</code>
592     * that it's part of.   The <code>setEditor</code> method knows about
593     * <code>DefaultEditor.dismiss</code>, so if the developer
594     * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
595     * its <code>ChangeListener</code> connection back to the
596     * <code>JSpinner</code> will be removed.  However after that,
597     * it's up to the developer to manage their editor listeners.
598     * Similarly, if a subclass overrides <code>createEditor</code>,
599     * it's up to the subclasser to deal with their editor
600     * subsequently being replaced (with <code>setEditor</code>).
601     * We expect that in most cases, and in editor installed
602     * with <code>setEditor</code> or created by a <code>createEditor</code>
603     * override, will not be replaced anyway.
604     * <p>
605     * This class is the <code>LayoutManager</code> for it's single
606     * <code>JFormattedTextField</code> child.   By default the
607     * child is just centered with the parents insets.
608     * @since 1.4
609     */
610    public static class DefaultEditor extends JPanel
611        implements ChangeListener, PropertyChangeListener, LayoutManager
612    {
613        /**
614         * Constructs an editor component for the specified <code>JSpinner</code>.
615         * This <code>DefaultEditor</code> is it's own layout manager and
616         * it is added to the spinner's <code>ChangeListener</code> list.
617         * The constructor creates a single <code>JFormattedTextField</code> child,
618         * initializes it's value to be the spinner model's current value
619         * and adds it to <code>this</code> <code>DefaultEditor</code>.
620         *
621         * @param spinner the spinner whose model <code>this</code> editor will monitor
622         * @see #getTextField
623         * @see JSpinner#addChangeListener
624         */
625        public DefaultEditor(JSpinner spinner) {
626            super(null);
627
628            JFormattedTextField ftf = new JFormattedTextField();
629            ftf.setName("Spinner.formattedTextField");
630            ftf.setValue(spinner.getValue());
631            ftf.addPropertyChangeListener(this);
632            ftf.setEditable(false);
633            ftf.setInheritsPopupMenu(true);
634
635            String toolTipText = spinner.getToolTipText();
636            if (toolTipText != null) {
637                ftf.setToolTipText(toolTipText);
638            }
639
640            add(ftf);
641
642            setLayout(this);
643            spinner.addChangeListener(this);
644
645            // We want the spinner's increment/decrement actions to be
646            // active vs those of the JFormattedTextField. As such we
647            // put disabled actions in the JFormattedTextField's actionmap.
648            // A binding to a disabled action is treated as a nonexistant
649            // binding.
650            ActionMap ftfMap = ftf.getActionMap();
651
652            if (ftfMap != null) {
653                ftfMap.put("increment", DISABLED_ACTION);
654                ftfMap.put("decrement", DISABLED_ACTION);
655            }
656        }
657
658
659        /**
660         * Disconnect <code>this</code> editor from the specified
661         * <code>JSpinner</code>.  By default, this method removes
662         * itself from the spinners <code>ChangeListener</code> list.
663         *
664         * @param spinner the <code>JSpinner</code> to disconnect this
665         *    editor from; the same spinner as was passed to the constructor.
666         */
667        public void dismiss(JSpinner spinner) {
668            spinner.removeChangeListener(this);
669        }
670
671
672        /**
673         * Returns the <code>JSpinner</code> ancestor of this editor or
674         * <code>null</code> if none of the ancestors are a
675         * <code>JSpinner</code>.
676         * Typically the editor's parent is a <code>JSpinner</code> however
677         * subclasses of <code>JSpinner</code> may override the
678         * the <code>createEditor</code> method and insert one or more containers
679         * between the <code>JSpinner</code> and it's editor.
680         *
681         * @return <code>JSpinner</code> ancestor; <code>null</code>
682         *         if none of the ancestors are a <code>JSpinner</code>
683         *
684         * @see JSpinner#createEditor
685         */
686        public JSpinner getSpinner() {
687            for (Component c = this; c != null; c = c.getParent()) {
688                if (c instanceof JSpinner) {
689                    return (JSpinner)c;
690                }
691            }
692            return null;
693        }
694
695
696        /**
697         * Returns the <code>JFormattedTextField</code> child of this
698         * editor.  By default the text field is the first and only
699         * child of editor.
700         *
701         * @return the <code>JFormattedTextField</code> that gives the user
702         *     access to the <code>SpinnerDateModel's</code> value.
703         * @see #getSpinner
704         * @see #getModel
705         */
706        public JFormattedTextField getTextField() {
707            return (JFormattedTextField)getComponent(0);
708        }
709
710
711        /**
712         * This method is called when the spinner's model's state changes.
713         * It sets the <code>value</code> of the text field to the current
714         * value of the spinners model.
715         *
716         * @param e the <code>ChangeEvent</code> whose source is the
717         * <code>JSpinner</code> whose model has changed.
718         * @see #getTextField
719         * @see JSpinner#getValue
720         */
721        public void stateChanged(ChangeEvent e) {
722            JSpinner spinner = (JSpinner)(e.getSource());
723            getTextField().setValue(spinner.getValue());
724        }
725
726
727        /**
728         * Called by the <code>JFormattedTextField</code>
729         * <code>PropertyChangeListener</code>.  When the <code>"value"</code>
730         * property changes, which implies that the user has typed a new
731         * number, we set the value of the spinners model.
732         * <p>
733         * This class ignores <code>PropertyChangeEvents</code> whose
734         * source is not the <code>JFormattedTextField</code>, so subclasses
735         * may safely make <code>this</code> <code>DefaultEditor</code> a
736         * <code>PropertyChangeListener</code> on other objects.
737         *
738         * @param e the <code>PropertyChangeEvent</code> whose source is
739         *    the <code>JFormattedTextField</code> created by this class.
740         * @see #getTextField
741         */
742        public void propertyChange(PropertyChangeEvent e)
743        {
744            JSpinner spinner = getSpinner();
745
746            if (spinner == null) {
747                // Indicates we aren't installed anywhere.
748                return;
749            }
750
751            Object source = e.getSource();
752            String name = e.getPropertyName();
753            if ((source instanceof JFormattedTextField) && "value".equals(name)) {
754                Object lastValue = spinner.getValue();
755
756                // Try to set the new value
757                try {
758                    spinner.setValue(getTextField().getValue());
759                } catch (IllegalArgumentException iae) {
760                    // SpinnerModel didn't like new value, reset
761                    try {
762                        ((JFormattedTextField)source).setValue(lastValue);
763                    } catch (IllegalArgumentException iae2) {
764                        // Still bogus, nothing else we can do, the
765                        // SpinnerModel and JFormattedTextField are now out
766                        // of sync.
767                    }
768                }
769            }
770        }
771
772
773        /**
774         * This <code>LayoutManager</code> method does nothing.  We're
775         * only managing a single child and there's no support
776         * for layout constraints.
777         *
778         * @param name ignored
779         * @param child ignored
780         */
781        public void addLayoutComponent(String name, Component child) {
782        }
783
784
785        /**
786         * This <code>LayoutManager</code> method does nothing.  There
787         * isn't any per-child state.
788         *
789         * @param child ignored
790         */
791        public void removeLayoutComponent(Component child) {
792        }
793
794
795        /**
796         * Returns the size of the parents insets.
797         */
798        private Dimension insetSize(Container parent) {
799            Insets insets = parent.getInsets();
800            int w = insets.left + insets.right;
801            int h = insets.top + insets.bottom;
802            return new Dimension(w, h);
803        }
804
805
806        /**
807         * Returns the preferred size of first (and only) child plus the
808         * size of the parents insets.
809         *
810         * @param parent the Container that's managing the layout
811         * @return the preferred dimensions to lay out the subcomponents
812         *          of the specified container.
813         */
814        public Dimension preferredLayoutSize(Container parent) {
815            Dimension preferredSize = insetSize(parent);
816            if (parent.getComponentCount() > 0) {
817                Dimension childSize = getComponent(0).getPreferredSize();
818                preferredSize.width += childSize.width;
819                preferredSize.height += childSize.height;
820            }
821            return preferredSize;
822        }
823
824
825        /**
826         * Returns the minimum size of first (and only) child plus the
827         * size of the parents insets.
828         *
829         * @param parent the Container that's managing the layout
830         * @return  the minimum dimensions needed to lay out the subcomponents
831         *          of the specified container.
832         */
833        public Dimension minimumLayoutSize(Container parent) {
834            Dimension minimumSize = insetSize(parent);
835            if (parent.getComponentCount() > 0) {
836                Dimension childSize = getComponent(0).getMinimumSize();
837                minimumSize.width += childSize.width;
838                minimumSize.height += childSize.height;
839            }
840            return minimumSize;
841        }
842
843
844        /**
845         * Resize the one (and only) child to completely fill the area
846         * within the parents insets.
847         */
848        public void layoutContainer(Container parent) {
849            if (parent.getComponentCount() > 0) {
850                Insets insets = parent.getInsets();
851                int w = parent.getWidth() - (insets.left + insets.right);
852                int h = parent.getHeight() - (insets.top + insets.bottom);
853                getComponent(0).setBounds(insets.left, insets.top, w, h);
854            }
855        }
856
857        /**
858         * Pushes the currently edited value to the <code>SpinnerModel</code>.
859         * <p>
860         * The default implementation invokes <code>commitEdit</code> on the
861         * <code>JFormattedTextField</code>.
862         *
863         * @throws ParseException if the edited value is not legal
864         */
865        public void commitEdit()  throws ParseException {
866            // If the value in the JFormattedTextField is legal, this will have
867            // the result of pushing the value to the SpinnerModel
868            // by way of the <code>propertyChange</code> method.
869            JFormattedTextField ftf = getTextField();
870
871            ftf.commitEdit();
872        }
873
874        /**
875         * Returns the baseline.
876         *
877         * @throws IllegalArgumentException {@inheritDoc}
878         * @see javax.swing.JComponent#getBaseline(int,int)
879         * @see javax.swing.JComponent#getBaselineResizeBehavior()
880         * @since 1.6
881         */
882        public int getBaseline(int width, int height) {
883            // check size.
884            super.getBaseline(width, height);
885            Insets insets = getInsets();
886            width = width - insets.left - insets.right;
887            height = height - insets.top - insets.bottom;
888            int baseline = getComponent(0).getBaseline(width, height);
889            if (baseline >= 0) {
890                return baseline + insets.top;
891            }
892            return -1;
893        }
894
895        /**
896         * Returns an enum indicating how the baseline of the component
897         * changes as the size changes.
898         *
899         * @throws NullPointerException {@inheritDoc}
900         * @see javax.swing.JComponent#getBaseline(int, int)
901         * @since 1.6
902         */
903        public BaselineResizeBehavior getBaselineResizeBehavior() {
904            return getComponent(0).getBaselineResizeBehavior();
905        }
906    }
907
908
909
910
911    /**
912     * This subclass of javax.swing.DateFormatter maps the minimum/maximum
913     * properties to the start/end properties of a SpinnerDateModel.
914     */
915    private static class DateEditorFormatter extends DateFormatter {
916        private final SpinnerDateModel model;
917
918        DateEditorFormatter(SpinnerDateModel model, DateFormat format) {
919            super(format);
920            this.model = model;
921        }
922
923        @Override
924        @SuppressWarnings("unchecked")
925        public void setMinimum(Comparable<?> min) {
926            model.setStart((Comparable<Date>)min);
927        }
928
929        @Override
930        public Comparable<Date> getMinimum() {
931            return  model.getStart();
932        }
933
934        @Override
935        @SuppressWarnings("unchecked")
936        public void setMaximum(Comparable<?> max) {
937            model.setEnd((Comparable<Date>)max);
938        }
939
940        @Override
941        public Comparable<Date> getMaximum() {
942            return model.getEnd();
943        }
944    }
945
946
947    /**
948     * An editor for a <code>JSpinner</code> whose model is a
949     * <code>SpinnerDateModel</code>.  The value of the editor is
950     * displayed with a <code>JFormattedTextField</code> whose format
951     * is defined by a <code>DateFormatter</code> instance whose
952     * <code>minimum</code> and <code>maximum</code> properties
953     * are mapped to the <code>SpinnerDateModel</code>.
954     * @since 1.4
955     */
956    // PENDING(hmuller): more example javadoc
957    public static class DateEditor extends DefaultEditor
958    {
959        // This is here until SimpleDateFormat gets a constructor that
960        // takes a Locale: 4923525
961        private static String getDefaultPattern(Locale loc) {
962            LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DateFormatProvider.class, loc);
963            LocaleResources lr = adapter.getLocaleResources(loc);
964            if (lr == null) {
965                lr = LocaleProviderAdapter.forJRE().getLocaleResources(loc);
966            }
967            return lr.getDateTimePattern(DateFormat.SHORT, DateFormat.SHORT, null);
968        }
969
970        /**
971         * Construct a <code>JSpinner</code> editor that supports displaying
972         * and editing the value of a <code>SpinnerDateModel</code>
973         * with a <code>JFormattedTextField</code>.  <code>This</code>
974         * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
975         * on the spinners model and a <code>PropertyChangeListener</code>
976         * on the new <code>JFormattedTextField</code>.
977         *
978         * @param spinner the spinner whose model <code>this</code> editor will monitor
979         * @exception IllegalArgumentException if the spinners model is not
980         *     an instance of <code>SpinnerDateModel</code>
981         *
982         * @see #getModel
983         * @see #getFormat
984         * @see SpinnerDateModel
985         */
986        public DateEditor(JSpinner spinner) {
987            this(spinner, getDefaultPattern(spinner.getLocale()));
988        }
989
990
991        /**
992         * Construct a <code>JSpinner</code> editor that supports displaying
993         * and editing the value of a <code>SpinnerDateModel</code>
994         * with a <code>JFormattedTextField</code>.  <code>This</code>
995         * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
996         * on the spinner and a <code>PropertyChangeListener</code>
997         * on the new <code>JFormattedTextField</code>.
998         *
999         * @param spinner the spinner whose model <code>this</code> editor will monitor
1000         * @param dateFormatPattern the initial pattern for the
1001         *     <code>SimpleDateFormat</code> object that's used to display
1002         *     and parse the value of the text field.
1003         * @exception IllegalArgumentException if the spinners model is not
1004         *     an instance of <code>SpinnerDateModel</code>
1005         *
1006         * @see #getModel
1007         * @see #getFormat
1008         * @see SpinnerDateModel
1009         * @see java.text.SimpleDateFormat
1010         */
1011        public DateEditor(JSpinner spinner, String dateFormatPattern) {
1012            this(spinner, new SimpleDateFormat(dateFormatPattern,
1013                                               spinner.getLocale()));
1014        }
1015
1016        /**
1017         * Construct a <code>JSpinner</code> editor that supports displaying
1018         * and editing the value of a <code>SpinnerDateModel</code>
1019         * with a <code>JFormattedTextField</code>.  <code>This</code>
1020         * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
1021         * on the spinner and a <code>PropertyChangeListener</code>
1022         * on the new <code>JFormattedTextField</code>.
1023         *
1024         * @param spinner the spinner whose model <code>this</code> editor
1025         *        will monitor
1026         * @param format <code>DateFormat</code> object that's used to display
1027         *     and parse the value of the text field.
1028         * @exception IllegalArgumentException if the spinners model is not
1029         *     an instance of <code>SpinnerDateModel</code>
1030         *
1031         * @see #getModel
1032         * @see #getFormat
1033         * @see SpinnerDateModel
1034         * @see java.text.SimpleDateFormat
1035         */
1036        private DateEditor(JSpinner spinner, DateFormat format) {
1037            super(spinner);
1038            if (!(spinner.getModel() instanceof SpinnerDateModel)) {
1039                throw new IllegalArgumentException(
1040                                 "model not a SpinnerDateModel");
1041            }
1042
1043            SpinnerDateModel model = (SpinnerDateModel)spinner.getModel();
1044            DateFormatter formatter = new DateEditorFormatter(model, format);
1045            DefaultFormatterFactory factory = new DefaultFormatterFactory(
1046                                                  formatter);
1047            JFormattedTextField ftf = getTextField();
1048            ftf.setEditable(true);
1049            ftf.setFormatterFactory(factory);
1050
1051            /* TBD - initializing the column width of the text field
1052             * is imprecise and doing it here is tricky because
1053             * the developer may configure the formatter later.
1054             */
1055            try {
1056                String maxString = formatter.valueToString(model.getStart());
1057                String minString = formatter.valueToString(model.getEnd());
1058                ftf.setColumns(Math.max(maxString.length(),
1059                                        minString.length()));
1060            }
1061            catch (ParseException e) {
1062                // PENDING: hmuller
1063            }
1064        }
1065
1066        /**
1067         * Returns the <code>java.text.SimpleDateFormat</code> object the
1068         * <code>JFormattedTextField</code> uses to parse and format
1069         * numbers.
1070         *
1071         * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1072         * @see #getTextField
1073         * @see java.text.SimpleDateFormat
1074         */
1075        public SimpleDateFormat getFormat() {
1076            return (SimpleDateFormat)((DateFormatter)(getTextField().getFormatter())).getFormat();
1077        }
1078
1079
1080        /**
1081         * Return our spinner ancestor's <code>SpinnerDateModel</code>.
1082         *
1083         * @return <code>getSpinner().getModel()</code>
1084         * @see #getSpinner
1085         * @see #getTextField
1086         */
1087        public SpinnerDateModel getModel() {
1088            return (SpinnerDateModel)(getSpinner().getModel());
1089        }
1090    }
1091
1092
1093    /**
1094     * This subclass of javax.swing.NumberFormatter maps the minimum/maximum
1095     * properties to a SpinnerNumberModel and initializes the valueClass
1096     * of the NumberFormatter to match the type of the initial models value.
1097     */
1098    private static class NumberEditorFormatter extends NumberFormatter {
1099        private final SpinnerNumberModel model;
1100
1101        NumberEditorFormatter(SpinnerNumberModel model, NumberFormat format) {
1102            super(format);
1103            this.model = model;
1104            setValueClass(model.getValue().getClass());
1105        }
1106
1107        @Override
1108        public void setMinimum(Comparable<?> min) {
1109            model.setMinimum(min);
1110        }
1111
1112        @Override
1113        public Comparable<?> getMinimum() {
1114            return  model.getMinimum();
1115        }
1116
1117        @Override
1118        public void setMaximum(Comparable<?> max) {
1119            model.setMaximum(max);
1120        }
1121
1122        @Override
1123        public Comparable<?> getMaximum() {
1124            return model.getMaximum();
1125        }
1126    }
1127
1128
1129
1130    /**
1131     * An editor for a <code>JSpinner</code> whose model is a
1132     * <code>SpinnerNumberModel</code>.  The value of the editor is
1133     * displayed with a <code>JFormattedTextField</code> whose format
1134     * is defined by a <code>NumberFormatter</code> instance whose
1135     * <code>minimum</code> and <code>maximum</code> properties
1136     * are mapped to the <code>SpinnerNumberModel</code>.
1137     * @since 1.4
1138     */
1139    // PENDING(hmuller): more example javadoc
1140    public static class NumberEditor extends DefaultEditor
1141    {
1142        // This is here until DecimalFormat gets a constructor that
1143        // takes a Locale: 4923525
1144        private static String getDefaultPattern(Locale locale) {
1145            // Get the pattern for the default locale.
1146            LocaleProviderAdapter adapter;
1147            adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
1148                                                       locale);
1149            LocaleResources lr = adapter.getLocaleResources(locale);
1150            if (lr == null) {
1151                lr = LocaleProviderAdapter.forJRE().getLocaleResources(locale);
1152            }
1153            String[] all = lr.getNumberPatterns();
1154            return all[0];
1155        }
1156
1157        /**
1158         * Construct a <code>JSpinner</code> editor that supports displaying
1159         * and editing the value of a <code>SpinnerNumberModel</code>
1160         * with a <code>JFormattedTextField</code>.  <code>This</code>
1161         * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1162         * on the spinner and a <code>PropertyChangeListener</code>
1163         * on the new <code>JFormattedTextField</code>.
1164         *
1165         * @param spinner the spinner whose model <code>this</code> editor will monitor
1166         * @exception IllegalArgumentException if the spinners model is not
1167         *     an instance of <code>SpinnerNumberModel</code>
1168         *
1169         * @see #getModel
1170         * @see #getFormat
1171         * @see SpinnerNumberModel
1172         */
1173        public NumberEditor(JSpinner spinner) {
1174            this(spinner, getDefaultPattern(spinner.getLocale()));
1175        }
1176
1177        /**
1178         * Construct a <code>JSpinner</code> editor that supports displaying
1179         * and editing the value of a <code>SpinnerNumberModel</code>
1180         * with a <code>JFormattedTextField</code>.  <code>This</code>
1181         * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1182         * on the spinner and a <code>PropertyChangeListener</code>
1183         * on the new <code>JFormattedTextField</code>.
1184         *
1185         * @param spinner the spinner whose model <code>this</code> editor will monitor
1186         * @param decimalFormatPattern the initial pattern for the
1187         *     <code>DecimalFormat</code> object that's used to display
1188         *     and parse the value of the text field.
1189         * @exception IllegalArgumentException if the spinners model is not
1190         *     an instance of <code>SpinnerNumberModel</code> or if
1191         *     <code>decimalFormatPattern</code> is not a legal
1192         *     argument to <code>DecimalFormat</code>
1193         *
1194         * @see #getTextField
1195         * @see SpinnerNumberModel
1196         * @see java.text.DecimalFormat
1197         */
1198        public NumberEditor(JSpinner spinner, String decimalFormatPattern) {
1199            this(spinner, new DecimalFormat(decimalFormatPattern));
1200        }
1201
1202
1203        /**
1204         * Construct a <code>JSpinner</code> editor that supports displaying
1205         * and editing the value of a <code>SpinnerNumberModel</code>
1206         * with a <code>JFormattedTextField</code>.  <code>This</code>
1207         * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1208         * on the spinner and a <code>PropertyChangeListener</code>
1209         * on the new <code>JFormattedTextField</code>.
1210         *
1211         * @param spinner the spinner whose model <code>this</code> editor will monitor
1212         * @param decimalFormatPattern the initial pattern for the
1213         *     <code>DecimalFormat</code> object that's used to display
1214         *     and parse the value of the text field.
1215         * @exception IllegalArgumentException if the spinners model is not
1216         *     an instance of <code>SpinnerNumberModel</code>
1217         *
1218         * @see #getTextField
1219         * @see SpinnerNumberModel
1220         * @see java.text.DecimalFormat
1221         */
1222        private NumberEditor(JSpinner spinner, DecimalFormat format) {
1223            super(spinner);
1224            if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
1225                throw new IllegalArgumentException(
1226                          "model not a SpinnerNumberModel");
1227            }
1228
1229            SpinnerNumberModel model = (SpinnerNumberModel)spinner.getModel();
1230            NumberFormatter formatter = new NumberEditorFormatter(model,
1231                                                                  format);
1232            DefaultFormatterFactory factory = new DefaultFormatterFactory(
1233                                                  formatter);
1234            JFormattedTextField ftf = getTextField();
1235            ftf.setEditable(true);
1236            ftf.setFormatterFactory(factory);
1237            // Change the text orientation for the NumberEditor
1238            ftf.setHorizontalAlignment(JTextField.RIGHT);
1239
1240            /* TBD - initializing the column width of the text field
1241             * is imprecise and doing it here is tricky because
1242             * the developer may configure the formatter later.
1243             */
1244            try {
1245                String maxString = formatter.valueToString(model.getMinimum());
1246                String minString = formatter.valueToString(model.getMaximum());
1247                ftf.setColumns(Math.max(maxString.length(),
1248                                        minString.length()));
1249            }
1250            catch (ParseException e) {
1251                // TBD should throw a chained error here
1252            }
1253
1254        }
1255
1256
1257        /**
1258         * Returns the <code>java.text.DecimalFormat</code> object the
1259         * <code>JFormattedTextField</code> uses to parse and format
1260         * numbers.
1261         *
1262         * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1263         * @see #getTextField
1264         * @see java.text.DecimalFormat
1265         */
1266        public DecimalFormat getFormat() {
1267            return (DecimalFormat)((NumberFormatter)(getTextField().getFormatter())).getFormat();
1268        }
1269
1270
1271        /**
1272         * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1273         *
1274         * @return <code>getSpinner().getModel()</code>
1275         * @see #getSpinner
1276         * @see #getTextField
1277         */
1278        public SpinnerNumberModel getModel() {
1279            return (SpinnerNumberModel)(getSpinner().getModel());
1280        }
1281
1282        /**
1283         * {@inheritDoc}
1284         */
1285        @Override
1286        public void setComponentOrientation(ComponentOrientation o) {
1287            super.setComponentOrientation(o);
1288            getTextField().setHorizontalAlignment(
1289                    o.isLeftToRight() ? JTextField.RIGHT : JTextField.LEFT);
1290        }
1291    }
1292
1293
1294    /**
1295     * An editor for a <code>JSpinner</code> whose model is a
1296     * <code>SpinnerListModel</code>.
1297     * @since 1.4
1298     */
1299    public static class ListEditor extends DefaultEditor
1300    {
1301        /**
1302         * Construct a <code>JSpinner</code> editor that supports displaying
1303         * and editing the value of a <code>SpinnerListModel</code>
1304         * with a <code>JFormattedTextField</code>.  <code>This</code>
1305         * <code>ListEditor</code> becomes both a <code>ChangeListener</code>
1306         * on the spinner and a <code>PropertyChangeListener</code>
1307         * on the new <code>JFormattedTextField</code>.
1308         *
1309         * @param spinner the spinner whose model <code>this</code> editor will monitor
1310         * @exception IllegalArgumentException if the spinners model is not
1311         *     an instance of <code>SpinnerListModel</code>
1312         *
1313         * @see #getModel
1314         * @see SpinnerListModel
1315         */
1316        public ListEditor(JSpinner spinner) {
1317            super(spinner);
1318            if (!(spinner.getModel() instanceof SpinnerListModel)) {
1319                throw new IllegalArgumentException("model not a SpinnerListModel");
1320            }
1321            getTextField().setEditable(true);
1322            getTextField().setFormatterFactory(new
1323                              DefaultFormatterFactory(new ListFormatter()));
1324        }
1325
1326        /**
1327         * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1328         *
1329         * @return <code>getSpinner().getModel()</code>
1330         * @see #getSpinner
1331         * @see #getTextField
1332         */
1333        public SpinnerListModel getModel() {
1334            return (SpinnerListModel)(getSpinner().getModel());
1335        }
1336
1337
1338        /**
1339         * ListFormatter provides completion while text is being input
1340         * into the JFormattedTextField. Completion is only done if the
1341         * user is inserting text at the end of the document. Completion
1342         * is done by way of the SpinnerListModel method findNextMatch.
1343         */
1344        private class ListFormatter extends
1345                          JFormattedTextField.AbstractFormatter {
1346            private DocumentFilter filter;
1347
1348            public String valueToString(Object value) throws ParseException {
1349                if (value == null) {
1350                    return "";
1351                }
1352                return value.toString();
1353            }
1354
1355            public Object stringToValue(String string) throws ParseException {
1356                return string;
1357            }
1358
1359            protected DocumentFilter getDocumentFilter() {
1360                if (filter == null) {
1361                    filter = new Filter();
1362                }
1363                return filter;
1364            }
1365
1366
1367            private class Filter extends DocumentFilter {
1368                public void replace(FilterBypass fb, int offset, int length,
1369                                    String string, AttributeSet attrs) throws
1370                                           BadLocationException {
1371                    if (string != null && (offset + length) ==
1372                                          fb.getDocument().getLength()) {
1373                        Object next = getModel().findNextMatch(
1374                                         fb.getDocument().getText(0, offset) +
1375                                         string);
1376                        String value = (next != null) ? next.toString() : null;
1377
1378                        if (value != null) {
1379                            fb.remove(0, offset + length);
1380                            fb.insertString(0, value, null);
1381                            getFormattedTextField().select(offset +
1382                                                           string.length(),
1383                                                           value.length());
1384                            return;
1385                        }
1386                    }
1387                    super.replace(fb, offset, length, string, attrs);
1388                }
1389
1390                public void insertString(FilterBypass fb, int offset,
1391                                     String string, AttributeSet attr)
1392                       throws BadLocationException {
1393                    replace(fb, offset, 0, string, attr);
1394                }
1395            }
1396        }
1397    }
1398
1399
1400    /**
1401     * An Action implementation that is always disabled.
1402     */
1403    private static class DisabledAction implements Action {
1404        public Object getValue(String key) {
1405            return null;
1406        }
1407        public void putValue(String key, Object value) {
1408        }
1409        public void setEnabled(boolean b) {
1410        }
1411        public boolean isEnabled() {
1412            return false;
1413        }
1414        public void addPropertyChangeListener(PropertyChangeListener l) {
1415        }
1416        public void removePropertyChangeListener(PropertyChangeListener l) {
1417        }
1418        public void actionPerformed(ActionEvent ae) {
1419        }
1420    }
1421
1422    /////////////////
1423    // Accessibility support
1424    ////////////////
1425
1426    /**
1427     * Gets the <code>AccessibleContext</code> for the <code>JSpinner</code>
1428     *
1429     * @return the <code>AccessibleContext</code> for the <code>JSpinner</code>
1430     * @since 1.5
1431     */
1432    public AccessibleContext getAccessibleContext() {
1433        if (accessibleContext == null) {
1434            accessibleContext = new AccessibleJSpinner();
1435        }
1436        return accessibleContext;
1437    }
1438
1439    /**
1440     * <code>AccessibleJSpinner</code> implements accessibility
1441     * support for the <code>JSpinner</code> class.
1442     * @since 1.5
1443     */
1444    protected class AccessibleJSpinner extends AccessibleJComponent
1445        implements AccessibleValue, AccessibleAction, AccessibleText,
1446                   AccessibleEditableText, ChangeListener {
1447
1448        private Object oldModelValue = null;
1449
1450        /**
1451         * AccessibleJSpinner constructor
1452         */
1453        protected AccessibleJSpinner() {
1454            // model is guaranteed to be non-null
1455            oldModelValue = model.getValue();
1456            JSpinner.this.addChangeListener(this);
1457        }
1458
1459        /**
1460         * Invoked when the target of the listener has changed its state.
1461         *
1462         * @param e  a <code>ChangeEvent</code> object. Must not be null.
1463         * @throws NullPointerException if the parameter is null.
1464         */
1465        public void stateChanged(ChangeEvent e) {
1466            if (e == null) {
1467                throw new NullPointerException();
1468            }
1469            Object newModelValue = model.getValue();
1470            firePropertyChange(ACCESSIBLE_VALUE_PROPERTY,
1471                               oldModelValue,
1472                               newModelValue);
1473            firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
1474                               null,
1475                               0); // entire text may have changed
1476
1477            oldModelValue = newModelValue;
1478        }
1479
1480        /* ===== Begin AccessibleContext methods ===== */
1481
1482        /**
1483         * Gets the role of this object.  The role of the object is the generic
1484         * purpose or use of the class of this object.  For example, the role
1485         * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1486         * AccessibleRole are provided so component developers can pick from
1487         * a set of predefined roles.  This enables assistive technologies to
1488         * provide a consistent interface to various tweaked subclasses of
1489         * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1490         * that act like a push button) as well as distinguish between subclasses
1491         * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1492         * and AccessibleRole.RADIO_BUTTON for radio buttons).
1493         * <p>Note that the AccessibleRole class is also extensible, so
1494         * custom component developers can define their own AccessibleRole's
1495         * if the set of predefined roles is inadequate.
1496         *
1497         * @return an instance of AccessibleRole describing the role of the object
1498         * @see AccessibleRole
1499         */
1500        public AccessibleRole getAccessibleRole() {
1501            return AccessibleRole.SPIN_BOX;
1502        }
1503
1504        /**
1505         * Returns the number of accessible children of the object.
1506         *
1507         * @return the number of accessible children of the object.
1508         */
1509        public int getAccessibleChildrenCount() {
1510            // the JSpinner has one child, the editor
1511            if (editor.getAccessibleContext() != null) {
1512                return 1;
1513            }
1514            return 0;
1515        }
1516
1517        /**
1518         * Returns the specified Accessible child of the object.  The Accessible
1519         * children of an Accessible object are zero-based, so the first child
1520         * of an Accessible child is at index 0, the second child is at index 1,
1521         * and so on.
1522         *
1523         * @param i zero-based index of child
1524         * @return the Accessible child of the object
1525         * @see #getAccessibleChildrenCount
1526         */
1527        public Accessible getAccessibleChild(int i) {
1528            // the JSpinner has one child, the editor
1529            if (i != 0) {
1530                return null;
1531            }
1532            if (editor.getAccessibleContext() != null) {
1533                return (Accessible)editor;
1534            }
1535            return null;
1536        }
1537
1538        /* ===== End AccessibleContext methods ===== */
1539
1540        /**
1541         * Gets the AccessibleAction associated with this object that supports
1542         * one or more actions.
1543         *
1544         * @return AccessibleAction if supported by object; else return null
1545         * @see AccessibleAction
1546         */
1547        public AccessibleAction getAccessibleAction() {
1548            return this;
1549        }
1550
1551        /**
1552         * Gets the AccessibleText associated with this object presenting
1553         * text on the display.
1554         *
1555         * @return AccessibleText if supported by object; else return null
1556         * @see AccessibleText
1557         */
1558        public AccessibleText getAccessibleText() {
1559            return this;
1560        }
1561
1562        /*
1563         * Returns the AccessibleContext for the JSpinner editor
1564         */
1565        private AccessibleContext getEditorAccessibleContext() {
1566            if (editor instanceof DefaultEditor) {
1567                JTextField textField = ((DefaultEditor)editor).getTextField();
1568                if (textField != null) {
1569                    return textField.getAccessibleContext();
1570                }
1571            } else if (editor instanceof Accessible) {
1572                return editor.getAccessibleContext();
1573            }
1574            return null;
1575        }
1576
1577        /*
1578         * Returns the AccessibleText for the JSpinner editor
1579         */
1580        private AccessibleText getEditorAccessibleText() {
1581            AccessibleContext ac = getEditorAccessibleContext();
1582            if (ac != null) {
1583                return ac.getAccessibleText();
1584            }
1585            return null;
1586        }
1587
1588        /*
1589         * Returns the AccessibleEditableText for the JSpinner editor
1590         */
1591        private AccessibleEditableText getEditorAccessibleEditableText() {
1592            AccessibleText at = getEditorAccessibleText();
1593            if (at instanceof AccessibleEditableText) {
1594                return (AccessibleEditableText)at;
1595            }
1596            return null;
1597        }
1598
1599        /**
1600         * Gets the AccessibleValue associated with this object.
1601         *
1602         * @return AccessibleValue if supported by object; else return null
1603         * @see AccessibleValue
1604         *
1605         */
1606        public AccessibleValue getAccessibleValue() {
1607            return this;
1608        }
1609
1610        /* ===== Begin AccessibleValue impl ===== */
1611
1612        /**
1613         * Get the value of this object as a Number.  If the value has not been
1614         * set, the return value will be null.
1615         *
1616         * @return value of the object
1617         * @see #setCurrentAccessibleValue
1618         */
1619        public Number getCurrentAccessibleValue() {
1620            Object o = model.getValue();
1621            if (o instanceof Number) {
1622                return (Number)o;
1623            }
1624            return null;
1625        }
1626
1627        /**
1628         * Set the value of this object as a Number.
1629         *
1630         * @param n the value to set for this object
1631         * @return true if the value was set; else False
1632         * @see #getCurrentAccessibleValue
1633         */
1634        public boolean setCurrentAccessibleValue(Number n) {
1635            // try to set the new value
1636            try {
1637                model.setValue(n);
1638                return true;
1639            } catch (IllegalArgumentException iae) {
1640                // SpinnerModel didn't like new value
1641            }
1642            return false;
1643        }
1644
1645        /**
1646         * Get the minimum value of this object as a Number.
1647         *
1648         * @return Minimum value of the object; null if this object does not
1649         * have a minimum value
1650         * @see #getMaximumAccessibleValue
1651         */
1652        public Number getMinimumAccessibleValue() {
1653            if (model instanceof SpinnerNumberModel) {
1654                SpinnerNumberModel numberModel = (SpinnerNumberModel)model;
1655                Object o = numberModel.getMinimum();
1656                if (o instanceof Number) {
1657                    return (Number)o;
1658                }
1659            }
1660            return null;
1661        }
1662
1663        /**
1664         * Get the maximum value of this object as a Number.
1665         *
1666         * @return Maximum value of the object; null if this object does not
1667         * have a maximum value
1668         * @see #getMinimumAccessibleValue
1669         */
1670        public Number getMaximumAccessibleValue() {
1671            if (model instanceof SpinnerNumberModel) {
1672                SpinnerNumberModel numberModel = (SpinnerNumberModel)model;
1673                Object o = numberModel.getMaximum();
1674                if (o instanceof Number) {
1675                    return (Number)o;
1676                }
1677            }
1678            return null;
1679        }
1680
1681        /* ===== End AccessibleValue impl ===== */
1682
1683        /* ===== Begin AccessibleAction impl ===== */
1684
1685        /**
1686         * Returns the number of accessible actions available in this object
1687         * If there are more than one, the first one is considered the "default"
1688         * action of the object.
1689         *
1690         * Two actions are supported: AccessibleAction.INCREMENT which
1691         * increments the spinner value and AccessibleAction.DECREMENT
1692         * which decrements the spinner value
1693         *
1694         * @return the zero-based number of Actions in this object
1695         */
1696        public int getAccessibleActionCount() {
1697            return 2;
1698        }
1699
1700        /**
1701         * Returns a description of the specified action of the object.
1702         *
1703         * @param i zero-based index of the actions
1704         * @return a String description of the action
1705         * @see #getAccessibleActionCount
1706         */
1707        public String getAccessibleActionDescription(int i) {
1708            if (i == 0) {
1709                return AccessibleAction.INCREMENT;
1710            } else if (i == 1) {
1711                return AccessibleAction.DECREMENT;
1712            }
1713            return null;
1714        }
1715
1716        /**
1717         * Performs the specified Action on the object
1718         *
1719         * @param i zero-based index of actions. The first action
1720         * (index 0) is AccessibleAction.INCREMENT and the second
1721         * action (index 1) is AccessibleAction.DECREMENT.
1722         * @return true if the action was performed; otherwise false.
1723         * @see #getAccessibleActionCount
1724         */
1725        public boolean doAccessibleAction(int i) {
1726            if (i < 0 || i > 1) {
1727                return false;
1728            }
1729            Object o;
1730            if (i == 0) {
1731                o = getNextValue(); // AccessibleAction.INCREMENT
1732            } else {
1733                o = getPreviousValue(); // AccessibleAction.DECREMENT
1734            }
1735            // try to set the new value
1736            try {
1737                model.setValue(o);
1738                return true;
1739            } catch (IllegalArgumentException iae) {
1740                // SpinnerModel didn't like new value
1741            }
1742            return false;
1743        }
1744
1745        /* ===== End AccessibleAction impl ===== */
1746
1747        /* ===== Begin AccessibleText impl ===== */
1748
1749        /*
1750         * Returns whether source and destination components have the
1751         * same window ancestor
1752         */
1753        private boolean sameWindowAncestor(Component src, Component dest) {
1754            if (src == null || dest == null) {
1755                return false;
1756            }
1757            return SwingUtilities.getWindowAncestor(src) ==
1758                SwingUtilities.getWindowAncestor(dest);
1759        }
1760
1761        /**
1762         * Given a point in local coordinates, return the zero-based index
1763         * of the character under that Point.  If the point is invalid,
1764         * this method returns -1.
1765         *
1766         * @param p the Point in local coordinates
1767         * @return the zero-based index of the character under Point p; if
1768         * Point is invalid return -1.
1769         */
1770        public int getIndexAtPoint(Point p) {
1771            AccessibleText at = getEditorAccessibleText();
1772            if (at != null && sameWindowAncestor(JSpinner.this, editor)) {
1773                // convert point from the JSpinner bounds (source) to
1774                // editor bounds (destination)
1775                Point editorPoint = SwingUtilities.convertPoint(JSpinner.this,
1776                                                                p,
1777                                                                editor);
1778                if (editorPoint != null) {
1779                    return at.getIndexAtPoint(editorPoint);
1780                }
1781            }
1782            return -1;
1783        }
1784
1785        /**
1786         * Determines the bounding box of the character at the given
1787         * index into the string.  The bounds are returned in local
1788         * coordinates.  If the index is invalid an empty rectangle is
1789         * returned.
1790         *
1791         * @param i the index into the String
1792         * @return the screen coordinates of the character's bounding box,
1793         * if index is invalid return an empty rectangle.
1794         */
1795        public Rectangle getCharacterBounds(int i) {
1796            AccessibleText at = getEditorAccessibleText();
1797            if (at != null ) {
1798                Rectangle editorRect = at.getCharacterBounds(i);
1799                if (editorRect != null &&
1800                    sameWindowAncestor(JSpinner.this, editor)) {
1801                    // return rectangle in the JSpinner bounds
1802                    return SwingUtilities.convertRectangle(editor,
1803                                                           editorRect,
1804                                                           JSpinner.this);
1805                }
1806            }
1807            return null;
1808        }
1809
1810        /**
1811         * Returns the number of characters (valid indicies)
1812         *
1813         * @return the number of characters
1814         */
1815        public int getCharCount() {
1816            AccessibleText at = getEditorAccessibleText();
1817            if (at != null) {
1818                return at.getCharCount();
1819            }
1820            return -1;
1821        }
1822
1823        /**
1824         * Returns the zero-based offset of the caret.
1825         *
1826         * Note: That to the right of the caret will have the same index
1827         * value as the offset (the caret is between two characters).
1828         * @return the zero-based offset of the caret.
1829         */
1830        public int getCaretPosition() {
1831            AccessibleText at = getEditorAccessibleText();
1832            if (at != null) {
1833                return at.getCaretPosition();
1834            }
1835            return -1;
1836        }
1837
1838        /**
1839         * Returns the String at a given index.
1840         *
1841         * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1842         * @param index an index within the text
1843         * @return the letter, word, or sentence
1844         */
1845        public String getAtIndex(int part, int index) {
1846            AccessibleText at = getEditorAccessibleText();
1847            if (at != null) {
1848                return at.getAtIndex(part, index);
1849            }
1850            return null;
1851        }
1852
1853        /**
1854         * Returns the String after a given index.
1855         *
1856         * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1857         * @param index an index within the text
1858         * @return the letter, word, or sentence
1859         */
1860        public String getAfterIndex(int part, int index) {
1861            AccessibleText at = getEditorAccessibleText();
1862            if (at != null) {
1863                return at.getAfterIndex(part, index);
1864            }
1865            return null;
1866        }
1867
1868        /**
1869         * Returns the String before a given index.
1870         *
1871         * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1872         * @param index an index within the text
1873         * @return the letter, word, or sentence
1874         */
1875        public String getBeforeIndex(int part, int index) {
1876            AccessibleText at = getEditorAccessibleText();
1877            if (at != null) {
1878                return at.getBeforeIndex(part, index);
1879            }
1880            return null;
1881        }
1882
1883        /**
1884         * Returns the AttributeSet for a given character at a given index
1885         *
1886         * @param i the zero-based index into the text
1887         * @return the AttributeSet of the character
1888         */
1889        public AttributeSet getCharacterAttribute(int i) {
1890            AccessibleText at = getEditorAccessibleText();
1891            if (at != null) {
1892                return at.getCharacterAttribute(i);
1893            }
1894            return null;
1895        }
1896
1897        /**
1898         * Returns the start offset within the selected text.
1899         * If there is no selection, but there is
1900         * a caret, the start and end offsets will be the same.
1901         *
1902         * @return the index into the text of the start of the selection
1903         */
1904        public int getSelectionStart() {
1905            AccessibleText at = getEditorAccessibleText();
1906            if (at != null) {
1907                return at.getSelectionStart();
1908            }
1909            return -1;
1910        }
1911
1912        /**
1913         * Returns the end offset within the selected text.
1914         * If there is no selection, but there is
1915         * a caret, the start and end offsets will be the same.
1916         *
1917         * @return the index into the text of the end of the selection
1918         */
1919        public int getSelectionEnd() {
1920            AccessibleText at = getEditorAccessibleText();
1921            if (at != null) {
1922                return at.getSelectionEnd();
1923            }
1924            return -1;
1925        }
1926
1927        /**
1928         * Returns the portion of the text that is selected.
1929         *
1930         * @return the String portion of the text that is selected
1931         */
1932        public String getSelectedText() {
1933            AccessibleText at = getEditorAccessibleText();
1934            if (at != null) {
1935                return at.getSelectedText();
1936            }
1937            return null;
1938        }
1939
1940        /* ===== End AccessibleText impl ===== */
1941
1942
1943        /* ===== Begin AccessibleEditableText impl ===== */
1944
1945        /**
1946         * Sets the text contents to the specified string.
1947         *
1948         * @param s the string to set the text contents
1949         */
1950        public void setTextContents(String s) {
1951            AccessibleEditableText at = getEditorAccessibleEditableText();
1952            if (at != null) {
1953                at.setTextContents(s);
1954            }
1955        }
1956
1957        /**
1958         * Inserts the specified string at the given index/
1959         *
1960         * @param index the index in the text where the string will
1961         * be inserted
1962         * @param s the string to insert in the text
1963         */
1964        public void insertTextAtIndex(int index, String s) {
1965            AccessibleEditableText at = getEditorAccessibleEditableText();
1966            if (at != null) {
1967                at.insertTextAtIndex(index, s);
1968            }
1969        }
1970
1971        /**
1972         * Returns the text string between two indices.
1973         *
1974         * @param startIndex the starting index in the text
1975         * @param endIndex the ending index in the text
1976         * @return the text string between the indices
1977         */
1978        public String getTextRange(int startIndex, int endIndex) {
1979            AccessibleEditableText at = getEditorAccessibleEditableText();
1980            if (at != null) {
1981                return at.getTextRange(startIndex, endIndex);
1982            }
1983            return null;
1984        }
1985
1986        /**
1987         * Deletes the text between two indices
1988         *
1989         * @param startIndex the starting index in the text
1990         * @param endIndex the ending index in the text
1991         */
1992        public void delete(int startIndex, int endIndex) {
1993            AccessibleEditableText at = getEditorAccessibleEditableText();
1994            if (at != null) {
1995                at.delete(startIndex, endIndex);
1996            }
1997        }
1998
1999        /**
2000         * Cuts the text between two indices into the system clipboard.
2001         *
2002         * @param startIndex the starting index in the text
2003         * @param endIndex the ending index in the text
2004         */
2005        public void cut(int startIndex, int endIndex) {
2006            AccessibleEditableText at = getEditorAccessibleEditableText();
2007            if (at != null) {
2008                at.cut(startIndex, endIndex);
2009            }
2010        }
2011
2012        /**
2013         * Pastes the text from the system clipboard into the text
2014         * starting at the specified index.
2015         *
2016         * @param startIndex the starting index in the text
2017         */
2018        public void paste(int startIndex) {
2019            AccessibleEditableText at = getEditorAccessibleEditableText();
2020            if (at != null) {
2021                at.paste(startIndex);
2022            }
2023        }
2024
2025        /**
2026         * Replaces the text between two indices with the specified
2027         * string.
2028         *
2029         * @param startIndex the starting index in the text
2030         * @param endIndex the ending index in the text
2031         * @param s the string to replace the text between two indices
2032         */
2033        public void replaceText(int startIndex, int endIndex, String s) {
2034            AccessibleEditableText at = getEditorAccessibleEditableText();
2035            if (at != null) {
2036                at.replaceText(startIndex, endIndex, s);
2037            }
2038        }
2039
2040        /**
2041         * Selects the text between two indices.
2042         *
2043         * @param startIndex the starting index in the text
2044         * @param endIndex the ending index in the text
2045         */
2046        public void selectText(int startIndex, int endIndex) {
2047            AccessibleEditableText at = getEditorAccessibleEditableText();
2048            if (at != null) {
2049                at.selectText(startIndex, endIndex);
2050            }
2051        }
2052
2053        /**
2054         * Sets attributes for the text between two indices.
2055         *
2056         * @param startIndex the starting index in the text
2057         * @param endIndex the ending index in the text
2058         * @param as the attribute set
2059         * @see AttributeSet
2060         */
2061        public void setAttributes(int startIndex, int endIndex, AttributeSet as) {
2062            AccessibleEditableText at = getEditorAccessibleEditableText();
2063            if (at != null) {
2064                at.setAttributes(startIndex, endIndex, as);
2065            }
2066        }
2067    }  /* End AccessibleJSpinner */
2068}
2069