0001 /*
0002 * Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package javax.swing;
0027
0028 import java.awt.*;
0029 import java.awt.event.*;
0030
0031 import javax.swing.*;
0032 import javax.swing.event.*;
0033 import javax.swing.text.*;
0034 import javax.swing.plaf.SpinnerUI;
0035
0036 import java.util.*;
0037 import java.beans.*;
0038 import java.text.*;
0039 import java.io.*;
0040 import java.util.HashMap;
0041 import sun.util.resources.LocaleData;
0042
0043 import javax.accessibility.*;
0044
0045 /**
0046 * A single line input field that lets the user select a
0047 * number or an object value from an ordered sequence. Spinners typically
0048 * provide a pair of tiny arrow buttons for stepping through the elements
0049 * of the sequence. The keyboard up/down arrow keys also cycle through the
0050 * elements. The user may also be allowed to type a (legal) value directly
0051 * into the spinner. Although combo boxes provide similar functionality,
0052 * spinners are sometimes preferred because they don't require a drop down list
0053 * that can obscure important data.
0054 * <p>
0055 * A <code>JSpinner</code>'s sequence value is defined by its
0056 * <code>SpinnerModel</code>.
0057 * The <code>model</code> can be specified as a constructor argument and
0058 * changed with the <code>model</code> property. <code>SpinnerModel</code>
0059 * classes for some common types are provided: <code>SpinnerListModel</code>,
0060 * <code>SpinnerNumberModel</code>, and <code>SpinnerDateModel</code>.
0061 * <p>
0062 * A <code>JSpinner</code> has a single child component that's
0063 * responsible for displaying
0064 * and potentially changing the current element or <i>value</i> of
0065 * the model, which is called the <code>editor</code>. The editor is created
0066 * by the <code>JSpinner</code>'s constructor and can be changed with the
0067 * <code>editor</code> property. The <code>JSpinner</code>'s editor stays
0068 * in sync with the model by listening for <code>ChangeEvent</code>s. If the
0069 * user has changed the value displayed by the <code>editor</code> it is
0070 * possible for the <code>model</code>'s value to differ from that of
0071 * the <code>editor</code>. To make sure the <code>model</code> has the same
0072 * value as the editor use the <code>commitEdit</code> method, eg:
0073 * <pre>
0074 * try {
0075 * spinner.commitEdit();
0076 * }
0077 * catch (ParseException pe) {{
0078 * // Edited value is invalid, spinner.getValue() will return
0079 * // the last valid value, you could revert the spinner to show that:
0080 * JComponent editor = spinner.getEditor()
0081 * if (editor instanceof DefaultEditor) {
0082 * ((DefaultEditor)editor).getTextField().setValue(spinner.getValue();
0083 * }
0084 * // reset the value to some known value:
0085 * spinner.setValue(fallbackValue);
0086 * // or treat the last valid value as the current, in which
0087 * // case you don't need to do anything.
0088 * }
0089 * return spinner.getValue();
0090 * </pre>
0091 * <p>
0092 * For information and examples of using spinner see
0093 * <a href="http://java.sun.com/doc/books/tutorial/uiswing/components/spinner.html">How to Use Spinners</a>,
0094 * a section in <em>The Java Tutorial.</em>
0095 * <p>
0096 * <strong>Warning:</strong> Swing is not thread safe. For more
0097 * information see <a
0098 * href="package-summary.html#threading">Swing's Threading
0099 * Policy</a>.
0100 * <p>
0101 * <strong>Warning:</strong>
0102 * Serialized objects of this class will not be compatible with
0103 * future Swing releases. The current serialization support is
0104 * appropriate for short term storage or RMI between applications running
0105 * the same version of Swing. As of 1.4, support for long term storage
0106 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0107 * has been added to the <code>java.beans</code> package.
0108 * Please see {@link java.beans.XMLEncoder}.
0109 *
0110 * @beaninfo
0111 * attribute: isContainer false
0112 * description: A single line input field that lets the user select a
0113 * number or an object value from an ordered set.
0114 *
0115 * @see SpinnerModel
0116 * @see AbstractSpinnerModel
0117 * @see SpinnerListModel
0118 * @see SpinnerNumberModel
0119 * @see SpinnerDateModel
0120 * @see JFormattedTextField
0121 *
0122 * @version 1.57 05/05/07
0123 * @author Hans Muller
0124 * @author Lynn Monsanto (accessibility)
0125 * @since 1.4
0126 */
0127 public class JSpinner extends JComponent implements Accessible {
0128 /**
0129 * @see #getUIClassID
0130 * @see #readObject
0131 */
0132 private static final String uiClassID = "SpinnerUI";
0133
0134 private static final Action DISABLED_ACTION = new DisabledAction();
0135
0136 private SpinnerModel model;
0137 private JComponent editor;
0138 private ChangeListener modelListener;
0139 private transient ChangeEvent changeEvent;
0140 private boolean editorExplicitlySet = false;
0141
0142 /**
0143 * Constructs a spinner for the given model. The spinner has
0144 * a set of previous/next buttons, and an editor appropriate
0145 * for the model.
0146 *
0147 * @throws NullPointerException if the model is {@code null}
0148 */
0149 public JSpinner(SpinnerModel model) {
0150 if (model == null) {
0151 throw new NullPointerException("model cannot be null");
0152 }
0153 this .model = model;
0154 this .editor = createEditor(model);
0155 setOpaque(true);
0156 updateUI();
0157 }
0158
0159 /**
0160 * Constructs a spinner with an <code>Integer SpinnerNumberModel</code>
0161 * with initial value 0 and no minimum or maximum limits.
0162 */
0163 public JSpinner() {
0164 this (new SpinnerNumberModel());
0165 }
0166
0167 /**
0168 * Returns the look and feel (L&F) object that renders this component.
0169 *
0170 * @return the <code>SpinnerUI</code> object that renders this component
0171 */
0172 public SpinnerUI getUI() {
0173 return (SpinnerUI) ui;
0174 }
0175
0176 /**
0177 * Sets the look and feel (L&F) object that renders this component.
0178 *
0179 * @param ui the <code>SpinnerUI</code> L&F object
0180 * @see UIDefaults#getUI
0181 */
0182 public void setUI(SpinnerUI ui) {
0183 super .setUI(ui);
0184 }
0185
0186 /**
0187 * Returns the suffix used to construct the name of the look and feel
0188 * (L&F) class used to render this component.
0189 *
0190 * @return the string "SpinnerUI"
0191 * @see JComponent#getUIClassID
0192 * @see UIDefaults#getUI
0193 */
0194 public String getUIClassID() {
0195 return uiClassID;
0196 }
0197
0198 /**
0199 * Resets the UI property with the value from the current look and feel.
0200 *
0201 * @see UIManager#getUI
0202 */
0203 public void updateUI() {
0204 setUI((SpinnerUI) UIManager.getUI(this ));
0205 invalidate();
0206 }
0207
0208 /**
0209 * This method is called by the constructors to create the
0210 * <code>JComponent</code>
0211 * that displays the current value of the sequence. The editor may
0212 * also allow the user to enter an element of the sequence directly.
0213 * An editor must listen for <code>ChangeEvents</code> on the
0214 * <code>model</code> and keep the value it displays
0215 * in sync with the value of the model.
0216 * <p>
0217 * Subclasses may override this method to add support for new
0218 * <code>SpinnerModel</code> classes. Alternatively one can just
0219 * replace the editor created here with the <code>setEditor</code>
0220 * method. The default mapping from model type to editor is:
0221 * <ul>
0222 * <li> <code>SpinnerNumberModel => JSpinner.NumberEditor</code>
0223 * <li> <code>SpinnerDateModel => JSpinner.DateEditor</code>
0224 * <li> <code>SpinnerListModel => JSpinner.ListEditor</code>
0225 * <li> <i>all others</i> => <code>JSpinner.DefaultEditor</code>
0226 * </ul>
0227 *
0228 * @return a component that displays the current value of the sequence
0229 * @param model the value of getModel
0230 * @see #getModel
0231 * @see #setEditor
0232 */
0233 protected JComponent createEditor(SpinnerModel model) {
0234 if (model instanceof SpinnerDateModel) {
0235 return new DateEditor(this );
0236 } else if (model instanceof SpinnerListModel) {
0237 return new ListEditor(this );
0238 } else if (model instanceof SpinnerNumberModel) {
0239 return new NumberEditor(this );
0240 } else {
0241 return new DefaultEditor(this );
0242 }
0243 }
0244
0245 /**
0246 * Changes the model that represents the value of this spinner.
0247 * If the editor property has not been explicitly set,
0248 * the editor property is (implicitly) set after the <code>"model"</code>
0249 * <code>PropertyChangeEvent</code> has been fired. The editor
0250 * property is set to the value returned by <code>createEditor</code>,
0251 * as in:
0252 * <pre>
0253 * setEditor(createEditor(model));
0254 * </pre>
0255 *
0256 * @param model the new <code>SpinnerModel</code>
0257 * @see #getModel
0258 * @see #getEditor
0259 * @see #setEditor
0260 * @throws IllegalArgumentException if model is <code>null</code>
0261 *
0262 * @beaninfo
0263 * bound: true
0264 * attribute: visualUpdate true
0265 * description: Model that represents the value of this spinner.
0266 */
0267 public void setModel(SpinnerModel model) {
0268 if (model == null) {
0269 throw new IllegalArgumentException("null model");
0270 }
0271 if (!model.equals(this .model)) {
0272 SpinnerModel oldModel = this .model;
0273 this .model = model;
0274 if (modelListener != null) {
0275 this .model.addChangeListener(modelListener);
0276 }
0277 firePropertyChange("model", oldModel, model);
0278 if (!editorExplicitlySet) {
0279 setEditor(createEditor(model)); // sets editorExplicitlySet true
0280 editorExplicitlySet = false;
0281 }
0282 repaint();
0283 revalidate();
0284 }
0285 }
0286
0287 /**
0288 * Returns the <code>SpinnerModel</code> that defines
0289 * this spinners sequence of values.
0290 *
0291 * @return the value of the model property
0292 * @see #setModel
0293 */
0294 public SpinnerModel getModel() {
0295 return model;
0296 }
0297
0298 /**
0299 * Returns the current value of the model, typically
0300 * this value is displayed by the <code>editor</code>. If the
0301 * user has changed the value displayed by the <code>editor</code> it is
0302 * possible for the <code>model</code>'s value to differ from that of
0303 * the <code>editor</code>, refer to the class level javadoc for examples
0304 * of how to deal with this.
0305 * <p>
0306 * This method simply delegates to the <code>model</code>.
0307 * It is equivalent to:
0308 * <pre>
0309 * getModel().getValue()
0310 * </pre>
0311 *
0312 * @see #setValue
0313 * @see SpinnerModel#getValue
0314 */
0315 public Object getValue() {
0316 return getModel().getValue();
0317 }
0318
0319 /**
0320 * Changes current value of the model, typically
0321 * this value is displayed by the <code>editor</code>.
0322 * If the <code>SpinnerModel</code> implementation
0323 * doesn't support the specified value then an
0324 * <code>IllegalArgumentException</code> is thrown.
0325 * <p>
0326 * This method simply delegates to the <code>model</code>.
0327 * It is equivalent to:
0328 * <pre>
0329 * getModel().setValue(value)
0330 * </pre>
0331 *
0332 * @throws IllegalArgumentException if <code>value</code> isn't allowed
0333 * @see #getValue
0334 * @see SpinnerModel#setValue
0335 */
0336 public void setValue(Object value) {
0337 getModel().setValue(value);
0338 }
0339
0340 /**
0341 * Returns the object in the sequence that comes after the object returned
0342 * by <code>getValue()</code>. If the end of the sequence has been reached
0343 * then return <code>null</code>.
0344 * Calling this method does not effect <code>value</code>.
0345 * <p>
0346 * This method simply delegates to the <code>model</code>.
0347 * It is equivalent to:
0348 * <pre>
0349 * getModel().getNextValue()
0350 * </pre>
0351 *
0352 * @return the next legal value or <code>null</code> if one doesn't exist
0353 * @see #getValue
0354 * @see #getPreviousValue
0355 * @see SpinnerModel#getNextValue
0356 */
0357 public Object getNextValue() {
0358 return getModel().getNextValue();
0359 }
0360
0361 /**
0362 * We pass <code>Change</code> events along to the listeners with the
0363 * the slider (instead of the model itself) as the event source.
0364 */
0365 private class ModelListener implements ChangeListener, Serializable {
0366 public void stateChanged(ChangeEvent e) {
0367 fireStateChanged();
0368 }
0369 }
0370
0371 /**
0372 * Adds a listener to the list that is notified each time a change
0373 * to the model occurs. The source of <code>ChangeEvents</code>
0374 * delivered to <code>ChangeListeners</code> will be this
0375 * <code>JSpinner</code>. Note also that replacing the model
0376 * will not affect listeners added directly to JSpinner.
0377 * Applications can add listeners to the model directly. In that
0378 * case is that the source of the event would be the
0379 * <code>SpinnerModel</code>.
0380 *
0381 * @param listener the <code>ChangeListener</code> to add
0382 * @see #removeChangeListener
0383 * @see #getModel
0384 */
0385 public void addChangeListener(ChangeListener listener) {
0386 if (modelListener == null) {
0387 modelListener = new ModelListener();
0388 getModel().addChangeListener(modelListener);
0389 }
0390 listenerList.add(ChangeListener.class, listener);
0391 }
0392
0393 /**
0394 * Removes a <code>ChangeListener</code> from this spinner.
0395 *
0396 * @param listener the <code>ChangeListener</code> to remove
0397 * @see #fireStateChanged
0398 * @see #addChangeListener
0399 */
0400 public void removeChangeListener(ChangeListener listener) {
0401 listenerList.remove(ChangeListener.class, listener);
0402 }
0403
0404 /**
0405 * Returns an array of all the <code>ChangeListener</code>s added
0406 * to this JSpinner with addChangeListener().
0407 *
0408 * @return all of the <code>ChangeListener</code>s added or an empty
0409 * array if no listeners have been added
0410 * @since 1.4
0411 */
0412 public ChangeListener[] getChangeListeners() {
0413 return (ChangeListener[]) listenerList
0414 .getListeners(ChangeListener.class);
0415 }
0416
0417 /**
0418 * Sends a <code>ChangeEvent</code>, whose source is this
0419 * <code>JSpinner</code>, to each <code>ChangeListener</code>.
0420 * When a <code>ChangeListener</code> has been added
0421 * to the spinner, this method method is called each time
0422 * a <code>ChangeEvent</code> is received from the model.
0423 *
0424 * @see #addChangeListener
0425 * @see #removeChangeListener
0426 * @see EventListenerList
0427 */
0428 protected void fireStateChanged() {
0429 Object[] listeners = listenerList.getListenerList();
0430 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0431 if (listeners[i] == ChangeListener.class) {
0432 if (changeEvent == null) {
0433 changeEvent = new ChangeEvent(this );
0434 }
0435 ((ChangeListener) listeners[i + 1])
0436 .stateChanged(changeEvent);
0437 }
0438 }
0439 }
0440
0441 /**
0442 * Returns the object in the sequence that comes
0443 * before the object returned by <code>getValue()</code>.
0444 * If the end of the sequence has been reached then
0445 * return <code>null</code>. Calling this method does
0446 * not effect <code>value</code>.
0447 * <p>
0448 * This method simply delegates to the <code>model</code>.
0449 * It is equivalent to:
0450 * <pre>
0451 * getModel().getPreviousValue()
0452 * </pre>
0453 *
0454 * @return the previous legal value or <code>null</code>
0455 * if one doesn't exist
0456 * @see #getValue
0457 * @see #getNextValue
0458 * @see SpinnerModel#getPreviousValue
0459 */
0460 public Object getPreviousValue() {
0461 return getModel().getPreviousValue();
0462 }
0463
0464 /**
0465 * Changes the <code>JComponent</code> that displays the current value
0466 * of the <code>SpinnerModel</code>. It is the responsibility of this
0467 * method to <i>disconnect</i> the old editor from the model and to
0468 * connect the new editor. This may mean removing the
0469 * old editors <code>ChangeListener</code> from the model or the
0470 * spinner itself and adding one for the new editor.
0471 *
0472 * @param editor the new editor
0473 * @see #getEditor
0474 * @see #createEditor
0475 * @see #getModel
0476 * @throws IllegalArgumentException if editor is <code>null</code>
0477 *
0478 * @beaninfo
0479 * bound: true
0480 * attribute: visualUpdate true
0481 * description: JComponent that displays the current value of the model
0482 */
0483 public void setEditor(JComponent editor) {
0484 if (editor == null) {
0485 throw new IllegalArgumentException("null editor");
0486 }
0487 if (!editor.equals(this .editor)) {
0488 JComponent oldEditor = this .editor;
0489 this .editor = editor;
0490 if (oldEditor instanceof DefaultEditor) {
0491 ((DefaultEditor) oldEditor).dismiss(this );
0492 }
0493 editorExplicitlySet = true;
0494 firePropertyChange("editor", oldEditor, editor);
0495 revalidate();
0496 repaint();
0497 }
0498 }
0499
0500 /**
0501 * Returns the component that displays and potentially
0502 * changes the model's value.
0503 *
0504 * @return the component that displays and potentially
0505 * changes the model's value
0506 * @see #setEditor
0507 * @see #createEditor
0508 */
0509 public JComponent getEditor() {
0510 return editor;
0511 }
0512
0513 /**
0514 * Commits the currently edited value to the <code>SpinnerModel</code>.
0515 * <p>
0516 * If the editor is an instance of <code>DefaultEditor</code>, the
0517 * call if forwarded to the editor, otherwise this does nothing.
0518 *
0519 * @throws ParseException if the currently edited value couldn't
0520 * be commited.
0521 */
0522 public void commitEdit() throws ParseException {
0523 JComponent editor = getEditor();
0524 if (editor instanceof DefaultEditor) {
0525 ((DefaultEditor) editor).commitEdit();
0526 }
0527 }
0528
0529 /*
0530 * See readObject and writeObject in JComponent for more
0531 * information about serialization in Swing.
0532 *
0533 * @param s Stream to write to
0534 */
0535 private void writeObject(ObjectOutputStream s) throws IOException {
0536 s.defaultWriteObject();
0537 if (getUIClassID().equals(uiClassID)) {
0538 byte count = JComponent.getWriteObjCounter(this );
0539 JComponent.setWriteObjCounter(this , --count);
0540 if (count == 0 && ui != null) {
0541 ui.installUI(this );
0542 }
0543 }
0544 }
0545
0546 /**
0547 * A simple base class for more specialized editors
0548 * that displays a read-only view of the model's current
0549 * value with a <code>JFormattedTextField</code>. Subclasses
0550 * can configure the <code>JFormattedTextField</code> to create
0551 * an editor that's appropriate for the type of model they
0552 * support and they may want to override
0553 * the <code>stateChanged</code> and <code>propertyChanged</code>
0554 * methods, which keep the model and the text field in sync.
0555 * <p>
0556 * This class defines a <code>dismiss</code> method that removes the
0557 * editors <code>ChangeListener</code> from the <code>JSpinner</code>
0558 * that it's part of. The <code>setEditor</code> method knows about
0559 * <code>DefaultEditor.dismiss</code>, so if the developer
0560 * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
0561 * its <code>ChangeListener</code> connection back to the
0562 * <code>JSpinner</code> will be removed. However after that,
0563 * it's up to the developer to manage their editor listeners.
0564 * Similarly, if a subclass overrides <code>createEditor</code>,
0565 * it's up to the subclasser to deal with their editor
0566 * subsequently being replaced (with <code>setEditor</code>).
0567 * We expect that in most cases, and in editor installed
0568 * with <code>setEditor</code> or created by a <code>createEditor</code>
0569 * override, will not be replaced anyway.
0570 * <p>
0571 * This class is the <code>LayoutManager</code> for it's single
0572 * <code>JFormattedTextField</code> child. By default the
0573 * child is just centered with the parents insets.
0574 * @since 1.4
0575 */
0576 public static class DefaultEditor extends JPanel implements
0577 ChangeListener, PropertyChangeListener, LayoutManager {
0578 /**
0579 * Constructs an editor component for the specified <code>JSpinner</code>.
0580 * This <code>DefaultEditor</code> is it's own layout manager and
0581 * it is added to the spinner's <code>ChangeListener</code> list.
0582 * The constructor creates a single <code>JFormattedTextField</code> child,
0583 * initializes it's value to be the spinner model's current value
0584 * and adds it to <code>this</code> <code>DefaultEditor</code>.
0585 *
0586 * @param spinner the spinner whose model <code>this</code> editor will monitor
0587 * @see #getTextField
0588 * @see JSpinner#addChangeListener
0589 */
0590 public DefaultEditor(JSpinner spinner) {
0591 super (null);
0592
0593 JFormattedTextField ftf = new JFormattedTextField();
0594 ftf.setName("Spinner.formattedTextField");
0595 ftf.setValue(spinner.getValue());
0596 ftf.addPropertyChangeListener(this );
0597 ftf.setEditable(false);
0598 ftf.setInheritsPopupMenu(true);
0599
0600 String toolTipText = spinner.getToolTipText();
0601 if (toolTipText != null) {
0602 ftf.setToolTipText(toolTipText);
0603 }
0604
0605 add(ftf);
0606
0607 setLayout(this );
0608 spinner.addChangeListener(this );
0609
0610 // We want the spinner's increment/decrement actions to be
0611 // active vs those of the JFormattedTextField. As such we
0612 // put disabled actions in the JFormattedTextField's actionmap.
0613 // A binding to a disabled action is treated as a nonexistant
0614 // binding.
0615 ActionMap ftfMap = ftf.getActionMap();
0616
0617 if (ftfMap != null) {
0618 ftfMap.put("increment", DISABLED_ACTION);
0619 ftfMap.put("decrement", DISABLED_ACTION);
0620 }
0621 }
0622
0623 /**
0624 * Disconnect <code>this</code> editor from the specified
0625 * <code>JSpinner</code>. By default, this method removes
0626 * itself from the spinners <code>ChangeListener</code> list.
0627 *
0628 * @param spinner the <code>JSpinner</code> to disconnect this
0629 * editor from; the same spinner as was passed to the constructor.
0630 */
0631 public void dismiss(JSpinner spinner) {
0632 spinner.removeChangeListener(this );
0633 }
0634
0635 /**
0636 * Returns the <code>JSpinner</code> ancestor of this editor or
0637 * <code>null</code> if none of the ancestors are a
0638 * <code>JSpinner</code>.
0639 * Typically the editor's parent is a <code>JSpinner</code> however
0640 * subclasses of <code>JSpinner</code> may override the
0641 * the <code>createEditor</code> method and insert one or more containers
0642 * between the <code>JSpinner</code> and it's editor.
0643 *
0644 * @return <code>JSpinner</code> ancestor; <code>null</code>
0645 * if none of the ancestors are a <code>JSpinner</code>
0646 *
0647 * @see JSpinner#createEditor
0648 */
0649 public JSpinner getSpinner() {
0650 for (Component c = this ; c != null; c = c.getParent()) {
0651 if (c instanceof JSpinner) {
0652 return (JSpinner) c;
0653 }
0654 }
0655 return null;
0656 }
0657
0658 /**
0659 * Returns the <code>JFormattedTextField</code> child of this
0660 * editor. By default the text field is the first and only
0661 * child of editor.
0662 *
0663 * @return the <code>JFormattedTextField</code> that gives the user
0664 * access to the <code>SpinnerDateModel's</code> value.
0665 * @see #getSpinner
0666 * @see #getModel
0667 */
0668 public JFormattedTextField getTextField() {
0669 return (JFormattedTextField) getComponent(0);
0670 }
0671
0672 /**
0673 * This method is called when the spinner's model's state changes.
0674 * It sets the <code>value</code> of the text field to the current
0675 * value of the spinners model.
0676 *
0677 * @param e the <code>ChangeEvent</code> whose source is the
0678 * <code>JSpinner</code> whose model has changed.
0679 * @see #getTextField
0680 * @see JSpinner#getValue
0681 */
0682 public void stateChanged(ChangeEvent e) {
0683 JSpinner spinner = (JSpinner) (e.getSource());
0684 getTextField().setValue(spinner.getValue());
0685 }
0686
0687 /**
0688 * Called by the <code>JFormattedTextField</code>
0689 * <code>PropertyChangeListener</code>. When the <code>"value"</code>
0690 * property changes, which implies that the user has typed a new
0691 * number, we set the value of the spinners model.
0692 * <p>
0693 * This class ignores <code>PropertyChangeEvents</code> whose
0694 * source is not the <code>JFormattedTextField</code>, so subclasses
0695 * may safely make <code>this</code> <code>DefaultEditor</code> a
0696 * <code>PropertyChangeListener</code> on other objects.
0697 *
0698 * @param e the <code>PropertyChangeEvent</code> whose source is
0699 * the <code>JFormattedTextField</code> created by this class.
0700 * @see #getTextField
0701 */
0702 public void propertyChange(PropertyChangeEvent e) {
0703 JSpinner spinner = getSpinner();
0704
0705 if (spinner == null) {
0706 // Indicates we aren't installed anywhere.
0707 return;
0708 }
0709
0710 Object source = e.getSource();
0711 String name = e.getPropertyName();
0712 if ((source instanceof JFormattedTextField)
0713 && "value".equals(name)) {
0714 Object lastValue = spinner.getValue();
0715
0716 // Try to set the new value
0717 try {
0718 spinner.setValue(getTextField().getValue());
0719 } catch (IllegalArgumentException iae) {
0720 // SpinnerModel didn't like new value, reset
0721 try {
0722 ((JFormattedTextField) source)
0723 .setValue(lastValue);
0724 } catch (IllegalArgumentException iae2) {
0725 // Still bogus, nothing else we can do, the
0726 // SpinnerModel and JFormattedTextField are now out
0727 // of sync.
0728 }
0729 }
0730 }
0731 }
0732
0733 /**
0734 * This <code>LayoutManager</code> method does nothing. We're
0735 * only managing a single child and there's no support
0736 * for layout constraints.
0737 *
0738 * @param name ignored
0739 * @param child ignored
0740 */
0741 public void addLayoutComponent(String name, Component child) {
0742 }
0743
0744 /**
0745 * This <code>LayoutManager</code> method does nothing. There
0746 * isn't any per-child state.
0747 *
0748 * @param child ignored
0749 */
0750 public void removeLayoutComponent(Component child) {
0751 }
0752
0753 /**
0754 * Returns the size of the parents insets.
0755 */
0756 private Dimension insetSize(Container parent) {
0757 Insets insets = parent.getInsets();
0758 int w = insets.left + insets.right;
0759 int h = insets.top + insets.bottom;
0760 return new Dimension(w, h);
0761 }
0762
0763 /**
0764 * Returns the preferred size of first (and only) child plus the
0765 * size of the parents insets.
0766 *
0767 * @param parent the Container that's managing the layout
0768 * @return the preferred dimensions to lay out the subcomponents
0769 * of the specified container.
0770 */
0771 public Dimension preferredLayoutSize(Container parent) {
0772 Dimension preferredSize = insetSize(parent);
0773 if (parent.getComponentCount() > 0) {
0774 Dimension childSize = getComponent(0)
0775 .getPreferredSize();
0776 preferredSize.width += childSize.width;
0777 preferredSize.height += childSize.height;
0778 }
0779 return preferredSize;
0780 }
0781
0782 /**
0783 * Returns the minimum size of first (and only) child plus the
0784 * size of the parents insets.
0785 *
0786 * @param parent the Container that's managing the layout
0787 * @return the minimum dimensions needed to lay out the subcomponents
0788 * of the specified container.
0789 */
0790 public Dimension minimumLayoutSize(Container parent) {
0791 Dimension minimumSize = insetSize(parent);
0792 if (parent.getComponentCount() > 0) {
0793 Dimension childSize = getComponent(0).getMinimumSize();
0794 minimumSize.width += childSize.width;
0795 minimumSize.height += childSize.height;
0796 }
0797 return minimumSize;
0798 }
0799
0800 /**
0801 * Resize the one (and only) child to completely fill the area
0802 * within the parents insets.
0803 */
0804 public void layoutContainer(Container parent) {
0805 if (parent.getComponentCount() > 0) {
0806 Insets insets = parent.getInsets();
0807 int w = parent.getWidth()
0808 - (insets.left + insets.right);
0809 int h = parent.getHeight()
0810 - (insets.top + insets.bottom);
0811 getComponent(0)
0812 .setBounds(insets.left, insets.top, w, h);
0813 }
0814 }
0815
0816 /**
0817 * Pushes the currently edited value to the <code>SpinnerModel</code>.
0818 * <p>
0819 * The default implementation invokes <code>commitEdit</code> on the
0820 * <code>JFormattedTextField</code>.
0821 *
0822 * @throws ParseException if the edited value is not legal
0823 */
0824 public void commitEdit() throws ParseException {
0825 // If the value in the JFormattedTextField is legal, this will have
0826 // the result of pushing the value to the SpinnerModel
0827 // by way of the <code>propertyChange</code> method.
0828 JFormattedTextField ftf = getTextField();
0829
0830 ftf.commitEdit();
0831 }
0832
0833 /**
0834 * Returns the baseline.
0835 *
0836 * @throws IllegalArgumentException {@inheritDoc}
0837 * @see javax.swing.JComponent#getBaseline(int,int)
0838 * @see javax.swing.JComponent#getBaselineResizeBehavior()
0839 * @since 1.6
0840 */
0841 public int getBaseline(int width, int height) {
0842 // check size.
0843 super .getBaseline(width, height);
0844 Insets insets = getInsets();
0845 width = width - insets.left - insets.right;
0846 height = height - insets.top - insets.bottom;
0847 int baseline = getComponent(0).getBaseline(width, height);
0848 if (baseline >= 0) {
0849 return baseline + insets.top;
0850 }
0851 return -1;
0852 }
0853
0854 /**
0855 * Returns an enum indicating how the baseline of the component
0856 * changes as the size changes.
0857 *
0858 * @throws NullPointerException {@inheritDoc}
0859 * @see javax.swing.JComponent#getBaseline(int, int)
0860 * @since 1.6
0861 */
0862 public BaselineResizeBehavior getBaselineResizeBehavior() {
0863 return getComponent(0).getBaselineResizeBehavior();
0864 }
0865 }
0866
0867 /**
0868 * This subclass of javax.swing.DateFormatter maps the minimum/maximum
0869 * properties to te start/end properties of a SpinnerDateModel.
0870 */
0871 private static class DateEditorFormatter extends DateFormatter {
0872 private final SpinnerDateModel model;
0873
0874 DateEditorFormatter(SpinnerDateModel model, DateFormat format) {
0875 super (format);
0876 this .model = model;
0877 }
0878
0879 public void setMinimum(Comparable min) {
0880 model.setStart(min);
0881 }
0882
0883 public Comparable getMinimum() {
0884 return model.getStart();
0885 }
0886
0887 public void setMaximum(Comparable max) {
0888 model.setEnd(max);
0889 }
0890
0891 public Comparable getMaximum() {
0892 return model.getEnd();
0893 }
0894 }
0895
0896 /**
0897 * An editor for a <code>JSpinner</code> whose model is a
0898 * <code>SpinnerDateModel</code>. The value of the editor is
0899 * displayed with a <code>JFormattedTextField</code> whose format
0900 * is defined by a <code>DateFormatter</code> instance whose
0901 * <code>minimum</code> and <code>maximum</code> properties
0902 * are mapped to the <code>SpinnerDateModel</code>.
0903 * @since 1.4
0904 */
0905 // PENDING(hmuller): more example javadoc
0906 public static class DateEditor extends DefaultEditor {
0907 // This is here until SimpleDateFormat gets a constructor that
0908 // takes a Locale: 4923525
0909 private static String getDefaultPattern(Locale loc) {
0910 ResourceBundle r = LocaleData.getDateFormatData(loc);
0911 String[] dateTimePatterns = r
0912 .getStringArray("DateTimePatterns");
0913 Object[] dateTimeArgs = {
0914 dateTimePatterns[DateFormat.SHORT],
0915 dateTimePatterns[DateFormat.SHORT + 4] };
0916 return MessageFormat.format(dateTimePatterns[8],
0917 dateTimeArgs);
0918 }
0919
0920 /**
0921 * Construct a <code>JSpinner</code> editor that supports displaying
0922 * and editing the value of a <code>SpinnerDateModel</code>
0923 * with a <code>JFormattedTextField</code>. <code>This</code>
0924 * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
0925 * on the spinners model and a <code>PropertyChangeListener</code>
0926 * on the new <code>JFormattedTextField</code>.
0927 *
0928 * @param spinner the spinner whose model <code>this</code> editor will monitor
0929 * @exception IllegalArgumentException if the spinners model is not
0930 * an instance of <code>SpinnerDateModel</code>
0931 *
0932 * @see #getModel
0933 * @see #getFormat
0934 * @see SpinnerDateModel
0935 */
0936 public DateEditor(JSpinner spinner) {
0937 this (spinner, getDefaultPattern(spinner.getLocale()));
0938 }
0939
0940 /**
0941 * Construct a <code>JSpinner</code> editor that supports displaying
0942 * and editing the value of a <code>SpinnerDateModel</code>
0943 * with a <code>JFormattedTextField</code>. <code>This</code>
0944 * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
0945 * on the spinner and a <code>PropertyChangeListener</code>
0946 * on the new <code>JFormattedTextField</code>.
0947 *
0948 * @param spinner the spinner whose model <code>this</code> editor will monitor
0949 * @param dateFormatPattern the initial pattern for the
0950 * <code>SimpleDateFormat</code> object that's used to display
0951 * and parse the value of the text field.
0952 * @exception IllegalArgumentException if the spinners model is not
0953 * an instance of <code>SpinnerDateModel</code>
0954 *
0955 * @see #getModel
0956 * @see #getFormat
0957 * @see SpinnerDateModel
0958 * @see java.text.SimpleDateFormat
0959 */
0960 public DateEditor(JSpinner spinner, String dateFormatPattern) {
0961 this (spinner, new SimpleDateFormat(dateFormatPattern,
0962 spinner.getLocale()));
0963 }
0964
0965 /**
0966 * Construct a <code>JSpinner</code> editor that supports displaying
0967 * and editing the value of a <code>SpinnerDateModel</code>
0968 * with a <code>JFormattedTextField</code>. <code>This</code>
0969 * <code>DateEditor</code> becomes both a <code>ChangeListener</code>
0970 * on the spinner and a <code>PropertyChangeListener</code>
0971 * on the new <code>JFormattedTextField</code>.
0972 *
0973 * @param spinner the spinner whose model <code>this</code> editor
0974 * will monitor
0975 * @param format <code>DateFormat</code> object that's used to display
0976 * and parse the value of the text field.
0977 * @exception IllegalArgumentException if the spinners model is not
0978 * an instance of <code>SpinnerDateModel</code>
0979 *
0980 * @see #getModel
0981 * @see #getFormat
0982 * @see SpinnerDateModel
0983 * @see java.text.SimpleDateFormat
0984 */
0985 private DateEditor(JSpinner spinner, DateFormat format) {
0986 super (spinner);
0987 if (!(spinner.getModel() instanceof SpinnerDateModel)) {
0988 throw new IllegalArgumentException(
0989 "model not a SpinnerDateModel");
0990 }
0991
0992 SpinnerDateModel model = (SpinnerDateModel) spinner
0993 .getModel();
0994 DateFormatter formatter = new DateEditorFormatter(model,
0995 format);
0996 DefaultFormatterFactory factory = new DefaultFormatterFactory(
0997 formatter);
0998 JFormattedTextField ftf = getTextField();
0999 ftf.setEditable(true);
1000 ftf.setFormatterFactory(factory);
1001
1002 /* TBD - initializing the column width of the text field
1003 * is imprecise and doing it here is tricky because
1004 * the developer may configure the formatter later.
1005 */
1006 try {
1007 String maxString = formatter.valueToString(model
1008 .getStart());
1009 String minString = formatter.valueToString(model
1010 .getEnd());
1011 ftf.setColumns(Math.max(maxString.length(), minString
1012 .length()));
1013 } catch (ParseException e) {
1014 // PENDING: hmuller
1015 }
1016 }
1017
1018 /**
1019 * Returns the <code>java.text.SimpleDateFormat</code> object the
1020 * <code>JFormattedTextField</code> uses to parse and format
1021 * numbers.
1022 *
1023 * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1024 * @see #getTextField
1025 * @see java.text.SimpleDateFormat
1026 */
1027 public SimpleDateFormat getFormat() {
1028 return (SimpleDateFormat) ((DateFormatter) (getTextField()
1029 .getFormatter())).getFormat();
1030 }
1031
1032 /**
1033 * Return our spinner ancestor's <code>SpinnerDateModel</code>.
1034 *
1035 * @return <code>getSpinner().getModel()</code>
1036 * @see #getSpinner
1037 * @see #getTextField
1038 */
1039 public SpinnerDateModel getModel() {
1040 return (SpinnerDateModel) (getSpinner().getModel());
1041 }
1042 }
1043
1044 /**
1045 * This subclass of javax.swing.NumberFormatter maps the minimum/maximum
1046 * properties to a SpinnerNumberModel and initializes the valueClass
1047 * of the NumberFormatter to match the type of the initial models value.
1048 */
1049 private static class NumberEditorFormatter extends NumberFormatter {
1050 private final SpinnerNumberModel model;
1051
1052 NumberEditorFormatter(SpinnerNumberModel model,
1053 NumberFormat format) {
1054 super (format);
1055 this .model = model;
1056 setValueClass(model.getValue().getClass());
1057 }
1058
1059 public void setMinimum(Comparable min) {
1060 model.setMinimum(min);
1061 }
1062
1063 public Comparable getMinimum() {
1064 return model.getMinimum();
1065 }
1066
1067 public void setMaximum(Comparable max) {
1068 model.setMaximum(max);
1069 }
1070
1071 public Comparable getMaximum() {
1072 return model.getMaximum();
1073 }
1074 }
1075
1076 /**
1077 * An editor for a <code>JSpinner</code> whose model is a
1078 * <code>SpinnerNumberModel</code>. The value of the editor is
1079 * displayed with a <code>JFormattedTextField</code> whose format
1080 * is defined by a <code>NumberFormatter</code> instance whose
1081 * <code>minimum</code> and <code>maximum</code> properties
1082 * are mapped to the <code>SpinnerNumberModel</code>.
1083 * @since 1.4
1084 */
1085 // PENDING(hmuller): more example javadoc
1086 public static class NumberEditor extends DefaultEditor {
1087 // This is here until DecimalFormat gets a constructor that
1088 // takes a Locale: 4923525
1089 private static String getDefaultPattern(Locale locale) {
1090 // Get the pattern for the default locale.
1091 ResourceBundle rb = LocaleData.getNumberFormatData(locale);
1092 String[] all = rb.getStringArray("NumberPatterns");
1093 return all[0];
1094 }
1095
1096 /**
1097 * Construct a <code>JSpinner</code> editor that supports displaying
1098 * and editing the value of a <code>SpinnerNumberModel</code>
1099 * with a <code>JFormattedTextField</code>. <code>This</code>
1100 * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1101 * on the spinner and a <code>PropertyChangeListener</code>
1102 * on the new <code>JFormattedTextField</code>.
1103 *
1104 * @param spinner the spinner whose model <code>this</code> editor will monitor
1105 * @exception IllegalArgumentException if the spinners model is not
1106 * an instance of <code>SpinnerNumberModel</code>
1107 *
1108 * @see #getModel
1109 * @see #getFormat
1110 * @see SpinnerNumberModel
1111 */
1112 public NumberEditor(JSpinner spinner) {
1113 this (spinner, getDefaultPattern(spinner.getLocale()));
1114 }
1115
1116 /**
1117 * Construct a <code>JSpinner</code> editor that supports displaying
1118 * and editing the value of a <code>SpinnerNumberModel</code>
1119 * with a <code>JFormattedTextField</code>. <code>This</code>
1120 * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1121 * on the spinner and a <code>PropertyChangeListener</code>
1122 * on the new <code>JFormattedTextField</code>.
1123 *
1124 * @param spinner the spinner whose model <code>this</code> editor will monitor
1125 * @param decimalFormatPattern the initial pattern for the
1126 * <code>DecimalFormat</code> object that's used to display
1127 * and parse the value of the text field.
1128 * @exception IllegalArgumentException if the spinners model is not
1129 * an instance of <code>SpinnerNumberModel</code> or if
1130 * <code>decimalFormatPattern</code> is not a legal
1131 * argument to <code>DecimalFormat</code>
1132 *
1133 * @see #getTextField
1134 * @see SpinnerNumberModel
1135 * @see java.text.DecimalFormat
1136 */
1137 public NumberEditor(JSpinner spinner,
1138 String decimalFormatPattern) {
1139 this (spinner, new DecimalFormat(decimalFormatPattern));
1140 }
1141
1142 /**
1143 * Construct a <code>JSpinner</code> editor that supports displaying
1144 * and editing the value of a <code>SpinnerNumberModel</code>
1145 * with a <code>JFormattedTextField</code>. <code>This</code>
1146 * <code>NumberEditor</code> becomes both a <code>ChangeListener</code>
1147 * on the spinner and a <code>PropertyChangeListener</code>
1148 * on the new <code>JFormattedTextField</code>.
1149 *
1150 * @param spinner the spinner whose model <code>this</code> editor will monitor
1151 * @param decimalFormatPattern the initial pattern for the
1152 * <code>DecimalFormat</code> object that's used to display
1153 * and parse the value of the text field.
1154 * @exception IllegalArgumentException if the spinners model is not
1155 * an instance of <code>SpinnerNumberModel</code>
1156 *
1157 * @see #getTextField
1158 * @see SpinnerNumberModel
1159 * @see java.text.DecimalFormat
1160 */
1161 private NumberEditor(JSpinner spinner, DecimalFormat format) {
1162 super (spinner);
1163 if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
1164 throw new IllegalArgumentException(
1165 "model not a SpinnerNumberModel");
1166 }
1167
1168 SpinnerNumberModel model = (SpinnerNumberModel) spinner
1169 .getModel();
1170 NumberFormatter formatter = new NumberEditorFormatter(
1171 model, format);
1172 DefaultFormatterFactory factory = new DefaultFormatterFactory(
1173 formatter);
1174 JFormattedTextField ftf = getTextField();
1175 ftf.setEditable(true);
1176 ftf.setFormatterFactory(factory);
1177 ftf.setHorizontalAlignment(JTextField.RIGHT);
1178
1179 /* TBD - initializing the column width of the text field
1180 * is imprecise and doing it here is tricky because
1181 * the developer may configure the formatter later.
1182 */
1183 try {
1184 String maxString = formatter.valueToString(model
1185 .getMinimum());
1186 String minString = formatter.valueToString(model
1187 .getMaximum());
1188 ftf.setColumns(Math.max(maxString.length(), minString
1189 .length()));
1190 } catch (ParseException e) {
1191 // TBD should throw a chained error here
1192 }
1193
1194 }
1195
1196 /**
1197 * Returns the <code>java.text.DecimalFormat</code> object the
1198 * <code>JFormattedTextField</code> uses to parse and format
1199 * numbers.
1200 *
1201 * @return the value of <code>getTextField().getFormatter().getFormat()</code>.
1202 * @see #getTextField
1203 * @see java.text.DecimalFormat
1204 */
1205 public DecimalFormat getFormat() {
1206 return (DecimalFormat) ((NumberFormatter) (getTextField()
1207 .getFormatter())).getFormat();
1208 }
1209
1210 /**
1211 * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1212 *
1213 * @return <code>getSpinner().getModel()</code>
1214 * @see #getSpinner
1215 * @see #getTextField
1216 */
1217 public SpinnerNumberModel getModel() {
1218 return (SpinnerNumberModel) (getSpinner().getModel());
1219 }
1220 }
1221
1222 /**
1223 * An editor for a <code>JSpinner</code> whose model is a
1224 * <code>SpinnerListModel</code>.
1225 * @since 1.4
1226 */
1227 public static class ListEditor extends DefaultEditor {
1228 /**
1229 * Construct a <code>JSpinner</code> editor that supports displaying
1230 * and editing the value of a <code>SpinnerListModel</code>
1231 * with a <code>JFormattedTextField</code>. <code>This</code>
1232 * <code>ListEditor</code> becomes both a <code>ChangeListener</code>
1233 * on the spinner and a <code>PropertyChangeListener</code>
1234 * on the new <code>JFormattedTextField</code>.
1235 *
1236 * @param spinner the spinner whose model <code>this</code> editor will monitor
1237 * @exception IllegalArgumentException if the spinners model is not
1238 * an instance of <code>SpinnerListModel</code>
1239 *
1240 * @see #getModel
1241 * @see SpinnerListModel
1242 */
1243 public ListEditor(JSpinner spinner) {
1244 super (spinner);
1245 if (!(spinner.getModel() instanceof SpinnerListModel)) {
1246 throw new IllegalArgumentException(
1247 "model not a SpinnerListModel");
1248 }
1249 getTextField().setEditable(true);
1250 getTextField().setFormatterFactory(
1251 new DefaultFormatterFactory(new ListFormatter()));
1252 }
1253
1254 /**
1255 * Return our spinner ancestor's <code>SpinnerNumberModel</code>.
1256 *
1257 * @return <code>getSpinner().getModel()</code>
1258 * @see #getSpinner
1259 * @see #getTextField
1260 */
1261 public SpinnerListModel getModel() {
1262 return (SpinnerListModel) (getSpinner().getModel());
1263 }
1264
1265 /**
1266 * ListFormatter provides completion while text is being input
1267 * into the JFormattedTextField. Completion is only done if the
1268 * user is inserting text at the end of the document. Completion
1269 * is done by way of the SpinnerListModel method findNextMatch.
1270 */
1271 private class ListFormatter extends
1272 JFormattedTextField.AbstractFormatter {
1273 private DocumentFilter filter;
1274
1275 public String valueToString(Object value)
1276 throws ParseException {
1277 if (value == null) {
1278 return "";
1279 }
1280 return value.toString();
1281 }
1282
1283 public Object stringToValue(String string)
1284 throws ParseException {
1285 return string;
1286 }
1287
1288 protected DocumentFilter getDocumentFilter() {
1289 if (filter == null) {
1290 filter = new Filter();
1291 }
1292 return filter;
1293 }
1294
1295 private class Filter extends DocumentFilter {
1296 public void replace(FilterBypass fb, int offset,
1297 int length, String string, AttributeSet attrs)
1298 throws BadLocationException {
1299 if (string != null
1300 && (offset + length) == fb.getDocument()
1301 .getLength()) {
1302 Object next = getModel().findNextMatch(
1303 fb.getDocument().getText(0, offset)
1304 + string);
1305 String value = (next != null) ? next.toString()
1306 : null;
1307
1308 if (value != null) {
1309 fb.remove(0, offset + length);
1310 fb.insertString(0, value, null);
1311 getFormattedTextField().select(
1312 offset + string.length(),
1313 value.length());
1314 return;
1315 }
1316 }
1317 super .replace(fb, offset, length, string, attrs);
1318 }
1319
1320 public void insertString(FilterBypass fb, int offset,
1321 String string, AttributeSet attr)
1322 throws BadLocationException {
1323 replace(fb, offset, 0, string, attr);
1324 }
1325 }
1326 }
1327 }
1328
1329 /**
1330 * An Action implementation that is always disabled.
1331 */
1332 private static class DisabledAction implements Action {
1333 public Object getValue(String key) {
1334 return null;
1335 }
1336
1337 public void putValue(String key, Object value) {
1338 }
1339
1340 public void setEnabled(boolean b) {
1341 }
1342
1343 public boolean isEnabled() {
1344 return false;
1345 }
1346
1347 public void addPropertyChangeListener(PropertyChangeListener l) {
1348 }
1349
1350 public void removePropertyChangeListener(
1351 PropertyChangeListener l) {
1352 }
1353
1354 public void actionPerformed(ActionEvent ae) {
1355 }
1356 }
1357
1358 /////////////////
1359 // Accessibility support
1360 ////////////////
1361
1362 /**
1363 * Gets the <code>AccessibleContext</code> for the <code>JSpinner</code>
1364 *
1365 * @return the <code>AccessibleContext</code> for the <code>JSpinner</code>
1366 * @since 1.5
1367 */
1368 public AccessibleContext getAccessibleContext() {
1369 if (accessibleContext == null) {
1370 accessibleContext = new AccessibleJSpinner();
1371 }
1372 return accessibleContext;
1373 }
1374
1375 /**
1376 * <code>AccessibleJSpinner</code> implements accessibility
1377 * support for the <code>JSpinner</code> class.
1378 * @since 1.5
1379 */
1380 protected class AccessibleJSpinner extends AccessibleJComponent
1381 implements AccessibleValue, AccessibleAction,
1382 AccessibleText, AccessibleEditableText, ChangeListener {
1383
1384 private Object oldModelValue = null;
1385
1386 /**
1387 * AccessibleJSpinner constructor
1388 */
1389 protected AccessibleJSpinner() {
1390 // model is guaranteed to be non-null
1391 oldModelValue = model.getValue();
1392 JSpinner.this .addChangeListener(this );
1393 }
1394
1395 /**
1396 * Invoked when the target of the listener has changed its state.
1397 *
1398 * @param e a <code>ChangeEvent</code> object. Must not be null.
1399 * @throws NullPointerException if the parameter is null.
1400 */
1401 public void stateChanged(ChangeEvent e) {
1402 if (e == null) {
1403 throw new NullPointerException();
1404 }
1405 Object newModelValue = model.getValue();
1406 firePropertyChange(ACCESSIBLE_VALUE_PROPERTY,
1407 oldModelValue, newModelValue);
1408 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, 0); // entire text may have changed
1409
1410 oldModelValue = newModelValue;
1411 }
1412
1413 /* ===== Begin AccessibleContext methods ===== */
1414
1415 /**
1416 * Gets the role of this object. The role of the object is the generic
1417 * purpose or use of the class of this object. For example, the role
1418 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
1419 * AccessibleRole are provided so component developers can pick from
1420 * a set of predefined roles. This enables assistive technologies to
1421 * provide a consistent interface to various tweaked subclasses of
1422 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1423 * that act like a push button) as well as distinguish between sublasses
1424 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1425 * and AccessibleRole.RADIO_BUTTON for radio buttons).
1426 * <p>Note that the AccessibleRole class is also extensible, so
1427 * custom component developers can define their own AccessibleRole's
1428 * if the set of predefined roles is inadequate.
1429 *
1430 * @return an instance of AccessibleRole describing the role of the object
1431 * @see AccessibleRole
1432 */
1433 public AccessibleRole getAccessibleRole() {
1434 return AccessibleRole.SPIN_BOX;
1435 }
1436
1437 /**
1438 * Returns the number of accessible children of the object.
1439 *
1440 * @return the number of accessible children of the object.
1441 */
1442 public int getAccessibleChildrenCount() {
1443 // the JSpinner has one child, the editor
1444 if (editor.getAccessibleContext() != null) {
1445 return 1;
1446 }
1447 return 0;
1448 }
1449
1450 /**
1451 * Returns the specified Accessible child of the object. The Accessible
1452 * children of an Accessible object are zero-based, so the first child
1453 * of an Accessible child is at index 0, the second child is at index 1,
1454 * and so on.
1455 *
1456 * @param i zero-based index of child
1457 * @return the Accessible child of the object
1458 * @see #getAccessibleChildrenCount
1459 */
1460 public Accessible getAccessibleChild(int i) {
1461 // the JSpinner has one child, the editor
1462 if (i != 0) {
1463 return null;
1464 }
1465 if (editor.getAccessibleContext() != null) {
1466 return (Accessible) editor;
1467 }
1468 return null;
1469 }
1470
1471 /* ===== End AccessibleContext methods ===== */
1472
1473 /**
1474 * Gets the AccessibleAction associated with this object that supports
1475 * one or more actions.
1476 *
1477 * @return AccessibleAction if supported by object; else return null
1478 * @see AccessibleAction
1479 */
1480 public AccessibleAction getAccessibleAction() {
1481 return this ;
1482 }
1483
1484 /**
1485 * Gets the AccessibleText associated with this object presenting
1486 * text on the display.
1487 *
1488 * @return AccessibleText if supported by object; else return null
1489 * @see AccessibleText
1490 */
1491 public AccessibleText getAccessibleText() {
1492 return this ;
1493 }
1494
1495 /*
1496 * Returns the AccessibleContext for the JSpinner editor
1497 */
1498 private AccessibleContext getEditorAccessibleContext() {
1499 if (editor instanceof DefaultEditor) {
1500 JTextField textField = ((DefaultEditor) editor)
1501 .getTextField();
1502 if (textField != null) {
1503 return textField.getAccessibleContext();
1504 }
1505 } else if (editor instanceof Accessible) {
1506 return ((Accessible) editor).getAccessibleContext();
1507 }
1508 return null;
1509 }
1510
1511 /*
1512 * Returns the AccessibleText for the JSpinner editor
1513 */
1514 private AccessibleText getEditorAccessibleText() {
1515 AccessibleContext ac = getEditorAccessibleContext();
1516 if (ac != null) {
1517 return ac.getAccessibleText();
1518 }
1519 return null;
1520 }
1521
1522 /*
1523 * Returns the AccessibleEditableText for the JSpinner editor
1524 */
1525 private AccessibleEditableText getEditorAccessibleEditableText() {
1526 AccessibleText at = getEditorAccessibleText();
1527 if (at instanceof AccessibleEditableText) {
1528 return (AccessibleEditableText) at;
1529 }
1530 return null;
1531 }
1532
1533 /**
1534 * Gets the AccessibleValue associated with this object.
1535 *
1536 * @return AccessibleValue if supported by object; else return null
1537 * @see AccessibleValue
1538 *
1539 */
1540 public AccessibleValue getAccessibleValue() {
1541 return this ;
1542 }
1543
1544 /* ===== Begin AccessibleValue impl ===== */
1545
1546 /**
1547 * Get the value of this object as a Number. If the value has not been
1548 * set, the return value will be null.
1549 *
1550 * @return value of the object
1551 * @see #setCurrentAccessibleValue
1552 */
1553 public Number getCurrentAccessibleValue() {
1554 Object o = model.getValue();
1555 if (o instanceof Number) {
1556 return (Number) o;
1557 }
1558 return null;
1559 }
1560
1561 /**
1562 * Set the value of this object as a Number.
1563 *
1564 * @param n the value to set for this object
1565 * @return true if the value was set; else False
1566 * @see #getCurrentAccessibleValue
1567 */
1568 public boolean setCurrentAccessibleValue(Number n) {
1569 // try to set the new value
1570 try {
1571 model.setValue(n);
1572 return true;
1573 } catch (IllegalArgumentException iae) {
1574 // SpinnerModel didn't like new value
1575 }
1576 return false;
1577 }
1578
1579 /**
1580 * Get the minimum value of this object as a Number.
1581 *
1582 * @return Minimum value of the object; null if this object does not
1583 * have a minimum value
1584 * @see #getMaximumAccessibleValue
1585 */
1586 public Number getMinimumAccessibleValue() {
1587 if (model instanceof SpinnerNumberModel) {
1588 SpinnerNumberModel numberModel = (SpinnerNumberModel) model;
1589 Object o = numberModel.getMinimum();
1590 if (o instanceof Number) {
1591 return (Number) o;
1592 }
1593 }
1594 return null;
1595 }
1596
1597 /**
1598 * Get the maximum value of this object as a Number.
1599 *
1600 * @return Maximum value of the object; null if this object does not
1601 * have a maximum value
1602 * @see #getMinimumAccessibleValue
1603 */
1604 public Number getMaximumAccessibleValue() {
1605 if (model instanceof SpinnerNumberModel) {
1606 SpinnerNumberModel numberModel = (SpinnerNumberModel) model;
1607 Object o = numberModel.getMaximum();
1608 if (o instanceof Number) {
1609 return (Number) o;
1610 }
1611 }
1612 return null;
1613 }
1614
1615 /* ===== End AccessibleValue impl ===== */
1616
1617 /* ===== Begin AccessibleAction impl ===== */
1618
1619 /**
1620 * Returns the number of accessible actions available in this object
1621 * If there are more than one, the first one is considered the "default"
1622 * action of the object.
1623 *
1624 * Two actions are supported: AccessibleAction.INCREMENT which
1625 * increments the spinner value and AccessibleAction.DECREMENT
1626 * which decrements the spinner value
1627 *
1628 * @return the zero-based number of Actions in this object
1629 */
1630 public int getAccessibleActionCount() {
1631 return 2;
1632 }
1633
1634 /**
1635 * Returns a description of the specified action of the object.
1636 *
1637 * @param i zero-based index of the actions
1638 * @return a String description of the action
1639 * @see #getAccessibleActionCount
1640 */
1641 public String getAccessibleActionDescription(int i) {
1642 if (i == 0) {
1643 return AccessibleAction.INCREMENT;
1644 } else if (i == 1) {
1645 return AccessibleAction.DECREMENT;
1646 }
1647 return null;
1648 }
1649
1650 /**
1651 * Performs the specified Action on the object
1652 *
1653 * @param i zero-based index of actions. The first action
1654 * (index 0) is AccessibleAction.INCREMENT and the second
1655 * action (index 1) is AccessibleAction.DECREMENT.
1656 * @return true if the action was performed; otherwise false.
1657 * @see #getAccessibleActionCount
1658 */
1659 public boolean doAccessibleAction(int i) {
1660 if (i < 0 || i > 1) {
1661 return false;
1662 }
1663 Object o = null;
1664 if (i == 0) {
1665 o = getNextValue(); // AccessibleAction.INCREMENT
1666 } else {
1667 o = getPreviousValue(); // AccessibleAction.DECREMENT
1668 }
1669 // try to set the new value
1670 try {
1671 model.setValue(o);
1672 return true;
1673 } catch (IllegalArgumentException iae) {
1674 // SpinnerModel didn't like new value
1675 }
1676 return false;
1677 }
1678
1679 /* ===== End AccessibleAction impl ===== */
1680
1681 /* ===== Begin AccessibleText impl ===== */
1682
1683 /*
1684 * Returns whether source and destination components have the
1685 * same window ancestor
1686 */
1687 private boolean sameWindowAncestor(Component src, Component dest) {
1688 if (src == null || dest == null) {
1689 return false;
1690 }
1691 return SwingUtilities.getWindowAncestor(src) == SwingUtilities
1692 .getWindowAncestor(dest);
1693 }
1694
1695 /**
1696 * Given a point in local coordinates, return the zero-based index
1697 * of the character under that Point. If the point is invalid,
1698 * this method returns -1.
1699 *
1700 * @param p the Point in local coordinates
1701 * @return the zero-based index of the character under Point p; if
1702 * Point is invalid return -1.
1703 */
1704 public int getIndexAtPoint(Point p) {
1705 AccessibleText at = getEditorAccessibleText();
1706 if (at != null && sameWindowAncestor(JSpinner.this , editor)) {
1707 // convert point from the JSpinner bounds (source) to
1708 // editor bounds (destination)
1709 Point editorPoint = SwingUtilities.convertPoint(
1710 JSpinner.this , p, editor);
1711 if (editorPoint != null) {
1712 return at.getIndexAtPoint(editorPoint);
1713 }
1714 }
1715 return -1;
1716 }
1717
1718 /**
1719 * Determines the bounding box of the character at the given
1720 * index into the string. The bounds are returned in local
1721 * coordinates. If the index is invalid an empty rectangle is
1722 * returned.
1723 *
1724 * @param i the index into the String
1725 * @return the screen coordinates of the character's bounding box,
1726 * if index is invalid return an empty rectangle.
1727 */
1728 public Rectangle getCharacterBounds(int i) {
1729 AccessibleText at = getEditorAccessibleText();
1730 if (at != null) {
1731 Rectangle editorRect = at.getCharacterBounds(i);
1732 if (editorRect != null
1733 && sameWindowAncestor(JSpinner.this , editor)) {
1734 // return rectangle in the the JSpinner bounds
1735 return SwingUtilities.convertRectangle(editor,
1736 editorRect, JSpinner.this );
1737 }
1738 }
1739 return null;
1740 }
1741
1742 /**
1743 * Returns the number of characters (valid indicies)
1744 *
1745 * @return the number of characters
1746 */
1747 public int getCharCount() {
1748 AccessibleText at = getEditorAccessibleText();
1749 if (at != null) {
1750 return at.getCharCount();
1751 }
1752 return -1;
1753 }
1754
1755 /**
1756 * Returns the zero-based offset of the caret.
1757 *
1758 * Note: That to the right of the caret will have the same index
1759 * value as the offset (the caret is between two characters).
1760 * @return the zero-based offset of the caret.
1761 */
1762 public int getCaretPosition() {
1763 AccessibleText at = getEditorAccessibleText();
1764 if (at != null) {
1765 return at.getCaretPosition();
1766 }
1767 return -1;
1768 }
1769
1770 /**
1771 * Returns the String at a given index.
1772 *
1773 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1774 * @param index an index within the text
1775 * @return the letter, word, or sentence
1776 */
1777 public String getAtIndex(int part, int index) {
1778 AccessibleText at = getEditorAccessibleText();
1779 if (at != null) {
1780 return at.getAtIndex(part, index);
1781 }
1782 return null;
1783 }
1784
1785 /**
1786 * Returns the String after a given index.
1787 *
1788 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1789 * @param index an index within the text
1790 * @return the letter, word, or sentence
1791 */
1792 public String getAfterIndex(int part, int index) {
1793 AccessibleText at = getEditorAccessibleText();
1794 if (at != null) {
1795 return at.getAfterIndex(part, index);
1796 }
1797 return null;
1798 }
1799
1800 /**
1801 * Returns the String before a given index.
1802 *
1803 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
1804 * @param index an index within the text
1805 * @return the letter, word, or sentence
1806 */
1807 public String getBeforeIndex(int part, int index) {
1808 AccessibleText at = getEditorAccessibleText();
1809 if (at != null) {
1810 return at.getBeforeIndex(part, index);
1811 }
1812 return null;
1813 }
1814
1815 /**
1816 * Returns the AttributeSet for a given character at a given index
1817 *
1818 * @param i the zero-based index into the text
1819 * @return the AttributeSet of the character
1820 */
1821 public AttributeSet getCharacterAttribute(int i) {
1822 AccessibleText at = getEditorAccessibleText();
1823 if (at != null) {
1824 return at.getCharacterAttribute(i);
1825 }
1826 return null;
1827 }
1828
1829 /**
1830 * Returns the start offset within the selected text.
1831 * If there is no selection, but there is
1832 * a caret, the start and end offsets will be the same.
1833 *
1834 * @return the index into the text of the start of the selection
1835 */
1836 public int getSelectionStart() {
1837 AccessibleText at = getEditorAccessibleText();
1838 if (at != null) {
1839 return at.getSelectionStart();
1840 }
1841 return -1;
1842 }
1843
1844 /**
1845 * Returns the end offset within the selected text.
1846 * If there is no selection, but there is
1847 * a caret, the start and end offsets will be the same.
1848 *
1849 * @return the index into teh text of the end of the selection
1850 */
1851 public int getSelectionEnd() {
1852 AccessibleText at = getEditorAccessibleText();
1853 if (at != null) {
1854 return at.getSelectionEnd();
1855 }
1856 return -1;
1857 }
1858
1859 /**
1860 * Returns the portion of the text that is selected.
1861 *
1862 * @return the String portion of the text that is selected
1863 */
1864 public String getSelectedText() {
1865 AccessibleText at = getEditorAccessibleText();
1866 if (at != null) {
1867 return at.getSelectedText();
1868 }
1869 return null;
1870 }
1871
1872 /* ===== End AccessibleText impl ===== */
1873
1874 /* ===== Begin AccessibleEditableText impl ===== */
1875
1876 /**
1877 * Sets the text contents to the specified string.
1878 *
1879 * @param s the string to set the text contents
1880 */
1881 public void setTextContents(String s) {
1882 AccessibleEditableText at = getEditorAccessibleEditableText();
1883 if (at != null) {
1884 at.setTextContents(s);
1885 }
1886 }
1887
1888 /**
1889 * Inserts the specified string at the given index/
1890 *
1891 * @param index the index in the text where the string will
1892 * be inserted
1893 * @param s the string to insert in the text
1894 */
1895 public void insertTextAtIndex(int index, String s) {
1896 AccessibleEditableText at = getEditorAccessibleEditableText();
1897 if (at != null) {
1898 at.insertTextAtIndex(index, s);
1899 }
1900 }
1901
1902 /**
1903 * Returns the text string between two indices.
1904 *
1905 * @param startIndex the starting index in the text
1906 * @param endIndex the ending index in the text
1907 * @return the text string between the indices
1908 */
1909 public String getTextRange(int startIndex, int endIndex) {
1910 AccessibleEditableText at = getEditorAccessibleEditableText();
1911 if (at != null) {
1912 return at.getTextRange(startIndex, endIndex);
1913 }
1914 return null;
1915 }
1916
1917 /**
1918 * Deletes the text between two indices
1919 *
1920 * @param startIndex the starting index in the text
1921 * @param endIndex the ending index in the text
1922 */
1923 public void delete(int startIndex, int endIndex) {
1924 AccessibleEditableText at = getEditorAccessibleEditableText();
1925 if (at != null) {
1926 at.delete(startIndex, endIndex);
1927 }
1928 }
1929
1930 /**
1931 * Cuts the text between two indices into the system clipboard.
1932 *
1933 * @param startIndex the starting index in the text
1934 * @param endIndex the ending index in the text
1935 */
1936 public void cut(int startIndex, int endIndex) {
1937 AccessibleEditableText at = getEditorAccessibleEditableText();
1938 if (at != null) {
1939 at.cut(startIndex, endIndex);
1940 }
1941 }
1942
1943 /**
1944 * Pastes the text from the system clipboard into the text
1945 * starting at the specified index.
1946 *
1947 * @param startIndex the starting index in the text
1948 */
1949 public void paste(int startIndex) {
1950 AccessibleEditableText at = getEditorAccessibleEditableText();
1951 if (at != null) {
1952 at.paste(startIndex);
1953 }
1954 }
1955
1956 /**
1957 * Replaces the text between two indices with the specified
1958 * string.
1959 *
1960 * @param startIndex the starting index in the text
1961 * @param endIndex the ending index in the text
1962 * @param s the string to replace the text between two indices
1963 */
1964 public void replaceText(int startIndex, int endIndex, String s) {
1965 AccessibleEditableText at = getEditorAccessibleEditableText();
1966 if (at != null) {
1967 at.replaceText(startIndex, endIndex, s);
1968 }
1969 }
1970
1971 /**
1972 * Selects the text between two indices.
1973 *
1974 * @param startIndex the starting index in the text
1975 * @param endIndex the ending index in the text
1976 */
1977 public void selectText(int startIndex, int endIndex) {
1978 AccessibleEditableText at = getEditorAccessibleEditableText();
1979 if (at != null) {
1980 at.selectText(startIndex, endIndex);
1981 }
1982 }
1983
1984 /**
1985 * Sets attributes for the text between two indices.
1986 *
1987 * @param startIndex the starting index in the text
1988 * @param endIndex the ending index in the text
1989 * @param as the attribute set
1990 * @see AttributeSet
1991 */
1992 public void setAttributes(int startIndex, int endIndex,
1993 AttributeSet as) {
1994 AccessibleEditableText at = getEditorAccessibleEditableText();
1995 if (at != null) {
1996 at.setAttributes(startIndex, endIndex, as);
1997 }
1998 }
1999 } /* End AccessibleJSpinner */
2000 }
|