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