1 /*
   2  * Copyright (c) 2002, 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.synth;
  27 
  28 import java.awt.event.*;
  29 import java.awt.Graphics;
  30 import java.awt.Dimension;
  31 import java.awt.FontMetrics;
  32 import java.awt.Rectangle;
  33 import java.awt.Point;
  34 import java.awt.Insets;
  35 import java.beans.*;
  36 import java.util.Dictionary;
  37 import java.util.Enumeration;
  38 import javax.swing.*;
  39 import javax.swing.plaf.*;
  40 import javax.swing.plaf.basic.BasicSliderUI;
  41 import sun.swing.SwingUtilities2;
  42 
  43 
  44 /**
  45  * Provides the Synth L&F UI delegate for
  46  * {@link JSlider}.
  47  *
  48  * @author Joshua Outwater
  49  * @since 1.7
  50  */
  51 public class SynthSliderUI extends BasicSliderUI
  52                            implements PropertyChangeListener, SynthUI {
  53     private Rectangle valueRect = new Rectangle();
  54     private boolean paintValue;
  55 
  56     /**
  57      * When a JSlider is used as a renderer in a JTable, its layout is not
  58      * being recomputed even though the size is changing. Even though there
  59      * is a ComponentListener installed, it is not being notified. As such,
  60      * at times when being asked to paint the layout should first be redone.
  61      * At the end of the layout method we set this lastSize variable, which
  62      * represents the size of the slider the last time it was layed out.
  63      *
  64      * In the paint method we then check to see that this is accurate, that
  65      * the slider has not changed sizes since being last layed out. If necessary
  66      * we recompute the layout.
  67      */
  68     private Dimension lastSize;
  69 
  70     private int trackHeight;
  71     private int trackBorder;
  72     private int thumbWidth;
  73     private int thumbHeight;
  74 
  75     private SynthStyle style;
  76     private SynthStyle sliderTrackStyle;
  77     private SynthStyle sliderThumbStyle;
  78 
  79     /** Used to determine the color to paint the thumb. */
  80     private transient boolean thumbActive; //happens on rollover, and when pressed
  81     private transient boolean thumbPressed; //happens when mouse was depressed while over thumb
  82 
  83     ///////////////////////////////////////////////////
  84     // ComponentUI Interface Implementation methods
  85     ///////////////////////////////////////////////////
  86     /**
  87      * Creates a new UI object for the given component.
  88      *
  89      * @param c component to create UI object for
  90      * @return the UI object
  91      */
  92     public static ComponentUI createUI(JComponent c) {
  93         return new SynthSliderUI((JSlider)c);
  94     }
  95 
  96     /**
  97      * Constructs a {@code SynthSliderUI}.
  98      * @param c a slider
  99      */
 100     protected SynthSliderUI(JSlider c) {
 101         super(c);
 102     }
 103 
 104     /**
 105      * {@inheritDoc}
 106      */
 107     @Override
 108     protected void installDefaults(JSlider slider) {
 109         updateStyle(slider);
 110     }
 111 
 112     /**
 113      * Uninstalls default setting. This method is called when a
 114      * {@code LookAndFeel} is uninstalled.
 115      */
 116     protected void uninstallDefaults(JSlider slider) {
 117         SynthContext context = getContext(slider, ENABLED);
 118         style.uninstallDefaults(context);
 119         context.dispose();
 120         style = null;
 121 
 122         context = getContext(slider, Region.SLIDER_TRACK, ENABLED);
 123         sliderTrackStyle.uninstallDefaults(context);
 124         context.dispose();
 125         sliderTrackStyle = null;
 126 
 127         context = getContext(slider, Region.SLIDER_THUMB, ENABLED);
 128         sliderThumbStyle.uninstallDefaults(context);
 129         context.dispose();
 130         sliderThumbStyle = null;
 131     }
 132 
 133     /**
 134      * {@inheritDoc}
 135      */
 136     @Override
 137     protected void installListeners(JSlider slider) {
 138         super.installListeners(slider);
 139         slider.addPropertyChangeListener(this);
 140     }
 141 
 142     /**
 143      * {@inheritDoc}
 144      */
 145     @Override
 146     protected void uninstallListeners(JSlider slider) {
 147         slider.removePropertyChangeListener(this);
 148         super.uninstallListeners(slider);
 149     }
 150 
 151     private void updateStyle(JSlider c) {
 152         SynthContext context = getContext(c, ENABLED);
 153         SynthStyle oldStyle = style;
 154         style = SynthLookAndFeel.updateStyle(context, this);
 155 
 156         if (style != oldStyle) {
 157             thumbWidth =
 158                 style.getInt(context, "Slider.thumbWidth", 30);
 159 
 160             thumbHeight =
 161                 style.getInt(context, "Slider.thumbHeight", 14);
 162 
 163             // handle scaling for sizeVarients for special case components. The
 164             // key "JComponent.sizeVariant" scales for large/small/mini
 165             // components are based on Apples LAF
 166             String scaleKey = (String)slider.getClientProperty(
 167                     "JComponent.sizeVariant");
 168             if (scaleKey != null){
 169                 if ("large".equals(scaleKey)){
 170                     thumbWidth *= 1.15;
 171                     thumbHeight *= 1.15;
 172                 } else if ("small".equals(scaleKey)){
 173                     thumbWidth *= 0.857;
 174                     thumbHeight *= 0.857;
 175                 } else if ("mini".equals(scaleKey)){
 176                     thumbWidth *= 0.784;
 177                     thumbHeight *= 0.784;
 178                 }
 179             }
 180 
 181             trackBorder =
 182                 style.getInt(context, "Slider.trackBorder", 1);
 183 
 184             trackHeight = thumbHeight + trackBorder * 2;
 185 
 186             paintValue = style.getBoolean(context,
 187                     "Slider.paintValue", true);
 188             if (oldStyle != null) {
 189                 uninstallKeyboardActions(c);
 190                 installKeyboardActions(c);
 191             }
 192         }
 193         context.dispose();
 194 
 195         context = getContext(c, Region.SLIDER_TRACK, ENABLED);
 196         sliderTrackStyle =
 197             SynthLookAndFeel.updateStyle(context, this);
 198         context.dispose();
 199 
 200         context = getContext(c, Region.SLIDER_THUMB, ENABLED);
 201         sliderThumbStyle =
 202             SynthLookAndFeel.updateStyle(context, this);
 203         context.dispose();
 204     }
 205 
 206     /**
 207      * {@inheritDoc}
 208      */
 209     @Override
 210     protected TrackListener createTrackListener(JSlider s) {
 211         return new SynthTrackListener();
 212     }
 213 
 214     private void updateThumbState(int x, int y) {
 215         setThumbActive(thumbRect.contains(x, y));
 216     }
 217 
 218     private void updateThumbState(int x, int y, boolean pressed) {
 219         updateThumbState(x, y);
 220         setThumbPressed(pressed);
 221     }
 222 
 223     private void setThumbActive(boolean active) {
 224         if (thumbActive != active) {
 225             thumbActive = active;
 226             slider.repaint(thumbRect);
 227         }
 228     }
 229 
 230     private void setThumbPressed(boolean pressed) {
 231         if (thumbPressed != pressed) {
 232             thumbPressed = pressed;
 233             slider.repaint(thumbRect);
 234         }
 235     }
 236 
 237     /**
 238      * {@inheritDoc}
 239      */
 240     @Override
 241     public int getBaseline(JComponent c, int width, int height) {
 242         if (c == null) {
 243             throw new NullPointerException("Component must be non-null");
 244         }
 245         if (width < 0 || height < 0) {
 246             throw new IllegalArgumentException(
 247                     "Width and height must be >= 0");
 248         }
 249         if (slider.getPaintLabels() && labelsHaveSameBaselines()) {
 250             // Get the insets for the track.
 251             Insets trackInsets = new Insets(0, 0, 0, 0);
 252             SynthContext trackContext = getContext(slider,
 253                                                    Region.SLIDER_TRACK);
 254             style.getInsets(trackContext, trackInsets);
 255             trackContext.dispose();
 256             if (slider.getOrientation() == JSlider.HORIZONTAL) {
 257                 int valueHeight = 0;
 258                 if (paintValue) {
 259                     SynthContext context = getContext(slider);
 260                     valueHeight = context.getStyle().getGraphicsUtils(context).
 261                             getMaximumCharHeight(context);
 262                     context.dispose();
 263                 }
 264                 int tickHeight = 0;
 265                 if (slider.getPaintTicks()) {
 266                     tickHeight = getTickLength();
 267                 }
 268                 int labelHeight = getHeightOfTallestLabel();
 269                 int contentHeight = valueHeight + trackHeight +
 270                         trackInsets.top + trackInsets.bottom +
 271                         tickHeight + labelHeight + 4;
 272                 int centerY = height / 2 - contentHeight / 2;
 273                 centerY += valueHeight + 2;
 274                 centerY += trackHeight + trackInsets.top + trackInsets.bottom;
 275                 centerY += tickHeight + 2;
 276                 JComponent label = (JComponent) slider.getLabelTable().elements().nextElement();
 277                 Dimension pref = label.getPreferredSize();
 278                 return centerY + label.getBaseline(pref.width, pref.height);
 279             }
 280             else { // VERTICAL
 281                 Integer value = slider.getInverted() ? getLowestValue() :
 282                                                        getHighestValue();
 283                 if (value != null) {
 284                     int valueY = insetCache.top;
 285                     int valueHeight = 0;
 286                     if (paintValue) {
 287                         SynthContext context = getContext(slider);
 288                         valueHeight = context.getStyle().getGraphicsUtils(
 289                                 context).getMaximumCharHeight(context);
 290                         context.dispose();
 291                     }
 292                     int contentHeight = height - insetCache.top -
 293                             insetCache.bottom;
 294                     int trackY = valueY + valueHeight;
 295                     int trackHeight = contentHeight - valueHeight;
 296                     int yPosition = yPositionForValue(value.intValue(), trackY,
 297                                                       trackHeight);
 298                     JComponent label = (JComponent) slider.getLabelTable().get(value);
 299                     Dimension pref = label.getPreferredSize();
 300                     return yPosition - pref.height / 2 +
 301                             label.getBaseline(pref.width, pref.height);
 302                 }
 303             }
 304         }
 305         return -1;
 306     }
 307 
 308     /**
 309      * {@inheritDoc}
 310      */
 311     @Override
 312     public Dimension getPreferredSize(JComponent c)  {
 313         recalculateIfInsetsChanged();
 314         Dimension d = new Dimension(contentRect.width, contentRect.height);
 315         Insets i = slider.getInsets();
 316         if (slider.getOrientation() == JSlider.VERTICAL) {
 317             d.height = 200;
 318             d.height += i.top + i.bottom;
 319         } else {
 320             d.width = 200;
 321             d.width += i.left + i.right;
 322         }
 323         return d;
 324     }
 325 
 326     /**
 327      * {@inheritDoc}
 328      */
 329     @Override
 330     public Dimension getMinimumSize(JComponent c) {
 331         recalculateIfInsetsChanged();
 332         Dimension d = new Dimension(contentRect.width, contentRect.height);
 333         if (slider.getOrientation() == JSlider.VERTICAL) {
 334             d.height = thumbRect.height + insetCache.top + insetCache.bottom;
 335         } else {
 336             d.width = thumbRect.width + insetCache.left + insetCache.right;
 337         }
 338         return d;
 339     }
 340 
 341     /**
 342      * {@inheritDoc}
 343      */
 344     @Override
 345     protected void calculateGeometry() {
 346         calculateThumbSize();
 347         layout();
 348         calculateThumbLocation();
 349     }
 350 
 351     /**
 352      * Lays out the slider.
 353      */
 354     protected void layout() {
 355         SynthContext context = getContext(slider);
 356         SynthGraphicsUtils synthGraphics = style.getGraphicsUtils(context);
 357 
 358         // Get the insets for the track.
 359         Insets trackInsets = new Insets(0, 0, 0, 0);
 360         SynthContext trackContext = getContext(slider, Region.SLIDER_TRACK);
 361         style.getInsets(trackContext, trackInsets);
 362         trackContext.dispose();
 363 
 364         if (slider.getOrientation() == JSlider.HORIZONTAL) {
 365             // Calculate the height of all the subcomponents so we can center
 366             // them.
 367             valueRect.height = 0;
 368             if (paintValue) {
 369                 valueRect.height =
 370                     synthGraphics.getMaximumCharHeight(context);
 371             }
 372 
 373             trackRect.height = trackHeight;
 374 
 375             tickRect.height = 0;
 376             if (slider.getPaintTicks()) {
 377                 tickRect.height = getTickLength();
 378             }
 379 
 380             labelRect.height = 0;
 381             if (slider.getPaintLabels()) {
 382                 labelRect.height = getHeightOfTallestLabel();
 383             }
 384 
 385             contentRect.height = valueRect.height + trackRect.height
 386                 + trackInsets.top + trackInsets.bottom
 387                 + tickRect.height + labelRect.height + 4;
 388             contentRect.width = slider.getWidth() - insetCache.left
 389                 - insetCache.right;
 390 
 391             // Check if any of the labels will paint out of bounds.
 392             int pad = 0;
 393             if (slider.getPaintLabels()) {
 394                 // Calculate the track rectangle.  It is necessary for
 395                 // xPositionForValue to return correct values.
 396                 trackRect.x = insetCache.left;
 397                 trackRect.width = contentRect.width;
 398 
 399                 @SuppressWarnings("rawtypes")
 400                 Dictionary dictionary = slider.getLabelTable();
 401                 if (dictionary != null) {
 402                     int minValue = slider.getMinimum();
 403                     int maxValue = slider.getMaximum();
 404 
 405                     // Iterate through the keys in the dictionary and find the
 406                     // first and last labels indices that fall within the
 407                     // slider range.
 408                     int firstLblIdx = Integer.MAX_VALUE;
 409                     int lastLblIdx = Integer.MIN_VALUE;
 410                     for (Enumeration<?> keys = dictionary.keys();
 411                             keys.hasMoreElements(); ) {
 412                         int keyInt = ((Integer)keys.nextElement()).intValue();
 413                         if (keyInt >= minValue && keyInt < firstLblIdx) {
 414                             firstLblIdx = keyInt;
 415                         }
 416                         if (keyInt <= maxValue && keyInt > lastLblIdx) {
 417                             lastLblIdx = keyInt;
 418                         }
 419                     }
 420                     // Calculate the pad necessary for the labels at the first
 421                     // and last visible indices.
 422                     pad = getPadForLabel(firstLblIdx);
 423                     pad = Math.max(pad, getPadForLabel(lastLblIdx));
 424                 }
 425             }
 426             // Calculate the painting rectangles for each of the different
 427             // slider areas.
 428             valueRect.x = trackRect.x = tickRect.x = labelRect.x =
 429                 (insetCache.left + pad);
 430             valueRect.width = trackRect.width = tickRect.width =
 431                 labelRect.width = (contentRect.width - (pad * 2));
 432 
 433             int centerY = slider.getHeight() / 2 - contentRect.height / 2;
 434 
 435             valueRect.y = centerY;
 436             centerY += valueRect.height + 2;
 437 
 438             trackRect.y = centerY + trackInsets.top;
 439             centerY += trackRect.height + trackInsets.top + trackInsets.bottom;
 440 
 441             tickRect.y = centerY;
 442             centerY += tickRect.height + 2;
 443 
 444             labelRect.y = centerY;
 445             centerY += labelRect.height;
 446         } else {
 447             // Calculate the width of all the subcomponents so we can center
 448             // them.
 449             trackRect.width = trackHeight;
 450 
 451             tickRect.width = 0;
 452             if (slider.getPaintTicks()) {
 453                 tickRect.width = getTickLength();
 454             }
 455 
 456             labelRect.width = 0;
 457             if (slider.getPaintLabels()) {
 458                 labelRect.width = getWidthOfWidestLabel();
 459             }
 460 
 461             valueRect.y = insetCache.top;
 462             valueRect.height = 0;
 463             if (paintValue) {
 464                 valueRect.height =
 465                     synthGraphics.getMaximumCharHeight(context);
 466             }
 467 
 468             // Get the max width of the min or max value of the slider.
 469             FontMetrics fm = slider.getFontMetrics(slider.getFont());
 470             valueRect.width = Math.max(
 471                 synthGraphics.computeStringWidth(context, slider.getFont(),
 472                     fm, "" + slider.getMaximum()),
 473                 synthGraphics.computeStringWidth(context, slider.getFont(),
 474                     fm, "" + slider.getMinimum()));
 475 
 476             int l = valueRect.width / 2;
 477             int w1 = trackInsets.left + trackRect.width / 2;
 478             int w2 = trackRect.width / 2 + trackInsets.right +
 479                               tickRect.width + labelRect.width;
 480             contentRect.width = Math.max(w1, l) + Math.max(w2, l) +
 481                     2 + insetCache.left + insetCache.right;
 482             contentRect.height = slider.getHeight() -
 483                                     insetCache.top - insetCache.bottom;
 484 
 485             // Layout the components.
 486             trackRect.y = tickRect.y = labelRect.y =
 487                 valueRect.y + valueRect.height;
 488             trackRect.height = tickRect.height = labelRect.height =
 489                 contentRect.height - valueRect.height;
 490 
 491             int startX = slider.getWidth() / 2 - contentRect.width / 2;
 492             if (SynthLookAndFeel.isLeftToRight(slider)) {
 493                 if (l > w1) {
 494                     startX += (l - w1);
 495                 }
 496                 trackRect.x = startX + trackInsets.left;
 497 
 498                 startX += trackInsets.left + trackRect.width + trackInsets.right;
 499                 tickRect.x = startX;
 500                 labelRect.x = startX + tickRect.width + 2;
 501             } else {
 502                 if (l > w2) {
 503                     startX += (l - w2);
 504                 }
 505                 labelRect.x = startX;
 506 
 507                 startX += labelRect.width + 2;
 508                 tickRect.x = startX;
 509                 trackRect.x = startX + tickRect.width + trackInsets.left;
 510             }
 511         }
 512         context.dispose();
 513         lastSize = slider.getSize();
 514     }
 515 
 516     /**
 517      * Calculates the pad for the label at the specified index.
 518      *
 519      * @param i index of the label to calculate pad for.
 520      * @return padding required to keep label visible.
 521      */
 522     private int getPadForLabel(int i) {
 523         int pad = 0;
 524 
 525         JComponent c = (JComponent) slider.getLabelTable().get(i);
 526         if (c != null) {
 527             int centerX = xPositionForValue(i);
 528             int cHalfWidth = c.getPreferredSize().width / 2;
 529             if (centerX - cHalfWidth < insetCache.left) {
 530                 pad = Math.max(pad, insetCache.left - (centerX - cHalfWidth));
 531             }
 532 
 533             if (centerX + cHalfWidth > slider.getWidth() - insetCache.right) {
 534                 pad = Math.max(pad, (centerX + cHalfWidth) -
 535                         (slider.getWidth() - insetCache.right));
 536             }
 537         }
 538         return pad;
 539     }
 540 
 541     /**
 542      * {@inheritDoc}
 543      */
 544     @Override
 545     protected void calculateThumbLocation() {
 546         super.calculateThumbLocation();
 547         if (slider.getOrientation() == JSlider.HORIZONTAL) {
 548             thumbRect.y += trackBorder;
 549         } else {
 550             thumbRect.x += trackBorder;
 551         }
 552         Point mousePosition = slider.getMousePosition();
 553         if(mousePosition != null) {
 554         updateThumbState(mousePosition.x, mousePosition.y);
 555        }
 556     }
 557 
 558     /**
 559      * {@inheritDoc}
 560      */
 561     @Override
 562     public void setThumbLocation(int x, int y) {
 563         super.setThumbLocation(x, y);
 564         // Value rect is tied to the thumb location.  We need to repaint when
 565         // the thumb repaints.
 566         slider.repaint(valueRect.x, valueRect.y,
 567                 valueRect.width, valueRect.height);
 568         setThumbActive(false);
 569     }
 570 
 571     /**
 572      * {@inheritDoc}
 573      */
 574     @Override
 575     protected int xPositionForValue(int value) {
 576         int min = slider.getMinimum();
 577         int max = slider.getMaximum();
 578         int trackLeft = trackRect.x + thumbRect.width / 2 + trackBorder;
 579         int trackRight = trackRect.x + trackRect.width - thumbRect.width / 2
 580             - trackBorder;
 581         int trackLength = trackRight - trackLeft;
 582         double valueRange = (double)max - (double)min;
 583         double pixelsPerValue = (double)trackLength / valueRange;
 584         int xPosition;
 585 
 586         if (!drawInverted()) {
 587             xPosition = trackLeft;
 588             xPosition += Math.round( pixelsPerValue * ((double)value - min));
 589         } else {
 590             xPosition = trackRight;
 591             xPosition -= Math.round( pixelsPerValue * ((double)value - min));
 592         }
 593 
 594         xPosition = Math.max(trackLeft, xPosition);
 595         xPosition = Math.min(trackRight, xPosition);
 596 
 597         return xPosition;
 598     }
 599 
 600     /**
 601      * {@inheritDoc}
 602      */
 603     @Override
 604     protected int yPositionForValue(int value, int trackY, int trackHeight) {
 605         int min = slider.getMinimum();
 606         int max = slider.getMaximum();
 607         int trackTop = trackY + thumbRect.height / 2 + trackBorder;
 608         int trackBottom = trackY + trackHeight - thumbRect.height / 2 -
 609                 trackBorder;
 610         int trackLength = trackBottom - trackTop;
 611         double valueRange = (double)max - (double)min;
 612         double pixelsPerValue = (double)trackLength / valueRange;
 613         int yPosition;
 614 
 615         if (!drawInverted()) {
 616             yPosition = trackTop;
 617             yPosition += Math.round(pixelsPerValue * ((double)max - value));
 618         } else {
 619             yPosition = trackTop;
 620             yPosition += Math.round(pixelsPerValue * ((double)value - min));
 621         }
 622 
 623         yPosition = Math.max(trackTop, yPosition);
 624         yPosition = Math.min(trackBottom, yPosition);
 625 
 626         return yPosition;
 627     }
 628 
 629     /**
 630      * {@inheritDoc}
 631      */
 632     @Override
 633     public int valueForYPosition(int yPos) {
 634         int value;
 635         int minValue = slider.getMinimum();
 636         int maxValue = slider.getMaximum();
 637         int trackTop = trackRect.y + thumbRect.height / 2 + trackBorder;
 638         int trackBottom = trackRect.y + trackRect.height
 639             - thumbRect.height / 2 - trackBorder;
 640         int trackLength = trackBottom - trackTop;
 641 
 642         if (yPos <= trackTop) {
 643             value = drawInverted() ? minValue : maxValue;
 644         } else if (yPos >= trackBottom) {
 645             value = drawInverted() ? maxValue : minValue;
 646         } else {
 647             int distanceFromTrackTop = yPos - trackTop;
 648             double valueRange = (double)maxValue - (double)minValue;
 649             double valuePerPixel = valueRange / (double)trackLength;
 650             int valueFromTrackTop =
 651                 (int)Math.round(distanceFromTrackTop * valuePerPixel);
 652             value = drawInverted() ?
 653                 minValue + valueFromTrackTop : maxValue - valueFromTrackTop;
 654         }
 655         return value;
 656     }
 657 
 658     /**
 659      * {@inheritDoc}
 660      */
 661     @Override
 662     public int valueForXPosition(int xPos) {
 663         int value;
 664         int minValue = slider.getMinimum();
 665         int maxValue = slider.getMaximum();
 666         int trackLeft = trackRect.x + thumbRect.width / 2 + trackBorder;
 667         int trackRight = trackRect.x + trackRect.width
 668             - thumbRect.width / 2 - trackBorder;
 669         int trackLength = trackRight - trackLeft;
 670 
 671         if (xPos <= trackLeft) {
 672             value = drawInverted() ? maxValue : minValue;
 673         } else if (xPos >= trackRight) {
 674             value = drawInverted() ? minValue : maxValue;
 675         } else {
 676             int distanceFromTrackLeft = xPos - trackLeft;
 677             double valueRange = (double)maxValue - (double)minValue;
 678             double valuePerPixel = valueRange / (double)trackLength;
 679             int valueFromTrackLeft =
 680                 (int)Math.round(distanceFromTrackLeft * valuePerPixel);
 681             value = drawInverted() ?
 682                 maxValue - valueFromTrackLeft : minValue + valueFromTrackLeft;
 683         }
 684         return value;
 685     }
 686 
 687     /**
 688      * {@inheritDoc}
 689      */
 690     @Override
 691     protected Dimension getThumbSize() {
 692         Dimension size = new Dimension();
 693 
 694         if (slider.getOrientation() == JSlider.VERTICAL) {
 695             size.width = thumbHeight;
 696             size.height = thumbWidth;
 697         } else {
 698             size.width = thumbWidth;
 699             size.height = thumbHeight;
 700         }
 701         return size;
 702     }
 703 
 704     /**
 705      * {@inheritDoc}
 706      */
 707     @Override
 708     protected void recalculateIfInsetsChanged() {
 709         SynthContext context = getContext(slider);
 710         Insets newInsets = style.getInsets(context, null);
 711         Insets compInsets = slider.getInsets();
 712         newInsets.left += compInsets.left; newInsets.right += compInsets.right;
 713         newInsets.top += compInsets.top; newInsets.bottom += compInsets.bottom;
 714         if (!newInsets.equals(insetCache)) {
 715             insetCache = newInsets;
 716             calculateGeometry();
 717         }
 718         context.dispose();
 719     }
 720 
 721     /**
 722      * {@inheritDoc}
 723      */
 724     @Override
 725     public SynthContext getContext(JComponent c) {
 726         return getContext(c, SynthLookAndFeel.getComponentState(c));
 727     }
 728 
 729     private SynthContext getContext(JComponent c, int state) {
 730         return SynthContext.getContext(c, style, state);
 731     }
 732 
 733     private SynthContext getContext(JComponent c, Region subregion) {
 734         return getContext(c, subregion, getComponentState(c, subregion));
 735     }
 736 
 737     private SynthContext getContext(JComponent c, Region subregion, int state) {
 738         SynthStyle style = null;
 739 
 740         if (subregion == Region.SLIDER_TRACK) {
 741             style = sliderTrackStyle;
 742         } else if (subregion == Region.SLIDER_THUMB) {
 743             style = sliderThumbStyle;
 744         }
 745         return SynthContext.getContext(c, subregion, style, state);
 746     }
 747 
 748     private int getComponentState(JComponent c, Region region) {
 749         if (region == Region.SLIDER_THUMB && thumbActive &&c.isEnabled()) {
 750             int state = thumbPressed ? PRESSED : MOUSE_OVER;
 751             if (c.isFocusOwner()) state |= FOCUSED;
 752             return state;
 753         }
 754         return SynthLookAndFeel.getComponentState(c);
 755     }
 756 
 757     /**
 758      * Notifies this UI delegate to repaint the specified component.
 759      * This method paints the component background, then calls
 760      * the {@link #paint(SynthContext,Graphics)} method.
 761      *
 762      * <p>In general, this method does not need to be overridden by subclasses.
 763      * All Look and Feel rendering code should reside in the {@code paint} method.
 764      *
 765      * @param g the {@code Graphics} object used for painting
 766      * @param c the component being painted
 767      * @see #paint(SynthContext,Graphics)
 768      */
 769     @Override
 770     public void update(Graphics g, JComponent c) {
 771         SynthContext context = getContext(c);
 772         SynthLookAndFeel.update(context, g);
 773         context.getPainter().paintSliderBackground(context,
 774                           g, 0, 0, c.getWidth(), c.getHeight(),
 775                           slider.getOrientation());
 776         paint(context, g);
 777         context.dispose();
 778     }
 779 
 780     /**
 781      * Paints the specified component according to the Look and Feel.
 782      * <p>This method is not used by Synth Look and Feel.
 783      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 784      *
 785      * @param g the {@code Graphics} object used for painting
 786      * @param c the component being painted
 787      * @see #paint(SynthContext,Graphics)
 788      */
 789     @Override
 790     public void paint(Graphics g, JComponent c) {
 791         SynthContext context = getContext(c);
 792         paint(context, g);
 793         context.dispose();
 794     }
 795 
 796     /**
 797      * Paints the specified component.
 798      *
 799      * @param context context for the component being painted
 800      * @param g the {@code Graphics} object used for painting
 801      * @see #update(Graphics,JComponent)
 802      */
 803     protected void paint(SynthContext context, Graphics g) {
 804         recalculateIfInsetsChanged();
 805         recalculateIfOrientationChanged();
 806         Rectangle clip = g.getClipBounds();
 807 
 808         if (lastSize == null || !lastSize.equals(slider.getSize())) {
 809             calculateGeometry();
 810         }
 811 
 812         if (paintValue) {
 813             FontMetrics fm = SwingUtilities2.getFontMetrics(slider, g);
 814             int labelWidth = context.getStyle().getGraphicsUtils(context).
 815                 computeStringWidth(context, g.getFont(), fm,
 816                     "" + slider.getValue());
 817             valueRect.x = thumbRect.x + (thumbRect.width - labelWidth) / 2;
 818 
 819             // For horizontal sliders, make sure value is not painted
 820             // outside slider bounds.
 821             if (slider.getOrientation() == JSlider.HORIZONTAL) {
 822                 if (valueRect.x + labelWidth > insetCache.left + contentRect.width) {
 823                     valueRect.x =  (insetCache.left + contentRect.width) - labelWidth;
 824                 }
 825                 valueRect.x = Math.max(valueRect.x, 0);
 826             }
 827 
 828             g.setColor(context.getStyle().getColor(
 829                     context, ColorType.TEXT_FOREGROUND));
 830             context.getStyle().getGraphicsUtils(context).paintText(
 831                     context, g, "" + slider.getValue(), valueRect.x,
 832                     valueRect.y, -1);
 833         }
 834 
 835         if (slider.getPaintTrack() && clip.intersects(trackRect)) {
 836             SynthContext subcontext = getContext(slider, Region.SLIDER_TRACK);
 837             paintTrack(subcontext, g, trackRect);
 838             subcontext.dispose();
 839         }
 840 
 841         if (clip.intersects(thumbRect)) {
 842             SynthContext subcontext = getContext(slider, Region.SLIDER_THUMB);
 843             paintThumb(subcontext, g, thumbRect);
 844             subcontext.dispose();
 845         }
 846 
 847         if (slider.getPaintTicks() && clip.intersects(tickRect)) {
 848             paintTicks(g);
 849         }
 850 
 851         if (slider.getPaintLabels() && clip.intersects(labelRect)) {
 852             paintLabels(g);
 853         }
 854     }
 855 
 856     /**
 857      * {@inheritDoc}
 858      */
 859     @Override
 860     public void paintBorder(SynthContext context, Graphics g, int x,
 861                             int y, int w, int h) {
 862         context.getPainter().paintSliderBorder(context, g, x, y, w, h,
 863                                                slider.getOrientation());
 864     }
 865 
 866     /**
 867      * Paints the slider thumb.
 868      *
 869      * @param context context for the component being painted
 870      * @param g {@code Graphics} object used for painting
 871      * @param thumbBounds bounding box for the thumb
 872      */
 873     protected void paintThumb(SynthContext context, Graphics g,
 874             Rectangle thumbBounds)  {
 875         int orientation = slider.getOrientation();
 876         SynthLookAndFeel.updateSubregion(context, g, thumbBounds);
 877         context.getPainter().paintSliderThumbBackground(context, g,
 878                              thumbBounds.x, thumbBounds.y, thumbBounds.width,
 879                              thumbBounds.height, orientation);
 880         context.getPainter().paintSliderThumbBorder(context, g,
 881                              thumbBounds.x, thumbBounds.y, thumbBounds.width,
 882                              thumbBounds.height, orientation);
 883     }
 884 
 885     /**
 886      * Paints the slider track.
 887      *
 888      * @param context context for the component being painted
 889      * @param g {@code Graphics} object used for painting
 890      * @param trackBounds bounding box for the track
 891      */
 892     protected void paintTrack(SynthContext context, Graphics g,
 893             Rectangle trackBounds) {
 894         int orientation = slider.getOrientation();
 895         SynthLookAndFeel.updateSubregion(context, g, trackBounds);
 896         context.getPainter().paintSliderTrackBackground(context, g,
 897                 trackBounds.x, trackBounds.y, trackBounds.width,
 898                 trackBounds.height, orientation);
 899         context.getPainter().paintSliderTrackBorder(context, g,
 900                 trackBounds.x, trackBounds.y, trackBounds.width,
 901                 trackBounds.height, orientation);
 902     }
 903 
 904     /**
 905      * {@inheritDoc}
 906      */
 907     @Override
 908     public void propertyChange(PropertyChangeEvent e) {
 909         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 910             updateStyle((JSlider)e.getSource());
 911         }
 912     }
 913 
 914     //////////////////////////////////////////////////
 915     /// Track Listener Class
 916     //////////////////////////////////////////////////
 917     /**
 918      * Track mouse movements.
 919      */
 920     private class SynthTrackListener extends TrackListener {
 921 
 922         @Override public void mouseExited(MouseEvent e) {
 923             setThumbActive(false);
 924         }
 925 
 926         @Override public void mousePressed(MouseEvent e) {
 927             super.mousePressed(e);
 928             setThumbPressed(thumbRect.contains(e.getX(), e.getY()));
 929         }
 930 
 931         @Override public void mouseReleased(MouseEvent e) {
 932             super.mouseReleased(e);
 933             updateThumbState(e.getX(), e.getY(), false);
 934         }
 935 
 936         @Override public void mouseDragged(MouseEvent e) {
 937             int thumbMiddle;
 938 
 939             if (!slider.isEnabled()) {
 940                 return;
 941             }
 942 
 943             currentMouseX = e.getX();
 944             currentMouseY = e.getY();
 945 
 946             if (!isDragging()) {
 947                 return;
 948             }
 949 
 950             slider.setValueIsAdjusting(true);
 951 
 952             switch (slider.getOrientation()) {
 953             case JSlider.VERTICAL:
 954                 int halfThumbHeight = thumbRect.height / 2;
 955                 int thumbTop = e.getY() - offset;
 956                 int trackTop = trackRect.y;
 957                 int trackBottom = trackRect.y + trackRect.height
 958                     - halfThumbHeight - trackBorder;
 959                 int vMax = yPositionForValue(slider.getMaximum() -
 960                     slider.getExtent());
 961 
 962                 if (drawInverted()) {
 963                     trackBottom = vMax;
 964                     trackTop = trackTop + halfThumbHeight;
 965                 } else {
 966                     trackTop = vMax;
 967                 }
 968                 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
 969                 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
 970 
 971                 setThumbLocation(thumbRect.x, thumbTop);
 972 
 973                 thumbMiddle = thumbTop + halfThumbHeight;
 974                 slider.setValue(valueForYPosition(thumbMiddle));
 975                 break;
 976             case JSlider.HORIZONTAL:
 977                 int halfThumbWidth = thumbRect.width / 2;
 978                 int thumbLeft = e.getX() - offset;
 979                 int trackLeft = trackRect.x + halfThumbWidth + trackBorder;
 980                 int trackRight = trackRect.x + trackRect.width
 981                     - halfThumbWidth - trackBorder;
 982                 int hMax = xPositionForValue(slider.getMaximum() -
 983                     slider.getExtent());
 984 
 985                 if (drawInverted()) {
 986                     trackLeft = hMax;
 987                 } else {
 988                     trackRight = hMax;
 989                 }
 990                 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
 991                 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
 992 
 993                 setThumbLocation(thumbLeft, thumbRect.y);
 994 
 995                 thumbMiddle = thumbLeft + halfThumbWidth;
 996                 slider.setValue(valueForXPosition(thumbMiddle));
 997                 break;
 998             default:
 999                 return;
1000             }
1001 
1002             if (slider.getValueIsAdjusting()) {
1003                 setThumbActive(true);
1004             }
1005         }
1006 
1007         @Override public void mouseMoved(MouseEvent e) {
1008             updateThumbState(e.getX(), e.getY());
1009         }
1010     }
1011 }