1 /*
2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.metal;
27 
28 import javax.swing.plaf.basic.BasicSliderUI;
29 
30 import java.awt.Graphics;
31 import java.awt.Dimension;
32 import java.awt.Rectangle;
33 import java.awt.Color;
34 import java.beans.*;
35 
36 import javax.swing.*;
37 import javax.swing.plaf.*;
38 
39 /**
40  * A Java L&F implementation of SliderUI.
41  * <p>
42  * <strong>Warning:</strong>
43  * Serialized objects of this class will not be compatible with
44  * future Swing releases. The current serialization support is
45  * appropriate for short term storage or RMI between applications running
46  * the same version of Swing.  As of 1.4, support for long term storage
47  * of all JavaBeans&trade;
48  * has been added to the <code>java.beans</code> package.
49  * Please see {@link java.beans.XMLEncoder}.
50  *
51  * @author Tom Santos
52  */
53 @SuppressWarnings("serial") // Same-version serialization only
54 public class MetalSliderUI extends BasicSliderUI {
55 
56     /**
57      * The buffer of a tick.
58      */
59     protected final int TICK_BUFFER = 4;
60 
61     /**
62      * The value of the property {@code JSlider.isFilled}.
63      * By default, {@code false} if the property is not set,
64      * {@code true} for Ocean theme.
65      */
66     protected boolean filledSlider = false;
67 
68     // NOTE: these next five variables are currently unused.
69     /**
70      * The color of a thumb
71      */
72     protected static Color thumbColor;
73 
74     /**
75      * The color of highlighting.
76      */
77     protected static Color highlightColor;
78 
79     /**
80      * The color of dark shadow.
81      */
82     protected static Color darkShadowColor;
83 
84     /**
85      * The width of a track.
86      */
87     protected static int trackWidth;
88 
89     /**
90      * The length of a tick.
91      */
92     protected static int tickLength;
93     private int safeLength;
94 
95    /**
96     * A default horizontal thumb <code>Icon</code>. This field might not be
97     * used. To change the <code>Icon</code> used by this delegate directly set it
98     * using the <code>Slider.horizontalThumbIcon</code> UIManager property.
99     */
100     protected static Icon horizThumbIcon;
101 
102    /**
103     * A default vertical thumb <code>Icon</code>. This field might not be
104     * used. To change the <code>Icon</code> used by this delegate directly set it
105     * using the <code>Slider.verticalThumbIcon</code> UIManager property.
106     */
107     protected static Icon vertThumbIcon;
108 
109     private static Icon SAFE_HORIZ_THUMB_ICON;
110     private static Icon SAFE_VERT_THUMB_ICON;
111 
112     /**
113      * Property for {@code JSlider.isFilled}.
114      */
115     protected final String SLIDER_FILL = "JSlider.isFilled";
116 
117     /**
118      * Constructs a {@code MetalSliderUI} instance.
119      *
120      * @param c a component
121      * @return a {@code MetalSliderUI} instance
122      */
createUI(JComponent c)123     public static ComponentUI createUI(JComponent c)    {
124         return new MetalSliderUI();
125     }
126 
127     /**
128      * Constructs a {@code MetalSliderUI} instance.
129      */
MetalSliderUI()130     public MetalSliderUI() {
131         super( null );
132     }
133 
getHorizThumbIcon()134     private static Icon getHorizThumbIcon() {
135         if (System.getSecurityManager() != null) {
136             return SAFE_HORIZ_THUMB_ICON;
137         } else {
138             return horizThumbIcon;
139         }
140     }
141 
getVertThumbIcon()142     private static Icon getVertThumbIcon() {
143         if (System.getSecurityManager() != null) {
144             return SAFE_VERT_THUMB_ICON;
145         } else {
146             return vertThumbIcon;
147         }
148     }
149 
installUI( JComponent c )150     public void installUI( JComponent c ) {
151         trackWidth = ((Integer)UIManager.get( "Slider.trackWidth" )).intValue();
152         tickLength = safeLength = ((Integer)UIManager.get( "Slider.majorTickLength" )).intValue();
153         horizThumbIcon = SAFE_HORIZ_THUMB_ICON =
154                 UIManager.getIcon( "Slider.horizontalThumbIcon" );
155         vertThumbIcon = SAFE_VERT_THUMB_ICON =
156                 UIManager.getIcon( "Slider.verticalThumbIcon" );
157 
158         super.installUI( c );
159 
160         thumbColor = UIManager.getColor("Slider.thumb");
161         highlightColor = UIManager.getColor("Slider.highlight");
162         darkShadowColor = UIManager.getColor("Slider.darkShadow");
163 
164         scrollListener.setScrollByBlock( false );
165 
166         prepareFilledSliderField();
167     }
168 
169     /**
170      * Constructs {@code MetalPropertyListener}.
171      *
172      * @param slider a {@code JSlider}
173      * @return the {@code MetalPropertyListener}
174      */
createPropertyChangeListener( JSlider slider )175     protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
176         return new MetalPropertyListener();
177     }
178 
179     /**
180      * {@code PropertyListener} for {@code JSlider.isFilled}.
181      */
182     protected class MetalPropertyListener extends BasicSliderUI.PropertyChangeHandler {
propertyChange( PropertyChangeEvent e )183         public void propertyChange( PropertyChangeEvent e ) {  // listen for slider fill
184             super.propertyChange( e );
185 
186             if (e.getPropertyName().equals(SLIDER_FILL)) {
187                 prepareFilledSliderField();
188             }
189         }
190     }
191 
prepareFilledSliderField()192     private void prepareFilledSliderField() {
193         // Use true for Ocean theme
194         filledSlider = MetalLookAndFeel.usingOcean();
195 
196         Object sliderFillProp = slider.getClientProperty(SLIDER_FILL);
197 
198         if (sliderFillProp != null) {
199             filledSlider = ((Boolean) sliderFillProp).booleanValue();
200         }
201     }
202 
paintThumb(Graphics g)203     public void paintThumb(Graphics g)  {
204         Rectangle knobBounds = thumbRect;
205 
206         g.translate( knobBounds.x, knobBounds.y );
207 
208         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
209             getHorizThumbIcon().paintIcon( slider, g, 0, 0 );
210         }
211         else {
212             getVertThumbIcon().paintIcon( slider, g, 0, 0 );
213         }
214 
215         g.translate( -knobBounds.x, -knobBounds.y );
216     }
217 
218     /**
219      * Returns a rectangle enclosing the track that will be painted.
220      */
getPaintTrackRect()221     private Rectangle getPaintTrackRect() {
222         int trackLeft = 0, trackRight, trackTop = 0, trackBottom;
223         if (slider.getOrientation() == JSlider.HORIZONTAL) {
224             trackBottom = (trackRect.height - 1) - getThumbOverhang();
225             trackTop = trackBottom - (getTrackWidth() - 1);
226             trackRight = trackRect.width - 1;
227         }
228         else {
229             if (MetalUtils.isLeftToRight(slider)) {
230                 trackLeft = (trackRect.width - getThumbOverhang()) -
231                                                          getTrackWidth();
232                 trackRight = (trackRect.width - getThumbOverhang()) - 1;
233             }
234             else {
235                 trackLeft = getThumbOverhang();
236                 trackRight = getThumbOverhang() + getTrackWidth() - 1;
237             }
238             trackBottom = trackRect.height - 1;
239         }
240         return new Rectangle(trackRect.x + trackLeft, trackRect.y + trackTop,
241                              trackRight - trackLeft, trackBottom - trackTop);
242     }
243 
paintTrack(Graphics g)244     public void paintTrack(Graphics g)  {
245         if (MetalLookAndFeel.usingOcean()) {
246             oceanPaintTrack(g);
247             return;
248         }
249         Color trackColor = !slider.isEnabled() ? MetalLookAndFeel.getControlShadow() :
250                            slider.getForeground();
251 
252         boolean leftToRight = MetalUtils.isLeftToRight(slider);
253 
254         g.translate( trackRect.x, trackRect.y );
255 
256         int trackLeft = 0;
257         int trackTop = 0;
258         int trackRight;
259         int trackBottom;
260 
261         // Draw the track
262         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
263             trackBottom = (trackRect.height - 1) - getThumbOverhang();
264             trackTop = trackBottom - (getTrackWidth() - 1);
265             trackRight = trackRect.width - 1;
266         }
267         else {
268             if (leftToRight) {
269                 trackLeft = (trackRect.width - getThumbOverhang()) -
270                                                          getTrackWidth();
271                 trackRight = (trackRect.width - getThumbOverhang()) - 1;
272             }
273             else {
274                 trackLeft = getThumbOverhang();
275                 trackRight = getThumbOverhang() + getTrackWidth() - 1;
276             }
277             trackBottom = trackRect.height - 1;
278         }
279 
280         if ( slider.isEnabled() ) {
281             g.setColor( MetalLookAndFeel.getControlDarkShadow() );
282             g.drawRect( trackLeft, trackTop,
283                         (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 );
284 
285             g.setColor( MetalLookAndFeel.getControlHighlight() );
286             g.drawLine( trackLeft + 1, trackBottom, trackRight, trackBottom );
287             g.drawLine( trackRight, trackTop + 1, trackRight, trackBottom );
288 
289             g.setColor( MetalLookAndFeel.getControlShadow() );
290             g.drawLine( trackLeft + 1, trackTop + 1, trackRight - 2, trackTop + 1 );
291             g.drawLine( trackLeft + 1, trackTop + 1, trackLeft + 1, trackBottom - 2 );
292         }
293         else {
294             g.setColor( MetalLookAndFeel.getControlShadow() );
295             g.drawRect( trackLeft, trackTop,
296                         (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 );
297         }
298 
299         // Draw the fill
300         if ( filledSlider ) {
301             int middleOfThumb;
302             int fillTop;
303             int fillLeft;
304             int fillBottom;
305             int fillRight;
306 
307             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
308                 middleOfThumb = thumbRect.x + (thumbRect.width / 2);
309                 middleOfThumb -= trackRect.x; // To compensate for the g.translate()
310                 fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
311                 fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2;
312 
313                 if ( !drawInverted() ) {
314                     fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
315                     fillRight = middleOfThumb;
316                 }
317                 else {
318                     fillLeft = middleOfThumb;
319                     fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2;
320                 }
321             }
322             else {
323                 middleOfThumb = thumbRect.y + (thumbRect.height / 2);
324                 middleOfThumb -= trackRect.y; // To compensate for the g.translate()
325                 fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
326                 fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2;
327 
328                 if ( !drawInverted() ) {
329                     fillTop = middleOfThumb;
330                     fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2;
331                 }
332                 else {
333                     fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
334                     fillBottom = middleOfThumb;
335                 }
336             }
337 
338             if ( slider.isEnabled() ) {
339                 g.setColor( slider.getBackground() );
340                 g.drawLine( fillLeft, fillTop, fillRight, fillTop );
341                 g.drawLine( fillLeft, fillTop, fillLeft, fillBottom );
342 
343                 g.setColor( MetalLookAndFeel.getControlShadow() );
344                 g.fillRect( fillLeft + 1, fillTop + 1,
345                             fillRight - fillLeft, fillBottom - fillTop );
346             }
347             else {
348                 g.setColor( MetalLookAndFeel.getControlShadow() );
349                 g.fillRect(fillLeft, fillTop, fillRight - fillLeft, fillBottom - fillTop);
350             }
351         }
352 
353         g.translate( -trackRect.x, -trackRect.y );
354     }
355 
oceanPaintTrack(Graphics g)356     private void oceanPaintTrack(Graphics g)  {
357         boolean leftToRight = MetalUtils.isLeftToRight(slider);
358         boolean drawInverted = drawInverted();
359         Color sliderAltTrackColor = (Color)UIManager.get(
360                                     "Slider.altTrackColor");
361 
362         // Translate to the origin of the painting rectangle
363         Rectangle paintRect = getPaintTrackRect();
364         g.translate(paintRect.x, paintRect.y);
365 
366         // Width and height of the painting rectangle.
367         int w = paintRect.width;
368         int h = paintRect.height;
369 
370         if (slider.getOrientation() == JSlider.HORIZONTAL) {
371             int middleOfThumb = thumbRect.x + thumbRect.width / 2 - paintRect.x;
372 
373             if (slider.isEnabled()) {
374                 int fillMinX;
375                 int fillMaxX;
376 
377                 if (middleOfThumb > 0) {
378                     g.setColor(drawInverted ? MetalLookAndFeel.getControlDarkShadow() :
379                             MetalLookAndFeel.getPrimaryControlDarkShadow());
380 
381                     g.drawRect(0, 0, middleOfThumb - 1, h - 1);
382                 }
383 
384                 if (middleOfThumb < w) {
385                     g.setColor(drawInverted ? MetalLookAndFeel.getPrimaryControlDarkShadow() :
386                             MetalLookAndFeel.getControlDarkShadow());
387 
388                     g.drawRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1);
389                 }
390 
391                 if (filledSlider) {
392                     g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
393                     if (drawInverted) {
394                         fillMinX = middleOfThumb;
395                         fillMaxX = w - 2;
396                         g.drawLine(1, 1, middleOfThumb, 1);
397                     } else {
398                         fillMinX = 1;
399                         fillMaxX = middleOfThumb;
400                         g.drawLine(middleOfThumb, 1, w - 1, 1);
401                     }
402                     if (h == 6) {
403                         g.setColor(MetalLookAndFeel.getWhite());
404                         g.drawLine(fillMinX, 1, fillMaxX, 1);
405                         g.setColor(sliderAltTrackColor);
406                         g.drawLine(fillMinX, 2, fillMaxX, 2);
407                         g.setColor(MetalLookAndFeel.getControlShadow());
408                         g.drawLine(fillMinX, 3, fillMaxX, 3);
409                         g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
410                         g.drawLine(fillMinX, 4, fillMaxX, 4);
411                     }
412                 }
413             } else {
414                 g.setColor(MetalLookAndFeel.getControlShadow());
415 
416                 if (middleOfThumb > 0) {
417                     if (!drawInverted && filledSlider) {
418                         g.fillRect(0, 0, middleOfThumb - 1, h - 1);
419                     } else {
420                         g.drawRect(0, 0, middleOfThumb - 1, h - 1);
421                     }
422                 }
423 
424                 if (middleOfThumb < w) {
425                     if (drawInverted && filledSlider) {
426                         g.fillRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1);
427                     } else {
428                         g.drawRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1);
429                     }
430                 }
431             }
432         } else {
433             int middleOfThumb = thumbRect.y + (thumbRect.height / 2) - paintRect.y;
434 
435             if (slider.isEnabled()) {
436                 int fillMinY;
437                 int fillMaxY;
438 
439                 if (middleOfThumb > 0) {
440                     g.setColor(drawInverted ? MetalLookAndFeel.getPrimaryControlDarkShadow() :
441                             MetalLookAndFeel.getControlDarkShadow());
442 
443                     g.drawRect(0, 0, w - 1, middleOfThumb - 1);
444                 }
445 
446                 if (middleOfThumb < h) {
447                     g.setColor(drawInverted ? MetalLookAndFeel.getControlDarkShadow() :
448                             MetalLookAndFeel.getPrimaryControlDarkShadow());
449 
450                     g.drawRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1);
451                 }
452 
453                 if (filledSlider) {
454                     g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
455                     if (drawInverted()) {
456                         fillMinY = 1;
457                         fillMaxY = middleOfThumb;
458                         if (leftToRight) {
459                             g.drawLine(1, middleOfThumb, 1, h - 1);
460                         } else {
461                             g.drawLine(w - 2, middleOfThumb, w - 2, h - 1);
462                         }
463                     } else {
464                         fillMinY = middleOfThumb;
465                         fillMaxY = h - 2;
466                         if (leftToRight) {
467                             g.drawLine(1, 1, 1, middleOfThumb);
468                         } else {
469                             g.drawLine(w - 2, 1, w - 2, middleOfThumb);
470                         }
471                     }
472                     if (w == 6) {
473                         g.setColor(leftToRight ? MetalLookAndFeel.getWhite() : MetalLookAndFeel.getPrimaryControlShadow());
474                         g.drawLine(1, fillMinY, 1, fillMaxY);
475                         g.setColor(leftToRight ? sliderAltTrackColor : MetalLookAndFeel.getControlShadow());
476                         g.drawLine(2, fillMinY, 2, fillMaxY);
477                         g.setColor(leftToRight ? MetalLookAndFeel.getControlShadow() : sliderAltTrackColor);
478                         g.drawLine(3, fillMinY, 3, fillMaxY);
479                         g.setColor(leftToRight ? MetalLookAndFeel.getPrimaryControlShadow() : MetalLookAndFeel.getWhite());
480                         g.drawLine(4, fillMinY, 4, fillMaxY);
481                     }
482                 }
483             } else {
484                 g.setColor(MetalLookAndFeel.getControlShadow());
485 
486                 if (middleOfThumb > 0) {
487                     if (drawInverted && filledSlider) {
488                         g.fillRect(0, 0, w - 1, middleOfThumb - 1);
489                     } else {
490                         g.drawRect(0, 0, w - 1, middleOfThumb - 1);
491                     }
492                 }
493 
494                 if (middleOfThumb < h) {
495                     if (!drawInverted && filledSlider) {
496                         g.fillRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1);
497                     } else {
498                         g.drawRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1);
499                     }
500                 }
501             }
502         }
503 
504         g.translate(-paintRect.x, -paintRect.y);
505     }
506 
paintFocus(Graphics g)507     public void paintFocus(Graphics g)  {
508     }
509 
getThumbSize()510     protected Dimension getThumbSize() {
511         Dimension size = new Dimension();
512 
513         if ( slider.getOrientation() == JSlider.VERTICAL ) {
514             size.width = getVertThumbIcon().getIconWidth();
515             size.height = getVertThumbIcon().getIconHeight();
516         }
517         else {
518             size.width = getHorizThumbIcon().getIconWidth();
519             size.height = getHorizThumbIcon().getIconHeight();
520         }
521 
522         return size;
523     }
524 
525     /**
526      * Gets the height of the tick area for horizontal sliders and the width of the
527      * tick area for vertical sliders.  BasicSliderUI uses the returned value to
528      * determine the tick area rectangle.
529      */
getTickLength()530     public int getTickLength() {
531         return slider.getOrientation() == JSlider.HORIZONTAL ? safeLength + TICK_BUFFER + 1 :
532         safeLength + TICK_BUFFER + 3;
533     }
534 
535     /**
536      * Returns the shorter dimension of the track.
537      *
538      * @return the shorter dimension of the track
539      */
getTrackWidth()540     protected int getTrackWidth() {
541         // This strange calculation is here to keep the
542         // track in proportion to the thumb.
543         final double kIdealTrackWidth = 7.0;
544         final double kIdealThumbHeight = 16.0;
545         final double kWidthScalar = kIdealTrackWidth / kIdealThumbHeight;
546 
547         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
548             return (int)(kWidthScalar * thumbRect.height);
549         }
550         else {
551             return (int)(kWidthScalar * thumbRect.width);
552         }
553     }
554 
555     /**
556      * Returns the longer dimension of the slide bar.  (The slide bar is only the
557      * part that runs directly under the thumb)
558      *
559      * @return the longer dimension of the slide bar
560      */
getTrackLength()561     protected int getTrackLength() {
562         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
563             return trackRect.width;
564         }
565         return trackRect.height;
566     }
567 
568     /**
569      * Returns the amount that the thumb goes past the slide bar.
570      *
571      * @return the amount that the thumb goes past the slide bar
572      */
getThumbOverhang()573     protected int getThumbOverhang() {
574         return (int)(getThumbSize().getHeight()-getTrackWidth())/2;
575     }
576 
scrollDueToClickInTrack( int dir )577     protected void scrollDueToClickInTrack( int dir ) {
578         scrollByUnit( dir );
579     }
580 
paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x )581     protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
582         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
583         g.drawLine( x, TICK_BUFFER, x, TICK_BUFFER + (safeLength / 2) );
584     }
585 
paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x )586     protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
587         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
588         g.drawLine( x, TICK_BUFFER , x, TICK_BUFFER + (safeLength - 1) );
589     }
590 
paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y )591     protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
592         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
593 
594         if (MetalUtils.isLeftToRight(slider)) {
595             g.drawLine( TICK_BUFFER, y, TICK_BUFFER + (safeLength / 2), y );
596         }
597         else {
598             g.drawLine( 0, y, safeLength/2, y );
599         }
600     }
601 
paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y )602     protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
603         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
604 
605         if (MetalUtils.isLeftToRight(slider)) {
606             g.drawLine( TICK_BUFFER, y, TICK_BUFFER + safeLength, y );
607         }
608         else {
609             g.drawLine( 0, y, safeLength, y );
610         }
611     }
612 }
613