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.plaf.basic;
  27 
  28 import java.awt.event.*;
  29 import java.awt.*;
  30 import java.beans.*;
  31 import java.util.Dictionary;
  32 import java.util.Enumeration;
  33 
  34 import javax.swing.*;
  35 import javax.swing.event.*;
  36 import javax.swing.plaf.*;
  37 import sun.swing.DefaultLookup;
  38 import sun.swing.UIAction;
  39 
  40 
  41 /**
  42  * A Basic L&F implementation of SliderUI.
  43  *
  44  * @author Tom Santos
  45  */
  46 public class BasicSliderUI extends SliderUI{
  47     // Old actions forward to an instance of this.
  48     private static final Actions SHARED_ACTION = new Actions();
  49 
  50     public static final int POSITIVE_SCROLL = +1;
  51     public static final int NEGATIVE_SCROLL = -1;
  52     public static final int MIN_SCROLL = -2;
  53     public static final int MAX_SCROLL = +2;
  54 
  55     protected Timer scrollTimer;
  56     protected JSlider slider;
  57 
  58     protected Insets focusInsets = null;
  59     protected Insets insetCache = null;
  60     protected boolean leftToRightCache = true;
  61     protected Rectangle focusRect = null;
  62     protected Rectangle contentRect = null;
  63     protected Rectangle labelRect = null;
  64     protected Rectangle tickRect = null;
  65     protected Rectangle trackRect = null;
  66     protected Rectangle thumbRect = null;
  67 
  68     protected int trackBuffer = 0;  // The distance that the track is from the side of the control
  69 
  70     private transient boolean isDragging;
  71 
  72     protected TrackListener trackListener;
  73     protected ChangeListener changeListener;
  74     protected ComponentListener componentListener;
  75     protected FocusListener focusListener;
  76     protected ScrollListener scrollListener;
  77     protected PropertyChangeListener propertyChangeListener;
  78     private Handler handler;
  79     private int lastValue;
  80 
  81     // Colors
  82     private Color shadowColor;
  83     private Color highlightColor;
  84     private Color focusColor;
  85 
  86     /**
  87      * Whther or not sameLabelBaselines is up to date.
  88      */
  89     private boolean checkedLabelBaselines;
  90     /**
  91      * Whether or not all the entries in the labeltable have the same
  92      * baseline.
  93      */
  94     private boolean sameLabelBaselines;
  95 
  96 
  97     protected Color getShadowColor() {
  98         return shadowColor;
  99     }
 100 
 101     protected Color getHighlightColor() {
 102         return highlightColor;
 103     }
 104 
 105     protected Color getFocusColor() {
 106         return focusColor;
 107     }
 108 
 109     /**
 110      * Returns true if the user is dragging the slider.
 111      *
 112      * @return true if the user is dragging the slider
 113      * @since 1.5
 114      */
 115     protected boolean isDragging() {
 116         return isDragging;
 117     }
 118 
 119     /////////////////////////////////////////////////////////////////////////////
 120     // ComponentUI Interface Implementation methods
 121     /////////////////////////////////////////////////////////////////////////////
 122     public static ComponentUI createUI(JComponent b)    {
 123         return new BasicSliderUI((JSlider)b);
 124     }
 125 
 126     public BasicSliderUI(JSlider b)   {
 127     }
 128 
 129     public void installUI(JComponent c)   {
 130         slider = (JSlider) c;
 131 
 132         checkedLabelBaselines = false;
 133 
 134         slider.setEnabled(slider.isEnabled());
 135         LookAndFeel.installProperty(slider, "opaque", Boolean.TRUE);
 136 
 137         isDragging = false;
 138         trackListener = createTrackListener( slider );
 139         changeListener = createChangeListener( slider );
 140         componentListener = createComponentListener( slider );
 141         focusListener = createFocusListener( slider );
 142         scrollListener = createScrollListener( slider );
 143         propertyChangeListener = createPropertyChangeListener( slider );
 144 
 145         installDefaults( slider );
 146         installListeners( slider );
 147         installKeyboardActions( slider );
 148 
 149         scrollTimer = new Timer( 100, scrollListener );
 150         scrollTimer.setInitialDelay( 300 );
 151 
 152         insetCache = slider.getInsets();
 153         leftToRightCache = BasicGraphicsUtils.isLeftToRight(slider);
 154         focusRect = new Rectangle();
 155         contentRect = new Rectangle();
 156         labelRect = new Rectangle();
 157         tickRect = new Rectangle();
 158         trackRect = new Rectangle();
 159         thumbRect = new Rectangle();
 160         lastValue = slider.getValue();
 161 
 162         calculateGeometry(); // This figures out where the labels, ticks, track, and thumb are.
 163     }
 164 
 165     public void uninstallUI(JComponent c) {
 166         if ( c != slider )
 167             throw new IllegalComponentStateException(
 168                                                     this + " was asked to deinstall() "
 169                                                     + c + " when it only knows about "
 170                                                     + slider + ".");
 171 
 172         scrollTimer.stop();
 173         scrollTimer = null;
 174 
 175         uninstallDefaults(slider);
 176         uninstallListeners( slider );
 177         uninstallKeyboardActions(slider);
 178 
 179         insetCache = null;
 180         leftToRightCache = true;
 181         focusRect = null;
 182         contentRect = null;
 183         labelRect = null;
 184         tickRect = null;
 185         trackRect = null;
 186         thumbRect = null;
 187         trackListener = null;
 188         changeListener = null;
 189         componentListener = null;
 190         focusListener = null;
 191         scrollListener = null;
 192         propertyChangeListener = null;
 193         slider = null;
 194     }
 195 
 196     protected void installDefaults( JSlider slider ) {
 197         LookAndFeel.installBorder(slider, "Slider.border");
 198         LookAndFeel.installColorsAndFont(slider, "Slider.background",
 199                                          "Slider.foreground", "Slider.font");
 200         highlightColor = UIManager.getColor("Slider.highlight");
 201 
 202         shadowColor = UIManager.getColor("Slider.shadow");
 203         focusColor = UIManager.getColor("Slider.focus");
 204 
 205         focusInsets = (Insets)UIManager.get( "Slider.focusInsets" );
 206         // use default if missing so that BasicSliderUI can be used in other
 207         // LAFs like Nimbus
 208         if (focusInsets == null) focusInsets = new InsetsUIResource(2,2,2,2);
 209     }
 210 
 211     protected void uninstallDefaults(JSlider slider) {
 212         LookAndFeel.uninstallBorder(slider);
 213 
 214         focusInsets = null;
 215     }
 216 
 217     protected TrackListener createTrackListener(JSlider slider) {
 218         return new TrackListener();
 219     }
 220 
 221     protected ChangeListener createChangeListener(JSlider slider) {
 222         return getHandler();
 223     }
 224 
 225     protected ComponentListener createComponentListener(JSlider slider) {
 226         return getHandler();
 227     }
 228 
 229     protected FocusListener createFocusListener(JSlider slider) {
 230         return getHandler();
 231     }
 232 
 233     protected ScrollListener createScrollListener( JSlider slider ) {
 234         return new ScrollListener();
 235     }
 236 
 237     protected PropertyChangeListener createPropertyChangeListener(
 238             JSlider slider) {
 239         return getHandler();
 240     }
 241 
 242     private Handler getHandler() {
 243         if (handler == null) {
 244             handler = new Handler();
 245         }
 246         return handler;
 247     }
 248 
 249     protected void installListeners( JSlider slider ) {
 250         slider.addMouseListener(trackListener);
 251         slider.addMouseMotionListener(trackListener);
 252         slider.addFocusListener(focusListener);
 253         slider.addComponentListener(componentListener);
 254         slider.addPropertyChangeListener( propertyChangeListener );
 255         slider.getModel().addChangeListener(changeListener);
 256     }
 257 
 258     protected void uninstallListeners( JSlider slider ) {
 259         slider.removeMouseListener(trackListener);
 260         slider.removeMouseMotionListener(trackListener);
 261         slider.removeFocusListener(focusListener);
 262         slider.removeComponentListener(componentListener);
 263         slider.removePropertyChangeListener( propertyChangeListener );
 264         slider.getModel().removeChangeListener(changeListener);
 265         handler = null;
 266     }
 267 
 268     protected void installKeyboardActions( JSlider slider ) {
 269         InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider);
 270         SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, km);
 271         LazyActionMap.installLazyActionMap(slider, BasicSliderUI.class,
 272                 "Slider.actionMap");
 273     }
 274 
 275     InputMap getInputMap(int condition, JSlider slider) {
 276         if (condition == JComponent.WHEN_FOCUSED) {
 277             InputMap keyMap = (InputMap)DefaultLookup.get(slider, this,
 278                   "Slider.focusInputMap");
 279             InputMap rtlKeyMap;
 280 
 281             if (slider.getComponentOrientation().isLeftToRight() ||
 282                 ((rtlKeyMap = (InputMap)DefaultLookup.get(slider, this,
 283                           "Slider.focusInputMap.RightToLeft")) == null)) {
 284                 return keyMap;
 285             } else {
 286                 rtlKeyMap.setParent(keyMap);
 287                 return rtlKeyMap;
 288             }
 289         }
 290         return null;
 291     }
 292 
 293     /**
 294      * Populates ComboBox's actions.
 295      */
 296     static void loadActionMap(LazyActionMap map) {
 297         map.put(new Actions(Actions.POSITIVE_UNIT_INCREMENT));
 298         map.put(new Actions(Actions.POSITIVE_BLOCK_INCREMENT));
 299         map.put(new Actions(Actions.NEGATIVE_UNIT_INCREMENT));
 300         map.put(new Actions(Actions.NEGATIVE_BLOCK_INCREMENT));
 301         map.put(new Actions(Actions.MIN_SCROLL_INCREMENT));
 302         map.put(new Actions(Actions.MAX_SCROLL_INCREMENT));
 303     }
 304 
 305     protected void uninstallKeyboardActions( JSlider slider ) {
 306         SwingUtilities.replaceUIActionMap(slider, null);
 307         SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED,
 308                                          null);
 309     }
 310 
 311 
 312     /**
 313      * Returns the baseline.
 314      *
 315      * @throws NullPointerException {@inheritDoc}
 316      * @throws IllegalArgumentException {@inheritDoc}
 317      * @see javax.swing.JComponent#getBaseline(int, int)
 318      * @since 1.6
 319      */
 320     public int getBaseline(JComponent c, int width, int height) {
 321         super.getBaseline(c, width, height);
 322         if (slider.getPaintLabels() && labelsHaveSameBaselines()) {
 323             FontMetrics metrics = slider.getFontMetrics(slider.getFont());
 324             Insets insets = slider.getInsets();
 325             Dimension thumbSize = getThumbSize();
 326             if (slider.getOrientation() == JSlider.HORIZONTAL) {
 327                 int tickLength = getTickLength();
 328                 int contentHeight = height - insets.top - insets.bottom -
 329                     focusInsets.top - focusInsets.bottom;
 330                 int thumbHeight = thumbSize.height;
 331                 int centerSpacing = thumbHeight;
 332                 if (slider.getPaintTicks()) {
 333                     centerSpacing += tickLength;
 334                 }
 335                 // Assume uniform labels.
 336                 centerSpacing += getHeightOfTallestLabel();
 337                 int trackY = insets.top + focusInsets.top +
 338                     (contentHeight - centerSpacing - 1) / 2;
 339                 int trackHeight = thumbHeight;
 340                 int tickY = trackY + trackHeight;
 341                 int tickHeight = tickLength;
 342                 if (!slider.getPaintTicks()) {
 343                     tickHeight = 0;
 344                 }
 345                 int labelY = tickY + tickHeight;
 346                 return labelY + metrics.getAscent();
 347             }
 348             else { // vertical
 349                 boolean inverted = slider.getInverted();
 350                 Integer value = inverted ? getLowestValue() :
 351                                            getHighestValue();
 352                 if (value != null) {
 353                     int thumbHeight = thumbSize.height;
 354                     int trackBuffer = Math.max(metrics.getHeight() / 2,
 355                                                thumbHeight / 2);
 356                     int contentY = focusInsets.top + insets.top;
 357                     int trackY = contentY + trackBuffer;
 358                     int trackHeight = height - focusInsets.top -
 359                         focusInsets.bottom - insets.top - insets.bottom -
 360                         trackBuffer - trackBuffer;
 361                     int yPosition = yPositionForValue(value, trackY,
 362                                                       trackHeight);
 363                     return yPosition - metrics.getHeight() / 2 +
 364                         metrics.getAscent();
 365                 }
 366             }
 367         }
 368         return 0;
 369     }
 370 
 371     /**
 372      * Returns an enum indicating how the baseline of the component
 373      * changes as the size changes.
 374      *
 375      * @throws NullPointerException {@inheritDoc}
 376      * @see javax.swing.JComponent#getBaseline(int, int)
 377      * @since 1.6
 378      */
 379     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
 380             JComponent c) {
 381         super.getBaselineResizeBehavior(c);
 382         // NOTE: BasicSpinner really provides for CENTER_OFFSET, but
 383         // the default min/pref size is smaller than it should be
 384         // so that getBaseline() doesn't implement the contract
 385         // for CENTER_OFFSET as defined in Component.
 386         return Component.BaselineResizeBehavior.OTHER;
 387     }
 388 
 389     /**
 390      * Returns true if all the labels from the label table have the same
 391      * baseline.
 392      *
 393      * @return true if all the labels from the label table have the
 394      *         same baseline
 395      * @since 1.6
 396      */
 397     protected boolean labelsHaveSameBaselines() {
 398         if (!checkedLabelBaselines) {
 399             checkedLabelBaselines = true;
 400             Dictionary<?, JComponent> dictionary = slider.getLabelTable();
 401             if (dictionary != null) {
 402                 sameLabelBaselines = true;
 403                 Enumeration<JComponent> elements = dictionary.elements();
 404                 int baseline = -1;
 405                 while (elements.hasMoreElements()) {
 406                     JComponent label = elements.nextElement();
 407                     Dimension pref = label.getPreferredSize();
 408                     int labelBaseline = label.getBaseline(pref.width,
 409                                                           pref.height);
 410                     if (labelBaseline >= 0) {
 411                         if (baseline == -1) {
 412                             baseline = labelBaseline;
 413                         }
 414                         else if (baseline != labelBaseline) {
 415                             sameLabelBaselines = false;
 416                             break;
 417                         }
 418                     }
 419                     else {
 420                         sameLabelBaselines = false;
 421                         break;
 422                     }
 423                 }
 424             }
 425             else {
 426                 sameLabelBaselines = false;
 427             }
 428         }
 429         return sameLabelBaselines;
 430     }
 431 
 432     public Dimension getPreferredHorizontalSize() {
 433         Dimension horizDim = (Dimension)DefaultLookup.get(slider,
 434                 this, "Slider.horizontalSize");
 435         if (horizDim == null) {
 436             horizDim = new Dimension(200, 21);
 437         }
 438         return horizDim;
 439     }
 440 
 441     public Dimension getPreferredVerticalSize() {
 442         Dimension vertDim = (Dimension)DefaultLookup.get(slider,
 443                 this, "Slider.verticalSize");
 444         if (vertDim == null) {
 445             vertDim = new Dimension(21, 200);
 446         }
 447         return vertDim;
 448     }
 449 
 450     public Dimension getMinimumHorizontalSize() {
 451         Dimension minHorizDim = (Dimension)DefaultLookup.get(slider,
 452                 this, "Slider.minimumHorizontalSize");
 453         if (minHorizDim == null) {
 454             minHorizDim = new Dimension(36, 21);
 455         }
 456         return minHorizDim;
 457     }
 458 
 459     public Dimension getMinimumVerticalSize() {
 460         Dimension minVertDim = (Dimension)DefaultLookup.get(slider,
 461                 this, "Slider.minimumVerticalSize");
 462         if (minVertDim == null) {
 463             minVertDim = new Dimension(21, 36);
 464         }
 465         return minVertDim;
 466     }
 467 
 468     public Dimension getPreferredSize(JComponent c)    {
 469         recalculateIfInsetsChanged();
 470         Dimension d;
 471         if ( slider.getOrientation() == JSlider.VERTICAL ) {
 472             d = new Dimension(getPreferredVerticalSize());
 473             d.width = insetCache.left + insetCache.right;
 474             d.width += focusInsets.left + focusInsets.right;
 475             d.width += trackRect.width + tickRect.width + labelRect.width;
 476         }
 477         else {
 478             d = new Dimension(getPreferredHorizontalSize());
 479             d.height = insetCache.top + insetCache.bottom;
 480             d.height += focusInsets.top + focusInsets.bottom;
 481             d.height += trackRect.height + tickRect.height + labelRect.height;
 482         }
 483 
 484         return d;
 485     }
 486 
 487     public Dimension getMinimumSize(JComponent c)  {
 488         recalculateIfInsetsChanged();
 489         Dimension d;
 490 
 491         if ( slider.getOrientation() == JSlider.VERTICAL ) {
 492             d = new Dimension(getMinimumVerticalSize());
 493             d.width = insetCache.left + insetCache.right;
 494             d.width += focusInsets.left + focusInsets.right;
 495             d.width += trackRect.width + tickRect.width + labelRect.width;
 496         }
 497         else {
 498             d = new Dimension(getMinimumHorizontalSize());
 499             d.height = insetCache.top + insetCache.bottom;
 500             d.height += focusInsets.top + focusInsets.bottom;
 501             d.height += trackRect.height + tickRect.height + labelRect.height;
 502         }
 503 
 504         return d;
 505     }
 506 
 507     public Dimension getMaximumSize(JComponent c) {
 508         Dimension d = getPreferredSize(c);
 509         if ( slider.getOrientation() == JSlider.VERTICAL ) {
 510             d.height = Short.MAX_VALUE;
 511         }
 512         else {
 513             d.width = Short.MAX_VALUE;
 514         }
 515 
 516         return d;
 517     }
 518 
 519     protected void calculateGeometry() {
 520         calculateFocusRect();
 521         calculateContentRect();
 522         calculateThumbSize();
 523         calculateTrackBuffer();
 524         calculateTrackRect();
 525         calculateTickRect();
 526         calculateLabelRect();
 527         calculateThumbLocation();
 528     }
 529 
 530     protected void calculateFocusRect() {
 531         focusRect.x = insetCache.left;
 532         focusRect.y = insetCache.top;
 533         focusRect.width = slider.getWidth() - (insetCache.left + insetCache.right);
 534         focusRect.height = slider.getHeight() - (insetCache.top + insetCache.bottom);
 535     }
 536 
 537     protected void calculateThumbSize() {
 538         Dimension size = getThumbSize();
 539         thumbRect.setSize( size.width, size.height );
 540     }
 541 
 542     protected void calculateContentRect() {
 543         contentRect.x = focusRect.x + focusInsets.left;
 544         contentRect.y = focusRect.y + focusInsets.top;
 545         contentRect.width = focusRect.width - (focusInsets.left + focusInsets.right);
 546         contentRect.height = focusRect.height - (focusInsets.top + focusInsets.bottom);
 547     }
 548 
 549     private int getTickSpacing() {
 550         int majorTickSpacing = slider.getMajorTickSpacing();
 551         int minorTickSpacing = slider.getMinorTickSpacing();
 552 
 553         int result;
 554 
 555         if (minorTickSpacing > 0) {
 556             result = minorTickSpacing;
 557         } else if (majorTickSpacing > 0) {
 558             result = majorTickSpacing;
 559         } else {
 560             result = 0;
 561         }
 562 
 563         return result;
 564     }
 565 
 566     protected void calculateThumbLocation() {
 567         if ( slider.getSnapToTicks() ) {
 568             int sliderValue = slider.getValue();
 569             int snappedValue = sliderValue;
 570             int tickSpacing = getTickSpacing();
 571 
 572             if ( tickSpacing != 0 ) {
 573                 // If it's not on a tick, change the value
 574                 if ( (sliderValue - slider.getMinimum()) % tickSpacing != 0 ) {
 575                     float temp = (float)(sliderValue - slider.getMinimum()) / (float)tickSpacing;
 576                     int whichTick = Math.round( temp );
 577 
 578                     // This is the fix for the bug #6401380
 579                     if (temp - (int)temp == .5 && sliderValue < lastValue) {
 580                       whichTick --;
 581                     }
 582                     snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
 583                 }
 584 
 585                 if( snappedValue != sliderValue ) {
 586                     slider.setValue( snappedValue );
 587                 }
 588             }
 589         }
 590 
 591         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 592             int valuePosition = xPositionForValue(slider.getValue());
 593 
 594             thumbRect.x = valuePosition - (thumbRect.width / 2);
 595             thumbRect.y = trackRect.y;
 596         }
 597         else {
 598             int valuePosition = yPositionForValue(slider.getValue());
 599 
 600             thumbRect.x = trackRect.x;
 601             thumbRect.y = valuePosition - (thumbRect.height / 2);
 602         }
 603     }
 604 
 605     protected void calculateTrackBuffer() {
 606         if ( slider.getPaintLabels() && slider.getLabelTable()  != null ) {
 607             Component highLabel = getHighestValueLabel();
 608             Component lowLabel = getLowestValueLabel();
 609 
 610             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 611                 trackBuffer = Math.max( highLabel.getBounds().width, lowLabel.getBounds().width ) / 2;
 612                 trackBuffer = Math.max( trackBuffer, thumbRect.width / 2 );
 613             }
 614             else {
 615                 trackBuffer = Math.max( highLabel.getBounds().height, lowLabel.getBounds().height ) / 2;
 616                 trackBuffer = Math.max( trackBuffer, thumbRect.height / 2 );
 617             }
 618         }
 619         else {
 620             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 621                 trackBuffer = thumbRect.width / 2;
 622             }
 623             else {
 624                 trackBuffer = thumbRect.height / 2;
 625             }
 626         }
 627     }
 628 
 629 
 630     protected void calculateTrackRect() {
 631         int centerSpacing; // used to center sliders added using BorderLayout.CENTER (bug 4275631)
 632         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 633             centerSpacing = thumbRect.height;
 634             if ( slider.getPaintTicks() ) centerSpacing += getTickLength();
 635             if ( slider.getPaintLabels() ) centerSpacing += getHeightOfTallestLabel();
 636             trackRect.x = contentRect.x + trackBuffer;
 637             trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1)/2;
 638             trackRect.width = contentRect.width - (trackBuffer * 2);
 639             trackRect.height = thumbRect.height;
 640         }
 641         else {
 642             centerSpacing = thumbRect.width;
 643             if (BasicGraphicsUtils.isLeftToRight(slider)) {
 644                 if ( slider.getPaintTicks() ) centerSpacing += getTickLength();
 645                 if ( slider.getPaintLabels() ) centerSpacing += getWidthOfWidestLabel();
 646             } else {
 647                 if ( slider.getPaintTicks() ) centerSpacing -= getTickLength();
 648                 if ( slider.getPaintLabels() ) centerSpacing -= getWidthOfWidestLabel();
 649             }
 650             trackRect.x = contentRect.x + (contentRect.width - centerSpacing - 1)/2;
 651             trackRect.y = contentRect.y + trackBuffer;
 652             trackRect.width = thumbRect.width;
 653             trackRect.height = contentRect.height - (trackBuffer * 2);
 654         }
 655 
 656     }
 657 
 658     /**
 659      * Gets the height of the tick area for horizontal sliders and the width of the
 660      * tick area for vertical sliders.  BasicSliderUI uses the returned value to
 661      * determine the tick area rectangle.  If you want to give your ticks some room,
 662      * make this larger than you need and paint your ticks away from the sides in paintTicks().
 663      */
 664     protected int getTickLength() {
 665         return 8;
 666     }
 667 
 668     protected void calculateTickRect() {
 669         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 670             tickRect.x = trackRect.x;
 671             tickRect.y = trackRect.y + trackRect.height;
 672             tickRect.width = trackRect.width;
 673             tickRect.height = (slider.getPaintTicks()) ? getTickLength() : 0;
 674         }
 675         else {
 676             tickRect.width = (slider.getPaintTicks()) ? getTickLength() : 0;
 677             if(BasicGraphicsUtils.isLeftToRight(slider)) {
 678                 tickRect.x = trackRect.x + trackRect.width;
 679             }
 680             else {
 681                 tickRect.x = trackRect.x - tickRect.width;
 682             }
 683             tickRect.y = trackRect.y;
 684             tickRect.height = trackRect.height;
 685         }
 686     }
 687 
 688     protected void calculateLabelRect() {
 689         if ( slider.getPaintLabels() ) {
 690             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 691                 labelRect.x = tickRect.x - trackBuffer;
 692                 labelRect.y = tickRect.y + tickRect.height;
 693                 labelRect.width = tickRect.width + (trackBuffer * 2);
 694                 labelRect.height = getHeightOfTallestLabel();
 695             }
 696             else {
 697                 if(BasicGraphicsUtils.isLeftToRight(slider)) {
 698                     labelRect.x = tickRect.x + tickRect.width;
 699                     labelRect.width = getWidthOfWidestLabel();
 700                 }
 701                 else {
 702                     labelRect.width = getWidthOfWidestLabel();
 703                     labelRect.x = tickRect.x - labelRect.width;
 704                 }
 705                 labelRect.y = tickRect.y - trackBuffer;
 706                 labelRect.height = tickRect.height + (trackBuffer * 2);
 707             }
 708         }
 709         else {
 710             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 711                 labelRect.x = tickRect.x;
 712                 labelRect.y = tickRect.y + tickRect.height;
 713                 labelRect.width = tickRect.width;
 714                 labelRect.height = 0;
 715             }
 716             else {
 717                 if(BasicGraphicsUtils.isLeftToRight(slider)) {
 718                     labelRect.x = tickRect.x + tickRect.width;
 719                 }
 720                 else {
 721                     labelRect.x = tickRect.x;
 722                 }
 723                 labelRect.y = tickRect.y;
 724                 labelRect.width = 0;
 725                 labelRect.height = tickRect.height;
 726             }
 727         }
 728     }
 729 
 730     protected Dimension getThumbSize() {
 731         Dimension size = new Dimension();
 732 
 733         if ( slider.getOrientation() == JSlider.VERTICAL ) {
 734             size.width = 20;
 735             size.height = 11;
 736         }
 737         else {
 738             size.width = 11;
 739             size.height = 20;
 740         }
 741 
 742         return size;
 743     }
 744 
 745     public class PropertyChangeHandler implements PropertyChangeListener {
 746         // NOTE: This class exists only for backward compatibility. All
 747         // its functionality has been moved into Handler. If you need to add
 748         // new functionality add it to the Handler, but make sure this
 749         // class calls into the Handler.
 750         public void propertyChange( PropertyChangeEvent e ) {
 751             getHandler().propertyChange(e);
 752         }
 753     }
 754 
 755     protected int getWidthOfWidestLabel() {
 756         Dictionary<?, JComponent> dictionary = slider.getLabelTable();
 757         int widest = 0;
 758         if ( dictionary != null ) {
 759             Enumeration<?> keys = dictionary.keys();
 760             while ( keys.hasMoreElements() ) {
 761                 JComponent label = dictionary.get(keys.nextElement());
 762                 widest = Math.max( label.getPreferredSize().width, widest );
 763             }
 764         }
 765         return widest;
 766     }
 767 
 768     protected int getHeightOfTallestLabel() {
 769         Dictionary<?, JComponent> dictionary = slider.getLabelTable();
 770         int tallest = 0;
 771         if ( dictionary != null ) {
 772             Enumeration<?> keys = dictionary.keys();
 773             while ( keys.hasMoreElements() ) {
 774                 JComponent label = dictionary.get(keys.nextElement());
 775                 tallest = Math.max( label.getPreferredSize().height, tallest );
 776             }
 777         }
 778         return tallest;
 779     }
 780 
 781     protected int getWidthOfHighValueLabel() {
 782         Component label = getHighestValueLabel();
 783         int width = 0;
 784 
 785         if ( label != null ) {
 786             width = label.getPreferredSize().width;
 787         }
 788 
 789         return width;
 790     }
 791 
 792     protected int getWidthOfLowValueLabel() {
 793         Component label = getLowestValueLabel();
 794         int width = 0;
 795 
 796         if ( label != null ) {
 797             width = label.getPreferredSize().width;
 798         }
 799 
 800         return width;
 801     }
 802 
 803     protected int getHeightOfHighValueLabel() {
 804         Component label = getHighestValueLabel();
 805         int height = 0;
 806 
 807         if ( label != null ) {
 808             height = label.getPreferredSize().height;
 809         }
 810 
 811         return height;
 812     }
 813 
 814     protected int getHeightOfLowValueLabel() {
 815         Component label = getLowestValueLabel();
 816         int height = 0;
 817 
 818         if ( label != null ) {
 819             height = label.getPreferredSize().height;
 820         }
 821 
 822         return height;
 823     }
 824 
 825     protected boolean drawInverted() {
 826         if (slider.getOrientation()==JSlider.HORIZONTAL) {
 827             if(BasicGraphicsUtils.isLeftToRight(slider)) {
 828                 return slider.getInverted();
 829             } else {
 830                 return !slider.getInverted();
 831             }
 832         } else {
 833             return slider.getInverted();
 834         }
 835     }
 836 
 837     /**
 838      * Returns the biggest value that has an entry in the label table.
 839      *
 840      * @return biggest value that has an entry in the label table, or
 841      *         null.
 842      * @since 1.6
 843      */
 844     protected Integer getHighestValue() {
 845         Dictionary<Integer, ?> dictionary = slider.getLabelTable();
 846 
 847         if (dictionary == null) {
 848             return null;
 849         }
 850 
 851         Enumeration<Integer> keys = dictionary.keys();
 852 
 853         Integer max = null;
 854 
 855         while (keys.hasMoreElements()) {
 856             Integer i = keys.nextElement();
 857 
 858             if (max == null || i > max) {
 859                 max = i;
 860             }
 861         }
 862 
 863         return max;
 864     }
 865 
 866     /**
 867      * Returns the smallest value that has an entry in the label table.
 868      *
 869      * @return smallest value that has an entry in the label table, or
 870      *         null.
 871      * @since 1.6
 872      */
 873     protected Integer getLowestValue() {
 874         Dictionary<Integer, JComponent> dictionary = slider.getLabelTable();
 875 
 876         if (dictionary == null) {
 877             return null;
 878         }
 879 
 880         Enumeration<Integer> keys = dictionary.keys();
 881 
 882         Integer min = null;
 883 
 884         while (keys.hasMoreElements()) {
 885             Integer i = keys.nextElement();
 886 
 887             if (min == null || i < min) {
 888                 min = i;
 889             }
 890         }
 891 
 892         return min;
 893     }
 894 
 895 
 896     /**
 897      * Returns the label that corresponds to the highest slider value in the label table.
 898      * @see JSlider#setLabelTable
 899      */
 900     protected Component getLowestValueLabel() {
 901         Integer min = getLowestValue();
 902         if (min != null) {
 903             return (Component)slider.getLabelTable().get(min);
 904         }
 905         return null;
 906     }
 907 
 908     /**
 909      * Returns the label that corresponds to the lowest slider value in the label table.
 910      * @see JSlider#setLabelTable
 911      */
 912     protected Component getHighestValueLabel() {
 913         Integer max = getHighestValue();
 914         if (max != null) {
 915             return (Component)slider.getLabelTable().get(max);
 916         }
 917         return null;
 918     }
 919 
 920     public void paint( Graphics g, JComponent c )   {
 921         recalculateIfInsetsChanged();
 922         recalculateIfOrientationChanged();
 923         Rectangle clip = g.getClipBounds();
 924 
 925         if ( !clip.intersects(trackRect) && slider.getPaintTrack())
 926             calculateGeometry();
 927 
 928         if ( slider.getPaintTrack() && clip.intersects( trackRect ) ) {
 929             paintTrack( g );
 930         }
 931         if ( slider.getPaintTicks() && clip.intersects( tickRect ) ) {
 932             paintTicks( g );
 933         }
 934         if ( slider.getPaintLabels() && clip.intersects( labelRect ) ) {
 935             paintLabels( g );
 936         }
 937         if ( slider.hasFocus() && clip.intersects( focusRect ) ) {
 938             paintFocus( g );
 939         }
 940         if ( clip.intersects( thumbRect ) ) {
 941             paintThumb( g );
 942         }
 943     }
 944 
 945     protected void recalculateIfInsetsChanged() {
 946         Insets newInsets = slider.getInsets();
 947         if ( !newInsets.equals( insetCache ) ) {
 948             insetCache = newInsets;
 949             calculateGeometry();
 950         }
 951     }
 952 
 953     protected void recalculateIfOrientationChanged() {
 954         boolean ltr = BasicGraphicsUtils.isLeftToRight(slider);
 955         if ( ltr!=leftToRightCache ) {
 956             leftToRightCache = ltr;
 957             calculateGeometry();
 958         }
 959     }
 960 
 961     public void paintFocus(Graphics g)  {
 962         g.setColor( getFocusColor() );
 963 
 964         BasicGraphicsUtils.drawDashedRect( g, focusRect.x, focusRect.y,
 965                                            focusRect.width, focusRect.height );
 966     }
 967 
 968     public void paintTrack(Graphics g)  {
 969 
 970         Rectangle trackBounds = trackRect;
 971 
 972         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 973             int cy = (trackBounds.height / 2) - 2;
 974             int cw = trackBounds.width;
 975 
 976             g.translate(trackBounds.x, trackBounds.y + cy);
 977 
 978             g.setColor(getShadowColor());
 979             g.drawLine(0, 0, cw - 1, 0);
 980             g.drawLine(0, 1, 0, 2);
 981             g.setColor(getHighlightColor());
 982             g.drawLine(0, 3, cw, 3);
 983             g.drawLine(cw, 0, cw, 3);
 984             g.setColor(Color.black);
 985             g.drawLine(1, 1, cw-2, 1);
 986 
 987             g.translate(-trackBounds.x, -(trackBounds.y + cy));
 988         }
 989         else {
 990             int cx = (trackBounds.width / 2) - 2;
 991             int ch = trackBounds.height;
 992 
 993             g.translate(trackBounds.x + cx, trackBounds.y);
 994 
 995             g.setColor(getShadowColor());
 996             g.drawLine(0, 0, 0, ch - 1);
 997             g.drawLine(1, 0, 2, 0);
 998             g.setColor(getHighlightColor());
 999             g.drawLine(3, 0, 3, ch);
1000             g.drawLine(0, ch, 3, ch);
1001             g.setColor(Color.black);
1002             g.drawLine(1, 1, 1, ch-2);
1003 
1004             g.translate(-(trackBounds.x + cx), -trackBounds.y);
1005         }
1006     }
1007 
1008     public void paintTicks(Graphics g)  {
1009         Rectangle tickBounds = tickRect;
1010 
1011         g.setColor(DefaultLookup.getColor(slider, this, "Slider.tickColor", Color.black));
1012 
1013         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1014             g.translate(0, tickBounds.y);
1015 
1016             if (slider.getMinorTickSpacing() > 0) {
1017                 int value = slider.getMinimum();
1018 
1019                 while ( value <= slider.getMaximum() ) {
1020                     int xPos = xPositionForValue(value);
1021                     paintMinorTickForHorizSlider( g, tickBounds, xPos );
1022 
1023                     // Overflow checking
1024                     if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) {
1025                         break;
1026                     }
1027 
1028                     value += slider.getMinorTickSpacing();
1029                 }
1030             }
1031 
1032             if (slider.getMajorTickSpacing() > 0) {
1033                 int value = slider.getMinimum();
1034 
1035                 while ( value <= slider.getMaximum() ) {
1036                     int xPos = xPositionForValue(value);
1037                     paintMajorTickForHorizSlider( g, tickBounds, xPos );
1038 
1039                     // Overflow checking
1040                     if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) {
1041                         break;
1042                     }
1043 
1044                     value += slider.getMajorTickSpacing();
1045                 }
1046             }
1047 
1048             g.translate( 0, -tickBounds.y);
1049         } else {
1050             g.translate(tickBounds.x, 0);
1051 
1052             if (slider.getMinorTickSpacing() > 0) {
1053                 int offset = 0;
1054                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1055                     offset = tickBounds.width - tickBounds.width / 2;
1056                     g.translate(offset, 0);
1057                 }
1058 
1059                 int value = slider.getMinimum();
1060 
1061                 while (value <= slider.getMaximum()) {
1062                     int yPos = yPositionForValue(value);
1063                     paintMinorTickForVertSlider( g, tickBounds, yPos );
1064 
1065                     // Overflow checking
1066                     if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) {
1067                         break;
1068                     }
1069 
1070                     value += slider.getMinorTickSpacing();
1071                 }
1072 
1073                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1074                     g.translate(-offset, 0);
1075                 }
1076             }
1077 
1078             if (slider.getMajorTickSpacing() > 0) {
1079                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1080                     g.translate(2, 0);
1081                 }
1082 
1083                 int value = slider.getMinimum();
1084 
1085                 while (value <= slider.getMaximum()) {
1086                     int yPos = yPositionForValue(value);
1087                     paintMajorTickForVertSlider( g, tickBounds, yPos );
1088 
1089                     // Overflow checking
1090                     if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) {
1091                         break;
1092                     }
1093 
1094                     value += slider.getMajorTickSpacing();
1095                 }
1096 
1097                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1098                     g.translate(-2, 0);
1099                 }
1100             }
1101             g.translate(-tickBounds.x, 0);
1102         }
1103     }
1104 
1105     protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1106         g.drawLine( x, 0, x, tickBounds.height / 2 - 1 );
1107     }
1108 
1109     protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1110         g.drawLine( x, 0, x, tickBounds.height - 2 );
1111     }
1112 
1113     protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1114         g.drawLine( 0, y, tickBounds.width / 2 - 1, y );
1115     }
1116 
1117     protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1118         g.drawLine( 0, y,  tickBounds.width - 2, y );
1119     }
1120 
1121     public void paintLabels( Graphics g ) {
1122         Rectangle labelBounds = labelRect;
1123 
1124         Dictionary<Integer, JComponent> dictionary = slider.getLabelTable();
1125         if ( dictionary != null ) {
1126             Enumeration<Integer> keys = dictionary.keys();
1127             int minValue = slider.getMinimum();
1128             int maxValue = slider.getMaximum();
1129             boolean enabled = slider.isEnabled();
1130             while ( keys.hasMoreElements() ) {
1131                 Integer key = keys.nextElement();
1132                 int value = key.intValue();
1133                 if (value >= minValue && value <= maxValue) {
1134                     JComponent label = dictionary.get(key);
1135                     label.setEnabled(enabled);
1136 
1137                     if (label instanceof JLabel) {
1138                         Icon icon = label.isEnabled() ? ((JLabel) label).getIcon() : ((JLabel) label).getDisabledIcon();
1139 
1140                         if (icon instanceof ImageIcon) {
1141                             // Register Slider as an image observer. It allows to catch notifications about
1142                             // image changes (e.g. gif animation)
1143                             Toolkit.getDefaultToolkit().checkImage(((ImageIcon) icon).getImage(), -1, -1, slider);
1144                         }
1145                     }
1146 
1147                     if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1148                         g.translate( 0, labelBounds.y );
1149                         paintHorizontalLabel( g, value, label );
1150                         g.translate( 0, -labelBounds.y );
1151                     }
1152                     else {
1153                         int offset = 0;
1154                         if (!BasicGraphicsUtils.isLeftToRight(slider)) {
1155                             offset = labelBounds.width -
1156                                 label.getPreferredSize().width;
1157                         }
1158                         g.translate( labelBounds.x + offset, 0 );
1159                         paintVerticalLabel( g, value, label );
1160                         g.translate( -labelBounds.x - offset, 0 );
1161                     }
1162                 }
1163             }
1164         }
1165 
1166     }
1167 
1168     /**
1169      * Called for every label in the label table.  Used to draw the labels for horizontal sliders.
1170      * The graphics have been translated to labelRect.y already.
1171      * @see JSlider#setLabelTable
1172      */
1173     protected void paintHorizontalLabel( Graphics g, int value, Component label ) {
1174         int labelCenter = xPositionForValue( value );
1175         int labelLeft = labelCenter - (label.getPreferredSize().width / 2);
1176         g.translate( labelLeft, 0 );
1177         label.paint( g );
1178         g.translate( -labelLeft, 0 );
1179     }
1180 
1181     /**
1182      * Called for every label in the label table.  Used to draw the labels for vertical sliders.
1183      * The graphics have been translated to labelRect.x already.
1184      * @see JSlider#setLabelTable
1185      */
1186     protected void paintVerticalLabel( Graphics g, int value, Component label ) {
1187         int labelCenter = yPositionForValue( value );
1188         int labelTop = labelCenter - (label.getPreferredSize().height / 2);
1189         g.translate( 0, labelTop );
1190         label.paint( g );
1191         g.translate( 0, -labelTop );
1192     }
1193 
1194     public void paintThumb(Graphics g)  {
1195         Rectangle knobBounds = thumbRect;
1196         int w = knobBounds.width;
1197         int h = knobBounds.height;
1198 
1199         g.translate(knobBounds.x, knobBounds.y);
1200 
1201         if ( slider.isEnabled() ) {
1202             g.setColor(slider.getBackground());
1203         }
1204         else {
1205             g.setColor(slider.getBackground().darker());
1206         }
1207 
1208         Boolean paintThumbArrowShape =
1209             (Boolean)slider.getClientProperty("Slider.paintThumbArrowShape");
1210 
1211         if ((!slider.getPaintTicks() && paintThumbArrowShape == null) ||
1212             paintThumbArrowShape == Boolean.FALSE) {
1213 
1214             // "plain" version
1215             g.fillRect(0, 0, w, h);
1216 
1217             g.setColor(Color.black);
1218             g.drawLine(0, h-1, w-1, h-1);
1219             g.drawLine(w-1, 0, w-1, h-1);
1220 
1221             g.setColor(highlightColor);
1222             g.drawLine(0, 0, 0, h-2);
1223             g.drawLine(1, 0, w-2, 0);
1224 
1225             g.setColor(shadowColor);
1226             g.drawLine(1, h-2, w-2, h-2);
1227             g.drawLine(w-2, 1, w-2, h-3);
1228         }
1229         else if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1230             int cw = w / 2;
1231             g.fillRect(1, 1, w-3, h-1-cw);
1232             Polygon p = new Polygon();
1233             p.addPoint(1, h-cw);
1234             p.addPoint(cw-1, h-1);
1235             p.addPoint(w-2, h-1-cw);
1236             g.fillPolygon(p);
1237 
1238             g.setColor(highlightColor);
1239             g.drawLine(0, 0, w-2, 0);
1240             g.drawLine(0, 1, 0, h-1-cw);
1241             g.drawLine(0, h-cw, cw-1, h-1);
1242 
1243             g.setColor(Color.black);
1244             g.drawLine(w-1, 0, w-1, h-2-cw);
1245             g.drawLine(w-1, h-1-cw, w-1-cw, h-1);
1246 
1247             g.setColor(shadowColor);
1248             g.drawLine(w-2, 1, w-2, h-2-cw);
1249             g.drawLine(w-2, h-1-cw, w-1-cw, h-2);
1250         }
1251         else {  // vertical
1252             int cw = h / 2;
1253             if(BasicGraphicsUtils.isLeftToRight(slider)) {
1254                   g.fillRect(1, 1, w-1-cw, h-3);
1255                   Polygon p = new Polygon();
1256                   p.addPoint(w-cw-1, 0);
1257                   p.addPoint(w-1, cw);
1258                   p.addPoint(w-1-cw, h-2);
1259                   g.fillPolygon(p);
1260 
1261                   g.setColor(highlightColor);
1262                   g.drawLine(0, 0, 0, h - 2);                  // left
1263                   g.drawLine(1, 0, w-1-cw, 0);                 // top
1264                   g.drawLine(w-cw-1, 0, w-1, cw);              // top slant
1265 
1266                   g.setColor(Color.black);
1267                   g.drawLine(0, h-1, w-2-cw, h-1);             // bottom
1268                   g.drawLine(w-1-cw, h-1, w-1, h-1-cw);        // bottom slant
1269 
1270                   g.setColor(shadowColor);
1271                   g.drawLine(1, h-2, w-2-cw,  h-2 );         // bottom
1272                   g.drawLine(w-1-cw, h-2, w-2, h-cw-1 );     // bottom slant
1273             }
1274             else {
1275                   g.fillRect(5, 1, w-1-cw, h-3);
1276                   Polygon p = new Polygon();
1277                   p.addPoint(cw, 0);
1278                   p.addPoint(0, cw);
1279                   p.addPoint(cw, h-2);
1280                   g.fillPolygon(p);
1281 
1282                   g.setColor(highlightColor);
1283                   g.drawLine(cw-1, 0, w-2, 0);             // top
1284                   g.drawLine(0, cw, cw, 0);                // top slant
1285 
1286                   g.setColor(Color.black);
1287                   g.drawLine(0, h-1-cw, cw, h-1 );         // bottom slant
1288                   g.drawLine(cw, h-1, w-1, h-1);           // bottom
1289 
1290                   g.setColor(shadowColor);
1291                   g.drawLine(cw, h-2, w-2,  h-2 );         // bottom
1292                   g.drawLine(w-1, 1, w-1,  h-2 );          // right
1293             }
1294         }
1295 
1296         g.translate(-knobBounds.x, -knobBounds.y);
1297     }
1298 
1299     // Used exclusively by setThumbLocation()
1300     private static Rectangle unionRect = new Rectangle();
1301 
1302     public void setThumbLocation(int x, int y)  {
1303         unionRect.setBounds( thumbRect );
1304 
1305         thumbRect.setLocation( x, y );
1306 
1307         SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, unionRect );
1308         slider.repaint( unionRect.x, unionRect.y, unionRect.width, unionRect.height );
1309     }
1310 
1311     public void scrollByBlock(int direction)    {
1312         synchronized(slider)    {
1313             int blockIncrement =
1314                 (slider.getMaximum() - slider.getMinimum()) / 10;
1315             if (blockIncrement == 0) {
1316                 blockIncrement = 1;
1317             }
1318 
1319             if (slider.getSnapToTicks()) {
1320                 int tickSpacing = getTickSpacing();
1321 
1322                 if (blockIncrement < tickSpacing) {
1323                     blockIncrement = tickSpacing;
1324                 }
1325             }
1326 
1327             int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1328             slider.setValue(slider.getValue() + delta);
1329         }
1330     }
1331 
1332     public void scrollByUnit(int direction) {
1333         synchronized(slider)    {
1334             int delta = ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1335 
1336             if (slider.getSnapToTicks()) {
1337                 delta *= getTickSpacing();
1338             }
1339 
1340             slider.setValue(slider.getValue() + delta);
1341         }
1342     }
1343 
1344     /**
1345      * This function is called when a mousePressed was detected in the track, not
1346      * in the thumb.  The default behavior is to scroll by block.  You can
1347      *  override this method to stop it from scrolling or to add additional behavior.
1348      */
1349     protected void scrollDueToClickInTrack( int dir ) {
1350         scrollByBlock( dir );
1351     }
1352 
1353     protected int xPositionForValue( int value )    {
1354         int min = slider.getMinimum();
1355         int max = slider.getMaximum();
1356         int trackLength = trackRect.width;
1357         double valueRange = (double)max - (double)min;
1358         double pixelsPerValue = (double)trackLength / valueRange;
1359         int trackLeft = trackRect.x;
1360         int trackRight = trackRect.x + (trackRect.width - 1);
1361         int xPosition;
1362 
1363         if ( !drawInverted() ) {
1364             xPosition = trackLeft;
1365             xPosition += Math.round( pixelsPerValue * ((double)value - min) );
1366         }
1367         else {
1368             xPosition = trackRight;
1369             xPosition -= Math.round( pixelsPerValue * ((double)value - min) );
1370         }
1371 
1372         xPosition = Math.max( trackLeft, xPosition );
1373         xPosition = Math.min( trackRight, xPosition );
1374 
1375         return xPosition;
1376     }
1377 
1378     protected int yPositionForValue( int value )  {
1379         return yPositionForValue(value, trackRect.y, trackRect.height);
1380     }
1381 
1382     /**
1383      * Returns the y location for the specified value.  No checking is
1384      * done on the arguments.  In particular if <code>trackHeight</code> is
1385      * negative undefined results may occur.
1386      *
1387      * @param value the slider value to get the location for
1388      * @param trackY y-origin of the track
1389      * @param trackHeight the height of the track
1390      * @since 1.6
1391      */
1392     protected int yPositionForValue(int value, int trackY, int trackHeight) {
1393         int min = slider.getMinimum();
1394         int max = slider.getMaximum();
1395         double valueRange = (double)max - (double)min;
1396         double pixelsPerValue = (double)trackHeight / valueRange;
1397         int trackBottom = trackY + (trackHeight - 1);
1398         int yPosition;
1399 
1400         if ( !drawInverted() ) {
1401             yPosition = trackY;
1402             yPosition += Math.round( pixelsPerValue * ((double)max - value ) );
1403         }
1404         else {
1405             yPosition = trackY;
1406             yPosition += Math.round( pixelsPerValue * ((double)value - min) );
1407         }
1408 
1409         yPosition = Math.max( trackY, yPosition );
1410         yPosition = Math.min( trackBottom, yPosition );
1411 
1412         return yPosition;
1413     }
1414 
1415     /**
1416      * Returns the value at the y position. If {@code yPos} is beyond the
1417      * track at the the bottom or the top, this method sets the value to either
1418      * the minimum or maximum value of the slider, depending on if the slider
1419      * is inverted or not.
1420      */
1421     public int valueForYPosition( int yPos ) {
1422         int value;
1423         final int minValue = slider.getMinimum();
1424         final int maxValue = slider.getMaximum();
1425         final int trackLength = trackRect.height;
1426         final int trackTop = trackRect.y;
1427         final int trackBottom = trackRect.y + (trackRect.height - 1);
1428 
1429         if ( yPos <= trackTop ) {
1430             value = drawInverted() ? minValue : maxValue;
1431         }
1432         else if ( yPos >= trackBottom ) {
1433             value = drawInverted() ? maxValue : minValue;
1434         }
1435         else {
1436             int distanceFromTrackTop = yPos - trackTop;
1437             double valueRange = (double)maxValue - (double)minValue;
1438             double valuePerPixel = valueRange / (double)trackLength;
1439             int valueFromTrackTop = (int)Math.round( distanceFromTrackTop * valuePerPixel );
1440 
1441             value = drawInverted() ? minValue + valueFromTrackTop : maxValue - valueFromTrackTop;
1442         }
1443 
1444         return value;
1445     }
1446 
1447     /**
1448      * Returns the value at the x position.  If {@code xPos} is beyond the
1449      * track at the left or the right, this method sets the value to either the
1450      * minimum or maximum value of the slider, depending on if the slider is
1451      * inverted or not.
1452      */
1453     public int valueForXPosition( int xPos ) {
1454         int value;
1455         final int minValue = slider.getMinimum();
1456         final int maxValue = slider.getMaximum();
1457         final int trackLength = trackRect.width;
1458         final int trackLeft = trackRect.x;
1459         final int trackRight = trackRect.x + (trackRect.width - 1);
1460 
1461         if ( xPos <= trackLeft ) {
1462             value = drawInverted() ? maxValue : minValue;
1463         }
1464         else if ( xPos >= trackRight ) {
1465             value = drawInverted() ? minValue : maxValue;
1466         }
1467         else {
1468             int distanceFromTrackLeft = xPos - trackLeft;
1469             double valueRange = (double)maxValue - (double)minValue;
1470             double valuePerPixel = valueRange / (double)trackLength;
1471             int valueFromTrackLeft = (int)Math.round( distanceFromTrackLeft * valuePerPixel );
1472 
1473             value = drawInverted() ? maxValue - valueFromTrackLeft :
1474               minValue + valueFromTrackLeft;
1475         }
1476 
1477         return value;
1478     }
1479 
1480 
1481     private class Handler implements ChangeListener,
1482             ComponentListener, FocusListener, PropertyChangeListener {
1483         // Change Handler
1484         public void stateChanged(ChangeEvent e) {
1485             if (!isDragging) {
1486                 calculateThumbLocation();
1487                 slider.repaint();
1488             }
1489             lastValue = slider.getValue();
1490         }
1491 
1492         // Component Handler
1493         public void componentHidden(ComponentEvent e) { }
1494         public void componentMoved(ComponentEvent e) { }
1495         public void componentResized(ComponentEvent e) {
1496             calculateGeometry();
1497             slider.repaint();
1498         }
1499         public void componentShown(ComponentEvent e) { }
1500 
1501         // Focus Handler
1502         public void focusGained(FocusEvent e) { slider.repaint(); }
1503         public void focusLost(FocusEvent e) { slider.repaint(); }
1504 
1505         // Property Change Handler
1506         public void propertyChange(PropertyChangeEvent e) {
1507             String propertyName = e.getPropertyName();
1508             if (propertyName == "orientation" ||
1509                     propertyName == "inverted" ||
1510                     propertyName == "labelTable" ||
1511                     propertyName == "majorTickSpacing" ||
1512                     propertyName == "minorTickSpacing" ||
1513                     propertyName == "paintTicks" ||
1514                     propertyName == "paintTrack" ||
1515                     propertyName == "font" ||
1516                     propertyName == "paintLabels" ||
1517                     propertyName == "Slider.paintThumbArrowShape") {
1518                 checkedLabelBaselines = false;
1519                 calculateGeometry();
1520                 slider.repaint();
1521             } else if (propertyName == "componentOrientation") {
1522                 calculateGeometry();
1523                 slider.repaint();
1524                 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider);
1525                 SwingUtilities.replaceUIInputMap(slider,
1526                     JComponent.WHEN_FOCUSED, km);
1527             } else if (propertyName == "model") {
1528                 ((BoundedRangeModel)e.getOldValue()).removeChangeListener(
1529                     changeListener);
1530                 ((BoundedRangeModel)e.getNewValue()).addChangeListener(
1531                     changeListener);
1532                 calculateThumbLocation();
1533                 slider.repaint();
1534             }
1535         }
1536     }
1537 
1538     /////////////////////////////////////////////////////////////////////////
1539     /// Model Listener Class
1540     /////////////////////////////////////////////////////////////////////////
1541     /**
1542      * Data model listener.
1543      *
1544      * This class should be treated as a &quot;protected&quot; inner class.
1545      * Instantiate it only within subclasses of <code>Foo</code>.
1546      */
1547     public class ChangeHandler implements ChangeListener {
1548         // NOTE: This class exists only for backward compatibility. All
1549         // its functionality has been moved into Handler. If you need to add
1550         // new functionality add it to the Handler, but make sure this
1551         // class calls into the Handler.
1552         public void stateChanged(ChangeEvent e) {
1553             getHandler().stateChanged(e);
1554         }
1555     }
1556 
1557     /////////////////////////////////////////////////////////////////////////
1558     /// Track Listener Class
1559     /////////////////////////////////////////////////////////////////////////
1560     /**
1561      * Track mouse movements.
1562      *
1563      * This class should be treated as a &quot;protected&quot; inner class.
1564      * Instantiate it only within subclasses of <code>Foo</code>.
1565      */
1566     public class TrackListener extends MouseInputAdapter {
1567         protected transient int offset;
1568         protected transient int currentMouseX, currentMouseY;
1569 
1570         public void mouseReleased(MouseEvent e) {
1571             if (!slider.isEnabled()) {
1572                 return;
1573             }
1574 
1575             offset = 0;
1576             scrollTimer.stop();
1577 
1578             isDragging = false;
1579             slider.setValueIsAdjusting(false);
1580             slider.repaint();
1581         }
1582 
1583         /**
1584         * If the mouse is pressed above the "thumb" component
1585         * then reduce the scrollbars value by one page ("page up"),
1586         * otherwise increase it by one page.  If there is no
1587         * thumb then page up if the mouse is in the upper half
1588         * of the track.
1589         */
1590         public void mousePressed(MouseEvent e) {
1591             if (!slider.isEnabled()) {
1592                 return;
1593             }
1594 
1595             // We should recalculate geometry just before
1596             // calculation of the thumb movement direction.
1597             // It is important for the case, when JSlider
1598             // is a cell editor in JTable. See 6348946.
1599             calculateGeometry();
1600 
1601             currentMouseX = e.getX();
1602             currentMouseY = e.getY();
1603 
1604             if (slider.isRequestFocusEnabled()) {
1605                 slider.requestFocus();
1606             }
1607 
1608             // Clicked in the Thumb area?
1609             if (thumbRect.contains(currentMouseX, currentMouseY)) {
1610                 if (UIManager.getBoolean("Slider.onlyLeftMouseButtonDrag")
1611                         && !SwingUtilities.isLeftMouseButton(e)) {
1612                     return;
1613                 }
1614 
1615                 switch (slider.getOrientation()) {
1616                 case JSlider.VERTICAL:
1617                     offset = currentMouseY - thumbRect.y;
1618                     break;
1619                 case JSlider.HORIZONTAL:
1620                     offset = currentMouseX - thumbRect.x;
1621                     break;
1622                 }
1623                 isDragging = true;
1624                 return;
1625             }
1626 
1627             if (!SwingUtilities.isLeftMouseButton(e)) {
1628                 return;
1629             }
1630 
1631             isDragging = false;
1632             slider.setValueIsAdjusting(true);
1633 
1634             Dimension sbSize = slider.getSize();
1635             int direction = POSITIVE_SCROLL;
1636 
1637             switch (slider.getOrientation()) {
1638             case JSlider.VERTICAL:
1639                 if ( thumbRect.isEmpty() ) {
1640                     int scrollbarCenter = sbSize.height / 2;
1641                     if ( !drawInverted() ) {
1642                         direction = (currentMouseY < scrollbarCenter) ?
1643                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1644                     }
1645                     else {
1646                         direction = (currentMouseY < scrollbarCenter) ?
1647                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1648                     }
1649                 }
1650                 else {
1651                     int thumbY = thumbRect.y;
1652                     if ( !drawInverted() ) {
1653                         direction = (currentMouseY < thumbY) ?
1654                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1655                     }
1656                     else {
1657                         direction = (currentMouseY < thumbY) ?
1658                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1659                     }
1660                 }
1661                 break;
1662             case JSlider.HORIZONTAL:
1663                 if ( thumbRect.isEmpty() ) {
1664                     int scrollbarCenter = sbSize.width / 2;
1665                     if ( !drawInverted() ) {
1666                         direction = (currentMouseX < scrollbarCenter) ?
1667                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1668                     }
1669                     else {
1670                         direction = (currentMouseX < scrollbarCenter) ?
1671                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1672                     }
1673                 }
1674                 else {
1675                     int thumbX = thumbRect.x;
1676                     if ( !drawInverted() ) {
1677                         direction = (currentMouseX < thumbX) ?
1678                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1679                     }
1680                     else {
1681                         direction = (currentMouseX < thumbX) ?
1682                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1683                     }
1684                 }
1685                 break;
1686             }
1687 
1688             if (shouldScroll(direction)) {
1689                 scrollDueToClickInTrack(direction);
1690             }
1691             if (shouldScroll(direction)) {
1692                 scrollTimer.stop();
1693                 scrollListener.setDirection(direction);
1694                 scrollTimer.start();
1695             }
1696         }
1697 
1698         public boolean shouldScroll(int direction) {
1699             Rectangle r = thumbRect;
1700             if (slider.getOrientation() == JSlider.VERTICAL) {
1701                 if (drawInverted() ? direction < 0 : direction > 0) {
1702                     if (r.y  <= currentMouseY) {
1703                         return false;
1704                     }
1705                 }
1706                 else if (r.y + r.height >= currentMouseY) {
1707                     return false;
1708                 }
1709             }
1710             else {
1711                 if (drawInverted() ? direction < 0 : direction > 0) {
1712                     if (r.x + r.width  >= currentMouseX) {
1713                         return false;
1714                     }
1715                 }
1716                 else if (r.x <= currentMouseX) {
1717                     return false;
1718                 }
1719             }
1720 
1721             if (direction > 0 && slider.getValue() + slider.getExtent() >=
1722                     slider.getMaximum()) {
1723                 return false;
1724             }
1725             else if (direction < 0 && slider.getValue() <=
1726                     slider.getMinimum()) {
1727                 return false;
1728             }
1729 
1730             return true;
1731         }
1732 
1733         /**
1734         * Set the models value to the position of the top/left
1735         * of the thumb relative to the origin of the track.
1736         */
1737         public void mouseDragged(MouseEvent e) {
1738             int thumbMiddle;
1739 
1740             if (!slider.isEnabled()) {
1741                 return;
1742             }
1743 
1744             currentMouseX = e.getX();
1745             currentMouseY = e.getY();
1746 
1747             if (!isDragging) {
1748                 return;
1749             }
1750 
1751             slider.setValueIsAdjusting(true);
1752 
1753             switch (slider.getOrientation()) {
1754             case JSlider.VERTICAL:
1755                 int halfThumbHeight = thumbRect.height / 2;
1756                 int thumbTop = e.getY() - offset;
1757                 int trackTop = trackRect.y;
1758                 int trackBottom = trackRect.y + (trackRect.height - 1);
1759                 int vMax = yPositionForValue(slider.getMaximum() -
1760                                             slider.getExtent());
1761 
1762                 if (drawInverted()) {
1763                     trackBottom = vMax;
1764                 }
1765                 else {
1766                     trackTop = vMax;
1767                 }
1768                 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
1769                 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
1770 
1771                 setThumbLocation(thumbRect.x, thumbTop);
1772 
1773                 thumbMiddle = thumbTop + halfThumbHeight;
1774                 slider.setValue( valueForYPosition( thumbMiddle ) );
1775                 break;
1776             case JSlider.HORIZONTAL:
1777                 int halfThumbWidth = thumbRect.width / 2;
1778                 int thumbLeft = e.getX() - offset;
1779                 int trackLeft = trackRect.x;
1780                 int trackRight = trackRect.x + (trackRect.width - 1);
1781                 int hMax = xPositionForValue(slider.getMaximum() -
1782                                             slider.getExtent());
1783 
1784                 if (drawInverted()) {
1785                     trackLeft = hMax;
1786                 }
1787                 else {
1788                     trackRight = hMax;
1789                 }
1790                 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
1791                 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
1792 
1793                 setThumbLocation(thumbLeft, thumbRect.y);
1794 
1795                 thumbMiddle = thumbLeft + halfThumbWidth;
1796                 slider.setValue(valueForXPosition(thumbMiddle));
1797                 break;
1798             }
1799         }
1800 
1801         public void mouseMoved(MouseEvent e) { }
1802     }
1803 
1804     /**
1805      * Scroll-event listener.
1806      *
1807      * This class should be treated as a &quot;protected&quot; inner class.
1808      * Instantiate it only within subclasses of <code>Foo</code>.
1809      */
1810     public class ScrollListener implements ActionListener {
1811         // changed this class to public to avoid bogus IllegalAccessException
1812         // bug in InternetExplorer browser.  It was protected.  Work around
1813         // for 4109432
1814         int direction = POSITIVE_SCROLL;
1815         boolean useBlockIncrement;
1816 
1817         public ScrollListener() {
1818             direction = POSITIVE_SCROLL;
1819             useBlockIncrement = true;
1820         }
1821 
1822         public ScrollListener(int dir, boolean block)   {
1823             direction = dir;
1824             useBlockIncrement = block;
1825         }
1826 
1827         public void setDirection(int direction) {
1828             this.direction = direction;
1829         }
1830 
1831         public void setScrollByBlock(boolean block) {
1832             this.useBlockIncrement = block;
1833         }
1834 
1835         public void actionPerformed(ActionEvent e) {
1836             if (useBlockIncrement) {
1837                 scrollByBlock(direction);
1838             }
1839             else {
1840                 scrollByUnit(direction);
1841             }
1842             if (!trackListener.shouldScroll(direction)) {
1843                 ((Timer)e.getSource()).stop();
1844             }
1845         }
1846     }
1847 
1848     /**
1849      * Listener for resizing events.
1850      * <p>
1851      * This class should be treated as a &quot;protected&quot; inner class.
1852      * Instantiate it only within subclasses of <code>Foo</code>.
1853      */
1854     public class ComponentHandler extends ComponentAdapter {
1855         // NOTE: This class exists only for backward compatibility. All
1856         // its functionality has been moved into Handler. If you need to add
1857         // new functionality add it to the Handler, but make sure this
1858         // class calls into the Handler.
1859         public void componentResized(ComponentEvent e)  {
1860             getHandler().componentResized(e);
1861         }
1862     }
1863 
1864     /**
1865      * Focus-change listener.
1866      * <p>
1867      * This class should be treated as a &quot;protected&quot; inner class.
1868      * Instantiate it only within subclasses of <code>Foo</code>.
1869      */
1870     public class FocusHandler implements FocusListener {
1871         // NOTE: This class exists only for backward compatibility. All
1872         // its functionality has been moved into Handler. If you need to add
1873         // new functionality add it to the Handler, but make sure this
1874         // class calls into the Handler.
1875         public void focusGained(FocusEvent e) {
1876             getHandler().focusGained(e);
1877         }
1878 
1879         public void focusLost(FocusEvent e) {
1880             getHandler().focusLost(e);
1881         }
1882     }
1883 
1884     /**
1885      * As of Java 2 platform v1.3 this undocumented class is no longer used.
1886      * The recommended approach to creating bindings is to use a
1887      * combination of an <code>ActionMap</code>, to contain the action,
1888      * and an <code>InputMap</code> to contain the mapping from KeyStroke
1889      * to action description. The InputMap is is usually described in the
1890      * LookAndFeel tables.
1891      * <p>
1892      * Please refer to the key bindings specification for further details.
1893      * <p>
1894      * This class should be treated as a &quot;protected&quot; inner class.
1895      * Instantiate it only within subclasses of <code>Foo</code>.
1896      */
1897     @SuppressWarnings("serial") // Superclass is not serializable across versions
1898     public class ActionScroller extends AbstractAction {
1899         // NOTE: This class exists only for backward compatibility. All
1900         // its functionality has been moved into Actions. If you need to add
1901         // new functionality add it to the Actions, but make sure this
1902         // class calls into the Actions.
1903         int dir;
1904         boolean block;
1905         JSlider slider;
1906 
1907         public ActionScroller( JSlider slider, int dir, boolean block) {
1908             this.dir = dir;
1909             this.block = block;
1910             this.slider = slider;
1911         }
1912 
1913         public void actionPerformed(ActionEvent e) {
1914             SHARED_ACTION.scroll(slider, BasicSliderUI.this, dir, block);
1915         }
1916 
1917         public boolean isEnabled() {
1918             boolean b = true;
1919             if (slider != null) {
1920                 b = slider.isEnabled();
1921             }
1922             return b;
1923         }
1924 
1925     }
1926 
1927 
1928     /**
1929      * A static version of the above.
1930      */
1931     @SuppressWarnings("serial") // Superclass is not serializable across versions
1932     static class SharedActionScroller extends AbstractAction {
1933         // NOTE: This class exists only for backward compatibility. All
1934         // its functionality has been moved into Actions. If you need to add
1935         // new functionality add it to the Actions, but make sure this
1936         // class calls into the Actions.
1937         int dir;
1938         boolean block;
1939 
1940         public SharedActionScroller(int dir, boolean block) {
1941             this.dir = dir;
1942             this.block = block;
1943         }
1944 
1945         public void actionPerformed(ActionEvent evt) {
1946             JSlider slider = (JSlider)evt.getSource();
1947             BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1948                     slider.getUI(), BasicSliderUI.class);
1949             if (ui == null) {
1950                 return;
1951             }
1952             SHARED_ACTION.scroll(slider, ui, dir, block);
1953         }
1954     }
1955 
1956     private static class Actions extends UIAction {
1957         public static final String POSITIVE_UNIT_INCREMENT =
1958             "positiveUnitIncrement";
1959         public static final String POSITIVE_BLOCK_INCREMENT =
1960             "positiveBlockIncrement";
1961         public static final String NEGATIVE_UNIT_INCREMENT =
1962             "negativeUnitIncrement";
1963         public static final String NEGATIVE_BLOCK_INCREMENT =
1964             "negativeBlockIncrement";
1965         public static final String MIN_SCROLL_INCREMENT = "minScroll";
1966         public static final String MAX_SCROLL_INCREMENT = "maxScroll";
1967 
1968 
1969         Actions() {
1970             super(null);
1971         }
1972 
1973         public Actions(String name) {
1974             super(name);
1975         }
1976 
1977         public void actionPerformed(ActionEvent evt) {
1978             JSlider slider = (JSlider)evt.getSource();
1979             BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1980                      slider.getUI(), BasicSliderUI.class);
1981             String name = getName();
1982 
1983             if (ui == null) {
1984                 return;
1985             }
1986             if (POSITIVE_UNIT_INCREMENT == name) {
1987                 scroll(slider, ui, POSITIVE_SCROLL, false);
1988             } else if (NEGATIVE_UNIT_INCREMENT == name) {
1989                 scroll(slider, ui, NEGATIVE_SCROLL, false);
1990             } else if (POSITIVE_BLOCK_INCREMENT == name) {
1991                 scroll(slider, ui, POSITIVE_SCROLL, true);
1992             } else if (NEGATIVE_BLOCK_INCREMENT == name) {
1993                 scroll(slider, ui, NEGATIVE_SCROLL, true);
1994             } else if (MIN_SCROLL_INCREMENT == name) {
1995                 scroll(slider, ui, MIN_SCROLL, false);
1996             } else if (MAX_SCROLL_INCREMENT == name) {
1997                 scroll(slider, ui, MAX_SCROLL, false);
1998             }
1999         }
2000 
2001         private void scroll(JSlider slider, BasicSliderUI ui, int direction,
2002                 boolean isBlock) {
2003             boolean invert = slider.getInverted();
2004 
2005             if (direction == NEGATIVE_SCROLL || direction == POSITIVE_SCROLL) {
2006                 if (invert) {
2007                     direction = (direction == POSITIVE_SCROLL) ?
2008                         NEGATIVE_SCROLL : POSITIVE_SCROLL;
2009                 }
2010 
2011                 if (isBlock) {
2012                     ui.scrollByBlock(direction);
2013                 } else {
2014                     ui.scrollByUnit(direction);
2015                 }
2016             } else {  // MIN or MAX
2017                 if (invert) {
2018                     direction = (direction == MIN_SCROLL) ?
2019                         MAX_SCROLL : MIN_SCROLL;
2020                 }
2021 
2022                 slider.setValue((direction == MIN_SCROLL) ?
2023                     slider.getMinimum() : slider.getMaximum());
2024             }
2025         }
2026     }
2027 }