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