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<Integer, ? extends JComponent> dictionary = slider.getLabelTable();
 401             if (dictionary != null) {
 402                 sameLabelBaselines = true;
 403                 Enumeration<? extends 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
 660      * the tick area for vertical sliders. BasicSliderUI uses the returned value
 661      * to determine the tick area rectangle. If you want to give your ticks some
 662      * room, make this larger than you need and paint your ticks away from the
 663      * sides in paintTicks().
 664      *
 665      * @return an integer representing the height of the tick area for
 666      * horizontal sliders, and the width of the tick area for the vertical
 667      * sliders
 668      */
 669     protected int getTickLength() {
 670         return 8;
 671     }
 672 
 673     protected void calculateTickRect() {
 674         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 675             tickRect.x = trackRect.x;
 676             tickRect.y = trackRect.y + trackRect.height;
 677             tickRect.width = trackRect.width;
 678             tickRect.height = (slider.getPaintTicks()) ? getTickLength() : 0;
 679         }
 680         else {
 681             tickRect.width = (slider.getPaintTicks()) ? getTickLength() : 0;
 682             if(BasicGraphicsUtils.isLeftToRight(slider)) {
 683                 tickRect.x = trackRect.x + trackRect.width;
 684             }
 685             else {
 686                 tickRect.x = trackRect.x - tickRect.width;
 687             }
 688             tickRect.y = trackRect.y;
 689             tickRect.height = trackRect.height;
 690         }
 691     }
 692 
 693     protected void calculateLabelRect() {
 694         if ( slider.getPaintLabels() ) {
 695             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 696                 labelRect.x = tickRect.x - trackBuffer;
 697                 labelRect.y = tickRect.y + tickRect.height;
 698                 labelRect.width = tickRect.width + (trackBuffer * 2);
 699                 labelRect.height = getHeightOfTallestLabel();
 700             }
 701             else {
 702                 if(BasicGraphicsUtils.isLeftToRight(slider)) {
 703                     labelRect.x = tickRect.x + tickRect.width;
 704                     labelRect.width = getWidthOfWidestLabel();
 705                 }
 706                 else {
 707                     labelRect.width = getWidthOfWidestLabel();
 708                     labelRect.x = tickRect.x - labelRect.width;
 709                 }
 710                 labelRect.y = tickRect.y - trackBuffer;
 711                 labelRect.height = tickRect.height + (trackBuffer * 2);
 712             }
 713         }
 714         else {
 715             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 716                 labelRect.x = tickRect.x;
 717                 labelRect.y = tickRect.y + tickRect.height;
 718                 labelRect.width = tickRect.width;
 719                 labelRect.height = 0;
 720             }
 721             else {
 722                 if(BasicGraphicsUtils.isLeftToRight(slider)) {
 723                     labelRect.x = tickRect.x + tickRect.width;
 724                 }
 725                 else {
 726                     labelRect.x = tickRect.x;
 727                 }
 728                 labelRect.y = tickRect.y;
 729                 labelRect.width = 0;
 730                 labelRect.height = tickRect.height;
 731             }
 732         }
 733     }
 734 
 735     protected Dimension getThumbSize() {
 736         Dimension size = new Dimension();
 737 
 738         if ( slider.getOrientation() == JSlider.VERTICAL ) {
 739             size.width = 20;
 740             size.height = 11;
 741         }
 742         else {
 743             size.width = 11;
 744             size.height = 20;
 745         }
 746 
 747         return size;
 748     }
 749 
 750     public class PropertyChangeHandler implements PropertyChangeListener {
 751         // NOTE: This class exists only for backward compatibility. All
 752         // its functionality has been moved into Handler. If you need to add
 753         // new functionality add it to the Handler, but make sure this
 754         // class calls into the Handler.
 755         public void propertyChange( PropertyChangeEvent e ) {
 756             getHandler().propertyChange(e);
 757         }
 758     }
 759 
 760     protected int getWidthOfWidestLabel() {
 761         Dictionary<?, ? extends JComponent> dictionary = slider.getLabelTable();
 762         int widest = 0;
 763         if ( dictionary != null ) {
 764             Enumeration<?> keys = dictionary.keys();
 765             while ( keys.hasMoreElements() ) {
 766                 JComponent label = dictionary.get(keys.nextElement());
 767                 widest = Math.max( label.getPreferredSize().width, widest );
 768             }
 769         }
 770         return widest;
 771     }
 772 
 773     protected int getHeightOfTallestLabel() {
 774         Dictionary<?, ? extends JComponent> dictionary = slider.getLabelTable();
 775         int tallest = 0;
 776         if ( dictionary != null ) {
 777             Enumeration<?> keys = dictionary.keys();
 778             while ( keys.hasMoreElements() ) {
 779                 JComponent label = dictionary.get(keys.nextElement());
 780                 tallest = Math.max( label.getPreferredSize().height, tallest );
 781             }
 782         }
 783         return tallest;
 784     }
 785 
 786     protected int getWidthOfHighValueLabel() {
 787         Component label = getHighestValueLabel();
 788         int width = 0;
 789 
 790         if ( label != null ) {
 791             width = label.getPreferredSize().width;
 792         }
 793 
 794         return width;
 795     }
 796 
 797     protected int getWidthOfLowValueLabel() {
 798         Component label = getLowestValueLabel();
 799         int width = 0;
 800 
 801         if ( label != null ) {
 802             width = label.getPreferredSize().width;
 803         }
 804 
 805         return width;
 806     }
 807 
 808     protected int getHeightOfHighValueLabel() {
 809         Component label = getHighestValueLabel();
 810         int height = 0;
 811 
 812         if ( label != null ) {
 813             height = label.getPreferredSize().height;
 814         }
 815 
 816         return height;
 817     }
 818 
 819     protected int getHeightOfLowValueLabel() {
 820         Component label = getLowestValueLabel();
 821         int height = 0;
 822 
 823         if ( label != null ) {
 824             height = label.getPreferredSize().height;
 825         }
 826 
 827         return height;
 828     }
 829 
 830     protected boolean drawInverted() {
 831         if (slider.getOrientation()==JSlider.HORIZONTAL) {
 832             if(BasicGraphicsUtils.isLeftToRight(slider)) {
 833                 return slider.getInverted();
 834             } else {
 835                 return !slider.getInverted();
 836             }
 837         } else {
 838             return slider.getInverted();
 839         }
 840     }
 841 
 842     /**
 843      * Returns the biggest value that has an entry in the label table.
 844      *
 845      * @return biggest value that has an entry in the label table, or
 846      *         null.
 847      * @since 1.6
 848      */
 849     protected Integer getHighestValue() {
 850         Dictionary<Integer, ?> dictionary = slider.getLabelTable();
 851 
 852         if (dictionary == null) {
 853             return null;
 854         }
 855 
 856         Enumeration<Integer> keys = dictionary.keys();
 857 
 858         Integer max = null;
 859 
 860         while (keys.hasMoreElements()) {
 861             Integer i = keys.nextElement();
 862 
 863             if (max == null || i > max) {
 864                 max = i;
 865             }
 866         }
 867 
 868         return max;
 869     }
 870 
 871     /**
 872      * Returns the smallest value that has an entry in the label table.
 873      *
 874      * @return smallest value that has an entry in the label table, or
 875      * null.
 876      * @since 1.6
 877      */
 878     protected Integer getLowestValue() {
 879         Dictionary<Integer, ? extends JComponent> dictionary = slider.getLabelTable();
 880 
 881         if (dictionary == null) {
 882             return null;
 883         }
 884 
 885         Enumeration<Integer> keys = dictionary.keys();
 886 
 887         Integer min = null;
 888 
 889         while (keys.hasMoreElements()) {
 890             Integer i = keys.nextElement();
 891 
 892             if (min == null || i < min) {
 893                 min = i;
 894             }
 895         }
 896 
 897         return min;
 898     }
 899 
 900 
 901     /**
 902      * Returns the label that corresponds to the highest slider value in the
 903      * label table.
 904      *
 905      * @return the label that corresponds to the highest slider value in the
 906      * label table
 907      * @see JSlider#setLabelTable
 908      */
 909     protected Component getLowestValueLabel() {
 910         Integer min = getLowestValue();
 911         if (min != null) {
 912             return (Component)slider.getLabelTable().get(min);
 913         }
 914         return null;
 915     }
 916 
 917     /**
 918      * Returns the label that corresponds to the lowest slider value in the
 919      * label table.
 920      *
 921      * @return the label that corresponds to the lowest slider value in the
 922      * label table
 923      * @see JSlider#setLabelTable
 924      */
 925     protected Component getHighestValueLabel() {
 926         Integer max = getHighestValue();
 927         if (max != null) {
 928             return (Component)slider.getLabelTable().get(max);
 929         }
 930         return null;
 931     }
 932 
 933     public void paint( Graphics g, JComponent c )   {
 934         recalculateIfInsetsChanged();
 935         recalculateIfOrientationChanged();
 936         Rectangle clip = g.getClipBounds();
 937 
 938         if ( !clip.intersects(trackRect) && slider.getPaintTrack())
 939             calculateGeometry();
 940 
 941         if ( slider.getPaintTrack() && clip.intersects( trackRect ) ) {
 942             paintTrack( g );
 943         }
 944         if ( slider.getPaintTicks() && clip.intersects( tickRect ) ) {
 945             paintTicks( g );
 946         }
 947         if ( slider.getPaintLabels() && clip.intersects( labelRect ) ) {
 948             paintLabels( g );
 949         }
 950         if ( slider.hasFocus() && clip.intersects( focusRect ) ) {
 951             paintFocus( g );
 952         }
 953         if ( clip.intersects( thumbRect ) ) {
 954             paintThumb( g );
 955         }
 956     }
 957 
 958     protected void recalculateIfInsetsChanged() {
 959         Insets newInsets = slider.getInsets();
 960         if ( !newInsets.equals( insetCache ) ) {
 961             insetCache = newInsets;
 962             calculateGeometry();
 963         }
 964     }
 965 
 966     protected void recalculateIfOrientationChanged() {
 967         boolean ltr = BasicGraphicsUtils.isLeftToRight(slider);
 968         if ( ltr!=leftToRightCache ) {
 969             leftToRightCache = ltr;
 970             calculateGeometry();
 971         }
 972     }
 973 
 974     public void paintFocus(Graphics g)  {
 975         g.setColor( getFocusColor() );
 976 
 977         BasicGraphicsUtils.drawDashedRect( g, focusRect.x, focusRect.y,
 978                                            focusRect.width, focusRect.height );
 979     }
 980 
 981     public void paintTrack(Graphics g)  {
 982 
 983         Rectangle trackBounds = trackRect;
 984 
 985         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
 986             int cy = (trackBounds.height / 2) - 2;
 987             int cw = trackBounds.width;
 988 
 989             g.translate(trackBounds.x, trackBounds.y + cy);
 990 
 991             g.setColor(getShadowColor());
 992             g.drawLine(0, 0, cw - 1, 0);
 993             g.drawLine(0, 1, 0, 2);
 994             g.setColor(getHighlightColor());
 995             g.drawLine(0, 3, cw, 3);
 996             g.drawLine(cw, 0, cw, 3);
 997             g.setColor(Color.black);
 998             g.drawLine(1, 1, cw-2, 1);
 999 
1000             g.translate(-trackBounds.x, -(trackBounds.y + cy));
1001         }
1002         else {
1003             int cx = (trackBounds.width / 2) - 2;
1004             int ch = trackBounds.height;
1005 
1006             g.translate(trackBounds.x + cx, trackBounds.y);
1007 
1008             g.setColor(getShadowColor());
1009             g.drawLine(0, 0, 0, ch - 1);
1010             g.drawLine(1, 0, 2, 0);
1011             g.setColor(getHighlightColor());
1012             g.drawLine(3, 0, 3, ch);
1013             g.drawLine(0, ch, 3, ch);
1014             g.setColor(Color.black);
1015             g.drawLine(1, 1, 1, ch-2);
1016 
1017             g.translate(-(trackBounds.x + cx), -trackBounds.y);
1018         }
1019     }
1020 
1021     public void paintTicks(Graphics g)  {
1022         Rectangle tickBounds = tickRect;
1023 
1024         g.setColor(DefaultLookup.getColor(slider, this, "Slider.tickColor", Color.black));
1025 
1026         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1027             g.translate(0, tickBounds.y);
1028 
1029             if (slider.getMinorTickSpacing() > 0) {
1030                 int value = slider.getMinimum();
1031 
1032                 while ( value <= slider.getMaximum() ) {
1033                     int xPos = xPositionForValue(value);
1034                     paintMinorTickForHorizSlider( g, tickBounds, xPos );
1035 
1036                     // Overflow checking
1037                     if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) {
1038                         break;
1039                     }
1040 
1041                     value += slider.getMinorTickSpacing();
1042                 }
1043             }
1044 
1045             if (slider.getMajorTickSpacing() > 0) {
1046                 int value = slider.getMinimum();
1047 
1048                 while ( value <= slider.getMaximum() ) {
1049                     int xPos = xPositionForValue(value);
1050                     paintMajorTickForHorizSlider( g, tickBounds, xPos );
1051 
1052                     // Overflow checking
1053                     if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) {
1054                         break;
1055                     }
1056 
1057                     value += slider.getMajorTickSpacing();
1058                 }
1059             }
1060 
1061             g.translate( 0, -tickBounds.y);
1062         } else {
1063             g.translate(tickBounds.x, 0);
1064 
1065             if (slider.getMinorTickSpacing() > 0) {
1066                 int offset = 0;
1067                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1068                     offset = tickBounds.width - tickBounds.width / 2;
1069                     g.translate(offset, 0);
1070                 }
1071 
1072                 int value = slider.getMinimum();
1073 
1074                 while (value <= slider.getMaximum()) {
1075                     int yPos = yPositionForValue(value);
1076                     paintMinorTickForVertSlider( g, tickBounds, yPos );
1077 
1078                     // Overflow checking
1079                     if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) {
1080                         break;
1081                     }
1082 
1083                     value += slider.getMinorTickSpacing();
1084                 }
1085 
1086                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1087                     g.translate(-offset, 0);
1088                 }
1089             }
1090 
1091             if (slider.getMajorTickSpacing() > 0) {
1092                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1093                     g.translate(2, 0);
1094                 }
1095 
1096                 int value = slider.getMinimum();
1097 
1098                 while (value <= slider.getMaximum()) {
1099                     int yPos = yPositionForValue(value);
1100                     paintMajorTickForVertSlider( g, tickBounds, yPos );
1101 
1102                     // Overflow checking
1103                     if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) {
1104                         break;
1105                     }
1106 
1107                     value += slider.getMajorTickSpacing();
1108                 }
1109 
1110                 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1111                     g.translate(-2, 0);
1112                 }
1113             }
1114             g.translate(-tickBounds.x, 0);
1115         }
1116     }
1117 
1118     protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1119         g.drawLine( x, 0, x, tickBounds.height / 2 - 1 );
1120     }
1121 
1122     protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1123         g.drawLine( x, 0, x, tickBounds.height - 2 );
1124     }
1125 
1126     protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1127         g.drawLine( 0, y, tickBounds.width / 2 - 1, y );
1128     }
1129 
1130     protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1131         g.drawLine( 0, y,  tickBounds.width - 2, y );
1132     }
1133 
1134     public void paintLabels( Graphics g ) {
1135         Rectangle labelBounds = labelRect;
1136 
1137         Dictionary<Integer, ? extends JComponent> dictionary = slider.getLabelTable();
1138         if ( dictionary != null ) {
1139             Enumeration<Integer> keys = dictionary.keys();
1140             int minValue = slider.getMinimum();
1141             int maxValue = slider.getMaximum();
1142             boolean enabled = slider.isEnabled();
1143             while ( keys.hasMoreElements() ) {
1144                 Integer key = keys.nextElement();
1145                 int value = key.intValue();
1146                 if (value >= minValue && value <= maxValue) {
1147                     JComponent label = dictionary.get(key);
1148                     label.setEnabled(enabled);
1149 
1150                     if (label instanceof JLabel) {
1151                         Icon icon = label.isEnabled() ? ((JLabel) label).getIcon() : ((JLabel) label).getDisabledIcon();
1152 
1153                         if (icon instanceof ImageIcon) {
1154                             // Register Slider as an image observer. It allows to catch notifications about
1155                             // image changes (e.g. gif animation)
1156                             Toolkit.getDefaultToolkit().checkImage(((ImageIcon) icon).getImage(), -1, -1, slider);
1157                         }
1158                     }
1159 
1160                     if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1161                         g.translate( 0, labelBounds.y );
1162                         paintHorizontalLabel( g, value, label );
1163                         g.translate( 0, -labelBounds.y );
1164                     }
1165                     else {
1166                         int offset = 0;
1167                         if (!BasicGraphicsUtils.isLeftToRight(slider)) {
1168                             offset = labelBounds.width -
1169                                 label.getPreferredSize().width;
1170                         }
1171                         g.translate( labelBounds.x + offset, 0 );
1172                         paintVerticalLabel( g, value, label );
1173                         g.translate( -labelBounds.x - offset, 0 );
1174                     }
1175                 }
1176             }
1177         }
1178 
1179     }
1180 
1181     /**
1182      * Called for every label in the label table. Used to draw the labels for
1183      * horizontal sliders. The graphics have been translated to labelRect.y
1184      * already.
1185      *
1186      * @param g the graphics context in which to paint
1187      * @param value the value of the slider
1188      * @param label the component label in the label table that needs to be
1189      * painted
1190      * @see JSlider#setLabelTable
1191      */
1192     protected void paintHorizontalLabel( Graphics g, int value, Component label ) {
1193         int labelCenter = xPositionForValue( value );
1194         int labelLeft = labelCenter - (label.getPreferredSize().width / 2);
1195         g.translate( labelLeft, 0 );
1196         label.paint( g );
1197         g.translate( -labelLeft, 0 );
1198     }
1199 
1200     /**
1201      * Called for every label in the label table. Used to draw the labels for
1202      * vertical sliders. The graphics have been translated to labelRect.x
1203      * already.
1204      *
1205      * @param g the graphics context in which to paint
1206      * @param value the value of the slider
1207      * @param label the component label in the label table that needs to be
1208      * painted
1209      * @see JSlider#setLabelTable
1210      */
1211     protected void paintVerticalLabel( Graphics g, int value, Component label ) {
1212         int labelCenter = yPositionForValue( value );
1213         int labelTop = labelCenter - (label.getPreferredSize().height / 2);
1214         g.translate( 0, labelTop );
1215         label.paint( g );
1216         g.translate( 0, -labelTop );
1217     }
1218 
1219     public void paintThumb(Graphics g)  {
1220         Rectangle knobBounds = thumbRect;
1221         int w = knobBounds.width;
1222         int h = knobBounds.height;
1223 
1224         g.translate(knobBounds.x, knobBounds.y);
1225 
1226         if ( slider.isEnabled() ) {
1227             g.setColor(slider.getBackground());
1228         }
1229         else {
1230             g.setColor(slider.getBackground().darker());
1231         }
1232 
1233         Boolean paintThumbArrowShape =
1234             (Boolean)slider.getClientProperty("Slider.paintThumbArrowShape");
1235 
1236         if ((!slider.getPaintTicks() && paintThumbArrowShape == null) ||
1237             paintThumbArrowShape == Boolean.FALSE) {
1238 
1239             // "plain" version
1240             g.fillRect(0, 0, w, h);
1241 
1242             g.setColor(Color.black);
1243             g.drawLine(0, h-1, w-1, h-1);
1244             g.drawLine(w-1, 0, w-1, h-1);
1245 
1246             g.setColor(highlightColor);
1247             g.drawLine(0, 0, 0, h-2);
1248             g.drawLine(1, 0, w-2, 0);
1249 
1250             g.setColor(shadowColor);
1251             g.drawLine(1, h-2, w-2, h-2);
1252             g.drawLine(w-2, 1, w-2, h-3);
1253         }
1254         else if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1255             int cw = w / 2;
1256             g.fillRect(1, 1, w-3, h-1-cw);
1257             Polygon p = new Polygon();
1258             p.addPoint(1, h-cw);
1259             p.addPoint(cw-1, h-1);
1260             p.addPoint(w-2, h-1-cw);
1261             g.fillPolygon(p);
1262 
1263             g.setColor(highlightColor);
1264             g.drawLine(0, 0, w-2, 0);
1265             g.drawLine(0, 1, 0, h-1-cw);
1266             g.drawLine(0, h-cw, cw-1, h-1);
1267 
1268             g.setColor(Color.black);
1269             g.drawLine(w-1, 0, w-1, h-2-cw);
1270             g.drawLine(w-1, h-1-cw, w-1-cw, h-1);
1271 
1272             g.setColor(shadowColor);
1273             g.drawLine(w-2, 1, w-2, h-2-cw);
1274             g.drawLine(w-2, h-1-cw, w-1-cw, h-2);
1275         }
1276         else {  // vertical
1277             int cw = h / 2;
1278             if(BasicGraphicsUtils.isLeftToRight(slider)) {
1279                   g.fillRect(1, 1, w-1-cw, h-3);
1280                   Polygon p = new Polygon();
1281                   p.addPoint(w-cw-1, 0);
1282                   p.addPoint(w-1, cw);
1283                   p.addPoint(w-1-cw, h-2);
1284                   g.fillPolygon(p);
1285 
1286                   g.setColor(highlightColor);
1287                   g.drawLine(0, 0, 0, h - 2);                  // left
1288                   g.drawLine(1, 0, w-1-cw, 0);                 // top
1289                   g.drawLine(w-cw-1, 0, w-1, cw);              // top slant
1290 
1291                   g.setColor(Color.black);
1292                   g.drawLine(0, h-1, w-2-cw, h-1);             // bottom
1293                   g.drawLine(w-1-cw, h-1, w-1, h-1-cw);        // bottom slant
1294 
1295                   g.setColor(shadowColor);
1296                   g.drawLine(1, h-2, w-2-cw,  h-2 );         // bottom
1297                   g.drawLine(w-1-cw, h-2, w-2, h-cw-1 );     // bottom slant
1298             }
1299             else {
1300                   g.fillRect(5, 1, w-1-cw, h-3);
1301                   Polygon p = new Polygon();
1302                   p.addPoint(cw, 0);
1303                   p.addPoint(0, cw);
1304                   p.addPoint(cw, h-2);
1305                   g.fillPolygon(p);
1306 
1307                   g.setColor(highlightColor);
1308                   g.drawLine(cw-1, 0, w-2, 0);             // top
1309                   g.drawLine(0, cw, cw, 0);                // top slant
1310 
1311                   g.setColor(Color.black);
1312                   g.drawLine(0, h-1-cw, cw, h-1 );         // bottom slant
1313                   g.drawLine(cw, h-1, w-1, h-1);           // bottom
1314 
1315                   g.setColor(shadowColor);
1316                   g.drawLine(cw, h-2, w-2,  h-2 );         // bottom
1317                   g.drawLine(w-1, 1, w-1,  h-2 );          // right
1318             }
1319         }
1320 
1321         g.translate(-knobBounds.x, -knobBounds.y);
1322     }
1323 
1324     // Used exclusively by setThumbLocation()
1325     private static Rectangle unionRect = new Rectangle();
1326 
1327     public void setThumbLocation(int x, int y)  {
1328         unionRect.setBounds( thumbRect );
1329 
1330         thumbRect.setLocation( x, y );
1331 
1332         SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, unionRect );
1333         slider.repaint( unionRect.x, unionRect.y, unionRect.width, unionRect.height );
1334     }
1335 
1336     public void scrollByBlock(int direction)    {
1337         synchronized(slider)    {
1338             int blockIncrement =
1339                 (slider.getMaximum() - slider.getMinimum()) / 10;
1340             if (blockIncrement == 0) {
1341                 blockIncrement = 1;
1342             }
1343 
1344             if (slider.getSnapToTicks()) {
1345                 int tickSpacing = getTickSpacing();
1346 
1347                 if (blockIncrement < tickSpacing) {
1348                     blockIncrement = tickSpacing;
1349                 }
1350             }
1351 
1352             int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1353             slider.setValue(slider.getValue() + delta);
1354         }
1355     }
1356 
1357     public void scrollByUnit(int direction) {
1358         synchronized(slider)    {
1359             int delta = ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1360 
1361             if (slider.getSnapToTicks()) {
1362                 delta *= getTickSpacing();
1363             }
1364 
1365             slider.setValue(slider.getValue() + delta);
1366         }
1367     }
1368 
1369     /**
1370      * This function is called when a mousePressed was detected in the track,
1371      * not in the thumb. The default behavior is to scroll by block. You can
1372      * override this method to stop it from scrolling or to add additional
1373      * behavior.
1374      *
1375      * @param dir the direction and number of blocks to scroll
1376      */
1377     protected void scrollDueToClickInTrack( int dir ) {
1378         scrollByBlock( dir );
1379     }
1380 
1381     protected int xPositionForValue( int value )    {
1382         int min = slider.getMinimum();
1383         int max = slider.getMaximum();
1384         int trackLength = trackRect.width;
1385         double valueRange = (double)max - (double)min;
1386         double pixelsPerValue = (double)trackLength / valueRange;
1387         int trackLeft = trackRect.x;
1388         int trackRight = trackRect.x + (trackRect.width - 1);
1389         int xPosition;
1390 
1391         if ( !drawInverted() ) {
1392             xPosition = trackLeft;
1393             xPosition += Math.round( pixelsPerValue * ((double)value - min) );
1394         }
1395         else {
1396             xPosition = trackRight;
1397             xPosition -= Math.round( pixelsPerValue * ((double)value - min) );
1398         }
1399 
1400         xPosition = Math.max( trackLeft, xPosition );
1401         xPosition = Math.min( trackRight, xPosition );
1402 
1403         return xPosition;
1404     }
1405 
1406     protected int yPositionForValue( int value )  {
1407         return yPositionForValue(value, trackRect.y, trackRect.height);
1408     }
1409 
1410     /**
1411      * Returns the y location for the specified value.  No checking is
1412      * done on the arguments.  In particular if <code>trackHeight</code> is
1413      * negative undefined results may occur.
1414      *
1415      * @param value the slider value to get the location for
1416      * @param trackY y-origin of the track
1417      * @param trackHeight the height of the track
1418      * @return the y location for the specified value of the slider
1419      * @since 1.6
1420      */
1421     protected int yPositionForValue(int value, int trackY, int trackHeight) {
1422         int min = slider.getMinimum();
1423         int max = slider.getMaximum();
1424         double valueRange = (double)max - (double)min;
1425         double pixelsPerValue = (double)trackHeight / valueRange;
1426         int trackBottom = trackY + (trackHeight - 1);
1427         int yPosition;
1428 
1429         if ( !drawInverted() ) {
1430             yPosition = trackY;
1431             yPosition += Math.round( pixelsPerValue * ((double)max - value ) );
1432         }
1433         else {
1434             yPosition = trackY;
1435             yPosition += Math.round( pixelsPerValue * ((double)value - min) );
1436         }
1437 
1438         yPosition = Math.max( trackY, yPosition );
1439         yPosition = Math.min( trackBottom, yPosition );
1440 
1441         return yPosition;
1442     }
1443 
1444     /**
1445      * Returns the value at the y position. If {@code yPos} is beyond the
1446      * track at the the bottom or the top, this method sets the value to either
1447      * the minimum or maximum value of the slider, depending on if the slider
1448      * is inverted or not.
1449      *
1450      * @param yPos the location of the slider along the y axis
1451      * @return the value at the y position
1452      */
1453     public int valueForYPosition( int yPos ) {
1454         int value;
1455         final int minValue = slider.getMinimum();
1456         final int maxValue = slider.getMaximum();
1457         final int trackLength = trackRect.height;
1458         final int trackTop = trackRect.y;
1459         final int trackBottom = trackRect.y + (trackRect.height - 1);
1460 
1461         if ( yPos <= trackTop ) {
1462             value = drawInverted() ? minValue : maxValue;
1463         }
1464         else if ( yPos >= trackBottom ) {
1465             value = drawInverted() ? maxValue : minValue;
1466         }
1467         else {
1468             int distanceFromTrackTop = yPos - trackTop;
1469             double valueRange = (double)maxValue - (double)minValue;
1470             double valuePerPixel = valueRange / (double)trackLength;
1471             int valueFromTrackTop = (int)Math.round( distanceFromTrackTop * valuePerPixel );
1472 
1473             value = drawInverted() ? minValue + valueFromTrackTop : maxValue - valueFromTrackTop;
1474         }
1475 
1476         return value;
1477     }
1478 
1479     /**
1480      * Returns the value at the x position.  If {@code xPos} is beyond the
1481      * track at the left or the right, this method sets the value to either the
1482      * minimum or maximum value of the slider, depending on if the slider is
1483      * inverted or not.
1484      *
1485      * @param xPos the location of the slider along the x axis
1486      * @return the value of the x position
1487      */
1488     public int valueForXPosition( int xPos ) {
1489         int value;
1490         final int minValue = slider.getMinimum();
1491         final int maxValue = slider.getMaximum();
1492         final int trackLength = trackRect.width;
1493         final int trackLeft = trackRect.x;
1494         final int trackRight = trackRect.x + (trackRect.width - 1);
1495 
1496         if ( xPos <= trackLeft ) {
1497             value = drawInverted() ? maxValue : minValue;
1498         }
1499         else if ( xPos >= trackRight ) {
1500             value = drawInverted() ? minValue : maxValue;
1501         }
1502         else {
1503             int distanceFromTrackLeft = xPos - trackLeft;
1504             double valueRange = (double)maxValue - (double)minValue;
1505             double valuePerPixel = valueRange / (double)trackLength;
1506             int valueFromTrackLeft = (int)Math.round( distanceFromTrackLeft * valuePerPixel );
1507 
1508             value = drawInverted() ? maxValue - valueFromTrackLeft :
1509               minValue + valueFromTrackLeft;
1510         }
1511 
1512         return value;
1513     }
1514 
1515 
1516     private class Handler implements ChangeListener,
1517             ComponentListener, FocusListener, PropertyChangeListener {
1518         // Change Handler
1519         public void stateChanged(ChangeEvent e) {
1520             if (!isDragging) {
1521                 calculateThumbLocation();
1522                 slider.repaint();
1523             }
1524             lastValue = slider.getValue();
1525         }
1526 
1527         // Component Handler
1528         public void componentHidden(ComponentEvent e) { }
1529         public void componentMoved(ComponentEvent e) { }
1530         public void componentResized(ComponentEvent e) {
1531             calculateGeometry();
1532             slider.repaint();
1533         }
1534         public void componentShown(ComponentEvent e) { }
1535 
1536         // Focus Handler
1537         public void focusGained(FocusEvent e) { slider.repaint(); }
1538         public void focusLost(FocusEvent e) { slider.repaint(); }
1539 
1540         // Property Change Handler
1541         public void propertyChange(PropertyChangeEvent e) {
1542             String propertyName = e.getPropertyName();
1543             if (propertyName == "orientation" ||
1544                     propertyName == "inverted" ||
1545                     propertyName == "labelTable" ||
1546                     propertyName == "majorTickSpacing" ||
1547                     propertyName == "minorTickSpacing" ||
1548                     propertyName == "paintTicks" ||
1549                     propertyName == "paintTrack" ||
1550                     propertyName == "font" ||
1551                     propertyName == "paintLabels" ||
1552                     propertyName == "Slider.paintThumbArrowShape") {
1553                 checkedLabelBaselines = false;
1554                 calculateGeometry();
1555                 slider.repaint();
1556             } else if (propertyName == "componentOrientation") {
1557                 calculateGeometry();
1558                 slider.repaint();
1559                 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider);
1560                 SwingUtilities.replaceUIInputMap(slider,
1561                     JComponent.WHEN_FOCUSED, km);
1562             } else if (propertyName == "model") {
1563                 ((BoundedRangeModel)e.getOldValue()).removeChangeListener(
1564                     changeListener);
1565                 ((BoundedRangeModel)e.getNewValue()).addChangeListener(
1566                     changeListener);
1567                 calculateThumbLocation();
1568                 slider.repaint();
1569             }
1570         }
1571     }
1572 
1573     /////////////////////////////////////////////////////////////////////////
1574     /// Model Listener Class
1575     /////////////////////////////////////////////////////////////////////////
1576     /**
1577      * Data model listener.
1578      *
1579      * This class should be treated as a &quot;protected&quot; inner class.
1580      * Instantiate it only within subclasses of <code>Foo</code>.
1581      */
1582     public class ChangeHandler implements ChangeListener {
1583         // NOTE: This class exists only for backward compatibility. All
1584         // its functionality has been moved into Handler. If you need to add
1585         // new functionality add it to the Handler, but make sure this
1586         // class calls into the Handler.
1587         public void stateChanged(ChangeEvent e) {
1588             getHandler().stateChanged(e);
1589         }
1590     }
1591 
1592     /////////////////////////////////////////////////////////////////////////
1593     /// Track Listener Class
1594     /////////////////////////////////////////////////////////////////////////
1595     /**
1596      * Track mouse movements.
1597      *
1598      * This class should be treated as a &quot;protected&quot; inner class.
1599      * Instantiate it only within subclasses of <code>Foo</code>.
1600      */
1601     public class TrackListener extends MouseInputAdapter {
1602         protected transient int offset;
1603         protected transient int currentMouseX, currentMouseY;
1604 
1605         public void mouseReleased(MouseEvent e) {
1606             if (!slider.isEnabled()) {
1607                 return;
1608             }
1609 
1610             offset = 0;
1611             scrollTimer.stop();
1612 
1613             isDragging = false;
1614             slider.setValueIsAdjusting(false);
1615             slider.repaint();
1616         }
1617 
1618         /**
1619         * If the mouse is pressed above the "thumb" component
1620         * then reduce the scrollbars value by one page ("page up"),
1621         * otherwise increase it by one page.  If there is no
1622         * thumb then page up if the mouse is in the upper half
1623         * of the track.
1624         */
1625         public void mousePressed(MouseEvent e) {
1626             if (!slider.isEnabled()) {
1627                 return;
1628             }
1629 
1630             // We should recalculate geometry just before
1631             // calculation of the thumb movement direction.
1632             // It is important for the case, when JSlider
1633             // is a cell editor in JTable. See 6348946.
1634             calculateGeometry();
1635 
1636             currentMouseX = e.getX();
1637             currentMouseY = e.getY();
1638 
1639             if (slider.isRequestFocusEnabled()) {
1640                 slider.requestFocus();
1641             }
1642 
1643             // Clicked in the Thumb area?
1644             if (thumbRect.contains(currentMouseX, currentMouseY)) {
1645                 if (UIManager.getBoolean("Slider.onlyLeftMouseButtonDrag")
1646                         && !SwingUtilities.isLeftMouseButton(e)) {
1647                     return;
1648                 }
1649 
1650                 switch (slider.getOrientation()) {
1651                 case JSlider.VERTICAL:
1652                     offset = currentMouseY - thumbRect.y;
1653                     break;
1654                 case JSlider.HORIZONTAL:
1655                     offset = currentMouseX - thumbRect.x;
1656                     break;
1657                 }
1658                 isDragging = true;
1659                 return;
1660             }
1661 
1662             if (!SwingUtilities.isLeftMouseButton(e)) {
1663                 return;
1664             }
1665 
1666             isDragging = false;
1667             slider.setValueIsAdjusting(true);
1668 
1669             Dimension sbSize = slider.getSize();
1670             int direction = POSITIVE_SCROLL;
1671 
1672             switch (slider.getOrientation()) {
1673             case JSlider.VERTICAL:
1674                 if ( thumbRect.isEmpty() ) {
1675                     int scrollbarCenter = sbSize.height / 2;
1676                     if ( !drawInverted() ) {
1677                         direction = (currentMouseY < scrollbarCenter) ?
1678                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1679                     }
1680                     else {
1681                         direction = (currentMouseY < scrollbarCenter) ?
1682                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1683                     }
1684                 }
1685                 else {
1686                     int thumbY = thumbRect.y;
1687                     if ( !drawInverted() ) {
1688                         direction = (currentMouseY < thumbY) ?
1689                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1690                     }
1691                     else {
1692                         direction = (currentMouseY < thumbY) ?
1693                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1694                     }
1695                 }
1696                 break;
1697             case JSlider.HORIZONTAL:
1698                 if ( thumbRect.isEmpty() ) {
1699                     int scrollbarCenter = sbSize.width / 2;
1700                     if ( !drawInverted() ) {
1701                         direction = (currentMouseX < scrollbarCenter) ?
1702                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1703                     }
1704                     else {
1705                         direction = (currentMouseX < scrollbarCenter) ?
1706                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1707                     }
1708                 }
1709                 else {
1710                     int thumbX = thumbRect.x;
1711                     if ( !drawInverted() ) {
1712                         direction = (currentMouseX < thumbX) ?
1713                             NEGATIVE_SCROLL : POSITIVE_SCROLL;
1714                     }
1715                     else {
1716                         direction = (currentMouseX < thumbX) ?
1717                             POSITIVE_SCROLL : NEGATIVE_SCROLL;
1718                     }
1719                 }
1720                 break;
1721             }
1722 
1723             if (shouldScroll(direction)) {
1724                 scrollDueToClickInTrack(direction);
1725             }
1726             if (shouldScroll(direction)) {
1727                 scrollTimer.stop();
1728                 scrollListener.setDirection(direction);
1729                 scrollTimer.start();
1730             }
1731         }
1732 
1733         public boolean shouldScroll(int direction) {
1734             Rectangle r = thumbRect;
1735             if (slider.getOrientation() == JSlider.VERTICAL) {
1736                 if (drawInverted() ? direction < 0 : direction > 0) {
1737                     if (r.y  <= currentMouseY) {
1738                         return false;
1739                     }
1740                 }
1741                 else if (r.y + r.height >= currentMouseY) {
1742                     return false;
1743                 }
1744             }
1745             else {
1746                 if (drawInverted() ? direction < 0 : direction > 0) {
1747                     if (r.x + r.width  >= currentMouseX) {
1748                         return false;
1749                     }
1750                 }
1751                 else if (r.x <= currentMouseX) {
1752                     return false;
1753                 }
1754             }
1755 
1756             if (direction > 0 && slider.getValue() + slider.getExtent() >=
1757                     slider.getMaximum()) {
1758                 return false;
1759             }
1760             else if (direction < 0 && slider.getValue() <=
1761                     slider.getMinimum()) {
1762                 return false;
1763             }
1764 
1765             return true;
1766         }
1767 
1768         /**
1769         * Set the models value to the position of the top/left
1770         * of the thumb relative to the origin of the track.
1771         */
1772         public void mouseDragged(MouseEvent e) {
1773             int thumbMiddle;
1774 
1775             if (!slider.isEnabled()) {
1776                 return;
1777             }
1778 
1779             currentMouseX = e.getX();
1780             currentMouseY = e.getY();
1781 
1782             if (!isDragging) {
1783                 return;
1784             }
1785 
1786             slider.setValueIsAdjusting(true);
1787 
1788             switch (slider.getOrientation()) {
1789             case JSlider.VERTICAL:
1790                 int halfThumbHeight = thumbRect.height / 2;
1791                 int thumbTop = e.getY() - offset;
1792                 int trackTop = trackRect.y;
1793                 int trackBottom = trackRect.y + (trackRect.height - 1);
1794                 int vMax = yPositionForValue(slider.getMaximum() -
1795                                             slider.getExtent());
1796 
1797                 if (drawInverted()) {
1798                     trackBottom = vMax;
1799                 }
1800                 else {
1801                     trackTop = vMax;
1802                 }
1803                 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
1804                 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
1805 
1806                 setThumbLocation(thumbRect.x, thumbTop);
1807 
1808                 thumbMiddle = thumbTop + halfThumbHeight;
1809                 slider.setValue( valueForYPosition( thumbMiddle ) );
1810                 break;
1811             case JSlider.HORIZONTAL:
1812                 int halfThumbWidth = thumbRect.width / 2;
1813                 int thumbLeft = e.getX() - offset;
1814                 int trackLeft = trackRect.x;
1815                 int trackRight = trackRect.x + (trackRect.width - 1);
1816                 int hMax = xPositionForValue(slider.getMaximum() -
1817                                             slider.getExtent());
1818 
1819                 if (drawInverted()) {
1820                     trackLeft = hMax;
1821                 }
1822                 else {
1823                     trackRight = hMax;
1824                 }
1825                 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
1826                 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
1827 
1828                 setThumbLocation(thumbLeft, thumbRect.y);
1829 
1830                 thumbMiddle = thumbLeft + halfThumbWidth;
1831                 slider.setValue(valueForXPosition(thumbMiddle));
1832                 break;
1833             }
1834         }
1835 
1836         public void mouseMoved(MouseEvent e) { }
1837     }
1838 
1839     /**
1840      * Scroll-event listener.
1841      *
1842      * This class should be treated as a &quot;protected&quot; inner class.
1843      * Instantiate it only within subclasses of <code>Foo</code>.
1844      */
1845     public class ScrollListener implements ActionListener {
1846         // changed this class to public to avoid bogus IllegalAccessException
1847         // bug in InternetExplorer browser.  It was protected.  Work around
1848         // for 4109432
1849         int direction = POSITIVE_SCROLL;
1850         boolean useBlockIncrement;
1851 
1852         public ScrollListener() {
1853             direction = POSITIVE_SCROLL;
1854             useBlockIncrement = true;
1855         }
1856 
1857         public ScrollListener(int dir, boolean block)   {
1858             direction = dir;
1859             useBlockIncrement = block;
1860         }
1861 
1862         public void setDirection(int direction) {
1863             this.direction = direction;
1864         }
1865 
1866         public void setScrollByBlock(boolean block) {
1867             this.useBlockIncrement = block;
1868         }
1869 
1870         public void actionPerformed(ActionEvent e) {
1871             if (useBlockIncrement) {
1872                 scrollByBlock(direction);
1873             }
1874             else {
1875                 scrollByUnit(direction);
1876             }
1877             if (!trackListener.shouldScroll(direction)) {
1878                 ((Timer)e.getSource()).stop();
1879             }
1880         }
1881     }
1882 
1883     /**
1884      * Listener for resizing events.
1885      * <p>
1886      * This class should be treated as a &quot;protected&quot; inner class.
1887      * Instantiate it only within subclasses of <code>Foo</code>.
1888      */
1889     public class ComponentHandler extends ComponentAdapter {
1890         // NOTE: This class exists only for backward compatibility. All
1891         // its functionality has been moved into Handler. If you need to add
1892         // new functionality add it to the Handler, but make sure this
1893         // class calls into the Handler.
1894         public void componentResized(ComponentEvent e)  {
1895             getHandler().componentResized(e);
1896         }
1897     }
1898 
1899     /**
1900      * Focus-change listener.
1901      * <p>
1902      * This class should be treated as a &quot;protected&quot; inner class.
1903      * Instantiate it only within subclasses of <code>Foo</code>.
1904      */
1905     public class FocusHandler implements FocusListener {
1906         // NOTE: This class exists only for backward compatibility. All
1907         // its functionality has been moved into Handler. If you need to add
1908         // new functionality add it to the Handler, but make sure this
1909         // class calls into the Handler.
1910         public void focusGained(FocusEvent e) {
1911             getHandler().focusGained(e);
1912         }
1913 
1914         public void focusLost(FocusEvent e) {
1915             getHandler().focusLost(e);
1916         }
1917     }
1918 
1919     /**
1920      * As of Java 2 platform v1.3 this undocumented class is no longer used.
1921      * The recommended approach to creating bindings is to use a
1922      * combination of an <code>ActionMap</code>, to contain the action,
1923      * and an <code>InputMap</code> to contain the mapping from KeyStroke
1924      * to action description. The InputMap is is usually described in the
1925      * LookAndFeel tables.
1926      * <p>
1927      * Please refer to the key bindings specification for further details.
1928      * <p>
1929      * This class should be treated as a &quot;protected&quot; inner class.
1930      * Instantiate it only within subclasses of <code>Foo</code>.
1931      */
1932     @SuppressWarnings("serial") // Superclass is not serializable across versions
1933     public class ActionScroller extends AbstractAction {
1934         // NOTE: This class exists only for backward compatibility. All
1935         // its functionality has been moved into Actions. If you need to add
1936         // new functionality add it to the Actions, but make sure this
1937         // class calls into the Actions.
1938         int dir;
1939         boolean block;
1940         JSlider slider;
1941 
1942         public ActionScroller( JSlider slider, int dir, boolean block) {
1943             this.dir = dir;
1944             this.block = block;
1945             this.slider = slider;
1946         }
1947 
1948         public void actionPerformed(ActionEvent e) {
1949             SHARED_ACTION.scroll(slider, BasicSliderUI.this, dir, block);
1950         }
1951 
1952         public boolean isEnabled() {
1953             boolean b = true;
1954             if (slider != null) {
1955                 b = slider.isEnabled();
1956             }
1957             return b;
1958         }
1959 
1960     }
1961 
1962 
1963     /**
1964      * A static version of the above.
1965      */
1966     @SuppressWarnings("serial") // Superclass is not serializable across versions
1967     static class SharedActionScroller extends AbstractAction {
1968         // NOTE: This class exists only for backward compatibility. All
1969         // its functionality has been moved into Actions. If you need to add
1970         // new functionality add it to the Actions, but make sure this
1971         // class calls into the Actions.
1972         int dir;
1973         boolean block;
1974 
1975         public SharedActionScroller(int dir, boolean block) {
1976             this.dir = dir;
1977             this.block = block;
1978         }
1979 
1980         public void actionPerformed(ActionEvent evt) {
1981             JSlider slider = (JSlider)evt.getSource();
1982             BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1983                     slider.getUI(), BasicSliderUI.class);
1984             if (ui == null) {
1985                 return;
1986             }
1987             SHARED_ACTION.scroll(slider, ui, dir, block);
1988         }
1989     }
1990 
1991     private static class Actions extends UIAction {
1992         public static final String POSITIVE_UNIT_INCREMENT =
1993             "positiveUnitIncrement";
1994         public static final String POSITIVE_BLOCK_INCREMENT =
1995             "positiveBlockIncrement";
1996         public static final String NEGATIVE_UNIT_INCREMENT =
1997             "negativeUnitIncrement";
1998         public static final String NEGATIVE_BLOCK_INCREMENT =
1999             "negativeBlockIncrement";
2000         public static final String MIN_SCROLL_INCREMENT = "minScroll";
2001         public static final String MAX_SCROLL_INCREMENT = "maxScroll";
2002 
2003 
2004         Actions() {
2005             super(null);
2006         }
2007 
2008         public Actions(String name) {
2009             super(name);
2010         }
2011 
2012         public void actionPerformed(ActionEvent evt) {
2013             JSlider slider = (JSlider)evt.getSource();
2014             BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
2015                      slider.getUI(), BasicSliderUI.class);
2016             String name = getName();
2017 
2018             if (ui == null) {
2019                 return;
2020             }
2021             if (POSITIVE_UNIT_INCREMENT == name) {
2022                 scroll(slider, ui, POSITIVE_SCROLL, false);
2023             } else if (NEGATIVE_UNIT_INCREMENT == name) {
2024                 scroll(slider, ui, NEGATIVE_SCROLL, false);
2025             } else if (POSITIVE_BLOCK_INCREMENT == name) {
2026                 scroll(slider, ui, POSITIVE_SCROLL, true);
2027             } else if (NEGATIVE_BLOCK_INCREMENT == name) {
2028                 scroll(slider, ui, NEGATIVE_SCROLL, true);
2029             } else if (MIN_SCROLL_INCREMENT == name) {
2030                 scroll(slider, ui, MIN_SCROLL, false);
2031             } else if (MAX_SCROLL_INCREMENT == name) {
2032                 scroll(slider, ui, MAX_SCROLL, false);
2033             }
2034         }
2035 
2036         private void scroll(JSlider slider, BasicSliderUI ui, int direction,
2037                 boolean isBlock) {
2038             boolean invert = slider.getInverted();
2039 
2040             if (direction == NEGATIVE_SCROLL || direction == POSITIVE_SCROLL) {
2041                 if (invert) {
2042                     direction = (direction == POSITIVE_SCROLL) ?
2043                         NEGATIVE_SCROLL : POSITIVE_SCROLL;
2044                 }
2045 
2046                 if (isBlock) {
2047                     ui.scrollByBlock(direction);
2048                 } else {
2049                     ui.scrollByUnit(direction);
2050                 }
2051             } else {  // MIN or MAX
2052                 if (invert) {
2053                     direction = (direction == MIN_SCROLL) ?
2054                         MAX_SCROLL : MIN_SCROLL;
2055                 }
2056 
2057                 slider.setValue((direction == MIN_SCROLL) ?
2058                     slider.getMinimum() : slider.getMaximum());
2059             }
2060         }
2061     }
2062 }