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