1/*
2 * Copyright (c) 1997, 2016, 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 */
25package javax.swing.plaf.basic;
26
27
28import sun.swing.DefaultLookup;
29import sun.swing.UIAction;
30
31import java.awt.*;
32import java.awt.event.*;
33
34import java.beans.*;
35
36import javax.swing.*;
37import javax.swing.event.*;
38import javax.swing.plaf.*;
39
40import static sun.swing.SwingUtilities2.drawHLine;
41import static sun.swing.SwingUtilities2.drawRect;
42import 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 */
52public 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 static final 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            setDragging(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                setDragging(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                setDragging(true);
1273                setValueFrom(e);
1274                return;
1275            }
1276            setDragging(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    private void setDragging(boolean dragging) {
1640        this.isDragging = dragging;
1641        scrollbar.repaint(getThumbBounds());
1642    }
1643
1644
1645    /** Property change handler */
1646    public class PropertyChangeHandler implements PropertyChangeListener
1647    {
1648        // NOTE: This class exists only for backward compatibility. All
1649        // its functionality has been moved into Handler. If you need to add
1650        // new functionality add it to the Handler, but make sure this
1651        // class calls into the Handler.
1652        /** {@inheritDoc} */
1653        public void propertyChange(PropertyChangeEvent e) {
1654            getHandler().propertyChange(e);
1655        }
1656    }
1657
1658
1659    /**
1660     * Used for scrolling the scrollbar.
1661     */
1662    private static class Actions extends UIAction {
1663        private static final String POSITIVE_UNIT_INCREMENT =
1664                                    "positiveUnitIncrement";
1665        private static final String POSITIVE_BLOCK_INCREMENT =
1666                                    "positiveBlockIncrement";
1667        private static final String NEGATIVE_UNIT_INCREMENT =
1668                                    "negativeUnitIncrement";
1669        private static final String NEGATIVE_BLOCK_INCREMENT =
1670                                    "negativeBlockIncrement";
1671        private static final String MIN_SCROLL = "minScroll";
1672        private static final String MAX_SCROLL = "maxScroll";
1673
1674        Actions(String name) {
1675            super(name);
1676        }
1677
1678        public void actionPerformed(ActionEvent e) {
1679            JScrollBar scrollBar = (JScrollBar)e.getSource();
1680            String key = getName();
1681            if (key == POSITIVE_UNIT_INCREMENT) {
1682                scroll(scrollBar, POSITIVE_SCROLL, false);
1683            }
1684            else if (key == POSITIVE_BLOCK_INCREMENT) {
1685                scroll(scrollBar, POSITIVE_SCROLL, true);
1686            }
1687            else if (key == NEGATIVE_UNIT_INCREMENT) {
1688                scroll(scrollBar, NEGATIVE_SCROLL, false);
1689            }
1690            else if (key == NEGATIVE_BLOCK_INCREMENT) {
1691                scroll(scrollBar, NEGATIVE_SCROLL, true);
1692            }
1693            else if (key == MIN_SCROLL) {
1694                scroll(scrollBar, BasicScrollBarUI.MIN_SCROLL, true);
1695            }
1696            else if (key == MAX_SCROLL) {
1697                scroll(scrollBar, BasicScrollBarUI.MAX_SCROLL, true);
1698            }
1699        }
1700        private void scroll(JScrollBar scrollBar, int dir, boolean block) {
1701
1702            if (dir == NEGATIVE_SCROLL || dir == POSITIVE_SCROLL) {
1703                int amount;
1704                // Don't use the BasicScrollBarUI.scrollByXXX methods as we
1705                // don't want to use an invokeLater to reset the trackHighlight
1706                // via an invokeLater
1707                if (block) {
1708                    if (dir == NEGATIVE_SCROLL) {
1709                        amount = -1 * scrollBar.getBlockIncrement(-1);
1710                    }
1711                    else {
1712                        amount = scrollBar.getBlockIncrement(1);
1713                    }
1714                }
1715                else {
1716                    if (dir == NEGATIVE_SCROLL) {
1717                        amount = -1 * scrollBar.getUnitIncrement(-1);
1718                    }
1719                    else {
1720                        amount = scrollBar.getUnitIncrement(1);
1721                    }
1722                }
1723                scrollBar.setValue(scrollBar.getValue() + amount);
1724            }
1725            else if (dir == BasicScrollBarUI.MIN_SCROLL) {
1726                scrollBar.setValue(scrollBar.getMinimum());
1727            }
1728            else if (dir == BasicScrollBarUI.MAX_SCROLL) {
1729                scrollBar.setValue(scrollBar.getMaximum());
1730            }
1731        }
1732    }
1733
1734
1735    //
1736    // EventHandler
1737    //
1738    private class Handler implements FocusListener, PropertyChangeListener {
1739        //
1740        // FocusListener
1741        //
1742        public void focusGained(FocusEvent e) {
1743            scrollbar.repaint();
1744        }
1745
1746        public void focusLost(FocusEvent e) {
1747            scrollbar.repaint();
1748        }
1749
1750
1751        //
1752        // PropertyChangeListener
1753        //
1754        public void propertyChange(PropertyChangeEvent e) {
1755            String propertyName = e.getPropertyName();
1756
1757            if ("model" == propertyName) {
1758                BoundedRangeModel oldModel = (BoundedRangeModel)e.getOldValue();
1759                BoundedRangeModel newModel = (BoundedRangeModel)e.getNewValue();
1760                oldModel.removeChangeListener(modelListener);
1761                newModel.addChangeListener(modelListener);
1762                scrollBarValue = scrollbar.getValue();
1763                scrollbar.repaint();
1764                scrollbar.revalidate();
1765            } else if ("orientation" == propertyName) {
1766                updateButtonDirections();
1767            } else if ("componentOrientation" == propertyName) {
1768                updateButtonDirections();
1769                InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
1770                SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED, inputMap);
1771            }
1772        }
1773    }
1774}
1775