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