1 /*
2  * Copyright (c) 1998, 2013, 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 public class MetalSliderUI extends BasicSliderUI {
54 
55     protected final int TICK_BUFFER = 4;
56     protected boolean filledSlider = false;
57     // NOTE: these next five variables are currently unused.
58     protected static Color thumbColor;
59     protected static Color highlightColor;
60     protected static Color darkShadowColor;
61     protected static int trackWidth;
62     protected static int tickLength;
63     private int safeLength;
64 
65    /**
66     * A default horizontal thumb <code>Icon</code>. This field might not be
67     * used. To change the <code>Icon</code> used by this delegate directly set it
68     * using the <code>Slider.horizontalThumbIcon</code> UIManager property.
69     */
70     protected static Icon horizThumbIcon;
71 
72    /**
73     * A default vertical thumb <code>Icon</code>. This field might not be
74     * used. To change the <code>Icon</code> used by this delegate directly set it
75     * using the <code>Slider.verticalThumbIcon</code> UIManager property.
76     */
77     protected static Icon vertThumbIcon;
78 
79     private static Icon SAFE_HORIZ_THUMB_ICON;
80     private static Icon SAFE_VERT_THUMB_ICON;
81 
82 
83     protected final String SLIDER_FILL = "JSlider.isFilled";
84 
createUI(JComponent c)85     public static ComponentUI createUI(JComponent c)    {
86         return new MetalSliderUI();
87     }
88 
MetalSliderUI()89     public MetalSliderUI() {
90         super( null );
91     }
92 
getHorizThumbIcon()93     private static Icon getHorizThumbIcon() {
94         if (System.getSecurityManager() != null) {
95             return SAFE_HORIZ_THUMB_ICON;
96         } else {
97             return horizThumbIcon;
98         }
99     }
100 
getVertThumbIcon()101     private static Icon getVertThumbIcon() {
102         if (System.getSecurityManager() != null) {
103             return SAFE_VERT_THUMB_ICON;
104         } else {
105             return vertThumbIcon;
106         }
107     }
108 
installUI( JComponent c )109     public void installUI( JComponent c ) {
110         trackWidth = ((Integer)UIManager.get( "Slider.trackWidth" )).intValue();
111         tickLength = safeLength = ((Integer)UIManager.get( "Slider.majorTickLength" )).intValue();
112         horizThumbIcon = SAFE_HORIZ_THUMB_ICON =
113                 UIManager.getIcon( "Slider.horizontalThumbIcon" );
114         vertThumbIcon = SAFE_VERT_THUMB_ICON =
115                 UIManager.getIcon( "Slider.verticalThumbIcon" );
116 
117         super.installUI( c );
118 
119         thumbColor = UIManager.getColor("Slider.thumb");
120         highlightColor = UIManager.getColor("Slider.highlight");
121         darkShadowColor = UIManager.getColor("Slider.darkShadow");
122 
123         scrollListener.setScrollByBlock( false );
124 
125         prepareFilledSliderField();
126     }
127 
createPropertyChangeListener( JSlider slider )128     protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
129         return new MetalPropertyListener();
130     }
131 
132     protected class MetalPropertyListener extends BasicSliderUI.PropertyChangeHandler {
propertyChange( PropertyChangeEvent e )133         public void propertyChange( PropertyChangeEvent e ) {  // listen for slider fill
134             super.propertyChange( e );
135 
136             if (e.getPropertyName().equals(SLIDER_FILL)) {
137                 prepareFilledSliderField();
138             }
139         }
140     }
141 
prepareFilledSliderField()142     private void prepareFilledSliderField() {
143         // Use true for Ocean theme
144         filledSlider = MetalLookAndFeel.usingOcean();
145 
146         Object sliderFillProp = slider.getClientProperty(SLIDER_FILL);
147 
148         if (sliderFillProp != null) {
149             filledSlider = ((Boolean) sliderFillProp).booleanValue();
150         }
151     }
152 
paintThumb(Graphics g)153     public void paintThumb(Graphics g)  {
154         Rectangle knobBounds = thumbRect;
155 
156         g.translate( knobBounds.x, knobBounds.y );
157 
158         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
159             getHorizThumbIcon().paintIcon( slider, g, 0, 0 );
160         }
161         else {
162             getVertThumbIcon().paintIcon( slider, g, 0, 0 );
163         }
164 
165         g.translate( -knobBounds.x, -knobBounds.y );
166     }
167 
168     /**
169      * Returns a rectangle enclosing the track that will be painted.
170      */
getPaintTrackRect()171     private Rectangle getPaintTrackRect() {
172         int trackLeft = 0, trackRight, trackTop = 0, trackBottom;
173         if (slider.getOrientation() == JSlider.HORIZONTAL) {
174             trackBottom = (trackRect.height - 1) - getThumbOverhang();
175             trackTop = trackBottom - (getTrackWidth() - 1);
176             trackRight = trackRect.width - 1;
177         }
178         else {
179             if (MetalUtils.isLeftToRight(slider)) {
180                 trackLeft = (trackRect.width - getThumbOverhang()) -
181                                                          getTrackWidth();
182                 trackRight = (trackRect.width - getThumbOverhang()) - 1;
183             }
184             else {
185                 trackLeft = getThumbOverhang();
186                 trackRight = getThumbOverhang() + getTrackWidth() - 1;
187             }
188             trackBottom = trackRect.height - 1;
189         }
190         return new Rectangle(trackRect.x + trackLeft, trackRect.y + trackTop,
191                              trackRight - trackLeft, trackBottom - trackTop);
192     }
193 
paintTrack(Graphics g)194     public void paintTrack(Graphics g)  {
195         if (MetalLookAndFeel.usingOcean()) {
196             oceanPaintTrack(g);
197             return;
198         }
199         Color trackColor = !slider.isEnabled() ? MetalLookAndFeel.getControlShadow() :
200                            slider.getForeground();
201 
202         boolean leftToRight = MetalUtils.isLeftToRight(slider);
203 
204         g.translate( trackRect.x, trackRect.y );
205 
206         int trackLeft = 0;
207         int trackTop = 0;
208         int trackRight;
209         int trackBottom;
210 
211         // Draw the track
212         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
213             trackBottom = (trackRect.height - 1) - getThumbOverhang();
214             trackTop = trackBottom - (getTrackWidth() - 1);
215             trackRight = trackRect.width - 1;
216         }
217         else {
218             if (leftToRight) {
219                 trackLeft = (trackRect.width - getThumbOverhang()) -
220                                                          getTrackWidth();
221                 trackRight = (trackRect.width - getThumbOverhang()) - 1;
222             }
223             else {
224                 trackLeft = getThumbOverhang();
225                 trackRight = getThumbOverhang() + getTrackWidth() - 1;
226             }
227             trackBottom = trackRect.height - 1;
228         }
229 
230         if ( slider.isEnabled() ) {
231             g.setColor( MetalLookAndFeel.getControlDarkShadow() );
232             g.drawRect( trackLeft, trackTop,
233                         (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 );
234 
235             g.setColor( MetalLookAndFeel.getControlHighlight() );
236             g.drawLine( trackLeft + 1, trackBottom, trackRight, trackBottom );
237             g.drawLine( trackRight, trackTop + 1, trackRight, trackBottom );
238 
239             g.setColor( MetalLookAndFeel.getControlShadow() );
240             g.drawLine( trackLeft + 1, trackTop + 1, trackRight - 2, trackTop + 1 );
241             g.drawLine( trackLeft + 1, trackTop + 1, trackLeft + 1, trackBottom - 2 );
242         }
243         else {
244             g.setColor( MetalLookAndFeel.getControlShadow() );
245             g.drawRect( trackLeft, trackTop,
246                         (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 );
247         }
248 
249         // Draw the fill
250         if ( filledSlider ) {
251             int middleOfThumb;
252             int fillTop;
253             int fillLeft;
254             int fillBottom;
255             int fillRight;
256 
257             if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
258                 middleOfThumb = thumbRect.x + (thumbRect.width / 2);
259                 middleOfThumb -= trackRect.x; // To compensate for the g.translate()
260                 fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
261                 fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2;
262 
263                 if ( !drawInverted() ) {
264                     fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
265                     fillRight = middleOfThumb;
266                 }
267                 else {
268                     fillLeft = middleOfThumb;
269                     fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2;
270                 }
271             }
272             else {
273                 middleOfThumb = thumbRect.y + (thumbRect.height / 2);
274                 middleOfThumb -= trackRect.y; // To compensate for the g.translate()
275                 fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
276                 fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2;
277 
278                 if ( !drawInverted() ) {
279                     fillTop = middleOfThumb;
280                     fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2;
281                 }
282                 else {
283                     fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
284                     fillBottom = middleOfThumb;
285                 }
286             }
287 
288             if ( slider.isEnabled() ) {
289                 g.setColor( slider.getBackground() );
290                 g.drawLine( fillLeft, fillTop, fillRight, fillTop );
291                 g.drawLine( fillLeft, fillTop, fillLeft, fillBottom );
292 
293                 g.setColor( MetalLookAndFeel.getControlShadow() );
294                 g.fillRect( fillLeft + 1, fillTop + 1,
295                             fillRight - fillLeft, fillBottom - fillTop );
296             }
297             else {
298                 g.setColor( MetalLookAndFeel.getControlShadow() );
299                 g.fillRect(fillLeft, fillTop, fillRight - fillLeft, fillBottom - fillTop);
300             }
301         }
302 
303         g.translate( -trackRect.x, -trackRect.y );
304     }
305 
oceanPaintTrack(Graphics g)306     private void oceanPaintTrack(Graphics g)  {
307         boolean leftToRight = MetalUtils.isLeftToRight(slider);
308         boolean drawInverted = drawInverted();
309         Color sliderAltTrackColor = (Color)UIManager.get(
310                                     "Slider.altTrackColor");
311 
312         // Translate to the origin of the painting rectangle
313         Rectangle paintRect = getPaintTrackRect();
314         g.translate(paintRect.x, paintRect.y);
315 
316         // Width and height of the painting rectangle.
317         int w = paintRect.width;
318         int h = paintRect.height;
319 
320         if (slider.getOrientation() == JSlider.HORIZONTAL) {
321             int middleOfThumb = thumbRect.x + thumbRect.width / 2 - paintRect.x;
322 
323             if (slider.isEnabled()) {
324                 int fillMinX;
325                 int fillMaxX;
326 
327                 if (middleOfThumb > 0) {
328                     g.setColor(drawInverted ? MetalLookAndFeel.getControlDarkShadow() :
329                             MetalLookAndFeel.getPrimaryControlDarkShadow());
330 
331                     g.drawRect(0, 0, middleOfThumb - 1, h - 1);
332                 }
333 
334                 if (middleOfThumb < w) {
335                     g.setColor(drawInverted ? MetalLookAndFeel.getPrimaryControlDarkShadow() :
336                             MetalLookAndFeel.getControlDarkShadow());
337 
338                     g.drawRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1);
339                 }
340 
341                 if (filledSlider) {
342                     g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
343                     if (drawInverted) {
344                         fillMinX = middleOfThumb;
345                         fillMaxX = w - 2;
346                         g.drawLine(1, 1, middleOfThumb, 1);
347                     } else {
348                         fillMinX = 1;
349                         fillMaxX = middleOfThumb;
350                         g.drawLine(middleOfThumb, 1, w - 1, 1);
351                     }
352                     if (h == 6) {
353                         g.setColor(MetalLookAndFeel.getWhite());
354                         g.drawLine(fillMinX, 1, fillMaxX, 1);
355                         g.setColor(sliderAltTrackColor);
356                         g.drawLine(fillMinX, 2, fillMaxX, 2);
357                         g.setColor(MetalLookAndFeel.getControlShadow());
358                         g.drawLine(fillMinX, 3, fillMaxX, 3);
359                         g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
360                         g.drawLine(fillMinX, 4, fillMaxX, 4);
361                     }
362                 }
363             } else {
364                 g.setColor(MetalLookAndFeel.getControlShadow());
365 
366                 if (middleOfThumb > 0) {
367                     if (!drawInverted && filledSlider) {
368                         g.fillRect(0, 0, middleOfThumb - 1, h - 1);
369                     } else {
370                         g.drawRect(0, 0, middleOfThumb - 1, h - 1);
371                     }
372                 }
373 
374                 if (middleOfThumb < w) {
375                     if (drawInverted && filledSlider) {
376                         g.fillRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1);
377                     } else {
378                         g.drawRect(middleOfThumb, 0, w - middleOfThumb - 1, h - 1);
379                     }
380                 }
381             }
382         } else {
383             int middleOfThumb = thumbRect.y + (thumbRect.height / 2) - paintRect.y;
384 
385             if (slider.isEnabled()) {
386                 int fillMinY;
387                 int fillMaxY;
388 
389                 if (middleOfThumb > 0) {
390                     g.setColor(drawInverted ? MetalLookAndFeel.getPrimaryControlDarkShadow() :
391                             MetalLookAndFeel.getControlDarkShadow());
392 
393                     g.drawRect(0, 0, w - 1, middleOfThumb - 1);
394                 }
395 
396                 if (middleOfThumb < h) {
397                     g.setColor(drawInverted ? MetalLookAndFeel.getControlDarkShadow() :
398                             MetalLookAndFeel.getPrimaryControlDarkShadow());
399 
400                     g.drawRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1);
401                 }
402 
403                 if (filledSlider) {
404                     g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
405                     if (drawInverted()) {
406                         fillMinY = 1;
407                         fillMaxY = middleOfThumb;
408                         if (leftToRight) {
409                             g.drawLine(1, middleOfThumb, 1, h - 1);
410                         } else {
411                             g.drawLine(w - 2, middleOfThumb, w - 2, h - 1);
412                         }
413                     } else {
414                         fillMinY = middleOfThumb;
415                         fillMaxY = h - 2;
416                         if (leftToRight) {
417                             g.drawLine(1, 1, 1, middleOfThumb);
418                         } else {
419                             g.drawLine(w - 2, 1, w - 2, middleOfThumb);
420                         }
421                     }
422                     if (w == 6) {
423                         g.setColor(leftToRight ? MetalLookAndFeel.getWhite() : MetalLookAndFeel.getPrimaryControlShadow());
424                         g.drawLine(1, fillMinY, 1, fillMaxY);
425                         g.setColor(leftToRight ? sliderAltTrackColor : MetalLookAndFeel.getControlShadow());
426                         g.drawLine(2, fillMinY, 2, fillMaxY);
427                         g.setColor(leftToRight ? MetalLookAndFeel.getControlShadow() : sliderAltTrackColor);
428                         g.drawLine(3, fillMinY, 3, fillMaxY);
429                         g.setColor(leftToRight ? MetalLookAndFeel.getPrimaryControlShadow() : MetalLookAndFeel.getWhite());
430                         g.drawLine(4, fillMinY, 4, fillMaxY);
431                     }
432                 }
433             } else {
434                 g.setColor(MetalLookAndFeel.getControlShadow());
435 
436                 if (middleOfThumb > 0) {
437                     if (drawInverted && filledSlider) {
438                         g.fillRect(0, 0, w - 1, middleOfThumb - 1);
439                     } else {
440                         g.drawRect(0, 0, w - 1, middleOfThumb - 1);
441                     }
442                 }
443 
444                 if (middleOfThumb < h) {
445                     if (!drawInverted && filledSlider) {
446                         g.fillRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1);
447                     } else {
448                         g.drawRect(0, middleOfThumb, w - 1, h - middleOfThumb - 1);
449                     }
450                 }
451             }
452         }
453 
454         g.translate(-paintRect.x, -paintRect.y);
455     }
456 
paintFocus(Graphics g)457     public void paintFocus(Graphics g)  {
458     }
459 
getThumbSize()460     protected Dimension getThumbSize() {
461         Dimension size = new Dimension();
462 
463         if ( slider.getOrientation() == JSlider.VERTICAL ) {
464             size.width = getVertThumbIcon().getIconWidth();
465             size.height = getVertThumbIcon().getIconHeight();
466         }
467         else {
468             size.width = getHorizThumbIcon().getIconWidth();
469             size.height = getHorizThumbIcon().getIconHeight();
470         }
471 
472         return size;
473     }
474 
475     /**
476      * Gets the height of the tick area for horizontal sliders and the width of the
477      * tick area for vertical sliders.  BasicSliderUI uses the returned value to
478      * determine the tick area rectangle.
479      */
getTickLength()480     public int getTickLength() {
481         return slider.getOrientation() == JSlider.HORIZONTAL ? safeLength + TICK_BUFFER + 1 :
482         safeLength + TICK_BUFFER + 3;
483     }
484 
485     /**
486      * Returns the shorter dimension of the track.
487      */
getTrackWidth()488     protected int getTrackWidth() {
489         // This strange calculation is here to keep the
490         // track in proportion to the thumb.
491         final double kIdealTrackWidth = 7.0;
492         final double kIdealThumbHeight = 16.0;
493         final double kWidthScalar = kIdealTrackWidth / kIdealThumbHeight;
494 
495         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
496             return (int)(kWidthScalar * thumbRect.height);
497         }
498         else {
499             return (int)(kWidthScalar * thumbRect.width);
500         }
501     }
502 
503     /**
504      * Returns the longer dimension of the slide bar.  (The slide bar is only the
505      * part that runs directly under the thumb)
506      */
getTrackLength()507     protected int getTrackLength() {
508         if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
509             return trackRect.width;
510         }
511         return trackRect.height;
512     }
513 
514     /**
515      * Returns the amount that the thumb goes past the slide bar.
516      */
getThumbOverhang()517     protected int getThumbOverhang() {
518         return (int)(getThumbSize().getHeight()-getTrackWidth())/2;
519     }
520 
scrollDueToClickInTrack( int dir )521     protected void scrollDueToClickInTrack( int dir ) {
522         scrollByUnit( dir );
523     }
524 
paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x )525     protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
526         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
527         g.drawLine( x, TICK_BUFFER, x, TICK_BUFFER + (safeLength / 2) );
528     }
529 
paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x )530     protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
531         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
532         g.drawLine( x, TICK_BUFFER , x, TICK_BUFFER + (safeLength - 1) );
533     }
534 
paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y )535     protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
536         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
537 
538         if (MetalUtils.isLeftToRight(slider)) {
539             g.drawLine( TICK_BUFFER, y, TICK_BUFFER + (safeLength / 2), y );
540         }
541         else {
542             g.drawLine( 0, y, safeLength/2, y );
543         }
544     }
545 
paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y )546     protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
547         g.setColor( slider.isEnabled() ? slider.getForeground() : MetalLookAndFeel.getControlShadow() );
548 
549         if (MetalUtils.isLeftToRight(slider)) {
550             g.drawLine( TICK_BUFFER, y, TICK_BUFFER + safeLength, y );
551         }
552         else {
553             g.drawLine( 0, y, safeLength, y );
554         }
555     }
556 }
557