1 /* 2 * Copyright (c) 1997, 2010, 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 javax.swing.event.*; 29 import javax.swing.plaf.*; 30 import javax.accessibility.*; 31 32 import java.io.Serializable; 33 import java.io.ObjectOutputStream; 34 import java.io.IOException; 35 36 import java.awt.*; 37 import java.util.*; 38 import java.beans.*; 39 40 41 /** 42 * A component that lets the user graphically select a value by sliding 43 * a knob within a bounded interval. The knob is always positioned 44 * at the points that match integer values within the specified interval. 45 * <p> 46 * The slider can show both 47 * major tick marks, and minor tick marks between the major ones. The number of 48 * values between the tick marks is controlled with 49 * <code>setMajorTickSpacing</code> and <code>setMinorTickSpacing</code>. 50 * Painting of tick marks is controlled by {@code setPaintTicks}. 51 * <p> 52 * Sliders can also print text labels at regular intervals (or at 53 * arbitrary locations) along the slider track. Painting of labels is 54 * controlled by {@code setLabelTable} and {@code setPaintLabels}. 55 * <p> 56 * For further information and examples see 57 * <a 58 href="http://java.sun.com/docs/books/tutorial/uiswing/components/slider.html">How to Use Sliders</a>, 59 * a section in <em>The Java Tutorial.</em> 60 * <p> 61 * <strong>Warning:</strong> Swing is not thread safe. For more 62 * information see <a 63 * href="package-summary.html#threading">Swing's Threading 64 * Policy</a>. 65 * <p> 66 * <strong>Warning:</strong> 67 * Serialized objects of this class will not be compatible with 68 * future Swing releases. The current serialization support is 69 * appropriate for short term storage or RMI between applications running 70 * the same version of Swing. As of 1.4, support for long term storage 71 * of all JavaBeans<sup><font size="-2">TM</font></sup> 72 * has been added to the <code>java.beans</code> package. 73 * Please see {@link java.beans.XMLEncoder}. 74 * 75 * @beaninfo 76 * attribute: isContainer false 77 * description: A component that supports selecting a integer value from a range. 78 * 79 * @author David Kloba 80 */ 81 public class JSlider extends JComponent implements SwingConstants, Accessible { 82 /** 83 * @see #getUIClassID 84 * @see #readObject 85 */ 86 private static final String uiClassID = "SliderUI"; 87 88 private boolean paintTicks = false; 89 private boolean paintTrack = true; 90 private boolean paintLabels = false; 91 private boolean isInverted = false; 92 93 /** 94 * The data model that handles the numeric maximum value, 95 * minimum value, and current-position value for the slider. 96 */ 97 protected BoundedRangeModel sliderModel; 98 99 /** 100 * The number of values between the major tick marks -- the 101 * larger marks that break up the minor tick marks. 102 */ 103 protected int majorTickSpacing; 104 105 /** 106 * The number of values between the minor tick marks -- the 107 * smaller marks that occur between the major tick marks. 108 * @see #setMinorTickSpacing 109 */ 110 protected int minorTickSpacing; 111 112 /** 113 * If true, the knob (and the data value it represents) 114 * resolve to the closest tick mark next to where the user 115 * positioned the knob. The default is false. 116 * @see #setSnapToTicks 117 */ 118 protected boolean snapToTicks = false; 119 120 /** 121 * If true, the knob (and the data value it represents) 122 * resolve to the closest slider value next to where the user 123 * positioned the knob. 124 */ 125 boolean snapToValue = true; 126 127 /** 128 * Whether the slider is horizontal or vertical 129 * The default is horizontal. 130 * 131 * @see #setOrientation 132 */ 133 protected int orientation; 134 135 136 /** 137 * {@code Dictionary} of what labels to draw at which values 138 */ 139 private Dictionary labelTable; 140 141 142 /** 143 * The changeListener (no suffix) is the listener we add to the 144 * slider's model. This listener is initialized to the 145 * {@code ChangeListener} returned from {@code createChangeListener}, 146 * which by default just forwards events 147 * to {@code ChangeListener}s (if any) added directly to the slider. 148 * 149 * @see #addChangeListener 150 * @see #createChangeListener 151 */ 152 protected ChangeListener changeListener = createChangeListener(); 153 154 155 /** 156 * Only one <code>ChangeEvent</code> is needed per slider instance since the 157 * event's only (read-only) state is the source property. The source 158 * of events generated here is always "this". The event is lazily 159 * created the first time that an event notification is fired. 160 * 161 * @see #fireStateChanged 162 */ 163 protected transient ChangeEvent changeEvent = null; 164 165 166 private void checkOrientation(int orientation) { 167 switch (orientation) { 168 case VERTICAL: 169 case HORIZONTAL: 170 break; 171 default: 172 throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL"); 173 } 174 } 175 176 177 /** 178 * Creates a horizontal slider with the range 0 to 100 and 179 * an initial value of 50. 180 */ 181 public JSlider() { 182 this(HORIZONTAL, 0, 100, 50); 183 } 184 185 186 /** 187 * Creates a slider using the specified orientation with the 188 * range {@code 0} to {@code 100} and an initial value of {@code 50}. 189 * The orientation can be 190 * either <code>SwingConstants.VERTICAL</code> or 191 * <code>SwingConstants.HORIZONTAL</code>. 192 * 193 * @param orientation the orientation of the slider 194 * @throws IllegalArgumentException if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL} 195 * @see #setOrientation 196 */ 197 public JSlider(int orientation) { 198 this(orientation, 0, 100, 50); 199 } 200 201 202 /** 203 * Creates a horizontal slider using the specified min and max 204 * with an initial value equal to the average of the min plus max. 205 * <p> 206 * The <code>BoundedRangeModel</code> that holds the slider's data 207 * handles any issues that may arise from improperly setting the 208 * minimum and maximum values on the slider. See the 209 * {@code BoundedRangeModel} documentation for details. 210 * 211 * @param min the minimum value of the slider 212 * @param max the maximum value of the slider 213 * 214 * @see BoundedRangeModel 215 * @see #setMinimum 216 * @see #setMaximum 217 */ 218 public JSlider(int min, int max) { 219 this(HORIZONTAL, min, max, (min + max) / 2); 220 } 221 222 223 /** 224 * Creates a horizontal slider using the specified min, max and value. 225 * <p> 226 * The <code>BoundedRangeModel</code> that holds the slider's data 227 * handles any issues that may arise from improperly setting the 228 * minimum, initial, and maximum values on the slider. See the 229 * {@code BoundedRangeModel} documentation for details. 230 * 231 * @param min the minimum value of the slider 232 * @param max the maximum value of the slider 233 * @param value the initial value of the slider 234 * 235 * @see BoundedRangeModel 236 * @see #setMinimum 237 * @see #setMaximum 238 * @see #setValue 239 */ 240 public JSlider(int min, int max, int value) { 241 this(HORIZONTAL, min, max, value); 242 } 243 244 245 /** 246 * Creates a slider with the specified orientation and the 247 * specified minimum, maximum, and initial values. 248 * The orientation can be 249 * either <code>SwingConstants.VERTICAL</code> or 250 * <code>SwingConstants.HORIZONTAL</code>. 251 * <p> 252 * The <code>BoundedRangeModel</code> that holds the slider's data 253 * handles any issues that may arise from improperly setting the 254 * minimum, initial, and maximum values on the slider. See the 255 * {@code BoundedRangeModel} documentation for details. 256 * 257 * @param orientation the orientation of the slider 258 * @param min the minimum value of the slider 259 * @param max the maximum value of the slider 260 * @param value the initial value of the slider 261 * 262 * @throws IllegalArgumentException if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL} 263 * 264 * @see BoundedRangeModel 265 * @see #setOrientation 266 * @see #setMinimum 267 * @see #setMaximum 268 * @see #setValue 269 */ 270 public JSlider(int orientation, int min, int max, int value) 271 { 272 checkOrientation(orientation); 273 this.orientation = orientation; 274 setModel(new DefaultBoundedRangeModel(value, 0, min, max)); 275 updateUI(); 276 } 277 278 279 /** 280 * Creates a horizontal slider using the specified 281 * BoundedRangeModel. 282 */ 283 public JSlider(BoundedRangeModel brm) 284 { 285 this.orientation = JSlider.HORIZONTAL; 286 setModel(brm); 287 updateUI(); 288 } 289 290 291 /** 292 * Gets the UI object which implements the L&F for this component. 293 * 294 * @return the SliderUI object that implements the Slider L&F 295 */ 296 public SliderUI getUI() { 297 return(SliderUI)ui; 298 } 299 300 301 /** 302 * Sets the UI object which implements the L&F for this component. 303 * 304 * @param ui the SliderUI L&F object 305 * @see UIDefaults#getUI 306 * @beaninfo 307 * bound: true 308 * hidden: true 309 * attribute: visualUpdate true 310 * description: The UI object that implements the slider's LookAndFeel. 311 */ 312 public void setUI(SliderUI ui) { 313 super.setUI(ui); 314 } 315 316 317 /** 318 * Resets the UI property to a value from the current look and feel. 319 * 320 * @see JComponent#updateUI 321 */ 322 public void updateUI() { 323 setUI((SliderUI)UIManager.getUI(this)); 324 // The labels preferred size may be derived from the font 325 // of the slider, so we must update the UI of the slider first, then 326 // that of labels. This way when setSize is called the right 327 // font is used. 328 updateLabelUIs(); 329 } 330 331 332 /** 333 * Returns the name of the L&F class that renders this component. 334 * 335 * @return "SliderUI" 336 * @see JComponent#getUIClassID 337 * @see UIDefaults#getUI 338 */ 339 public String getUIClassID() { 340 return uiClassID; 341 } 342 343 344 /** 345 * We pass Change events along to the listeners with the 346 * the slider (instead of the model itself) as the event source. 347 */ 348 private class ModelListener implements ChangeListener, Serializable { 349 public void stateChanged(ChangeEvent e) { 350 fireStateChanged(); 351 } 352 } 353 354 355 /** 356 * Subclasses that want to handle {@code ChangeEvent}s 357 * from the model differently 358 * can override this to return 359 * an instance of a custom <code>ChangeListener</code> implementation. 360 * The default {@code ChangeListener} simply calls the 361 * {@code fireStateChanged} method to forward {@code ChangeEvent}s 362 * to the {@code ChangeListener}s that have been added directly to the 363 * slider. 364 * @see #changeListener 365 * @see #fireStateChanged 366 * @see javax.swing.event.ChangeListener 367 * @see javax.swing.BoundedRangeModel 368 */ 369 protected ChangeListener createChangeListener() { 370 return new ModelListener(); 371 } 372 373 374 /** 375 * Adds a ChangeListener to the slider. 376 * 377 * @param l the ChangeListener to add 378 * @see #fireStateChanged 379 * @see #removeChangeListener 380 */ 381 public void addChangeListener(ChangeListener l) { 382 listenerList.add(ChangeListener.class, l); 383 } 384 385 386 /** 387 * Removes a ChangeListener from the slider. 388 * 389 * @param l the ChangeListener to remove 390 * @see #fireStateChanged 391 * @see #addChangeListener 392 393 */ 394 public void removeChangeListener(ChangeListener l) { 395 listenerList.remove(ChangeListener.class, l); 396 } 397 398 399 /** 400 * Returns an array of all the <code>ChangeListener</code>s added 401 * to this JSlider with addChangeListener(). 402 * 403 * @return all of the <code>ChangeListener</code>s added or an empty 404 * array if no listeners have been added 405 * @since 1.4 406 */ 407 public ChangeListener[] getChangeListeners() { 408 return listenerList.getListeners(ChangeListener.class); 409 } 410 411 412 /** 413 * Send a {@code ChangeEvent}, whose source is this {@code JSlider}, to 414 * all {@code ChangeListener}s that have registered interest in 415 * {@code ChangeEvent}s. 416 * This method is called each time a {@code ChangeEvent} is received from 417 * the model. 418 * <p> 419 * The event instance is created if necessary, and stored in 420 * {@code changeEvent}. 421 * 422 * @see #addChangeListener 423 * @see EventListenerList 424 */ 425 protected void fireStateChanged() { 426 Object[] listeners = listenerList.getListenerList(); 427 for (int i = listeners.length - 2; i >= 0; i -= 2) { 428 if (listeners[i]==ChangeListener.class) { 429 if (changeEvent == null) { 430 changeEvent = new ChangeEvent(this); 431 } 432 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 433 } 434 } 435 } 436 437 438 /** 439 * Returns the {@code BoundedRangeModel} that handles the slider's three 440 * fundamental properties: minimum, maximum, value. 441 * 442 * @return the data model for this component 443 * @see #setModel 444 * @see BoundedRangeModel 445 */ 446 public BoundedRangeModel getModel() { 447 return sliderModel; 448 } 449 450 451 /** 452 * Sets the {@code BoundedRangeModel} that handles the slider's three 453 * fundamental properties: minimum, maximum, value. 454 *<p> 455 * Attempts to pass a {@code null} model to this method result in 456 * undefined behavior, and, most likely, exceptions. 457 * 458 * @param newModel the new, {@code non-null} <code>BoundedRangeModel</code> to use 459 * 460 * @see #getModel 461 * @see BoundedRangeModel 462 * @beaninfo 463 * bound: true 464 * description: The sliders BoundedRangeModel. 465 */ 466 public void setModel(BoundedRangeModel newModel) 467 { 468 BoundedRangeModel oldModel = getModel(); 469 470 if (oldModel != null) { 471 oldModel.removeChangeListener(changeListener); 472 } 473 474 sliderModel = newModel; 475 476 if (newModel != null) { 477 newModel.addChangeListener(changeListener); 478 } 479 480 if (accessibleContext != null) { 481 accessibleContext.firePropertyChange( 482 AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, 483 (oldModel == null 484 ? null : Integer.valueOf(oldModel.getValue())), 485 (newModel == null 486 ? null : Integer.valueOf(newModel.getValue()))); 487 } 488 489 firePropertyChange("model", oldModel, sliderModel); 490 } 491 492 493 /** 494 * Returns the slider's current value 495 * from the {@code BoundedRangeModel}. 496 * 497 * @return the current value of the slider 498 * @see #setValue 499 * @see BoundedRangeModel#getValue 500 */ 501 public int getValue() { 502 return getModel().getValue(); 503 } 504 505 /** 506 * Sets the slider's current value to {@code n}. This method 507 * forwards the new value to the model. 508 * <p> 509 * The data model (an instance of {@code BoundedRangeModel}) 510 * handles any mathematical 511 * issues arising from assigning faulty values. See the 512 * {@code BoundedRangeModel} documentation for details. 513 * <p> 514 * If the new value is different from the previous value, 515 * all change listeners are notified. 516 * 517 * @param n the new value 518 * @see #getValue 519 * @see #addChangeListener 520 * @see BoundedRangeModel#setValue 521 * @beaninfo 522 * preferred: true 523 * description: The sliders current value. 524 */ 525 public void setValue(int n) { 526 BoundedRangeModel m = getModel(); 527 int oldValue = m.getValue(); 528 if (oldValue == n) { 529 return; 530 } 531 m.setValue(n); 532 533 if (accessibleContext != null) { 534 accessibleContext.firePropertyChange( 535 AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, 536 Integer.valueOf(oldValue), 537 Integer.valueOf(m.getValue())); 538 } 539 } 540 541 542 /** 543 * Returns the minimum value supported by the slider 544 * from the <code>BoundedRangeModel</code>. 545 * 546 * @return the value of the model's minimum property 547 * @see #setMinimum 548 * @see BoundedRangeModel#getMinimum 549 */ 550 public int getMinimum() { 551 return getModel().getMinimum(); 552 } 553 554 555 /** 556 * Sets the slider's minimum value to {@code minimum}. This method 557 * forwards the new minimum value to the model. 558 * <p> 559 * The data model (an instance of {@code BoundedRangeModel}) 560 * handles any mathematical 561 * issues arising from assigning faulty values. See the 562 * {@code BoundedRangeModel} documentation for details. 563 * <p> 564 * If the new minimum value is different from the previous minimum value, 565 * all change listeners are notified. 566 * 567 * @param minimum the new minimum 568 * @see #getMinimum 569 * @see #addChangeListener 570 * @see BoundedRangeModel#setMinimum 571 * @beaninfo 572 * bound: true 573 * preferred: true 574 * description: The sliders minimum value. 575 */ 576 public void setMinimum(int minimum) { 577 int oldMin = getModel().getMinimum(); 578 getModel().setMinimum(minimum); 579 firePropertyChange( "minimum", Integer.valueOf( oldMin ), Integer.valueOf( minimum ) ); 580 } 581 582 583 /** 584 * Returns the maximum value supported by the slider 585 * from the <code>BoundedRangeModel</code>. 586 * 587 * @return the value of the model's maximum property 588 * @see #setMaximum 589 * @see BoundedRangeModel#getMaximum 590 */ 591 public int getMaximum() { 592 return getModel().getMaximum(); 593 } 594 595 596 /** 597 * Sets the slider's maximum value to {@code maximum}. This method 598 * forwards the new maximum value to the model. 599 * <p> 600 * The data model (an instance of {@code BoundedRangeModel}) 601 * handles any mathematical 602 * issues arising from assigning faulty values. See the 603 * {@code BoundedRangeModel} documentation for details. 604 * <p> 605 * If the new maximum value is different from the previous maximum value, 606 * all change listeners are notified. 607 * 608 * @param maximum the new maximum 609 * @see #getMaximum 610 * @see #addChangeListener 611 * @see BoundedRangeModel#setMaximum 612 * @beaninfo 613 * bound: true 614 * preferred: true 615 * description: The sliders maximum value. 616 */ 617 public void setMaximum(int maximum) { 618 int oldMax = getModel().getMaximum(); 619 getModel().setMaximum(maximum); 620 firePropertyChange( "maximum", Integer.valueOf( oldMax ), Integer.valueOf( maximum ) ); 621 } 622 623 624 /** 625 * Returns the {@code valueIsAdjusting} property from the model. For 626 * details on how this is used, see the {@code setValueIsAdjusting} 627 * documentation. 628 * 629 * @return the value of the model's {@code valueIsAdjusting} property 630 * @see #setValueIsAdjusting 631 */ 632 public boolean getValueIsAdjusting() { 633 return getModel().getValueIsAdjusting(); 634 } 635 636 637 /** 638 * Sets the model's {@code valueIsAdjusting} property. Slider look and 639 * feel implementations should set this property to {@code true} when 640 * a knob drag begins, and to {@code false} when the drag ends. 641 * 642 * @param b the new value for the {@code valueIsAdjusting} property 643 * @see #getValueIsAdjusting 644 * @see BoundedRangeModel#setValueIsAdjusting 645 * @beaninfo 646 * expert: true 647 * description: True if the slider knob is being dragged. 648 */ 649 public void setValueIsAdjusting(boolean b) { 650 BoundedRangeModel m = getModel(); 651 boolean oldValue = m.getValueIsAdjusting(); 652 m.setValueIsAdjusting(b); 653 654 if ((oldValue != b) && (accessibleContext != null)) { 655 accessibleContext.firePropertyChange( 656 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 657 ((oldValue) ? AccessibleState.BUSY : null), 658 ((b) ? AccessibleState.BUSY : null)); 659 } 660 } 661 662 663 /** 664 * Returns the "extent" from the <code>BoundedRangeModel</code>. 665 * This respresents the range of values "covered" by the knob. 666 * 667 * @return an int representing the extent 668 * @see #setExtent 669 * @see BoundedRangeModel#getExtent 670 */ 671 public int getExtent() { 672 return getModel().getExtent(); 673 } 674 675 676 /** 677 * Sets the size of the range "covered" by the knob. Most look 678 * and feel implementations will change the value by this amount 679 * if the user clicks on either side of the knob. This method just 680 * forwards the new extent value to the model. 681 * <p> 682 * The data model (an instance of {@code BoundedRangeModel}) 683 * handles any mathematical 684 * issues arising from assigning faulty values. See the 685 * {@code BoundedRangeModel} documentation for details. 686 * <p> 687 * If the new extent value is different from the previous extent value, 688 * all change listeners are notified. 689 * 690 * @param extent the new extent 691 * @see #getExtent 692 * @see BoundedRangeModel#setExtent 693 * @beaninfo 694 * expert: true 695 * description: Size of the range covered by the knob. 696 */ 697 public void setExtent(int extent) { 698 getModel().setExtent(extent); 699 } 700 701 702 /** 703 * Return this slider's vertical or horizontal orientation. 704 * @return {@code SwingConstants.VERTICAL} or 705 * {@code SwingConstants.HORIZONTAL} 706 * @see #setOrientation 707 */ 708 public int getOrientation() { 709 return orientation; 710 } 711 712 713 /** 714 * Set the slider's orientation to either {@code SwingConstants.VERTICAL} or 715 * {@code SwingConstants.HORIZONTAL}. 716 * 717 * @param orientation {@code HORIZONTAL} or {@code VERTICAL} 718 * @throws IllegalArgumentException if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL} 719 * @see #getOrientation 720 * @beaninfo 721 * preferred: true 722 * bound: true 723 * attribute: visualUpdate true 724 * description: Set the scrollbars orientation to either VERTICAL or HORIZONTAL. 725 * enum: VERTICAL JSlider.VERTICAL 726 * HORIZONTAL JSlider.HORIZONTAL 727 * 728 */ 729 public void setOrientation(int orientation) 730 { 731 checkOrientation(orientation); 732 int oldValue = this.orientation; 733 this.orientation = orientation; 734 firePropertyChange("orientation", oldValue, orientation); 735 736 if ((oldValue != orientation) && (accessibleContext != null)) { 737 accessibleContext.firePropertyChange( 738 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 739 ((oldValue == VERTICAL) 740 ? AccessibleState.VERTICAL : AccessibleState.HORIZONTAL), 741 ((orientation == VERTICAL) 742 ? AccessibleState.VERTICAL : AccessibleState.HORIZONTAL)); 743 } 744 if (orientation != oldValue) { 745 revalidate(); 746 } 747 } 748 749 750 /** 751 * {@inheritDoc} 752 * 753 * @since 1.6 754 */ 755 public void setFont(Font font) { 756 super.setFont(font); 757 updateLabelSizes(); 758 } 759 760 /** 761 * {@inheritDoc} 762 * @since 1.7 763 */ 764 public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) { 765 if (!isShowing()) { 766 return false; 767 } 768 769 // Check that there is a label with such image 770 Enumeration elements = labelTable.elements(); 771 772 while (elements.hasMoreElements()) { 773 Component component = (Component) elements.nextElement(); 774 775 if (component instanceof JLabel) { 776 JLabel label = (JLabel) component; 777 778 if (SwingUtilities.doesIconReferenceImage(label.getIcon(), img) || 779 SwingUtilities.doesIconReferenceImage(label.getDisabledIcon(), img)) { 780 return super.imageUpdate(img, infoflags, x, y, w, h); 781 } 782 } 783 } 784 785 return false; 786 } 787 788 /** 789 * Returns the dictionary of what labels to draw at which values. 790 * 791 * @return the <code>Dictionary</code> containing labels and 792 * where to draw them 793 */ 794 public Dictionary getLabelTable() { 795 /* 796 if ( labelTable == null && getMajorTickSpacing() > 0 ) { 797 setLabelTable( createStandardLabels( getMajorTickSpacing() ) ); 798 } 799 */ 800 return labelTable; 801 } 802 803 804 /** 805 * Used to specify what label will be drawn at any given value. 806 * The key-value pairs are of this format: 807 * <code>{ Integer value, java.swing.JComponent label }</code>. 808 * <p> 809 * An easy way to generate a standard table of value labels is by using the 810 * {@code createStandardLabels} method. 811 * <p> 812 * Once the labels have been set, this method calls {@link #updateLabelUIs}. 813 * Note that the labels are only painted if the {@code paintLabels} 814 * property is {@code true}. 815 * 816 * @param labels new {@code Dictionary} of labels, or {@code null} to 817 * remove all labels 818 * @see #createStandardLabels(int) 819 * @see #getLabelTable 820 * @see #setPaintLabels 821 * @beaninfo 822 * hidden: true 823 * bound: true 824 * attribute: visualUpdate true 825 * description: Specifies what labels will be drawn for any given value. 826 */ 827 public void setLabelTable( Dictionary labels ) { 828 Dictionary oldTable = labelTable; 829 labelTable = labels; 830 updateLabelUIs(); 831 firePropertyChange("labelTable", oldTable, labelTable ); 832 if (labels != oldTable) { 833 revalidate(); 834 repaint(); 835 } 836 } 837 838 839 /** 840 * Updates the UIs for the labels in the label table by calling 841 * {@code updateUI} on each label. The UIs are updated from 842 * the current look and feel. The labels are also set to their 843 * preferred size. 844 * 845 * @see #setLabelTable 846 * @see JComponent#updateUI 847 */ 848 protected void updateLabelUIs() { 849 Dictionary labelTable = getLabelTable(); 850 851 if (labelTable == null) { 852 return; 853 } 854 Enumeration labels = labelTable.keys(); 855 while ( labels.hasMoreElements() ) { 856 JComponent component = (JComponent) labelTable.get(labels.nextElement()); 857 component.updateUI(); 858 component.setSize(component.getPreferredSize()); 859 } 860 } 861 862 private void updateLabelSizes() { 863 Dictionary labelTable = getLabelTable(); 864 if (labelTable != null) { 865 Enumeration labels = labelTable.elements(); 866 while (labels.hasMoreElements()) { 867 JComponent component = (JComponent) labels.nextElement(); 868 component.setSize(component.getPreferredSize()); 869 } 870 } 871 } 872 873 874 /** 875 * Creates a {@code Hashtable} of numerical text labels, starting at the 876 * slider minimum, and using the increment specified. 877 * For example, if you call <code>createStandardLabels( 10 )</code> 878 * and the slider minimum is zero, 879 * then labels will be created for the values 0, 10, 20, 30, and so on. 880 * <p> 881 * For the labels to be drawn on the slider, the returned {@code Hashtable} 882 * must be passed into {@code setLabelTable}, and {@code setPaintLabels} 883 * must be set to {@code true}. 884 * <p> 885 * For further details on the makeup of the returned {@code Hashtable}, see 886 * the {@code setLabelTable} documentation. 887 * 888 * @param increment distance between labels in the generated hashtable 889 * @return a new {@code Hashtable} of labels 890 * @see #setLabelTable 891 * @see #setPaintLabels 892 * @throws IllegalArgumentException if {@code increment} is less than or 893 * equal to zero 894 */ 895 public Hashtable createStandardLabels( int increment ) { 896 return createStandardLabels( increment, getMinimum() ); 897 } 898 899 900 /** 901 * Creates a {@code Hashtable} of numerical text labels, starting at the 902 * starting point specified, and using the increment specified. 903 * For example, if you call 904 * <code>createStandardLabels( 10, 2 )</code>, 905 * then labels will be created for the values 2, 12, 22, 32, and so on. 906 * <p> 907 * For the labels to be drawn on the slider, the returned {@code Hashtable} 908 * must be passed into {@code setLabelTable}, and {@code setPaintLabels} 909 * must be set to {@code true}. 910 * <p> 911 * For further details on the makeup of the returned {@code Hashtable}, see 912 * the {@code setLabelTable} documentation. 913 * 914 * @param increment distance between labels in the generated hashtable 915 * @param start value at which the labels will begin 916 * @return a new {@code Hashtable} of labels 917 * @see #setLabelTable 918 * @see #setPaintLabels 919 * @exception IllegalArgumentException if {@code start} is 920 * out of range, or if {@code increment} is less than or equal 921 * to zero 922 */ 923 public Hashtable createStandardLabels( int increment, int start ) { 924 if ( start > getMaximum() || start < getMinimum() ) { 925 throw new IllegalArgumentException( "Slider label start point out of range." ); 926 } 927 928 if ( increment <= 0 ) { 929 throw new IllegalArgumentException( "Label incremement must be > 0" ); 930 } 931 932 class SmartHashtable extends Hashtable<Object, Object> implements PropertyChangeListener { 933 int increment = 0; 934 int start = 0; 935 boolean startAtMin = false; 936 937 class LabelUIResource extends JLabel implements UIResource { 938 public LabelUIResource( String text, int alignment ) { 939 super( text, alignment ); 940 setName("Slider.label"); 941 } 942 943 public Font getFont() { 944 Font font = super.getFont(); 945 if (font != null && !(font instanceof UIResource)) { 946 return font; 947 } 948 return JSlider.this.getFont(); 949 } 950 951 public Color getForeground() { 952 Color fg = super.getForeground(); 953 if (fg != null && !(fg instanceof UIResource)) { 954 return fg; 955 } 956 if (!(JSlider.this.getForeground() instanceof UIResource)) { 957 return JSlider.this.getForeground(); 958 } 959 return fg; 960 } 961 } 962 963 public SmartHashtable( int increment, int start ) { 964 super(); 965 this.increment = increment; 966 this.start = start; 967 startAtMin = start == getMinimum(); 968 createLabels(); 969 } 970 971 public void propertyChange( PropertyChangeEvent e ) { 972 if ( e.getPropertyName().equals( "minimum" ) && startAtMin ) { 973 start = getMinimum(); 974 } 975 976 if ( e.getPropertyName().equals( "minimum" ) || 977 e.getPropertyName().equals( "maximum" ) ) { 978 979 Enumeration keys = getLabelTable().keys(); 980 Hashtable<Object, Object> hashtable = new Hashtable<Object, Object>(); 981 982 // Save the labels that were added by the developer 983 while ( keys.hasMoreElements() ) { 984 Object key = keys.nextElement(); 985 Object value = labelTable.get(key); 986 if ( !(value instanceof LabelUIResource) ) { 987 hashtable.put( key, value ); 988 } 989 } 990 991 clear(); 992 createLabels(); 993 994 // Add the saved labels 995 keys = hashtable.keys(); 996 while ( keys.hasMoreElements() ) { 997 Object key = keys.nextElement(); 998 put( key, hashtable.get( key ) ); 999 } 1000 1001 ((JSlider)e.getSource()).setLabelTable( this ); 1002 } 1003 } 1004 1005 void createLabels() { 1006 for ( int labelIndex = start; labelIndex <= getMaximum(); labelIndex += increment ) { 1007 put( Integer.valueOf( labelIndex ), new LabelUIResource( ""+labelIndex, JLabel.CENTER ) ); 1008 } 1009 } 1010 } 1011 1012 SmartHashtable table = new SmartHashtable( increment, start ); 1013 1014 Dictionary labelTable = getLabelTable(); 1015 1016 if (labelTable != null && (labelTable instanceof PropertyChangeListener)) { 1017 removePropertyChangeListener((PropertyChangeListener) labelTable); 1018 } 1019 1020 addPropertyChangeListener( table ); 1021 1022 return table; 1023 } 1024 1025 1026 /** 1027 * Returns true if the value-range shown for the slider is reversed, 1028 * 1029 * @return true if the slider values are reversed from their normal order 1030 * @see #setInverted 1031 */ 1032 public boolean getInverted() { 1033 return isInverted; 1034 } 1035 1036 1037 /** 1038 * Specify true to reverse the value-range shown for the slider and false to 1039 * put the value range in the normal order. The order depends on the 1040 * slider's <code>ComponentOrientation</code> property. Normal (non-inverted) 1041 * horizontal sliders with a <code>ComponentOrientation</code> value of 1042 * <code>LEFT_TO_RIGHT</code> have their maximum on the right. 1043 * Normal horizontal sliders with a <code>ComponentOrientation</code> value of 1044 * <code>RIGHT_TO_LEFT</code> have their maximum on the left. Normal vertical 1045 * sliders have their maximum on the top. These labels are reversed when the 1046 * slider is inverted. 1047 * <p> 1048 * By default, the value of this property is {@code false}. 1049 * 1050 * @param b true to reverse the slider values from their normal order 1051 * @beaninfo 1052 * bound: true 1053 * attribute: visualUpdate true 1054 * description: If true reverses the slider values from their normal order 1055 * 1056 */ 1057 public void setInverted( boolean b ) { 1058 boolean oldValue = isInverted; 1059 isInverted = b; 1060 firePropertyChange("inverted", oldValue, isInverted); 1061 if (b != oldValue) { 1062 repaint(); 1063 } 1064 } 1065 1066 1067 /** 1068 * This method returns the major tick spacing. The number that is returned 1069 * represents the distance, measured in values, between each major tick mark. 1070 * If you have a slider with a range from 0 to 50 and the major tick spacing 1071 * is set to 10, you will get major ticks next to the following values: 1072 * 0, 10, 20, 30, 40, 50. 1073 * 1074 * @return the number of values between major ticks 1075 * @see #setMajorTickSpacing 1076 */ 1077 public int getMajorTickSpacing() { 1078 return majorTickSpacing; 1079 } 1080 1081 1082 /** 1083 * This method sets the major tick spacing. The number that is passed in 1084 * represents the distance, measured in values, between each major tick mark. 1085 * If you have a slider with a range from 0 to 50 and the major tick spacing 1086 * is set to 10, you will get major ticks next to the following values: 1087 * 0, 10, 20, 30, 40, 50. 1088 * <p> 1089 * In order for major ticks to be painted, {@code setPaintTicks} must be 1090 * set to {@code true}. 1091 * <p> 1092 * This method will also set up a label table for you. 1093 * If there is not already a label table, and the major tick spacing is 1094 * {@code > 0}, and {@code getPaintLabels} returns 1095 * {@code true}, a standard label table will be generated (by calling 1096 * {@code createStandardLabels}) with labels at the major tick marks. 1097 * For the example above, you would get text labels: "0", 1098 * "10", "20", "30", "40", "50". 1099 * The label table is then set on the slider by calling 1100 * {@code setLabelTable}. 1101 * 1102 * @param n new value for the {@code majorTickSpacing} property 1103 * @see #getMajorTickSpacing 1104 * @see #setPaintTicks 1105 * @see #setLabelTable 1106 * @see #createStandardLabels(int) 1107 * @beaninfo 1108 * bound: true 1109 * attribute: visualUpdate true 1110 * description: Sets the number of values between major tick marks. 1111 * 1112 */ 1113 public void setMajorTickSpacing(int n) { 1114 int oldValue = majorTickSpacing; 1115 majorTickSpacing = n; 1116 if ( labelTable == null && getMajorTickSpacing() > 0 && getPaintLabels() ) { 1117 setLabelTable( createStandardLabels( getMajorTickSpacing() ) ); 1118 } 1119 firePropertyChange("majorTickSpacing", oldValue, majorTickSpacing); 1120 if (majorTickSpacing != oldValue && getPaintTicks()) { 1121 repaint(); 1122 } 1123 } 1124 1125 1126 1127 /** 1128 * This method returns the minor tick spacing. The number that is returned 1129 * represents the distance, measured in values, between each minor tick mark. 1130 * If you have a slider with a range from 0 to 50 and the minor tick spacing 1131 * is set to 10, you will get minor ticks next to the following values: 1132 * 0, 10, 20, 30, 40, 50. 1133 * 1134 * @return the number of values between minor ticks 1135 * @see #getMinorTickSpacing 1136 */ 1137 public int getMinorTickSpacing() { 1138 return minorTickSpacing; 1139 } 1140 1141 1142 /** 1143 * This method sets the minor tick spacing. The number that is passed in 1144 * represents the distance, measured in values, between each minor tick mark. 1145 * If you have a slider with a range from 0 to 50 and the minor tick spacing 1146 * is set to 10, you will get minor ticks next to the following values: 1147 * 0, 10, 20, 30, 40, 50. 1148 * <p> 1149 * In order for minor ticks to be painted, {@code setPaintTicks} must be 1150 * set to {@code true}. 1151 * 1152 * @param n new value for the {@code minorTickSpacing} property 1153 * @see #getMinorTickSpacing 1154 * @see #setPaintTicks 1155 * @beaninfo 1156 * bound: true 1157 * attribute: visualUpdate true 1158 * description: Sets the number of values between minor tick marks. 1159 */ 1160 public void setMinorTickSpacing(int n) { 1161 int oldValue = minorTickSpacing; 1162 minorTickSpacing = n; 1163 firePropertyChange("minorTickSpacing", oldValue, minorTickSpacing); 1164 if (minorTickSpacing != oldValue && getPaintTicks()) { 1165 repaint(); 1166 } 1167 } 1168 1169 1170 /** 1171 * Returns true if the knob (and the data value it represents) 1172 * resolve to the closest tick mark next to where the user 1173 * positioned the knob. 1174 * 1175 * @return true if the value snaps to the nearest tick mark, else false 1176 * @see #setSnapToTicks 1177 */ 1178 public boolean getSnapToTicks() { 1179 return snapToTicks; 1180 } 1181 1182 1183 /** 1184 * Returns true if the knob (and the data value it represents) 1185 * resolve to the closest slider value next to where the user 1186 * positioned the knob. 1187 * 1188 * @return true if the value snaps to the nearest slider value, else false 1189 * @see #setSnapToValue 1190 */ 1191 boolean getSnapToValue() { 1192 return snapToValue; 1193 } 1194 1195 1196 /** 1197 * Specifying true makes the knob (and the data value it represents) 1198 * resolve to the closest tick mark next to where the user 1199 * positioned the knob. 1200 * By default, this property is {@code false}. 1201 * 1202 * @param b true to snap the knob to the nearest tick mark 1203 * @see #getSnapToTicks 1204 * @beaninfo 1205 * bound: true 1206 * description: If true snap the knob to the nearest tick mark. 1207 */ 1208 public void setSnapToTicks(boolean b) { 1209 boolean oldValue = snapToTicks; 1210 snapToTicks = b; 1211 firePropertyChange("snapToTicks", oldValue, snapToTicks); 1212 } 1213 1214 1215 /** 1216 * Specifying true makes the knob (and the data value it represents) 1217 * resolve to the closest slider value next to where the user 1218 * positioned the knob. If the {@code snapToTicks} property has also been 1219 * set to {@code true}, the snap-to-ticks behavior will prevail. 1220 * By default, the snapToValue property is {@code true}. 1221 * 1222 * @param b true to snap the knob to the nearest slider value 1223 * @see #getSnapToValue 1224 * @see #setSnapToTicks 1225 * @beaninfo 1226 * bound: true 1227 * description: If true snap the knob to the nearest slider value. 1228 */ 1229 void setSnapToValue(boolean b) { 1230 boolean oldValue = snapToValue; 1231 snapToValue = b; 1232 firePropertyChange("snapToValue", oldValue, snapToValue); 1233 } 1234 1235 1236 /** 1237 * Tells if tick marks are to be painted. 1238 * @return true if tick marks are painted, else false 1239 * @see #setPaintTicks 1240 */ 1241 public boolean getPaintTicks() { 1242 return paintTicks; 1243 } 1244 1245 1246 /** 1247 * Determines whether tick marks are painted on the slider. 1248 * By default, this property is {@code false}. 1249 * 1250 * @param b whether or not tick marks should be painted 1251 * @see #getPaintTicks 1252 * @beaninfo 1253 * bound: true 1254 * attribute: visualUpdate true 1255 * description: If true tick marks are painted on the slider. 1256 */ 1257 public void setPaintTicks(boolean b) { 1258 boolean oldValue = paintTicks; 1259 paintTicks = b; 1260 firePropertyChange("paintTicks", oldValue, paintTicks); 1261 if (paintTicks != oldValue) { 1262 revalidate(); 1263 repaint(); 1264 } 1265 } 1266 1267 /** 1268 * Tells if the track (area the slider slides in) is to be painted. 1269 * @return true if track is painted, else false 1270 * @see #setPaintTrack 1271 */ 1272 public boolean getPaintTrack() { 1273 return paintTrack; 1274 } 1275 1276 1277 /** 1278 * Determines whether the track is painted on the slider. 1279 * By default, this property is {@code true}. 1280 * 1281 * @param b whether or not to paint the slider track 1282 * @see #getPaintTrack 1283 * @beaninfo 1284 * bound: true 1285 * attribute: visualUpdate true 1286 * description: If true, the track is painted on the slider. 1287 */ 1288 public void setPaintTrack(boolean b) { 1289 boolean oldValue = paintTrack; 1290 paintTrack = b; 1291 firePropertyChange("paintTrack", oldValue, paintTrack); 1292 if (paintTrack != oldValue) { 1293 repaint(); 1294 } 1295 } 1296 1297 1298 /** 1299 * Tells if labels are to be painted. 1300 * @return true if labels are painted, else false 1301 * @see #setPaintLabels 1302 */ 1303 public boolean getPaintLabels() { 1304 return paintLabels; 1305 } 1306 1307 1308 /** 1309 * Determines whether labels are painted on the slider. 1310 * <p> 1311 * This method will also set up a label table for you. 1312 * If there is not already a label table, and the major tick spacing is 1313 * {@code > 0}, 1314 * a standard label table will be generated (by calling 1315 * {@code createStandardLabels}) with labels at the major tick marks. 1316 * The label table is then set on the slider by calling 1317 * {@code setLabelTable}. 1318 * <p> 1319 * By default, this property is {@code false}. 1320 * 1321 * @param b whether or not to paint labels 1322 * @see #getPaintLabels 1323 * @see #getLabelTable 1324 * @see #createStandardLabels(int) 1325 * @beaninfo 1326 * bound: true 1327 * attribute: visualUpdate true 1328 * description: If true labels are painted on the slider. 1329 */ 1330 public void setPaintLabels(boolean b) { 1331 boolean oldValue = paintLabels; 1332 paintLabels = b; 1333 if ( labelTable == null && getMajorTickSpacing() > 0 ) { 1334 setLabelTable( createStandardLabels( getMajorTickSpacing() ) ); 1335 } 1336 firePropertyChange("paintLabels", oldValue, paintLabels); 1337 if (paintLabels != oldValue) { 1338 revalidate(); 1339 repaint(); 1340 } 1341 } 1342 1343 1344 /** 1345 * See readObject() and writeObject() in JComponent for more 1346 * information about serialization in Swing. 1347 */ 1348 private void writeObject(ObjectOutputStream s) throws IOException { 1349 s.defaultWriteObject(); 1350 if (getUIClassID().equals(uiClassID)) { 1351 byte count = JComponent.getWriteObjCounter(this); 1352 JComponent.setWriteObjCounter(this, --count); 1353 if (count == 0 && ui != null) { 1354 ui.installUI(this); 1355 } 1356 } 1357 } 1358 1359 1360 /** 1361 * Returns a string representation of this JSlider. This method 1362 * is intended to be used only for debugging purposes, and the 1363 * content and format of the returned string may vary between 1364 * implementations. The returned string may be empty but may not 1365 * be <code>null</code>. 1366 * 1367 * @return a string representation of this JSlider. 1368 */ 1369 protected String paramString() { 1370 String paintTicksString = (paintTicks ? 1371 "true" : "false"); 1372 String paintTrackString = (paintTrack ? 1373 "true" : "false"); 1374 String paintLabelsString = (paintLabels ? 1375 "true" : "false"); 1376 String isInvertedString = (isInverted ? 1377 "true" : "false"); 1378 String snapToTicksString = (snapToTicks ? 1379 "true" : "false"); 1380 String snapToValueString = (snapToValue ? 1381 "true" : "false"); 1382 String orientationString = (orientation == HORIZONTAL ? 1383 "HORIZONTAL" : "VERTICAL"); 1384 1385 return super.paramString() + 1386 ",isInverted=" + isInvertedString + 1387 ",majorTickSpacing=" + majorTickSpacing + 1388 ",minorTickSpacing=" + minorTickSpacing + 1389 ",orientation=" + orientationString + 1390 ",paintLabels=" + paintLabelsString + 1391 ",paintTicks=" + paintTicksString + 1392 ",paintTrack=" + paintTrackString + 1393 ",snapToTicks=" + snapToTicksString + 1394 ",snapToValue=" + snapToValueString; 1395 } 1396 1397 1398 ///////////////// 1399 // Accessibility support 1400 //////////////// 1401 1402 /** 1403 * Gets the AccessibleContext associated with this JSlider. 1404 * For sliders, the AccessibleContext takes the form of an 1405 * AccessibleJSlider. 1406 * A new AccessibleJSlider instance is created if necessary. 1407 * 1408 * @return an AccessibleJSlider that serves as the 1409 * AccessibleContext of this JSlider 1410 */ 1411 public AccessibleContext getAccessibleContext() { 1412 if (accessibleContext == null) { 1413 accessibleContext = new AccessibleJSlider(); 1414 } 1415 return accessibleContext; 1416 } 1417 1418 /** 1419 * This class implements accessibility support for the 1420 * <code>JSlider</code> class. It provides an implementation of the 1421 * Java Accessibility API appropriate to slider user-interface elements. 1422 * <p> 1423 * <strong>Warning:</strong> 1424 * Serialized objects of this class will not be compatible with 1425 * future Swing releases. The current serialization support is 1426 * appropriate for short term storage or RMI between applications running 1427 * the same version of Swing. As of 1.4, support for long term storage 1428 * of all JavaBeans<sup><font size="-2">TM</font></sup> 1429 * has been added to the <code>java.beans</code> package. 1430 * Please see {@link java.beans.XMLEncoder}. 1431 */ 1432 protected class AccessibleJSlider extends AccessibleJComponent 1433 implements AccessibleValue { 1434 1435 /** 1436 * Get the state set of this object. 1437 * 1438 * @return an instance of AccessibleState containing the current state 1439 * of the object 1440 * @see AccessibleState 1441 */ 1442 public AccessibleStateSet getAccessibleStateSet() { 1443 AccessibleStateSet states = super.getAccessibleStateSet(); 1444 if (getValueIsAdjusting()) { 1445 states.add(AccessibleState.BUSY); 1446 } 1447 if (getOrientation() == VERTICAL) { 1448 states.add(AccessibleState.VERTICAL); 1449 } 1450 else { 1451 states.add(AccessibleState.HORIZONTAL); 1452 } 1453 return states; 1454 } 1455 1456 /** 1457 * Get the role of this object. 1458 * 1459 * @return an instance of AccessibleRole describing the role of the object 1460 */ 1461 public AccessibleRole getAccessibleRole() { 1462 return AccessibleRole.SLIDER; 1463 } 1464 1465 /** 1466 * Get the AccessibleValue associated with this object. In the 1467 * implementation of the Java Accessibility API for this class, 1468 * return this object, which is responsible for implementing the 1469 * AccessibleValue interface on behalf of itself. 1470 * 1471 * @return this object 1472 */ 1473 public AccessibleValue getAccessibleValue() { 1474 return this; 1475 } 1476 1477 /** 1478 * Get the accessible value of this object. 1479 * 1480 * @return The current value of this object. 1481 */ 1482 public Number getCurrentAccessibleValue() { 1483 return Integer.valueOf(getValue()); 1484 } 1485 1486 /** 1487 * Set the value of this object as a Number. 1488 * 1489 * @return True if the value was set. 1490 */ 1491 public boolean setCurrentAccessibleValue(Number n) { 1492 // TIGER - 4422535 1493 if (n == null) { 1494 return false; 1495 } 1496 setValue(n.intValue()); 1497 return true; 1498 } 1499 1500 /** 1501 * Get the minimum accessible value of this object. 1502 * 1503 * @return The minimum value of this object. 1504 */ 1505 public Number getMinimumAccessibleValue() { 1506 return Integer.valueOf(getMinimum()); 1507 } 1508 1509 /** 1510 * Get the maximum accessible value of this object. 1511 * 1512 * @return The maximum value of this object. 1513 */ 1514 public Number getMaximumAccessibleValue() { 1515 // TIGER - 4422362 1516 BoundedRangeModel model = JSlider.this.getModel(); 1517 return Integer.valueOf(model.getMaximum() - model.getExtent()); 1518 } 1519 } // AccessibleJSlider 1520 }