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