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
  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      */
 123     public static ComponentUI createUI(JComponent c)    {
 124         return new MetalSliderUI();
 125     }
 126 
 127     /**
 128      * Constructs a {@code MetalSliderUI} instance.
 129      */
 130     public MetalSliderUI() {
 131         super( null );
 132     }
 133 
 134     private static Icon getHorizThumbIcon() {
 135         if (System.getSecurityManager() != null) {
 136             return SAFE_HORIZ_THUMB_ICON;
 137         } else {
 138             return horizThumbIcon;
 139         }
 140     }
 141 
 142     private static Icon getVertThumbIcon() {
 143         if (System.getSecurityManager() != null) {
 144             return SAFE_VERT_THUMB_ICON;
 145         } else {
 146             return vertThumbIcon;
 147         }
 148     }
 149 
 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      */
 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 {
 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 
 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 
 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      */
 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 
 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 
 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 
 507     public void paintFocus(Graphics g)  {
 508     }
 509 
 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      */
 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      */
 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      */
 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      */
 573     protected int getThumbOverhang() {
 574         return (int)(getThumbSize().getHeight()-getTrackWidth())/2;
 575     }
 576 
 577     protected void scrollDueToClickInTrack( int dir ) {
 578         scrollByUnit( dir );
 579     }
 580 
 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 
 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 
 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 
 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 }