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