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 }