1 /*
   2  * Copyright (c) 2002, 2010, 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(SynthContext.class, c,
 726                             SynthLookAndFeel.getRegion(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         Class klass = SynthContext.class;
 736 
 737         if (subregion == Region.SLIDER_TRACK) {
 738             style = sliderTrackStyle;
 739         } else if (subregion == Region.SLIDER_THUMB) {
 740             style = sliderThumbStyle;
 741         }
 742         return SynthContext.getContext(klass, c, subregion, style, state);
 743     }
 744 
 745     private int getComponentState(JComponent c, Region region) {
 746         if (region == Region.SLIDER_THUMB && thumbActive &&c.isEnabled()) {
 747             int state = thumbPressed ? PRESSED : MOUSE_OVER;
 748             if (c.isFocusOwner()) state |= FOCUSED;
 749             return state;
 750         }
 751         return SynthLookAndFeel.getComponentState(c);
 752     }
 753 
 754     /**
 755      * Notifies this UI delegate to repaint the specified component.
 756      * This method paints the component background, then calls
 757      * the {@link #paint(SynthContext,Graphics)} method.
 758      *
 759      * <p>In general, this method does not need to be overridden by subclasses.
 760      * All Look and Feel rendering code should reside in the {@code paint} method.
 761      *
 762      * @param g the {@code Graphics} object used for painting
 763      * @param c the component being painted
 764      * @see #paint(SynthContext,Graphics)
 765      */
 766     @Override
 767     public void update(Graphics g, JComponent c) {
 768         SynthContext context = getContext(c);
 769         SynthLookAndFeel.update(context, g);
 770         context.getPainter().paintSliderBackground(context,
 771                           g, 0, 0, c.getWidth(), c.getHeight(),
 772                           slider.getOrientation());
 773         paint(context, g);
 774         context.dispose();
 775     }
 776 
 777     /**
 778      * Paints the specified component according to the Look and Feel.
 779      * <p>This method is not used by Synth Look and Feel.
 780      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 781      *
 782      * @param g the {@code Graphics} object used for painting
 783      * @param c the component being painted
 784      * @see #paint(SynthContext,Graphics)
 785      */
 786     @Override
 787     public void paint(Graphics g, JComponent c) {
 788         SynthContext context = getContext(c);
 789         paint(context, g);
 790         context.dispose();
 791     }
 792 
 793     /**
 794      * Paints the specified component.
 795      *
 796      * @param context context for the component being painted
 797      * @param g the {@code Graphics} object used for painting
 798      * @see #update(Graphics,JComponent)
 799      */
 800     protected void paint(SynthContext context, Graphics g) {
 801         recalculateIfInsetsChanged();
 802         recalculateIfOrientationChanged();
 803         Rectangle clip = g.getClipBounds();
 804 
 805         if (lastSize == null || !lastSize.equals(slider.getSize())) {
 806             calculateGeometry();
 807         }
 808 
 809         if (paintValue) {
 810             FontMetrics fm = SwingUtilities2.getFontMetrics(slider, g);
 811             int labelWidth = context.getStyle().getGraphicsUtils(context).
 812                 computeStringWidth(context, g.getFont(), fm,
 813                     "" + slider.getValue());
 814             valueRect.x = thumbRect.x + (thumbRect.width - labelWidth) / 2;
 815 
 816             // For horizontal sliders, make sure value is not painted
 817             // outside slider bounds.
 818             if (slider.getOrientation() == JSlider.HORIZONTAL) {
 819                 if (valueRect.x + labelWidth > insetCache.left + contentRect.width) {
 820                     valueRect.x =  (insetCache.left + contentRect.width) - labelWidth;
 821                 }
 822                 valueRect.x = Math.max(valueRect.x, 0);
 823             }
 824 
 825             g.setColor(context.getStyle().getColor(
 826                     context, ColorType.TEXT_FOREGROUND));
 827             context.getStyle().getGraphicsUtils(context).paintText(
 828                     context, g, "" + slider.getValue(), valueRect.x,
 829                     valueRect.y, -1);
 830         }
 831 
 832         if (slider.getPaintTrack() && clip.intersects(trackRect)) {
 833             SynthContext subcontext = getContext(slider, Region.SLIDER_TRACK);
 834             paintTrack(subcontext, g, trackRect);
 835             subcontext.dispose();
 836         }
 837 
 838         if (clip.intersects(thumbRect)) {
 839             SynthContext subcontext = getContext(slider, Region.SLIDER_THUMB);
 840             paintThumb(subcontext, g, thumbRect);
 841             subcontext.dispose();
 842         }
 843 
 844         if (slider.getPaintTicks() && clip.intersects(tickRect)) {
 845             paintTicks(g);
 846         }
 847 
 848         if (slider.getPaintLabels() && clip.intersects(labelRect)) {
 849             paintLabels(g);
 850         }
 851     }
 852 
 853     /**
 854      * @inheritDoc
 855      */
 856     @Override
 857     public void paintBorder(SynthContext context, Graphics g, int x,
 858                             int y, int w, int h) {
 859         context.getPainter().paintSliderBorder(context, g, x, y, w, h,
 860                                                slider.getOrientation());
 861     }
 862 
 863     /**
 864      * Paints the slider thumb.
 865      *
 866      * @param context context for the component being painted
 867      * @param g {@code Graphics} object used for painting
 868      * @param thumbBounds bounding box for the thumb
 869      */
 870     protected void paintThumb(SynthContext context, Graphics g,
 871             Rectangle thumbBounds)  {
 872         int orientation = slider.getOrientation();
 873         SynthLookAndFeel.updateSubregion(context, g, thumbBounds);
 874         context.getPainter().paintSliderThumbBackground(context, g,
 875                              thumbBounds.x, thumbBounds.y, thumbBounds.width,
 876                              thumbBounds.height, orientation);
 877         context.getPainter().paintSliderThumbBorder(context, g,
 878                              thumbBounds.x, thumbBounds.y, thumbBounds.width,
 879                              thumbBounds.height, orientation);
 880     }
 881 
 882     /**
 883      * Paints the slider track.
 884      *
 885      * @param context context for the component being painted
 886      * @param g {@code Graphics} object used for painting
 887      * @param trackBounds bounding box for the track
 888      */
 889     protected void paintTrack(SynthContext context, Graphics g,
 890             Rectangle trackBounds) {
 891         int orientation = slider.getOrientation();
 892         SynthLookAndFeel.updateSubregion(context, g, trackBounds);
 893         context.getPainter().paintSliderTrackBackground(context, g,
 894                 trackBounds.x, trackBounds.y, trackBounds.width,
 895                 trackBounds.height, orientation);
 896         context.getPainter().paintSliderTrackBorder(context, g,
 897                 trackBounds.x, trackBounds.y, trackBounds.width,
 898                 trackBounds.height, orientation);
 899     }
 900 
 901     /**
 902      * @inheritDoc
 903      */
 904     @Override
 905     public void propertyChange(PropertyChangeEvent e) {
 906         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 907             updateStyle((JSlider)e.getSource());
 908         }
 909     }
 910 
 911     //////////////////////////////////////////////////
 912     /// Track Listener Class
 913     //////////////////////////////////////////////////
 914     /**
 915      * Track mouse movements.
 916      */
 917     private class SynthTrackListener extends TrackListener {
 918 
 919         @Override public void mouseExited(MouseEvent e) {
 920             setThumbActive(false);
 921         }
 922 
 923         @Override public void mousePressed(MouseEvent e) {
 924             super.mousePressed(e);
 925             setThumbPressed(thumbRect.contains(e.getX(), e.getY()));
 926         }
 927 
 928         @Override public void mouseReleased(MouseEvent e) {
 929             super.mouseReleased(e);
 930             updateThumbState(e.getX(), e.getY(), false);
 931         }
 932 
 933         @Override public void mouseDragged(MouseEvent e) {
 934             int thumbMiddle;
 935 
 936             if (!slider.isEnabled()) {
 937                 return;
 938             }
 939 
 940             currentMouseX = e.getX();
 941             currentMouseY = e.getY();
 942 
 943             if (!isDragging()) {
 944                 return;
 945             }
 946 
 947             slider.setValueIsAdjusting(true);
 948 
 949             switch (slider.getOrientation()) {
 950             case JSlider.VERTICAL:
 951                 int halfThumbHeight = thumbRect.height / 2;
 952                 int thumbTop = e.getY() - offset;
 953                 int trackTop = trackRect.y;
 954                 int trackBottom = trackRect.y + trackRect.height
 955                     - halfThumbHeight - trackBorder;
 956                 int vMax = yPositionForValue(slider.getMaximum() -
 957                     slider.getExtent());
 958 
 959                 if (drawInverted()) {
 960                     trackBottom = vMax;
 961                     trackTop = trackTop + halfThumbHeight;
 962                 } else {
 963                     trackTop = vMax;
 964                 }
 965                 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
 966                 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
 967 
 968                 setThumbLocation(thumbRect.x, thumbTop);
 969 
 970                 thumbMiddle = thumbTop + halfThumbHeight;
 971                 slider.setValue(valueForYPosition(thumbMiddle));
 972                 break;
 973             case JSlider.HORIZONTAL:
 974                 int halfThumbWidth = thumbRect.width / 2;
 975                 int thumbLeft = e.getX() - offset;
 976                 int trackLeft = trackRect.x + halfThumbWidth + trackBorder;
 977                 int trackRight = trackRect.x + trackRect.width
 978                     - halfThumbWidth - trackBorder;
 979                 int hMax = xPositionForValue(slider.getMaximum() -
 980                     slider.getExtent());
 981 
 982                 if (drawInverted()) {
 983                     trackLeft = hMax;
 984                 } else {
 985                     trackRight = hMax;
 986                 }
 987                 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
 988                 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
 989 
 990                 setThumbLocation(thumbLeft, thumbRect.y);
 991 
 992                 thumbMiddle = thumbLeft + halfThumbWidth;
 993                 slider.setValue(valueForXPosition(thumbMiddle));
 994                 break;
 995             default:
 996                 return;
 997             }
 998 
 999             if (slider.getValueIsAdjusting()) {
1000                 setThumbActive(true);
1001             }
1002         }
1003 
1004         @Override public void mouseMoved(MouseEvent e) {
1005             updateThumbState(e.getX(), e.getY());
1006         }
1007     }
1008 }