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