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