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