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