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