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     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                 @SuppressWarnings("rawtypes")
 396                 Dictionary dictionary = slider.getLabelTable();
 397                 if (dictionary != null) {
 398                     int minValue = slider.getMinimum();
 399                     int maxValue = slider.getMaximum();
 400 
 401                     // Iterate through the keys in the dictionary and find the
 402                     // first and last labels indices that fall within the
 403                     // slider range.
 404                     int firstLblIdx = Integer.MAX_VALUE;
 405                     int lastLblIdx = Integer.MIN_VALUE;
 406                     for (Enumeration<?> keys = dictionary.keys();
 407                             keys.hasMoreElements(); ) {
 408                         int keyInt = ((Integer)keys.nextElement()).intValue();
 409                         if (keyInt >= minValue && keyInt < firstLblIdx) {
 410                             firstLblIdx = keyInt;
 411                         }
 412                         if (keyInt <= maxValue && keyInt > lastLblIdx) {
 413                             lastLblIdx = keyInt;
 414                         }
 415                     }
 416                     // Calculate the pad necessary for the labels at the first
 417                     // and last visible indices.
 418                     pad = getPadForLabel(firstLblIdx);
 419                     pad = Math.max(pad, getPadForLabel(lastLblIdx));
 420                 }
 421             }
 422             // Calculate the painting rectangles for each of the different
 423             // slider areas.
 424             valueRect.x = trackRect.x = tickRect.x = labelRect.x =
 425                 (insetCache.left + pad);
 426             valueRect.width = trackRect.width = tickRect.width =
 427                 labelRect.width = (contentRect.width - (pad * 2));
 428 
 429             int centerY = slider.getHeight() / 2 - contentRect.height / 2;
 430 
 431             valueRect.y = centerY;
 432             centerY += valueRect.height + 2;
 433 
 434             trackRect.y = centerY + trackInsets.top;
 435             centerY += trackRect.height + trackInsets.top + trackInsets.bottom;
 436 
 437             tickRect.y = centerY;
 438             centerY += tickRect.height + 2;
 439 
 440             labelRect.y = centerY;
 441             centerY += labelRect.height;
 442         } else {
 443             // Calculate the width of all the subcomponents so we can center
 444             // them.
 445             trackRect.width = trackHeight;
 446 
 447             tickRect.width = 0;
 448             if (slider.getPaintTicks()) {
 449                 tickRect.width = getTickLength();
 450             }
 451 
 452             labelRect.width = 0;
 453             if (slider.getPaintLabels()) {
 454                 labelRect.width = getWidthOfWidestLabel();
 455             }
 456 
 457             valueRect.y = insetCache.top;
 458             valueRect.height = 0;
 459             if (paintValue) {
 460                 valueRect.height =
 461                     synthGraphics.getMaximumCharHeight(context);
 462             }
 463 
 464             // Get the max width of the min or max value of the slider.
 465             FontMetrics fm = slider.getFontMetrics(slider.getFont());
 466             valueRect.width = Math.max(
 467                 synthGraphics.computeStringWidth(context, slider.getFont(),
 468                     fm, "" + slider.getMaximum()),
 469                 synthGraphics.computeStringWidth(context, slider.getFont(),
 470                     fm, "" + slider.getMinimum()));
 471 
 472             int l = valueRect.width / 2;
 473             int w1 = trackInsets.left + trackRect.width / 2;
 474             int w2 = trackRect.width / 2 + trackInsets.right +
 475                               tickRect.width + labelRect.width;
 476             contentRect.width = Math.max(w1, l) + Math.max(w2, l) +
 477                     2 + insetCache.left + insetCache.right;
 478             contentRect.height = slider.getHeight() -
 479                                     insetCache.top - insetCache.bottom;
 480 
 481             // Layout the components.
 482             trackRect.y = tickRect.y = labelRect.y =
 483                 valueRect.y + valueRect.height;
 484             trackRect.height = tickRect.height = labelRect.height =
 485                 contentRect.height - valueRect.height;
 486 
 487             int startX = slider.getWidth() / 2 - contentRect.width / 2;
 488             if (SynthLookAndFeel.isLeftToRight(slider)) {
 489                 if (l > w1) {
 490                     startX += (l - w1);
 491                 }
 492                 trackRect.x = startX + trackInsets.left;
 493 
 494                 startX += trackInsets.left + trackRect.width + trackInsets.right;
 495                 tickRect.x = startX;
 496                 labelRect.x = startX + tickRect.width + 2;
 497             } else {
 498                 if (l > w2) {
 499                     startX += (l - w2);
 500                 }
 501                 labelRect.x = startX;
 502 
 503                 startX += labelRect.width + 2;
 504                 tickRect.x = startX;
 505                 trackRect.x = startX + tickRect.width + trackInsets.left;
 506             }
 507         }
 508         context.dispose();
 509         lastSize = slider.getSize();
 510     }
 511 
 512     /**
 513      * Calculates the pad for the label at the specified index.
 514      *
 515      * @param i index of the label to calculate pad for.
 516      * @return padding required to keep label visible.
 517      */
 518     private int getPadForLabel(int i) {
 519         int pad = 0;
 520 
 521         JComponent c = (JComponent) slider.getLabelTable().get(i);
 522         if (c != null) {
 523             int centerX = xPositionForValue(i);
 524             int cHalfWidth = c.getPreferredSize().width / 2;
 525             if (centerX - cHalfWidth < insetCache.left) {
 526                 pad = Math.max(pad, insetCache.left - (centerX - cHalfWidth));
 527             }
 528 
 529             if (centerX + cHalfWidth > slider.getWidth() - insetCache.right) {
 530                 pad = Math.max(pad, (centerX + cHalfWidth) -
 531                         (slider.getWidth() - insetCache.right));
 532             }
 533         }
 534         return pad;
 535     }
 536 
 537     /**
 538      * {@inheritDoc}
 539      */
 540     @Override
 541     protected void calculateThumbLocation() {
 542         super.calculateThumbLocation();
 543         if (slider.getOrientation() == JSlider.HORIZONTAL) {
 544             thumbRect.y += trackBorder;
 545         } else {
 546             thumbRect.x += trackBorder;
 547         }
 548         Point mousePosition = slider.getMousePosition();
 549         if(mousePosition != null) {
 550         updateThumbState(mousePosition.x, mousePosition.y);
 551        }
 552     }
 553 
 554     /**
 555      * {@inheritDoc}
 556      */
 557     @Override
 558     public void setThumbLocation(int x, int y) {
 559         super.setThumbLocation(x, y);
 560         // Value rect is tied to the thumb location.  We need to repaint when
 561         // the thumb repaints.
 562         slider.repaint(valueRect.x, valueRect.y,
 563                 valueRect.width, valueRect.height);
 564         setThumbActive(false);
 565     }
 566 
 567     /**
 568      * {@inheritDoc}
 569      */
 570     @Override
 571     protected int xPositionForValue(int value) {
 572         int min = slider.getMinimum();
 573         int max = slider.getMaximum();
 574         int trackLeft = trackRect.x + thumbRect.width / 2 + trackBorder;
 575         int trackRight = trackRect.x + trackRect.width - thumbRect.width / 2
 576             - trackBorder;
 577         int trackLength = trackRight - trackLeft;
 578         double valueRange = (double)max - (double)min;
 579         double pixelsPerValue = (double)trackLength / valueRange;
 580         int xPosition;
 581 
 582         if (!drawInverted()) {
 583             xPosition = trackLeft;
 584             xPosition += Math.round( pixelsPerValue * ((double)value - min));
 585         } else {
 586             xPosition = trackRight;
 587             xPosition -= Math.round( pixelsPerValue * ((double)value - min));
 588         }
 589 
 590         xPosition = Math.max(trackLeft, xPosition);
 591         xPosition = Math.min(trackRight, xPosition);
 592 
 593         return xPosition;
 594     }
 595 
 596     /**
 597      * {@inheritDoc}
 598      */
 599     @Override
 600     protected int yPositionForValue(int value, int trackY, int trackHeight) {
 601         int min = slider.getMinimum();
 602         int max = slider.getMaximum();
 603         int trackTop = trackY + thumbRect.height / 2 + trackBorder;
 604         int trackBottom = trackY + trackHeight - thumbRect.height / 2 -
 605                 trackBorder;
 606         int trackLength = trackBottom - trackTop;
 607         double valueRange = (double)max - (double)min;
 608         double pixelsPerValue = (double)trackLength / valueRange;
 609         int yPosition;
 610 
 611         if (!drawInverted()) {
 612             yPosition = trackTop;
 613             yPosition += Math.round(pixelsPerValue * ((double)max - value));
 614         } else {
 615             yPosition = trackTop;
 616             yPosition += Math.round(pixelsPerValue * ((double)value - min));
 617         }
 618 
 619         yPosition = Math.max(trackTop, yPosition);
 620         yPosition = Math.min(trackBottom, yPosition);
 621 
 622         return yPosition;
 623     }
 624 
 625     /**
 626      * {@inheritDoc}
 627      */
 628     @Override
 629     public int valueForYPosition(int yPos) {
 630         int value;
 631         int minValue = slider.getMinimum();
 632         int maxValue = slider.getMaximum();
 633         int trackTop = trackRect.y + thumbRect.height / 2 + trackBorder;
 634         int trackBottom = trackRect.y + trackRect.height
 635             - thumbRect.height / 2 - trackBorder;
 636         int trackLength = trackBottom - trackTop;
 637 
 638         if (yPos <= trackTop) {
 639             value = drawInverted() ? minValue : maxValue;
 640         } else if (yPos >= trackBottom) {
 641             value = drawInverted() ? maxValue : minValue;
 642         } else {
 643             int distanceFromTrackTop = yPos - trackTop;
 644             double valueRange = (double)maxValue - (double)minValue;
 645             double valuePerPixel = valueRange / (double)trackLength;
 646             int valueFromTrackTop =
 647                 (int)Math.round(distanceFromTrackTop * valuePerPixel);
 648             value = drawInverted() ?
 649                 minValue + valueFromTrackTop : maxValue - valueFromTrackTop;
 650         }
 651         return value;
 652     }
 653 
 654     /**
 655      * {@inheritDoc}
 656      */
 657     @Override
 658     public int valueForXPosition(int xPos) {
 659         int value;
 660         int minValue = slider.getMinimum();
 661         int maxValue = slider.getMaximum();
 662         int trackLeft = trackRect.x + thumbRect.width / 2 + trackBorder;
 663         int trackRight = trackRect.x + trackRect.width
 664             - thumbRect.width / 2 - trackBorder;
 665         int trackLength = trackRight - trackLeft;
 666 
 667         if (xPos <= trackLeft) {
 668             value = drawInverted() ? maxValue : minValue;
 669         } else if (xPos >= trackRight) {
 670             value = drawInverted() ? minValue : maxValue;
 671         } else {
 672             int distanceFromTrackLeft = xPos - trackLeft;
 673             double valueRange = (double)maxValue - (double)minValue;
 674             double valuePerPixel = valueRange / (double)trackLength;
 675             int valueFromTrackLeft =
 676                 (int)Math.round(distanceFromTrackLeft * valuePerPixel);
 677             value = drawInverted() ?
 678                 maxValue - valueFromTrackLeft : minValue + valueFromTrackLeft;
 679         }
 680         return value;
 681     }
 682 
 683     /**
 684      * {@inheritDoc}
 685      */
 686     @Override
 687     protected Dimension getThumbSize() {
 688         Dimension size = new Dimension();
 689 
 690         if (slider.getOrientation() == JSlider.VERTICAL) {
 691             size.width = thumbHeight;
 692             size.height = thumbWidth;
 693         } else {
 694             size.width = thumbWidth;
 695             size.height = thumbHeight;
 696         }
 697         return size;
 698     }
 699 
 700     /**
 701      * {@inheritDoc}
 702      */
 703     @Override
 704     protected void recalculateIfInsetsChanged() {
 705         SynthContext context = getContext(slider);
 706         Insets newInsets = style.getInsets(context, null);
 707         Insets compInsets = slider.getInsets();
 708         newInsets.left += compInsets.left; newInsets.right += compInsets.right;
 709         newInsets.top += compInsets.top; newInsets.bottom += compInsets.bottom;
 710         if (!newInsets.equals(insetCache)) {
 711             insetCache = newInsets;
 712             calculateGeometry();
 713         }
 714         context.dispose();
 715     }
 716 
 717     /**
 718      * {@inheritDoc}
 719      */
 720     @Override
 721     public SynthContext getContext(JComponent c) {
 722         return getContext(c, SynthLookAndFeel.getComponentState(c));
 723     }
 724 
 725     private SynthContext getContext(JComponent c, int state) {
 726         return SynthContext.getContext(c, style, state);
 727     }
 728 
 729     private SynthContext getContext(JComponent c, Region subregion) {
 730         return getContext(c, subregion, getComponentState(c, subregion));
 731     }
 732 
 733     private SynthContext getContext(JComponent c, Region subregion, int state) {
 734         SynthStyle style = null;
 735 
 736         if (subregion == Region.SLIDER_TRACK) {
 737             style = sliderTrackStyle;
 738         } else if (subregion == Region.SLIDER_THUMB) {
 739             style = sliderThumbStyle;
 740         }
 741         return SynthContext.getContext(c, subregion, style, state);
 742     }
 743 
 744     private int getComponentState(JComponent c, Region region) {
 745         if (region == Region.SLIDER_THUMB && thumbActive &&c.isEnabled()) {
 746             int state = thumbPressed ? PRESSED : MOUSE_OVER;
 747             if (c.isFocusOwner()) state |= FOCUSED;
 748             return state;
 749         }
 750         return SynthLookAndFeel.getComponentState(c);
 751     }
 752 
 753     /**
 754      * Notifies this UI delegate to repaint the specified component.
 755      * This method paints the component background, then calls
 756      * the {@link #paint(SynthContext,Graphics)} method.
 757      *
 758      * <p>In general, this method does not need to be overridden by subclasses.
 759      * All Look and Feel rendering code should reside in the {@code paint} method.
 760      *
 761      * @param g the {@code Graphics} object used for painting
 762      * @param c the component being painted
 763      * @see #paint(SynthContext,Graphics)
 764      */
 765     @Override
 766     public void update(Graphics g, JComponent c) {
 767         SynthContext context = getContext(c);
 768         SynthLookAndFeel.update(context, g);
 769         context.getPainter().paintSliderBackground(context,
 770                           g, 0, 0, c.getWidth(), c.getHeight(),
 771                           slider.getOrientation());
 772         paint(context, g);
 773         context.dispose();
 774     }
 775 
 776     /**
 777      * Paints the specified component according to the Look and Feel.
 778      * <p>This method is not used by Synth Look and Feel.
 779      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 780      *
 781      * @param g the {@code Graphics} object used for painting
 782      * @param c the component being painted
 783      * @see #paint(SynthContext,Graphics)
 784      */
 785     @Override
 786     public void paint(Graphics g, JComponent c) {
 787         SynthContext context = getContext(c);
 788         paint(context, g);
 789         context.dispose();
 790     }
 791 
 792     /**
 793      * Paints the specified component.
 794      *
 795      * @param context context for the component being painted
 796      * @param g the {@code Graphics} object used for painting
 797      * @see #update(Graphics,JComponent)
 798      */
 799     protected void paint(SynthContext context, Graphics g) {
 800         recalculateIfInsetsChanged();
 801         recalculateIfOrientationChanged();
 802         Rectangle clip = g.getClipBounds();
 803 
 804         if (lastSize == null || !lastSize.equals(slider.getSize())) {
 805             calculateGeometry();
 806         }
 807 
 808         if (paintValue) {
 809             FontMetrics fm = SwingUtilities2.getFontMetrics(slider, g);
 810             int labelWidth = context.getStyle().getGraphicsUtils(context).
 811                 computeStringWidth(context, g.getFont(), fm,
 812                     "" + slider.getValue());
 813             valueRect.x = thumbRect.x + (thumbRect.width - labelWidth) / 2;
 814 
 815             // For horizontal sliders, make sure value is not painted
 816             // outside slider bounds.
 817             if (slider.getOrientation() == JSlider.HORIZONTAL) {
 818                 if (valueRect.x + labelWidth > insetCache.left + contentRect.width) {
 819                     valueRect.x =  (insetCache.left + contentRect.width) - labelWidth;
 820                 }
 821                 valueRect.x = Math.max(valueRect.x, 0);
 822             }
 823 
 824             g.setColor(context.getStyle().getColor(
 825                     context, ColorType.TEXT_FOREGROUND));
 826             context.getStyle().getGraphicsUtils(context).paintText(
 827                     context, g, "" + slider.getValue(), valueRect.x,
 828                     valueRect.y, -1);
 829         }
 830 
 831         if (slider.getPaintTrack() && clip.intersects(trackRect)) {
 832             SynthContext subcontext = getContext(slider, Region.SLIDER_TRACK);
 833             paintTrack(subcontext, g, trackRect);
 834             subcontext.dispose();
 835         }
 836 
 837         if (clip.intersects(thumbRect)) {
 838             SynthContext subcontext = getContext(slider, Region.SLIDER_THUMB);
 839             paintThumb(subcontext, g, thumbRect);
 840             subcontext.dispose();
 841         }
 842 
 843         if (slider.getPaintTicks() && clip.intersects(tickRect)) {
 844             paintTicks(g);
 845         }
 846 
 847         if (slider.getPaintLabels() && clip.intersects(labelRect)) {
 848             paintLabels(g);
 849         }
 850     }
 851 
 852     /**
 853      * {@inheritDoc}
 854      */
 855     @Override
 856     public void paintBorder(SynthContext context, Graphics g, int x,
 857                             int y, int w, int h) {
 858         context.getPainter().paintSliderBorder(context, g, x, y, w, h,
 859                                                slider.getOrientation());
 860     }
 861 
 862     /**
 863      * Paints the slider thumb.
 864      *
 865      * @param context context for the component being painted
 866      * @param g {@code Graphics} object used for painting
 867      * @param thumbBounds bounding box for the thumb
 868      */
 869     protected void paintThumb(SynthContext context, Graphics g,
 870             Rectangle thumbBounds)  {
 871         int orientation = slider.getOrientation();
 872         SynthLookAndFeel.updateSubregion(context, g, thumbBounds);
 873         context.getPainter().paintSliderThumbBackground(context, g,
 874                              thumbBounds.x, thumbBounds.y, thumbBounds.width,
 875                              thumbBounds.height, orientation);
 876         context.getPainter().paintSliderThumbBorder(context, g,
 877                              thumbBounds.x, thumbBounds.y, thumbBounds.width,
 878                              thumbBounds.height, orientation);
 879     }
 880 
 881     /**
 882      * Paints the slider track.
 883      *
 884      * @param context context for the component being painted
 885      * @param g {@code Graphics} object used for painting
 886      * @param trackBounds bounding box for the track
 887      */
 888     protected void paintTrack(SynthContext context, Graphics g,
 889             Rectangle trackBounds) {
 890         int orientation = slider.getOrientation();
 891         SynthLookAndFeel.updateSubregion(context, g, trackBounds);
 892         context.getPainter().paintSliderTrackBackground(context, g,
 893                 trackBounds.x, trackBounds.y, trackBounds.width,
 894                 trackBounds.height, orientation);
 895         context.getPainter().paintSliderTrackBorder(context, g,
 896                 trackBounds.x, trackBounds.y, trackBounds.width,
 897                 trackBounds.height, orientation);
 898     }
 899 
 900     /**
 901      * {@inheritDoc}
 902      */
 903     @Override
 904     public void propertyChange(PropertyChangeEvent e) {
 905         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 906             updateStyle((JSlider)e.getSource());
 907         }
 908     }
 909 
 910     //////////////////////////////////////////////////
 911     /// Track Listener Class
 912     //////////////////////////////////////////////////
 913     /**
 914      * Track mouse movements.
 915      */
 916     private class SynthTrackListener extends TrackListener {
 917 
 918         @Override public void mouseExited(MouseEvent e) {
 919             setThumbActive(false);
 920         }
 921 
 922         @Override public void mousePressed(MouseEvent e) {
 923             super.mousePressed(e);
 924             setThumbPressed(thumbRect.contains(e.getX(), e.getY()));
 925         }
 926 
 927         @Override public void mouseReleased(MouseEvent e) {
 928             super.mouseReleased(e);
 929             updateThumbState(e.getX(), e.getY(), false);
 930         }
 931 
 932         @Override public void mouseDragged(MouseEvent e) {
 933             int thumbMiddle;
 934 
 935             if (!slider.isEnabled()) {
 936                 return;
 937             }
 938 
 939             currentMouseX = e.getX();
 940             currentMouseY = e.getY();
 941 
 942             if (!isDragging()) {
 943                 return;
 944             }
 945 
 946             slider.setValueIsAdjusting(true);
 947 
 948             switch (slider.getOrientation()) {
 949             case JSlider.VERTICAL:
 950                 int halfThumbHeight = thumbRect.height / 2;
 951                 int thumbTop = e.getY() - offset;
 952                 int trackTop = trackRect.y;
 953                 int trackBottom = trackRect.y + trackRect.height
 954                     - halfThumbHeight - trackBorder;
 955                 int vMax = yPositionForValue(slider.getMaximum() -
 956                     slider.getExtent());
 957 
 958                 if (drawInverted()) {
 959                     trackBottom = vMax;
 960                     trackTop = trackTop + halfThumbHeight;
 961                 } else {
 962                     trackTop = vMax;
 963                 }
 964                 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
 965                 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
 966 
 967                 setThumbLocation(thumbRect.x, thumbTop);
 968 
 969                 thumbMiddle = thumbTop + halfThumbHeight;
 970                 slider.setValue(valueForYPosition(thumbMiddle));
 971                 break;
 972             case JSlider.HORIZONTAL:
 973                 int halfThumbWidth = thumbRect.width / 2;
 974                 int thumbLeft = e.getX() - offset;
 975                 int trackLeft = trackRect.x + halfThumbWidth + trackBorder;
 976                 int trackRight = trackRect.x + trackRect.width
 977                     - halfThumbWidth - trackBorder;
 978                 int hMax = xPositionForValue(slider.getMaximum() -
 979                     slider.getExtent());
 980 
 981                 if (drawInverted()) {
 982                     trackLeft = hMax;
 983                 } else {
 984                     trackRight = hMax;
 985                 }
 986                 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
 987                 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
 988 
 989                 setThumbLocation(thumbLeft, thumbRect.y);
 990 
 991                 thumbMiddle = thumbLeft + halfThumbWidth;
 992                 slider.setValue(valueForXPosition(thumbMiddle));
 993                 break;
 994             default:
 995                 return;
 996             }
 997 
 998             if (slider.getValueIsAdjusting()) {
 999                 setThumbActive(true);
1000             }
1001         }
1002 
1003         @Override public void mouseMoved(MouseEvent e) {
1004             updateThumbState(e.getX(), e.getY());
1005         }
1006     }
1007 }