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