1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.plaf.basic;
  26 
  27 
  28 import sun.swing.DefaultLookup;
  29 import sun.swing.UIAction;
  30 
  31 import java.awt.*;
  32 import java.awt.event.*;
  33 
  34 import java.beans.*;
  35 
  36 import javax.swing.*;
  37 import javax.swing.event.*;
  38 import javax.swing.plaf.*;
  39 
  40 import static sun.swing.SwingUtilities2.drawHLine;
  41 import static sun.swing.SwingUtilities2.drawRect;
  42 import static sun.swing.SwingUtilities2.drawVLine;
  43 
  44 
  45 /**
  46  * Implementation of ScrollBarUI for the Basic Look and Feel
  47  *
  48  * @author Rich Schiavi
  49  * @author David Kloba
  50  * @author Hans Muller
  51  */
  52 public class BasicScrollBarUI
  53     extends ScrollBarUI implements LayoutManager, SwingConstants
  54 {
  55     private static final int POSITIVE_SCROLL = 1;
  56     private static final int NEGATIVE_SCROLL = -1;
  57 
  58     private static final int MIN_SCROLL = 2;
  59     private static final int MAX_SCROLL = 3;
  60 
  61     // NOTE: DO NOT use this field directly, SynthScrollBarUI assumes you'll
  62     // call getMinimumThumbSize to access it.
  63     /** Minimum thumb size */
  64     protected Dimension minimumThumbSize;
  65     /** Maximum thumb size */
  66     protected Dimension maximumThumbSize;
  67 
  68     /** Thumb highlight color */
  69     protected Color thumbHighlightColor;
  70     /** Thumb light shadow color */
  71     protected Color thumbLightShadowColor;
  72     /** Thumb dark shadow color */
  73     protected Color thumbDarkShadowColor;
  74     /** Thumb color */
  75     protected Color thumbColor;
  76     /** Track color */
  77     protected Color trackColor;
  78     /** Track highlight color */
  79     protected Color trackHighlightColor;
  80 
  81     /** Scrollbar */
  82     protected JScrollBar scrollbar;
  83     /** Increment button */
  84     protected JButton incrButton;
  85     /** Decrement button */
  86     protected JButton decrButton;
  87     /** Dragging */
  88     protected boolean isDragging;
  89     /** Track listener */
  90     protected TrackListener trackListener;
  91     /** Button listener */
  92     protected ArrowButtonListener buttonListener;
  93     /** Model listener */
  94     protected ModelListener modelListener;
  95 
  96     /** Thumb rectangle */
  97     protected Rectangle thumbRect;
  98     /** Track rectangle */
  99     protected Rectangle trackRect;
 100 
 101     /** Track highlight */
 102     protected int trackHighlight;
 103 
 104     /** No highlight */
 105     protected static final int NO_HIGHLIGHT = 0;
 106     /** Decrease highlight */
 107     protected static final int DECREASE_HIGHLIGHT = 1;
 108     /** Increase highlight */
 109     protected static final int INCREASE_HIGHLIGHT = 2;
 110 
 111     /** Scroll listener */
 112     protected ScrollListener scrollListener;
 113     /** Property change listener */
 114     protected PropertyChangeListener propertyChangeListener;
 115     /** Scroll timer */
 116     protected Timer scrollTimer;
 117 
 118     private final static int scrollSpeedThrottle = 60; // delay in milli seconds
 119 
 120     /**
 121      * True indicates a middle click will absolutely position the
 122      * scrollbar.
 123      */
 124     private boolean supportsAbsolutePositioning;
 125 
 126     /**
 127      * Hint as to what width (when vertical) or height (when horizontal)
 128      * should be.
 129      *
 130      * @since 1.7
 131      */
 132     protected int scrollBarWidth;
 133 
 134     private Handler handler;
 135 
 136     private boolean thumbActive;
 137 
 138     /**
 139      * Determine whether scrollbar layout should use cached value or adjusted
 140      * value returned by scrollbar's <code>getValue</code>.
 141      */
 142     private boolean useCachedValue = false;
 143     /**
 144      * The scrollbar value is cached to save real value if the view is adjusted.
 145      */
 146     private int scrollBarValue;
 147 
 148     /**
 149      * Distance between the increment button and the track. This may be a negative
 150      * number. If negative, then an overlap between the button and track will occur,
 151      * which is useful for shaped buttons.
 152      *
 153      * @since 1.7
 154      */
 155     protected int incrGap;
 156 
 157     /**
 158      * Distance between the decrement button and the track. This may be a negative
 159      * number. If negative, then an overlap between the button and track will occur,
 160      * which is useful for shaped buttons.
 161      *
 162      * @since 1.7
 163      */
 164     protected int decrGap;
 165 
 166     static void loadActionMap(LazyActionMap map) {
 167         map.put(new Actions(Actions.POSITIVE_UNIT_INCREMENT));
 168         map.put(new Actions(Actions.POSITIVE_BLOCK_INCREMENT));
 169         map.put(new Actions(Actions.NEGATIVE_UNIT_INCREMENT));
 170         map.put(new Actions(Actions.NEGATIVE_BLOCK_INCREMENT));
 171         map.put(new Actions(Actions.MIN_SCROLL));
 172         map.put(new Actions(Actions.MAX_SCROLL));
 173     }
 174 
 175     /**
 176      * Creates the UI.
 177      * @param c the component
 178      * @return the UI
 179      */
 180     public static ComponentUI createUI(JComponent c)    {
 181         return new BasicScrollBarUI();
 182     }
 183 
 184     /**
 185      * Configures the scroll bar colors.
 186      */
 187     protected void configureScrollBarColors()
 188     {
 189         LookAndFeel.installColors(scrollbar, "ScrollBar.background",
 190                                   "ScrollBar.foreground");
 191         thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight");
 192         thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow");
 193         thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow");
 194         thumbColor = UIManager.getColor("ScrollBar.thumb");
 195         trackColor = UIManager.getColor("ScrollBar.track");
 196         trackHighlightColor = UIManager.getColor("ScrollBar.trackHighlight");
 197     }
 198 
 199     /**
 200      * Installs the UI.
 201      * @param c the component
 202      */
 203     public void installUI(JComponent c)   {
 204         scrollbar = (JScrollBar)c;
 205         thumbRect = new Rectangle(0, 0, 0, 0);
 206         trackRect = new Rectangle(0, 0, 0, 0);
 207         installDefaults();
 208         installComponents();
 209         installListeners();
 210         installKeyboardActions();
 211     }
 212 
 213     /**
 214      * Uninstalls the UI.
 215      * @param c the component
 216      */
 217     public void uninstallUI(JComponent c) {
 218         scrollbar = (JScrollBar)c;
 219         uninstallListeners();
 220         uninstallDefaults();
 221         uninstallComponents();
 222         uninstallKeyboardActions();
 223         thumbRect = null;
 224         scrollbar = null;
 225         incrButton = null;
 226         decrButton = null;
 227     }
 228 
 229     /**
 230      * Installs the defaults.
 231      */
 232     protected void installDefaults()
 233     {
 234         scrollBarWidth = UIManager.getInt("ScrollBar.width");
 235         if (scrollBarWidth <= 0) {
 236             scrollBarWidth = 16;
 237         }
 238         minimumThumbSize = (Dimension)UIManager.get("ScrollBar.minimumThumbSize");
 239         maximumThumbSize = (Dimension)UIManager.get("ScrollBar.maximumThumbSize");
 240 
 241         Boolean absB = (Boolean)UIManager.get("ScrollBar.allowsAbsolutePositioning");
 242         supportsAbsolutePositioning = (absB != null) ? absB.booleanValue() :
 243                                       false;
 244 
 245         trackHighlight = NO_HIGHLIGHT;
 246         if (scrollbar.getLayout() == null ||
 247                      (scrollbar.getLayout() instanceof UIResource)) {
 248             scrollbar.setLayout(this);
 249         }
 250         configureScrollBarColors();
 251         LookAndFeel.installBorder(scrollbar, "ScrollBar.border");
 252         LookAndFeel.installProperty(scrollbar, "opaque", Boolean.TRUE);
 253 
 254         scrollBarValue = scrollbar.getValue();
 255 
 256         incrGap = UIManager.getInt("ScrollBar.incrementButtonGap");
 257         decrGap = UIManager.getInt("ScrollBar.decrementButtonGap");
 258 
 259         // TODO this can be removed when incrGap/decrGap become protected
 260         // handle scaling for sizeVarients for special case components. The
 261         // key "JComponent.sizeVariant" scales for large/small/mini
 262         // components are based on Apples LAF
 263         String scaleKey = (String)scrollbar.getClientProperty(
 264                 "JComponent.sizeVariant");
 265         if (scaleKey != null){
 266             if ("large".equals(scaleKey)){
 267                 scrollBarWidth *= 1.15;
 268                 incrGap *= 1.15;
 269                 decrGap *= 1.15;
 270             } else if ("small".equals(scaleKey)){
 271                 scrollBarWidth *= 0.857;
 272                 incrGap *= 0.857;
 273                 decrGap *= 0.714;
 274             } else if ("mini".equals(scaleKey)){
 275                 scrollBarWidth *= 0.714;
 276                 incrGap *= 0.714;
 277                 decrGap *= 0.714;
 278             }
 279         }
 280     }
 281 
 282     /**
 283      * Installs the components.
 284      */
 285     protected void installComponents(){
 286         switch (scrollbar.getOrientation()) {
 287         case JScrollBar.VERTICAL:
 288             incrButton = createIncreaseButton(SOUTH);
 289             decrButton = createDecreaseButton(NORTH);
 290             break;
 291 
 292         case JScrollBar.HORIZONTAL:
 293             if (scrollbar.getComponentOrientation().isLeftToRight()) {
 294                 incrButton = createIncreaseButton(EAST);
 295                 decrButton = createDecreaseButton(WEST);
 296             } else {
 297                 incrButton = createIncreaseButton(WEST);
 298                 decrButton = createDecreaseButton(EAST);
 299             }
 300             break;
 301         }
 302         scrollbar.add(incrButton);
 303         scrollbar.add(decrButton);
 304         // Force the children's enabled state to be updated.
 305         scrollbar.setEnabled(scrollbar.isEnabled());
 306     }
 307 
 308     /**
 309      * Uninstalls the components.
 310      */
 311     protected void uninstallComponents(){
 312         scrollbar.remove(incrButton);
 313         scrollbar.remove(decrButton);
 314     }
 315 
 316     /**
 317      * Installs the listeners.
 318      */
 319     protected void installListeners(){
 320         trackListener = createTrackListener();
 321         buttonListener = createArrowButtonListener();
 322         modelListener = createModelListener();
 323         propertyChangeListener = createPropertyChangeListener();
 324 
 325         scrollbar.addMouseListener(trackListener);
 326         scrollbar.addMouseMotionListener(trackListener);
 327         scrollbar.getModel().addChangeListener(modelListener);
 328         scrollbar.addPropertyChangeListener(propertyChangeListener);
 329         scrollbar.addFocusListener(getHandler());
 330 
 331         if (incrButton != null) {
 332             incrButton.addMouseListener(buttonListener);
 333         }
 334         if (decrButton != null) {
 335             decrButton.addMouseListener(buttonListener);
 336         }
 337 
 338         scrollListener = createScrollListener();
 339         scrollTimer = new Timer(scrollSpeedThrottle, scrollListener);
 340         scrollTimer.setInitialDelay(300);  // default InitialDelay?
 341     }
 342 
 343     /**
 344      * Installs the keyboard actions.
 345      */
 346     protected void installKeyboardActions(){
 347         LazyActionMap.installLazyActionMap(scrollbar, BasicScrollBarUI.class,
 348                                            "ScrollBar.actionMap");
 349 
 350         InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
 351         SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED,
 352                                          inputMap);
 353         inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 354         SwingUtilities.replaceUIInputMap(scrollbar,
 355                    JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap);
 356     }
 357 
 358     /**
 359      * Uninstalls the keyboard actions.
 360      */
 361     protected void uninstallKeyboardActions(){
 362         SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED,
 363                                          null);
 364         SwingUtilities.replaceUIActionMap(scrollbar, null);
 365     }
 366 
 367     private InputMap getInputMap(int condition) {
 368         if (condition == JComponent.WHEN_FOCUSED) {
 369             InputMap keyMap = (InputMap)DefaultLookup.get(
 370                         scrollbar, this, "ScrollBar.focusInputMap");
 371             InputMap rtlKeyMap;
 372 
 373             if (scrollbar.getComponentOrientation().isLeftToRight() ||
 374                 ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollbar, this, "ScrollBar.focusInputMap.RightToLeft")) == null)) {
 375                 return keyMap;
 376             } else {
 377                 rtlKeyMap.setParent(keyMap);
 378                 return rtlKeyMap;
 379             }
 380         }
 381         else if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
 382             InputMap keyMap = (InputMap)DefaultLookup.get(
 383                         scrollbar, this, "ScrollBar.ancestorInputMap");
 384             InputMap rtlKeyMap;
 385 
 386             if (scrollbar.getComponentOrientation().isLeftToRight() ||
 387                 ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollbar, this, "ScrollBar.ancestorInputMap.RightToLeft")) == null)) {
 388                 return keyMap;
 389             } else {
 390                 rtlKeyMap.setParent(keyMap);
 391                 return rtlKeyMap;
 392             }
 393         }
 394         return null;
 395     }
 396 
 397     /**
 398      * Uninstall the listeners.
 399      */
 400     protected void uninstallListeners() {
 401         scrollTimer.stop();
 402         scrollTimer = null;
 403 
 404         if (decrButton != null){
 405             decrButton.removeMouseListener(buttonListener);
 406         }
 407         if (incrButton != null){
 408             incrButton.removeMouseListener(buttonListener);
 409         }
 410 
 411         scrollbar.getModel().removeChangeListener(modelListener);
 412         scrollbar.removeMouseListener(trackListener);
 413         scrollbar.removeMouseMotionListener(trackListener);
 414         scrollbar.removePropertyChangeListener(propertyChangeListener);
 415         scrollbar.removeFocusListener(getHandler());
 416         handler = null;
 417     }
 418 
 419     /**
 420      * Uninstalls the defaults.
 421      */
 422     protected void uninstallDefaults(){
 423         LookAndFeel.uninstallBorder(scrollbar);
 424         if (scrollbar.getLayout() == this) {
 425             scrollbar.setLayout(null);
 426         }
 427     }
 428 
 429 
 430     private Handler getHandler() {
 431         if (handler == null) {
 432             handler = new Handler();
 433         }
 434         return handler;
 435     }
 436 
 437     /**
 438      * Creates a track listener.
 439      * @return a track listener
 440      */
 441     protected TrackListener createTrackListener(){
 442         return new TrackListener();
 443     }
 444 
 445     /**
 446      * Creates an arrow button listener.
 447      * @return an arrow button   listener
 448      */
 449     protected ArrowButtonListener createArrowButtonListener(){
 450         return new ArrowButtonListener();
 451     }
 452 
 453     /**
 454      * Creates a model listener.
 455      * @return a model listener
 456      */
 457     protected ModelListener createModelListener(){
 458         return new ModelListener();
 459     }
 460 
 461     /**
 462      * Creates a scroll listener.
 463      * @return a scroll listener
 464      */
 465     protected ScrollListener createScrollListener(){
 466         return new ScrollListener();
 467     }
 468 
 469     /**
 470      * Creates a property change listener.
 471      * @return a property change listener
 472      */
 473     protected PropertyChangeListener createPropertyChangeListener() {
 474         return getHandler();
 475     }
 476 
 477     private void updateThumbState(int x, int y) {
 478         Rectangle rect = getThumbBounds();
 479 
 480         setThumbRollover(rect.contains(x, y));
 481     }
 482 
 483     /**
 484      * Sets whether or not the mouse is currently over the thumb.
 485      *
 486      * @param active True indicates the thumb is currently active.
 487      * @since 1.5
 488      */
 489     protected void setThumbRollover(boolean active) {
 490         if (thumbActive != active) {
 491             thumbActive = active;
 492             scrollbar.repaint(getThumbBounds());
 493         }
 494     }
 495 
 496     /**
 497      * Returns true if the mouse is currently over the thumb.
 498      *
 499      * @return true if the thumb is currently active
 500      * @since 1.5
 501      */
 502     public boolean isThumbRollover() {
 503         return thumbActive;
 504     }
 505 
 506     public void paint(Graphics g, JComponent c) {
 507         paintTrack(g, c, getTrackBounds());
 508         Rectangle thumbBounds = getThumbBounds();
 509         if (thumbBounds.intersects(g.getClipBounds())) {
 510             paintThumb(g, c, thumbBounds);
 511         }
 512     }
 513 
 514 
 515     /**
 516      * A vertical scrollbar's preferred width is the maximum of
 517      * preferred widths of the (non <code>null</code>)
 518      * increment/decrement buttons,
 519      * and the minimum width of the thumb. The preferred height is the
 520      * sum of the preferred heights of the same parts.  The basis for
 521      * the preferred size of a horizontal scrollbar is similar.
 522      * <p>
 523      * The <code>preferredSize</code> is only computed once, subsequent
 524      * calls to this method just return a cached size.
 525      *
 526      * @param c the <code>JScrollBar</code> that's delegating this method to us
 527      * @return the preferred size of a Basic JScrollBar
 528      * @see #getMaximumSize
 529      * @see #getMinimumSize
 530      */
 531     public Dimension getPreferredSize(JComponent c) {
 532         return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
 533             ? new Dimension(scrollBarWidth, 48)
 534             : new Dimension(48, scrollBarWidth);
 535     }
 536 
 537 
 538     /**
 539      * @param c The JScrollBar that's delegating this method to us.
 540      * @return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 541      * @see #getMinimumSize
 542      * @see #getPreferredSize
 543      */
 544     public Dimension getMaximumSize(JComponent c) {
 545         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 546     }
 547 
 548     /**
 549      * Creates a decrease button.
 550      * @param orientation the orientation
 551      * @return a decrease button
 552      */
 553     protected JButton createDecreaseButton(int orientation)  {
 554         return new BasicArrowButton(orientation,
 555                                     UIManager.getColor("ScrollBar.thumb"),
 556                                     UIManager.getColor("ScrollBar.thumbShadow"),
 557                                     UIManager.getColor("ScrollBar.thumbDarkShadow"),
 558                                     UIManager.getColor("ScrollBar.thumbHighlight"));
 559     }
 560 
 561     /**
 562      * Creates an increase button.
 563      * @param orientation the orientation
 564      * @return an increase button
 565      */
 566     protected JButton createIncreaseButton(int orientation)  {
 567         return new BasicArrowButton(orientation,
 568                                     UIManager.getColor("ScrollBar.thumb"),
 569                                     UIManager.getColor("ScrollBar.thumbShadow"),
 570                                     UIManager.getColor("ScrollBar.thumbDarkShadow"),
 571                                     UIManager.getColor("ScrollBar.thumbHighlight"));
 572     }
 573 
 574 
 575     /**
 576      * Paints the decrease highlight.
 577      * @param g the graphics
 578      */
 579     protected void paintDecreaseHighlight(Graphics g)
 580     {
 581         Insets insets = scrollbar.getInsets();
 582         Rectangle thumbR = getThumbBounds();
 583         g.setColor(trackHighlightColor);
 584 
 585         if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
 586             //paint the distance between the start of the track and top of the thumb
 587             int x = insets.left;
 588             int y = trackRect.y;
 589             int w = scrollbar.getWidth() - (insets.left + insets.right);
 590             int h = thumbR.y - y;
 591             g.fillRect(x, y, w, h);
 592         } else {
 593             //if left-to-right, fill the area between the start of the track and
 594             //the left edge of the thumb. If right-to-left, fill the area between
 595             //the end of the thumb and end of the track.
 596             int x, w;
 597             if (scrollbar.getComponentOrientation().isLeftToRight()) {
 598                x = trackRect.x;
 599                 w = thumbR.x - x;
 600             } else {
 601                 x = thumbR.x + thumbR.width;
 602                 w = trackRect.x + trackRect.width - x;
 603             }
 604             int y = insets.top;
 605             int h = scrollbar.getHeight() - (insets.top + insets.bottom);
 606             g.fillRect(x, y, w, h);
 607         }
 608     }
 609 
 610 
 611     /**
 612      * Paints the increase highlight.
 613      * @param g the graphics
 614      */
 615     protected void paintIncreaseHighlight(Graphics g)
 616     {
 617         Insets insets = scrollbar.getInsets();
 618         Rectangle thumbR = getThumbBounds();
 619         g.setColor(trackHighlightColor);
 620 
 621         if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
 622             //fill the area between the bottom of the thumb and the end of the track.
 623             int x = insets.left;
 624             int y = thumbR.y + thumbR.height;
 625             int w = scrollbar.getWidth() - (insets.left + insets.right);
 626             int h = trackRect.y + trackRect.height - y;
 627             g.fillRect(x, y, w, h);
 628         }
 629         else {
 630             //if left-to-right, fill the area between the right of the thumb and the
 631             //end of the track. If right-to-left, then fill the area to the left of
 632             //the thumb and the start of the track.
 633             int x, w;
 634             if (scrollbar.getComponentOrientation().isLeftToRight()) {
 635                 x = thumbR.x + thumbR.width;
 636                 w = trackRect.x + trackRect.width - x;
 637             } else {
 638                 x = trackRect.x;
 639                 w = thumbR.x - x;
 640             }
 641             int y = insets.top;
 642             int h = scrollbar.getHeight() - (insets.top + insets.bottom);
 643             g.fillRect(x, y, w, h);
 644         }
 645     }
 646 
 647 
 648     /**
 649      * Paints the track.
 650      * @param g the graphics
 651      * @param c the component
 652      * @param trackBounds the track bounds
 653      */
 654     protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds)
 655     {
 656         g.setColor(trackColor);
 657         g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);
 658 
 659         if(trackHighlight == DECREASE_HIGHLIGHT)        {
 660             paintDecreaseHighlight(g);
 661         }
 662         else if(trackHighlight == INCREASE_HIGHLIGHT)           {
 663             paintIncreaseHighlight(g);
 664         }
 665     }
 666 
 667     /**
 668      * Paints the thumb.
 669      * @param g the graphics
 670      * @param c the component
 671      * @param thumbBounds the thumb bounds
 672      */
 673     protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)
 674     {
 675         if(thumbBounds.isEmpty() || !scrollbar.isEnabled())     {
 676             return;
 677         }
 678 
 679         int w = thumbBounds.width;
 680         int h = thumbBounds.height;
 681 
 682         g.translate(thumbBounds.x, thumbBounds.y);
 683 
 684         g.setColor(thumbDarkShadowColor);
 685         drawRect(g, 0, 0, w - 1, h - 1);
 686         g.setColor(thumbColor);
 687         g.fillRect(0, 0, w - 1, h - 1);
 688 
 689         g.setColor(thumbHighlightColor);
 690         drawVLine(g, 1, 1, h - 2);
 691         drawHLine(g, 2, w - 3, 1);
 692 
 693         g.setColor(thumbLightShadowColor);
 694         drawHLine(g, 2, w - 2, h - 2);
 695         drawVLine(g, w - 2, 1, h - 3);
 696 
 697         g.translate(-thumbBounds.x, -thumbBounds.y);
 698     }
 699 
 700 
 701     /**
 702      * Returns the smallest acceptable size for the thumb.  If the scrollbar
 703      * becomes so small that this size isn't available, the thumb will be
 704      * hidden.
 705      * <p>
 706      * <b>Warning </b>: the value returned by this method should not be
 707      * be modified, it's a shared static constant.
 708      *
 709      * @return The smallest acceptable size for the thumb.
 710      * @see #getMaximumThumbSize
 711      */
 712     protected Dimension getMinimumThumbSize() {
 713         return minimumThumbSize;
 714     }
 715 
 716     /**
 717      * Returns the largest acceptable size for the thumb.  To create a fixed
 718      * size thumb one make this method and <code>getMinimumThumbSize</code>
 719      * return the same value.
 720      * <p>
 721      * <b>Warning </b>: the value returned by this method should not be
 722      * be modified, it's a shared static constant.
 723      *
 724      * @return The largest acceptable size for the thumb.
 725      * @see #getMinimumThumbSize
 726      */
 727     protected Dimension getMaximumThumbSize()   {
 728         return maximumThumbSize;
 729     }
 730 
 731 
 732     /*
 733      * LayoutManager Implementation
 734      */
 735 
 736     public void addLayoutComponent(String name, Component child) {}
 737     public void removeLayoutComponent(Component child) {}
 738 
 739     public Dimension preferredLayoutSize(Container scrollbarContainer)  {
 740         return getPreferredSize((JComponent)scrollbarContainer);
 741     }
 742 
 743     public Dimension minimumLayoutSize(Container scrollbarContainer) {
 744         return getMinimumSize((JComponent)scrollbarContainer);
 745     }
 746 
 747     private int getValue(JScrollBar sb) {
 748         return (useCachedValue) ? scrollBarValue : sb.getValue();
 749     }
 750 
 751     /**
 752      * Laysouts a  vertical scroll bar.
 753      * @param sb the scroll bar
 754      */
 755     protected void layoutVScrollbar(JScrollBar sb)
 756     {
 757         Dimension sbSize = sb.getSize();
 758         Insets sbInsets = sb.getInsets();
 759 
 760         /*
 761          * Width and left edge of the buttons and thumb.
 762          */
 763         int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
 764         int itemX = sbInsets.left;
 765 
 766         /* Nominal locations of the buttons, assuming their preferred
 767          * size will fit.
 768          */
 769         boolean squareButtons = DefaultLookup.getBoolean(
 770             scrollbar, this, "ScrollBar.squareButtons", false);
 771         int decrButtonH = squareButtons ? itemW :
 772                           decrButton.getPreferredSize().height;
 773         int decrButtonY = sbInsets.top;
 774 
 775         int incrButtonH = squareButtons ? itemW :
 776                           incrButton.getPreferredSize().height;
 777         int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 778 
 779         /* The thumb must fit within the height left over after we
 780          * subtract the preferredSize of the buttons and the insets
 781          * and the gaps
 782          */
 783         int sbInsetsH = sbInsets.top + sbInsets.bottom;
 784         int sbButtonsH = decrButtonH + incrButtonH;
 785         int gaps = decrGap + incrGap;
 786         float trackH = sbSize.height - (sbInsetsH + sbButtonsH) - gaps;
 787 
 788         /* Compute the height and origin of the thumb.   The case
 789          * where the thumb is at the bottom edge is handled specially
 790          * to avoid numerical problems in computing thumbY.  Enforce
 791          * the thumbs min/max dimensions.  If the thumb doesn't
 792          * fit in the track (trackH) we'll hide it later.
 793          */
 794         float min = sb.getMinimum();
 795         float extent = sb.getVisibleAmount();
 796         float range = sb.getMaximum() - min;
 797         float value = getValue(sb);
 798 
 799         int thumbH = (range <= 0)
 800             ? getMaximumThumbSize().height : (int)(trackH * (extent / range));
 801         thumbH = Math.max(thumbH, getMinimumThumbSize().height);
 802         thumbH = Math.min(thumbH, getMaximumThumbSize().height);
 803 
 804         int thumbY = incrButtonY - incrGap - thumbH;
 805         if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
 806             float thumbRange = trackH - thumbH;
 807             thumbY = (int)(0.5f + (thumbRange * ((value - min) / (range - extent))));
 808             thumbY +=  decrButtonY + decrButtonH + decrGap;
 809         }
 810 
 811         /* If the buttons don't fit, allocate half of the available
 812          * space to each and move the lower one (incrButton) down.
 813          */
 814         int sbAvailButtonH = (sbSize.height - sbInsetsH);
 815         if (sbAvailButtonH < sbButtonsH) {
 816             incrButtonH = decrButtonH = sbAvailButtonH / 2;
 817             incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 818         }
 819         decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
 820         incrButton.setBounds(itemX, incrButtonY, itemW, incrButtonH);
 821 
 822         /* Update the trackRect field.
 823          */
 824         int itrackY = decrButtonY + decrButtonH + decrGap;
 825         int itrackH = incrButtonY - incrGap - itrackY;
 826         trackRect.setBounds(itemX, itrackY, itemW, itrackH);
 827 
 828         /* If the thumb isn't going to fit, zero it's bounds.  Otherwise
 829          * make sure it fits between the buttons.  Note that setting the
 830          * thumbs bounds will cause a repaint.
 831          */
 832         if(thumbH >= (int)trackH)       {
 833             if (UIManager.getBoolean("ScrollBar.alwaysShowThumb")) {
 834                 // This is used primarily for GTK L&F, which expands the
 835                 // thumb to fit the track when it would otherwise be hidden.
 836                 setThumbBounds(itemX, itrackY, itemW, itrackH);
 837             } else {
 838                 // Other L&F's simply hide the thumb in this case.
 839                 setThumbBounds(0, 0, 0, 0);
 840             }
 841         }
 842         else {
 843             if ((thumbY + thumbH) > incrButtonY - incrGap) {
 844                 thumbY = incrButtonY - incrGap - thumbH;
 845             }
 846             if (thumbY  < (decrButtonY + decrButtonH + decrGap)) {
 847                 thumbY = decrButtonY + decrButtonH + decrGap + 1;
 848             }
 849             setThumbBounds(itemX, thumbY, itemW, thumbH);
 850         }
 851     }
 852 
 853     /**
 854      * Laysouts a  vertical scroll bar.
 855      * @param sb the scroll bar
 856      */
 857     protected void layoutHScrollbar(JScrollBar sb)
 858     {
 859         Dimension sbSize = sb.getSize();
 860         Insets sbInsets = sb.getInsets();
 861 
 862         /* Height and top edge of the buttons and thumb.
 863          */
 864         int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
 865         int itemY = sbInsets.top;
 866 
 867         boolean ltr = sb.getComponentOrientation().isLeftToRight();
 868 
 869         /* Nominal locations of the buttons, assuming their preferred
 870          * size will fit.
 871          */
 872         boolean squareButtons = DefaultLookup.getBoolean(
 873             scrollbar, this, "ScrollBar.squareButtons", false);
 874         int leftButtonW = squareButtons ? itemH :
 875                           decrButton.getPreferredSize().width;
 876         int rightButtonW = squareButtons ? itemH :
 877                           incrButton.getPreferredSize().width;
 878         if (!ltr) {
 879             int temp = leftButtonW;
 880             leftButtonW = rightButtonW;
 881             rightButtonW = temp;
 882         }
 883         int leftButtonX = sbInsets.left;
 884         int rightButtonX = sbSize.width - (sbInsets.right + rightButtonW);
 885         int leftGap = ltr ? decrGap : incrGap;
 886         int rightGap = ltr ? incrGap : decrGap;
 887 
 888         /* The thumb must fit within the width left over after we
 889          * subtract the preferredSize of the buttons and the insets
 890          * and the gaps
 891          */
 892         int sbInsetsW = sbInsets.left + sbInsets.right;
 893         int sbButtonsW = leftButtonW + rightButtonW;
 894         float trackW = sbSize.width - (sbInsetsW + sbButtonsW) - (leftGap + rightGap);
 895 
 896         /* Compute the width and origin of the thumb.  Enforce
 897          * the thumbs min/max dimensions.  The case where the thumb
 898          * is at the right edge is handled specially to avoid numerical
 899          * problems in computing thumbX.  If the thumb doesn't
 900          * fit in the track (trackH) we'll hide it later.
 901          */
 902         float min = sb.getMinimum();
 903         float max = sb.getMaximum();
 904         float extent = sb.getVisibleAmount();
 905         float range = max - min;
 906         float value = getValue(sb);
 907 
 908         int thumbW = (range <= 0)
 909             ? getMaximumThumbSize().width : (int)(trackW * (extent / range));
 910         thumbW = Math.max(thumbW, getMinimumThumbSize().width);
 911         thumbW = Math.min(thumbW, getMaximumThumbSize().width);
 912 
 913         int thumbX = ltr ? rightButtonX - rightGap - thumbW : leftButtonX + leftButtonW + leftGap;
 914         if (value < (max - sb.getVisibleAmount())) {
 915             float thumbRange = trackW - thumbW;
 916             if( ltr ) {
 917                 thumbX = (int)(0.5f + (thumbRange * ((value - min) / (range - extent))));
 918             } else {
 919                 thumbX = (int)(0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
 920             }
 921             thumbX += leftButtonX + leftButtonW + leftGap;
 922         }
 923 
 924         /* If the buttons don't fit, allocate half of the available
 925          * space to each and move the right one over.
 926          */
 927         int sbAvailButtonW = (sbSize.width - sbInsetsW);
 928         if (sbAvailButtonW < sbButtonsW) {
 929             rightButtonW = leftButtonW = sbAvailButtonW / 2;
 930             rightButtonX = sbSize.width - (sbInsets.right + rightButtonW + rightGap);
 931         }
 932 
 933         (ltr ? decrButton : incrButton).setBounds(leftButtonX, itemY, leftButtonW, itemH);
 934         (ltr ? incrButton : decrButton).setBounds(rightButtonX, itemY, rightButtonW, itemH);
 935 
 936         /* Update the trackRect field.
 937          */
 938         int itrackX = leftButtonX + leftButtonW + leftGap;
 939         int itrackW = rightButtonX - rightGap - itrackX;
 940         trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 941 
 942         /* Make sure the thumb fits between the buttons.  Note
 943          * that setting the thumbs bounds causes a repaint.
 944          */
 945         if (thumbW >= (int)trackW) {
 946             if (UIManager.getBoolean("ScrollBar.alwaysShowThumb")) {
 947                 // This is used primarily for GTK L&F, which expands the
 948                 // thumb to fit the track when it would otherwise be hidden.
 949                 setThumbBounds(itrackX, itemY, itrackW, itemH);
 950             } else {
 951                 // Other L&F's simply hide the thumb in this case.
 952                 setThumbBounds(0, 0, 0, 0);
 953             }
 954         }
 955         else {
 956             if (thumbX + thumbW > rightButtonX - rightGap) {
 957                 thumbX = rightButtonX - rightGap - thumbW;
 958             }
 959             if (thumbX  < leftButtonX + leftButtonW + leftGap) {
 960                 thumbX = leftButtonX + leftButtonW + leftGap + 1;
 961             }
 962             setThumbBounds(thumbX, itemY, thumbW, itemH);
 963         }
 964     }
 965 
 966     public void layoutContainer(Container scrollbarContainer)
 967     {
 968         /* If the user is dragging the value, we'll assume that the
 969          * scrollbars layout is OK modulo the thumb which is being
 970          * handled by the dragging code.
 971          */
 972         if (isDragging) {
 973             return;
 974         }
 975 
 976         JScrollBar scrollbar = (JScrollBar)scrollbarContainer;
 977         switch (scrollbar.getOrientation()) {
 978         case JScrollBar.VERTICAL:
 979             layoutVScrollbar(scrollbar);
 980             break;
 981 
 982         case JScrollBar.HORIZONTAL:
 983             layoutHScrollbar(scrollbar);
 984             break;
 985         }
 986     }
 987 
 988 
 989     /**
 990      * Set the bounds of the thumb and force a repaint that includes
 991      * the old thumbBounds and the new one.
 992      *
 993      * @param x set the x location of the thumb
 994      * @param y set the y location of the thumb
 995      * @param width set the width of the thumb
 996      * @param height set the height of the thumb
 997      * @see #getThumbBounds
 998      */
 999     protected void setThumbBounds(int x, int y, int width, int height)
1000     {
1001         /* If the thumbs bounds haven't changed, we're done.
1002          */
1003         if ((thumbRect.x == x) &&
1004             (thumbRect.y == y) &&
1005             (thumbRect.width == width) &&
1006             (thumbRect.height == height)) {
1007             return;
1008         }
1009 
1010         /* Update thumbRect, and repaint the union of x,y,w,h and
1011          * the old thumbRect.
1012          */
1013         int minX = Math.min(x, thumbRect.x);
1014         int minY = Math.min(y, thumbRect.y);
1015         int maxX = Math.max(x + width, thumbRect.x + thumbRect.width);
1016         int maxY = Math.max(y + height, thumbRect.y + thumbRect.height);
1017 
1018         thumbRect.setBounds(x, y, width, height);
1019         scrollbar.repaint(minX, minY, maxX - minX, maxY - minY);
1020 
1021         // Once there is API to determine the mouse location this will need
1022         // to be changed.
1023         setThumbRollover(false);
1024     }
1025 
1026 
1027     /**
1028      * Return the current size/location of the thumb.
1029      * <p>
1030      * <b>Warning </b>: the value returned by this method should not be
1031      * be modified, it's a reference to the actual rectangle, not a copy.
1032      *
1033      * @return The current size/location of the thumb.
1034      * @see #setThumbBounds
1035      */
1036     protected Rectangle getThumbBounds() {
1037         return thumbRect;
1038     }
1039 
1040 
1041     /**
1042      * Returns the current bounds of the track, i.e. the space in between
1043      * the increment and decrement buttons, less the insets.  The value
1044      * returned by this method is updated each time the scrollbar is
1045      * laid out (validated).
1046      * <p>
1047      * <b>Warning </b>: the value returned by this method should not be
1048      * be modified, it's a reference to the actual rectangle, not a copy.
1049      *
1050      * @return the current bounds of the scrollbar track
1051      * @see #layoutContainer
1052      */
1053     protected Rectangle getTrackBounds() {
1054         return trackRect;
1055     }
1056 
1057     /*
1058      * Method for scrolling by a block increment.
1059      * Added for mouse wheel scrolling support, RFE 4202656.
1060      */
1061     static void scrollByBlock(JScrollBar scrollbar, int direction) {
1062         // This method is called from BasicScrollPaneUI to implement wheel
1063         // scrolling, and also from scrollByBlock().
1064             int oldValue = scrollbar.getValue();
1065             int blockIncrement = scrollbar.getBlockIncrement(direction);
1066             int delta = blockIncrement * ((direction > 0) ? +1 : -1);
1067             int newValue = oldValue + delta;
1068 
1069             // Check for overflow.
1070             if (delta > 0 && newValue < oldValue) {
1071                 newValue = scrollbar.getMaximum();
1072             }
1073             else if (delta < 0 && newValue > oldValue) {
1074                 newValue = scrollbar.getMinimum();
1075             }
1076 
1077             scrollbar.setValue(newValue);
1078     }
1079 
1080     /**
1081      * Scrolls by block.
1082      * @param direction the direction to scroll
1083      */
1084     protected void scrollByBlock(int direction)
1085     {
1086         scrollByBlock(scrollbar, direction);
1087             trackHighlight = direction > 0 ? INCREASE_HIGHLIGHT : DECREASE_HIGHLIGHT;
1088             Rectangle dirtyRect = getTrackBounds();
1089             scrollbar.repaint(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
1090     }
1091 
1092     /*
1093      * Method for scrolling by a unit increment.
1094      * Added for mouse wheel scrolling support, RFE 4202656.
1095      *
1096      * If limitByBlock is set to true, the scrollbar will scroll at least 1
1097      * unit increment, but will not scroll farther than the block increment.
1098      * See BasicScrollPaneUI.Handler.mouseWheelMoved().
1099      */
1100     static void scrollByUnits(JScrollBar scrollbar, int direction,
1101                               int units, boolean limitToBlock) {
1102         // This method is called from BasicScrollPaneUI to implement wheel
1103         // scrolling, as well as from scrollByUnit().
1104         int delta;
1105         int limit = -1;
1106 
1107         if (limitToBlock) {
1108             if (direction < 0) {
1109                 limit = scrollbar.getValue() -
1110                                          scrollbar.getBlockIncrement(direction);
1111             }
1112             else {
1113                 limit = scrollbar.getValue() +
1114                                          scrollbar.getBlockIncrement(direction);
1115             }
1116         }
1117 
1118         for (int i=0; i<units; i++) {
1119             if (direction > 0) {
1120                 delta = scrollbar.getUnitIncrement(direction);
1121             }
1122             else {
1123                 delta = -scrollbar.getUnitIncrement(direction);
1124             }
1125 
1126             int oldValue = scrollbar.getValue();
1127             int newValue = oldValue + delta;
1128 
1129             // Check for overflow.
1130             if (delta > 0 && newValue < oldValue) {
1131                 newValue = scrollbar.getMaximum();
1132             }
1133             else if (delta < 0 && newValue > oldValue) {
1134                 newValue = scrollbar.getMinimum();
1135             }
1136             if (oldValue == newValue) {
1137                 break;
1138             }
1139 
1140             if (limitToBlock && i > 0) {
1141                 assert limit != -1;
1142                 if ((direction < 0 && newValue < limit) ||
1143                     (direction > 0 && newValue > limit)) {
1144                     break;
1145                 }
1146             }
1147             scrollbar.setValue(newValue);
1148         }
1149     }
1150 
1151     /**
1152      * Scrolls by unit.
1153      * @param direction the direction to scroll
1154      */
1155     protected void scrollByUnit(int direction)  {
1156         scrollByUnits(scrollbar, direction, 1, false);
1157     }
1158 
1159     /**
1160      * Indicates whether the user can absolutely position the thumb with
1161      * a mouse gesture (usually the middle mouse button).
1162      *
1163      * @return true if a mouse gesture can absolutely position the thumb
1164      * @since 1.5
1165      */
1166     public boolean getSupportsAbsolutePositioning() {
1167         return supportsAbsolutePositioning;
1168     }
1169 
1170     /**
1171      * A listener to listen for model changes.
1172      */
1173     protected class ModelListener implements ChangeListener {
1174         public void stateChanged(ChangeEvent e) {
1175             if (!useCachedValue) {
1176                 scrollBarValue = scrollbar.getValue();
1177             }
1178             layoutContainer(scrollbar);
1179             useCachedValue = false;
1180         }
1181     }
1182 
1183 
1184     /**
1185      * Track mouse drags.
1186      */
1187     protected class TrackListener
1188         extends MouseAdapter implements MouseMotionListener
1189     {
1190         /** The offset */
1191         protected transient int offset;
1192         /** Current mouse x position */
1193         protected transient int currentMouseX;
1194         /** Current mouse y position */
1195         protected transient int currentMouseY;
1196         private transient int direction = +1;
1197 
1198         /** {@inheritDoc} */
1199         public void mouseReleased(MouseEvent e)
1200         {
1201             if (isDragging) {
1202                 updateThumbState(e.getX(), e.getY());
1203             }
1204             if (SwingUtilities.isRightMouseButton(e) ||
1205                 (!getSupportsAbsolutePositioning() &&
1206                  SwingUtilities.isMiddleMouseButton(e)))
1207                 return;
1208             if(!scrollbar.isEnabled())
1209                 return;
1210 
1211             Rectangle r = getTrackBounds();
1212             scrollbar.repaint(r.x, r.y, r.width, r.height);
1213 
1214             trackHighlight = NO_HIGHLIGHT;
1215             isDragging = false;
1216             offset = 0;
1217             scrollTimer.stop();
1218             useCachedValue = true;
1219             scrollbar.setValueIsAdjusting(false);
1220         }
1221 
1222 
1223         /**
1224          * If the mouse is pressed above the "thumb" component
1225          * then reduce the scrollbars value by one page ("page up"),
1226          * otherwise increase it by one page.  If there is no
1227          * thumb then page up if the mouse is in the upper half
1228          * of the track.
1229          */
1230         public void mousePressed(MouseEvent e)
1231         {
1232             if (SwingUtilities.isRightMouseButton(e) ||
1233                 (!getSupportsAbsolutePositioning() &&
1234                  SwingUtilities.isMiddleMouseButton(e)))
1235                 return;
1236             if(!scrollbar.isEnabled())
1237                 return;
1238 
1239             if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
1240                 scrollbar.requestFocus();
1241             }
1242 
1243             useCachedValue = true;
1244             scrollbar.setValueIsAdjusting(true);
1245 
1246             currentMouseX = e.getX();
1247             currentMouseY = e.getY();
1248 
1249             // Clicked in the Thumb area?
1250             if(getThumbBounds().contains(currentMouseX, currentMouseY)) {
1251                 switch (scrollbar.getOrientation()) {
1252                 case JScrollBar.VERTICAL:
1253                     offset = currentMouseY - getThumbBounds().y;
1254                     break;
1255                 case JScrollBar.HORIZONTAL:
1256                     offset = currentMouseX - getThumbBounds().x;
1257                     break;
1258                 }
1259                 isDragging = true;
1260                 return;
1261             }
1262             else if (getSupportsAbsolutePositioning() &&
1263                      SwingUtilities.isMiddleMouseButton(e)) {
1264                 switch (scrollbar.getOrientation()) {
1265                 case JScrollBar.VERTICAL:
1266                     offset = getThumbBounds().height / 2;
1267                     break;
1268                 case JScrollBar.HORIZONTAL:
1269                     offset = getThumbBounds().width / 2;
1270                     break;
1271                 }
1272                 isDragging = true;
1273                 setValueFrom(e);
1274                 return;
1275             }
1276             isDragging = false;
1277 
1278             Dimension sbSize = scrollbar.getSize();
1279             direction = +1;
1280 
1281             switch (scrollbar.getOrientation()) {
1282             case JScrollBar.VERTICAL:
1283                 if (getThumbBounds().isEmpty()) {
1284                     int scrollbarCenter = sbSize.height / 2;
1285                     direction = (currentMouseY < scrollbarCenter) ? -1 : +1;
1286                 } else {
1287                     int thumbY = getThumbBounds().y;
1288                     direction = (currentMouseY < thumbY) ? -1 : +1;
1289                 }
1290                 break;
1291             case JScrollBar.HORIZONTAL:
1292                 if (getThumbBounds().isEmpty()) {
1293                     int scrollbarCenter = sbSize.width / 2;
1294                     direction = (currentMouseX < scrollbarCenter) ? -1 : +1;
1295                 } else {
1296                     int thumbX = getThumbBounds().x;
1297                     direction = (currentMouseX < thumbX) ? -1 : +1;
1298                 }
1299                 if (!scrollbar.getComponentOrientation().isLeftToRight()) {
1300                     direction = -direction;
1301                 }
1302                 break;
1303             }
1304             scrollByBlock(direction);
1305 
1306             scrollTimer.stop();
1307             scrollListener.setDirection(direction);
1308             scrollListener.setScrollByBlock(true);
1309             startScrollTimerIfNecessary();
1310         }
1311 
1312 
1313         /**
1314          * Set the models value to the position of the thumb's top of Vertical
1315          * scrollbar, or the left/right of Horizontal scrollbar in
1316          * left-to-right/right-to-left scrollbar relative to the origin of the
1317          * track.
1318          */
1319         public void mouseDragged(MouseEvent e) {
1320             if (SwingUtilities.isRightMouseButton(e) ||
1321                 (!getSupportsAbsolutePositioning() &&
1322                  SwingUtilities.isMiddleMouseButton(e)))
1323                 return;
1324             if(!scrollbar.isEnabled() || getThumbBounds().isEmpty()) {
1325                 return;
1326             }
1327             if (isDragging) {
1328                 setValueFrom(e);
1329             } else {
1330                 currentMouseX = e.getX();
1331                 currentMouseY = e.getY();
1332                 updateThumbState(currentMouseX, currentMouseY);
1333                 startScrollTimerIfNecessary();
1334             }
1335         }
1336 
1337         private void setValueFrom(MouseEvent e) {
1338             boolean active = isThumbRollover();
1339             BoundedRangeModel model = scrollbar.getModel();
1340             Rectangle thumbR = getThumbBounds();
1341             float trackLength;
1342             int thumbMin, thumbMax, thumbPos;
1343 
1344             if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
1345                 thumbMin = trackRect.y;
1346                 thumbMax = trackRect.y + trackRect.height - thumbR.height;
1347                 thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getY() - offset)));
1348                 setThumbBounds(thumbR.x, thumbPos, thumbR.width, thumbR.height);
1349                 trackLength = getTrackBounds().height;
1350             }
1351             else {
1352                 thumbMin = trackRect.x;
1353                 thumbMax = trackRect.x + trackRect.width - thumbR.width;
1354                 thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getX() - offset)));
1355                 setThumbBounds(thumbPos, thumbR.y, thumbR.width, thumbR.height);
1356                 trackLength = getTrackBounds().width;
1357             }
1358 
1359             /* Set the scrollbars value.  If the thumb has reached the end of
1360              * the scrollbar, then just set the value to its maximum.  Otherwise
1361              * compute the value as accurately as possible.
1362              */
1363             if (thumbPos == thumbMax) {
1364                 if (scrollbar.getOrientation() == JScrollBar.VERTICAL ||
1365                     scrollbar.getComponentOrientation().isLeftToRight()) {
1366                     scrollbar.setValue(model.getMaximum() - model.getExtent());
1367                 } else {
1368                     scrollbar.setValue(model.getMinimum());
1369                 }
1370             }
1371             else {
1372                 float valueMax = model.getMaximum() - model.getExtent();
1373                 float valueRange = valueMax - model.getMinimum();
1374                 float thumbValue = thumbPos - thumbMin;
1375                 float thumbRange = thumbMax - thumbMin;
1376                 int value;
1377                 if (scrollbar.getOrientation() == JScrollBar.VERTICAL ||
1378                     scrollbar.getComponentOrientation().isLeftToRight()) {
1379                     value = (int)(0.5 + ((thumbValue / thumbRange) * valueRange));
1380                 } else {
1381                     value = (int)(0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange));
1382                 }
1383 
1384                 useCachedValue = true;
1385                 scrollBarValue = value + model.getMinimum();
1386                 scrollbar.setValue(adjustValueIfNecessary(scrollBarValue));
1387             }
1388             setThumbRollover(active);
1389         }
1390 
1391         private int adjustValueIfNecessary(int value) {
1392             if (scrollbar.getParent() instanceof JScrollPane) {
1393                 JScrollPane scrollpane = (JScrollPane)scrollbar.getParent();
1394                 JViewport viewport = scrollpane.getViewport();
1395                 Component view = viewport.getView();
1396                 if (view instanceof JList) {
1397                     JList<?> list = (JList)view;
1398                     if (DefaultLookup.getBoolean(list, list.getUI(),
1399                                                  "List.lockToPositionOnScroll", false)) {
1400                         int adjustedValue = value;
1401                         int mode = list.getLayoutOrientation();
1402                         int orientation = scrollbar.getOrientation();
1403                         if (orientation == JScrollBar.VERTICAL && mode == JList.VERTICAL) {
1404                             int index = list.locationToIndex(new Point(0, value));
1405                             Rectangle rect = list.getCellBounds(index, index);
1406                             if (rect != null) {
1407                                 adjustedValue = rect.y;
1408                             }
1409                         }
1410                         if (orientation == JScrollBar.HORIZONTAL &&
1411                             (mode == JList.VERTICAL_WRAP || mode == JList.HORIZONTAL_WRAP)) {
1412                             if (scrollpane.getComponentOrientation().isLeftToRight()) {
1413                                 int index = list.locationToIndex(new Point(value, 0));
1414                                 Rectangle rect = list.getCellBounds(index, index);
1415                                 if (rect != null) {
1416                                     adjustedValue = rect.x;
1417                                 }
1418                             }
1419                             else {
1420                                 Point loc = new Point(value, 0);
1421                                 int extent = viewport.getExtentSize().width;
1422                                 loc.x += extent - 1;
1423                                 int index = list.locationToIndex(loc);
1424                                 Rectangle rect = list.getCellBounds(index, index);
1425                                 if (rect != null) {
1426                                     adjustedValue = rect.x + rect.width - extent;
1427                                 }
1428                             }
1429                         }
1430                         value = adjustedValue;
1431 
1432                     }
1433                 }
1434             }
1435             return value;
1436         }
1437 
1438         private void startScrollTimerIfNecessary() {
1439             if (scrollTimer.isRunning()) {
1440                 return;
1441             }
1442 
1443             Rectangle tb = getThumbBounds();
1444 
1445             switch (scrollbar.getOrientation()) {
1446             case JScrollBar.VERTICAL:
1447                 if (direction > 0) {
1448                     if (tb.y + tb.height < trackListener.currentMouseY) {
1449                         scrollTimer.start();
1450                     }
1451                 } else if (tb.y > trackListener.currentMouseY) {
1452                     scrollTimer.start();
1453                 }
1454                 break;
1455             case JScrollBar.HORIZONTAL:
1456                 if ((direction > 0 && isMouseAfterThumb())
1457                         || (direction < 0 && isMouseBeforeThumb())) {
1458 
1459                     scrollTimer.start();
1460                 }
1461                 break;
1462             }
1463         }
1464 
1465         /** {@inheritDoc} */
1466         public void mouseMoved(MouseEvent e) {
1467             if (!isDragging) {
1468                 updateThumbState(e.getX(), e.getY());
1469             }
1470         }
1471 
1472         /**
1473          * Invoked when the mouse exits the scrollbar.
1474          *
1475          * @param e MouseEvent further describing the event
1476          * @since 1.5
1477          */
1478         public void mouseExited(MouseEvent e) {
1479             if (!isDragging) {
1480                 setThumbRollover(false);
1481             }
1482         }
1483     }
1484 
1485 
1486     /**
1487      * Listener for cursor keys.
1488      */
1489     protected class ArrowButtonListener extends MouseAdapter
1490     {
1491         // Because we are handling both mousePressed and Actions
1492         // we need to make sure we don't fire under both conditions.
1493         // (keyfocus on scrollbars causes action without mousePress
1494         boolean handledEvent;
1495 
1496         public void mousePressed(MouseEvent e)          {
1497             if(!scrollbar.isEnabled()) { return; }
1498             // not an unmodified left mouse button
1499             //if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
1500             if( ! SwingUtilities.isLeftMouseButton(e)) { return; }
1501 
1502             int direction = (e.getSource() == incrButton) ? 1 : -1;
1503 
1504             scrollByUnit(direction);
1505             scrollTimer.stop();
1506             scrollListener.setDirection(direction);
1507             scrollListener.setScrollByBlock(false);
1508             scrollTimer.start();
1509 
1510             handledEvent = true;
1511             if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
1512                 scrollbar.requestFocus();
1513             }
1514         }
1515 
1516         public void mouseReleased(MouseEvent e)         {
1517             scrollTimer.stop();
1518             handledEvent = false;
1519             scrollbar.setValueIsAdjusting(false);
1520         }
1521     }
1522 
1523 
1524     /**
1525      * Listener for scrolling events initiated in the
1526      * <code>ScrollPane</code>.
1527      */
1528     protected class ScrollListener implements ActionListener
1529     {
1530         int direction = +1;
1531         boolean useBlockIncrement;
1532 
1533         /** Constructs a {@code ScrollListener}. */
1534         public ScrollListener() {
1535             direction = +1;
1536             useBlockIncrement = false;
1537         }
1538 
1539         /**
1540          * Constructs a {@code ScrollListener}.
1541          * @param dir direction
1542          * @param block use block increment
1543          */
1544         public ScrollListener(int dir, boolean block)   {
1545             direction = dir;
1546             useBlockIncrement = block;
1547         }
1548 
1549         /**
1550          * Sets the direction.
1551          * @param direction the new direction
1552          */
1553         public void setDirection(int direction) { this.direction = direction; }
1554         /**
1555          * Sets the scrolling by block
1556          * @param block whether or not to scroll by block
1557          */
1558         public void setScrollByBlock(boolean block) { this.useBlockIncrement = block; }
1559 
1560         /** {@inheritDoc} */
1561         public void actionPerformed(ActionEvent e) {
1562             if(useBlockIncrement)       {
1563                 scrollByBlock(direction);
1564                 // Stop scrolling if the thumb catches up with the mouse
1565                 if(scrollbar.getOrientation() == JScrollBar.VERTICAL)   {
1566                     if(direction > 0)   {
1567                         if(getThumbBounds().y + getThumbBounds().height
1568                                 >= trackListener.currentMouseY)
1569                                     ((Timer)e.getSource()).stop();
1570                     } else if(getThumbBounds().y <= trackListener.currentMouseY)        {
1571                         ((Timer)e.getSource()).stop();
1572                     }
1573                 } else {
1574                     if ((direction > 0 && !isMouseAfterThumb())
1575                            || (direction < 0 && !isMouseBeforeThumb())) {
1576 
1577                        ((Timer)e.getSource()).stop();
1578                     }
1579                 }
1580             } else {
1581                 scrollByUnit(direction);
1582             }
1583 
1584             if(direction > 0
1585                 && scrollbar.getValue()+scrollbar.getVisibleAmount()
1586                         >= scrollbar.getMaximum())
1587                 ((Timer)e.getSource()).stop();
1588             else if(direction < 0
1589                 && scrollbar.getValue() <= scrollbar.getMinimum())
1590                 ((Timer)e.getSource()).stop();
1591         }
1592     }
1593 
1594     private boolean isMouseLeftOfThumb() {
1595         return trackListener.currentMouseX < getThumbBounds().x;
1596     }
1597 
1598     private boolean isMouseRightOfThumb() {
1599         Rectangle tb = getThumbBounds();
1600         return trackListener.currentMouseX > tb.x + tb.width;
1601     }
1602 
1603     private boolean isMouseBeforeThumb() {
1604         return scrollbar.getComponentOrientation().isLeftToRight()
1605             ? isMouseLeftOfThumb()
1606             : isMouseRightOfThumb();
1607     }
1608 
1609     private boolean isMouseAfterThumb() {
1610         return scrollbar.getComponentOrientation().isLeftToRight()
1611             ? isMouseRightOfThumb()
1612             : isMouseLeftOfThumb();
1613     }
1614 
1615     private void updateButtonDirections() {
1616         int orient = scrollbar.getOrientation();
1617         if (scrollbar.getComponentOrientation().isLeftToRight()) {
1618             if (incrButton instanceof BasicArrowButton) {
1619                 ((BasicArrowButton)incrButton).setDirection(
1620                         orient == HORIZONTAL? EAST : SOUTH);
1621             }
1622             if (decrButton instanceof BasicArrowButton) {
1623                 ((BasicArrowButton)decrButton).setDirection(
1624                         orient == HORIZONTAL? WEST : NORTH);
1625             }
1626         }
1627         else {
1628             if (incrButton instanceof BasicArrowButton) {
1629                 ((BasicArrowButton)incrButton).setDirection(
1630                         orient == HORIZONTAL? WEST : SOUTH);
1631             }
1632             if (decrButton instanceof BasicArrowButton) {
1633                 ((BasicArrowButton)decrButton).setDirection(
1634                         orient == HORIZONTAL ? EAST : NORTH);
1635             }
1636         }
1637     }
1638 
1639     /** Property change handler */
1640     public class PropertyChangeHandler implements PropertyChangeListener
1641     {
1642         // NOTE: This class exists only for backward compatibility. All
1643         // its functionality has been moved into Handler. If you need to add
1644         // new functionality add it to the Handler, but make sure this
1645         // class calls into the Handler.
1646         /** {@inheritDoc} */
1647         public void propertyChange(PropertyChangeEvent e) {
1648             getHandler().propertyChange(e);
1649         }
1650     }
1651 
1652 
1653     /**
1654      * Used for scrolling the scrollbar.
1655      */
1656     private static class Actions extends UIAction {
1657         private static final String POSITIVE_UNIT_INCREMENT =
1658                                     "positiveUnitIncrement";
1659         private static final String POSITIVE_BLOCK_INCREMENT =
1660                                     "positiveBlockIncrement";
1661         private static final String NEGATIVE_UNIT_INCREMENT =
1662                                     "negativeUnitIncrement";
1663         private static final String NEGATIVE_BLOCK_INCREMENT =
1664                                     "negativeBlockIncrement";
1665         private static final String MIN_SCROLL = "minScroll";
1666         private static final String MAX_SCROLL = "maxScroll";
1667 
1668         Actions(String name) {
1669             super(name);
1670         }
1671 
1672         public void actionPerformed(ActionEvent e) {
1673             JScrollBar scrollBar = (JScrollBar)e.getSource();
1674             String key = getName();
1675             if (key == POSITIVE_UNIT_INCREMENT) {
1676                 scroll(scrollBar, POSITIVE_SCROLL, false);
1677             }
1678             else if (key == POSITIVE_BLOCK_INCREMENT) {
1679                 scroll(scrollBar, POSITIVE_SCROLL, true);
1680             }
1681             else if (key == NEGATIVE_UNIT_INCREMENT) {
1682                 scroll(scrollBar, NEGATIVE_SCROLL, false);
1683             }
1684             else if (key == NEGATIVE_BLOCK_INCREMENT) {
1685                 scroll(scrollBar, NEGATIVE_SCROLL, true);
1686             }
1687             else if (key == MIN_SCROLL) {
1688                 scroll(scrollBar, BasicScrollBarUI.MIN_SCROLL, true);
1689             }
1690             else if (key == MAX_SCROLL) {
1691                 scroll(scrollBar, BasicScrollBarUI.MAX_SCROLL, true);
1692             }
1693         }
1694         private void scroll(JScrollBar scrollBar, int dir, boolean block) {
1695 
1696             if (dir == NEGATIVE_SCROLL || dir == POSITIVE_SCROLL) {
1697                 int amount;
1698                 // Don't use the BasicScrollBarUI.scrollByXXX methods as we
1699                 // don't want to use an invokeLater to reset the trackHighlight
1700                 // via an invokeLater
1701                 if (block) {
1702                     if (dir == NEGATIVE_SCROLL) {
1703                         amount = -1 * scrollBar.getBlockIncrement(-1);
1704                     }
1705                     else {
1706                         amount = scrollBar.getBlockIncrement(1);
1707                     }
1708                 }
1709                 else {
1710                     if (dir == NEGATIVE_SCROLL) {
1711                         amount = -1 * scrollBar.getUnitIncrement(-1);
1712                     }
1713                     else {
1714                         amount = scrollBar.getUnitIncrement(1);
1715                     }
1716                 }
1717                 scrollBar.setValue(scrollBar.getValue() + amount);
1718             }
1719             else if (dir == BasicScrollBarUI.MIN_SCROLL) {
1720                 scrollBar.setValue(scrollBar.getMinimum());
1721             }
1722             else if (dir == BasicScrollBarUI.MAX_SCROLL) {
1723                 scrollBar.setValue(scrollBar.getMaximum());
1724             }
1725         }
1726     }
1727 
1728 
1729     //
1730     // EventHandler
1731     //
1732     private class Handler implements FocusListener, PropertyChangeListener {
1733         //
1734         // FocusListener
1735         //
1736         public void focusGained(FocusEvent e) {
1737             scrollbar.repaint();
1738         }
1739 
1740         public void focusLost(FocusEvent e) {
1741             scrollbar.repaint();
1742         }
1743 
1744 
1745         //
1746         // PropertyChangeListener
1747         //
1748         public void propertyChange(PropertyChangeEvent e) {
1749             String propertyName = e.getPropertyName();
1750 
1751             if ("model" == propertyName) {
1752                 BoundedRangeModel oldModel = (BoundedRangeModel)e.getOldValue();
1753                 BoundedRangeModel newModel = (BoundedRangeModel)e.getNewValue();
1754                 oldModel.removeChangeListener(modelListener);
1755                 newModel.addChangeListener(modelListener);
1756                 scrollBarValue = scrollbar.getValue();
1757                 scrollbar.repaint();
1758                 scrollbar.revalidate();
1759             } else if ("orientation" == propertyName) {
1760                 updateButtonDirections();
1761             } else if ("componentOrientation" == propertyName) {
1762                 updateButtonDirections();
1763                 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
1764                 SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED, inputMap);
1765             }
1766         }
1767     }
1768 }