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