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