1 /* 2 * Copyright (c) 1997, 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.basic; 27 28 import java.awt.event.*; 29 import java.awt.*; 30 import java.beans.*; 31 import java.util.Dictionary; 32 import java.util.Enumeration; 33 34 import javax.swing.*; 35 import javax.swing.event.*; 36 import javax.swing.plaf.*; 37 import sun.swing.DefaultLookup; 38 import sun.swing.UIAction; 39 40 41 /** 42 * A Basic L&F implementation of SliderUI. 43 * 44 * @author Tom Santos 45 */ 46 public class BasicSliderUI extends SliderUI{ 47 // Old actions forward to an instance of this. 48 private static final Actions SHARED_ACTION = new Actions(); 49 50 public static final int POSITIVE_SCROLL = +1; 51 public static final int NEGATIVE_SCROLL = -1; 52 public static final int MIN_SCROLL = -2; 53 public static final int MAX_SCROLL = +2; 54 55 protected Timer scrollTimer; 56 protected JSlider slider; 57 58 protected Insets focusInsets = null; 59 protected Insets insetCache = null; 60 protected boolean leftToRightCache = true; 61 protected Rectangle focusRect = null; 62 protected Rectangle contentRect = null; 63 protected Rectangle labelRect = null; 64 protected Rectangle tickRect = null; 65 protected Rectangle trackRect = null; 66 protected Rectangle thumbRect = null; 67 68 protected int trackBuffer = 0; // The distance that the track is from the side of the control 69 70 private transient boolean isDragging; 71 72 protected TrackListener trackListener; 73 protected ChangeListener changeListener; 74 protected ComponentListener componentListener; 75 protected FocusListener focusListener; 76 protected ScrollListener scrollListener; 77 protected PropertyChangeListener propertyChangeListener; 78 private Handler handler; 79 private int lastValue; 80 81 // Colors 82 private Color shadowColor; 83 private Color highlightColor; 84 private Color focusColor; 85 86 /** 87 * Whther or not sameLabelBaselines is up to date. 88 */ 89 private boolean checkedLabelBaselines; 90 /** 91 * Whether or not all the entries in the labeltable have the same 92 * baseline. 93 */ 94 private boolean sameLabelBaselines; 95 96 97 protected Color getShadowColor() { 98 return shadowColor; 99 } 100 101 protected Color getHighlightColor() { 102 return highlightColor; 103 } 104 105 protected Color getFocusColor() { 106 return focusColor; 107 } 108 109 /** 110 * Returns true if the user is dragging the slider. 111 * 112 * @return true if the user is dragging the slider 113 * @since 1.5 114 */ 115 protected boolean isDragging() { 116 return isDragging; 117 } 118 119 ///////////////////////////////////////////////////////////////////////////// 120 // ComponentUI Interface Implementation methods 121 ///////////////////////////////////////////////////////////////////////////// 122 public static ComponentUI createUI(JComponent b) { 123 return new BasicSliderUI((JSlider)b); 124 } 125 126 public BasicSliderUI(JSlider b) { 127 } 128 129 public void installUI(JComponent c) { 130 slider = (JSlider) c; 131 132 checkedLabelBaselines = false; 133 134 slider.setEnabled(slider.isEnabled()); 135 LookAndFeel.installProperty(slider, "opaque", Boolean.TRUE); 136 137 isDragging = false; 138 trackListener = createTrackListener( slider ); 139 changeListener = createChangeListener( slider ); 140 componentListener = createComponentListener( slider ); 141 focusListener = createFocusListener( slider ); 142 scrollListener = createScrollListener( slider ); 143 propertyChangeListener = createPropertyChangeListener( slider ); 144 145 installDefaults( slider ); 146 installListeners( slider ); 147 installKeyboardActions( slider ); 148 149 scrollTimer = new Timer( 100, scrollListener ); 150 scrollTimer.setInitialDelay( 300 ); 151 152 insetCache = slider.getInsets(); 153 leftToRightCache = BasicGraphicsUtils.isLeftToRight(slider); 154 focusRect = new Rectangle(); 155 contentRect = new Rectangle(); 156 labelRect = new Rectangle(); 157 tickRect = new Rectangle(); 158 trackRect = new Rectangle(); 159 thumbRect = new Rectangle(); 160 lastValue = slider.getValue(); 161 162 calculateGeometry(); // This figures out where the labels, ticks, track, and thumb are. 163 } 164 165 public void uninstallUI(JComponent c) { 166 if ( c != slider ) 167 throw new IllegalComponentStateException( 168 this + " was asked to deinstall() " 169 + c + " when it only knows about " 170 + slider + "."); 171 172 scrollTimer.stop(); 173 scrollTimer = null; 174 175 uninstallDefaults(slider); 176 uninstallListeners( slider ); 177 uninstallKeyboardActions(slider); 178 179 insetCache = null; 180 leftToRightCache = true; 181 focusRect = null; 182 contentRect = null; 183 labelRect = null; 184 tickRect = null; 185 trackRect = null; 186 thumbRect = null; 187 trackListener = null; 188 changeListener = null; 189 componentListener = null; 190 focusListener = null; 191 scrollListener = null; 192 propertyChangeListener = null; 193 slider = null; 194 } 195 196 protected void installDefaults( JSlider slider ) { 197 LookAndFeel.installBorder(slider, "Slider.border"); 198 LookAndFeel.installColorsAndFont(slider, "Slider.background", 199 "Slider.foreground", "Slider.font"); 200 highlightColor = UIManager.getColor("Slider.highlight"); 201 202 shadowColor = UIManager.getColor("Slider.shadow"); 203 focusColor = UIManager.getColor("Slider.focus"); 204 205 focusInsets = (Insets)UIManager.get( "Slider.focusInsets" ); 206 // use default if missing so that BasicSliderUI can be used in other 207 // LAFs like Nimbus 208 if (focusInsets == null) focusInsets = new InsetsUIResource(2,2,2,2); 209 } 210 211 protected void uninstallDefaults(JSlider slider) { 212 LookAndFeel.uninstallBorder(slider); 213 214 focusInsets = null; 215 } 216 217 protected TrackListener createTrackListener(JSlider slider) { 218 return new TrackListener(); 219 } 220 221 protected ChangeListener createChangeListener(JSlider slider) { 222 return getHandler(); 223 } 224 225 protected ComponentListener createComponentListener(JSlider slider) { 226 return getHandler(); 227 } 228 229 protected FocusListener createFocusListener(JSlider slider) { 230 return getHandler(); 231 } 232 233 protected ScrollListener createScrollListener( JSlider slider ) { 234 return new ScrollListener(); 235 } 236 237 protected PropertyChangeListener createPropertyChangeListener( 238 JSlider slider) { 239 return getHandler(); 240 } 241 242 private Handler getHandler() { 243 if (handler == null) { 244 handler = new Handler(); 245 } 246 return handler; 247 } 248 249 protected void installListeners( JSlider slider ) { 250 slider.addMouseListener(trackListener); 251 slider.addMouseMotionListener(trackListener); 252 slider.addFocusListener(focusListener); 253 slider.addComponentListener(componentListener); 254 slider.addPropertyChangeListener( propertyChangeListener ); 255 slider.getModel().addChangeListener(changeListener); 256 } 257 258 protected void uninstallListeners( JSlider slider ) { 259 slider.removeMouseListener(trackListener); 260 slider.removeMouseMotionListener(trackListener); 261 slider.removeFocusListener(focusListener); 262 slider.removeComponentListener(componentListener); 263 slider.removePropertyChangeListener( propertyChangeListener ); 264 slider.getModel().removeChangeListener(changeListener); 265 handler = null; 266 } 267 268 protected void installKeyboardActions( JSlider slider ) { 269 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider); 270 SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, km); 271 LazyActionMap.installLazyActionMap(slider, BasicSliderUI.class, 272 "Slider.actionMap"); 273 } 274 275 InputMap getInputMap(int condition, JSlider slider) { 276 if (condition == JComponent.WHEN_FOCUSED) { 277 InputMap keyMap = (InputMap)DefaultLookup.get(slider, this, 278 "Slider.focusInputMap"); 279 InputMap rtlKeyMap; 280 281 if (slider.getComponentOrientation().isLeftToRight() || 282 ((rtlKeyMap = (InputMap)DefaultLookup.get(slider, this, 283 "Slider.focusInputMap.RightToLeft")) == null)) { 284 return keyMap; 285 } else { 286 rtlKeyMap.setParent(keyMap); 287 return rtlKeyMap; 288 } 289 } 290 return null; 291 } 292 293 /** 294 * Populates ComboBox's actions. 295 */ 296 static void loadActionMap(LazyActionMap map) { 297 map.put(new Actions(Actions.POSITIVE_UNIT_INCREMENT)); 298 map.put(new Actions(Actions.POSITIVE_BLOCK_INCREMENT)); 299 map.put(new Actions(Actions.NEGATIVE_UNIT_INCREMENT)); 300 map.put(new Actions(Actions.NEGATIVE_BLOCK_INCREMENT)); 301 map.put(new Actions(Actions.MIN_SCROLL_INCREMENT)); 302 map.put(new Actions(Actions.MAX_SCROLL_INCREMENT)); 303 } 304 305 protected void uninstallKeyboardActions( JSlider slider ) { 306 SwingUtilities.replaceUIActionMap(slider, null); 307 SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, 308 null); 309 } 310 311 312 /** 313 * Returns the baseline. 314 * 315 * @throws NullPointerException {@inheritDoc} 316 * @throws IllegalArgumentException {@inheritDoc} 317 * @see javax.swing.JComponent#getBaseline(int, int) 318 * @since 1.6 319 */ 320 public int getBaseline(JComponent c, int width, int height) { 321 super.getBaseline(c, width, height); 322 if (slider.getPaintLabels() && labelsHaveSameBaselines()) { 323 FontMetrics metrics = slider.getFontMetrics(slider.getFont()); 324 Insets insets = slider.getInsets(); 325 Dimension thumbSize = getThumbSize(); 326 if (slider.getOrientation() == JSlider.HORIZONTAL) { 327 int tickLength = getTickLength(); 328 int contentHeight = height - insets.top - insets.bottom - 329 focusInsets.top - focusInsets.bottom; 330 int thumbHeight = thumbSize.height; 331 int centerSpacing = thumbHeight; 332 if (slider.getPaintTicks()) { 333 centerSpacing += tickLength; 334 } 335 // Assume uniform labels. 336 centerSpacing += getHeightOfTallestLabel(); 337 int trackY = insets.top + focusInsets.top + 338 (contentHeight - centerSpacing - 1) / 2; 339 int trackHeight = thumbHeight; 340 int tickY = trackY + trackHeight; 341 int tickHeight = tickLength; 342 if (!slider.getPaintTicks()) { 343 tickHeight = 0; 344 } 345 int labelY = tickY + tickHeight; 346 return labelY + metrics.getAscent(); 347 } 348 else { // vertical 349 boolean inverted = slider.getInverted(); 350 Integer value = inverted ? getLowestValue() : 351 getHighestValue(); 352 if (value != null) { 353 int thumbHeight = thumbSize.height; 354 int trackBuffer = Math.max(metrics.getHeight() / 2, 355 thumbHeight / 2); 356 int contentY = focusInsets.top + insets.top; 357 int trackY = contentY + trackBuffer; 358 int trackHeight = height - focusInsets.top - 359 focusInsets.bottom - insets.top - insets.bottom - 360 trackBuffer - trackBuffer; 361 int yPosition = yPositionForValue(value, trackY, 362 trackHeight); 363 return yPosition - metrics.getHeight() / 2 + 364 metrics.getAscent(); 365 } 366 } 367 } 368 return 0; 369 } 370 371 /** 372 * Returns an enum indicating how the baseline of the component 373 * changes as the size changes. 374 * 375 * @throws NullPointerException {@inheritDoc} 376 * @see javax.swing.JComponent#getBaseline(int, int) 377 * @since 1.6 378 */ 379 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 380 JComponent c) { 381 super.getBaselineResizeBehavior(c); 382 // NOTE: BasicSpinner really provides for CENTER_OFFSET, but 383 // the default min/pref size is smaller than it should be 384 // so that getBaseline() doesn't implement the contract 385 // for CENTER_OFFSET as defined in Component. 386 return Component.BaselineResizeBehavior.OTHER; 387 } 388 389 /** 390 * Returns true if all the labels from the label table have the same 391 * baseline. 392 * 393 * @return true if all the labels from the label table have the 394 * same baseline 395 * @since 1.6 396 */ 397 protected boolean labelsHaveSameBaselines() { 398 if (!checkedLabelBaselines) { 399 checkedLabelBaselines = true; 400 @SuppressWarnings("rawtypes") 401 Dictionary dictionary = slider.getLabelTable(); 402 if (dictionary != null) { 403 sameLabelBaselines = true; 404 Enumeration<?> elements = dictionary.elements(); 405 int baseline = -1; 406 while (elements.hasMoreElements()) { 407 JComponent label = (JComponent) elements.nextElement(); 408 Dimension pref = label.getPreferredSize(); 409 int labelBaseline = label.getBaseline(pref.width, 410 pref.height); 411 if (labelBaseline >= 0) { 412 if (baseline == -1) { 413 baseline = labelBaseline; 414 } 415 else if (baseline != labelBaseline) { 416 sameLabelBaselines = false; 417 break; 418 } 419 } 420 else { 421 sameLabelBaselines = false; 422 break; 423 } 424 } 425 } 426 else { 427 sameLabelBaselines = false; 428 } 429 } 430 return sameLabelBaselines; 431 } 432 433 public Dimension getPreferredHorizontalSize() { 434 Dimension horizDim = (Dimension)DefaultLookup.get(slider, 435 this, "Slider.horizontalSize"); 436 if (horizDim == null) { 437 horizDim = new Dimension(200, 21); 438 } 439 return horizDim; 440 } 441 442 public Dimension getPreferredVerticalSize() { 443 Dimension vertDim = (Dimension)DefaultLookup.get(slider, 444 this, "Slider.verticalSize"); 445 if (vertDim == null) { 446 vertDim = new Dimension(21, 200); 447 } 448 return vertDim; 449 } 450 451 public Dimension getMinimumHorizontalSize() { 452 Dimension minHorizDim = (Dimension)DefaultLookup.get(slider, 453 this, "Slider.minimumHorizontalSize"); 454 if (minHorizDim == null) { 455 minHorizDim = new Dimension(36, 21); 456 } 457 return minHorizDim; 458 } 459 460 public Dimension getMinimumVerticalSize() { 461 Dimension minVertDim = (Dimension)DefaultLookup.get(slider, 462 this, "Slider.minimumVerticalSize"); 463 if (minVertDim == null) { 464 minVertDim = new Dimension(21, 36); 465 } 466 return minVertDim; 467 } 468 469 public Dimension getPreferredSize(JComponent c) { 470 recalculateIfInsetsChanged(); 471 Dimension d; 472 if ( slider.getOrientation() == JSlider.VERTICAL ) { 473 d = new Dimension(getPreferredVerticalSize()); 474 d.width = insetCache.left + insetCache.right; 475 d.width += focusInsets.left + focusInsets.right; 476 d.width += trackRect.width + tickRect.width + labelRect.width; 477 } 478 else { 479 d = new Dimension(getPreferredHorizontalSize()); 480 d.height = insetCache.top + insetCache.bottom; 481 d.height += focusInsets.top + focusInsets.bottom; 482 d.height += trackRect.height + tickRect.height + labelRect.height; 483 } 484 485 return d; 486 } 487 488 public Dimension getMinimumSize(JComponent c) { 489 recalculateIfInsetsChanged(); 490 Dimension d; 491 492 if ( slider.getOrientation() == JSlider.VERTICAL ) { 493 d = new Dimension(getMinimumVerticalSize()); 494 d.width = insetCache.left + insetCache.right; 495 d.width += focusInsets.left + focusInsets.right; 496 d.width += trackRect.width + tickRect.width + labelRect.width; 497 } 498 else { 499 d = new Dimension(getMinimumHorizontalSize()); 500 d.height = insetCache.top + insetCache.bottom; 501 d.height += focusInsets.top + focusInsets.bottom; 502 d.height += trackRect.height + tickRect.height + labelRect.height; 503 } 504 505 return d; 506 } 507 508 public Dimension getMaximumSize(JComponent c) { 509 Dimension d = getPreferredSize(c); 510 if ( slider.getOrientation() == JSlider.VERTICAL ) { 511 d.height = Short.MAX_VALUE; 512 } 513 else { 514 d.width = Short.MAX_VALUE; 515 } 516 517 return d; 518 } 519 520 protected void calculateGeometry() { 521 calculateFocusRect(); 522 calculateContentRect(); 523 calculateThumbSize(); 524 calculateTrackBuffer(); 525 calculateTrackRect(); 526 calculateTickRect(); 527 calculateLabelRect(); 528 calculateThumbLocation(); 529 } 530 531 protected void calculateFocusRect() { 532 focusRect.x = insetCache.left; 533 focusRect.y = insetCache.top; 534 focusRect.width = slider.getWidth() - (insetCache.left + insetCache.right); 535 focusRect.height = slider.getHeight() - (insetCache.top + insetCache.bottom); 536 } 537 538 protected void calculateThumbSize() { 539 Dimension size = getThumbSize(); 540 thumbRect.setSize( size.width, size.height ); 541 } 542 543 protected void calculateContentRect() { 544 contentRect.x = focusRect.x + focusInsets.left; 545 contentRect.y = focusRect.y + focusInsets.top; 546 contentRect.width = focusRect.width - (focusInsets.left + focusInsets.right); 547 contentRect.height = focusRect.height - (focusInsets.top + focusInsets.bottom); 548 } 549 550 private int getTickSpacing() { 551 int majorTickSpacing = slider.getMajorTickSpacing(); 552 int minorTickSpacing = slider.getMinorTickSpacing(); 553 554 int result; 555 556 if (minorTickSpacing > 0) { 557 result = minorTickSpacing; 558 } else if (majorTickSpacing > 0) { 559 result = majorTickSpacing; 560 } else { 561 result = 0; 562 } 563 564 return result; 565 } 566 567 protected void calculateThumbLocation() { 568 if ( slider.getSnapToTicks() ) { 569 int sliderValue = slider.getValue(); 570 int snappedValue = sliderValue; 571 int tickSpacing = getTickSpacing(); 572 573 if ( tickSpacing != 0 ) { 574 // If it's not on a tick, change the value 575 if ( (sliderValue - slider.getMinimum()) % tickSpacing != 0 ) { 576 float temp = (float)(sliderValue - slider.getMinimum()) / (float)tickSpacing; 577 int whichTick = Math.round( temp ); 578 579 // This is the fix for the bug #6401380 580 if (temp - (int)temp == .5 && sliderValue < lastValue) { 581 whichTick --; 582 } 583 snappedValue = slider.getMinimum() + (whichTick * tickSpacing); 584 } 585 586 if( snappedValue != sliderValue ) { 587 slider.setValue( snappedValue ); 588 } 589 } 590 } 591 592 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 593 int valuePosition = xPositionForValue(slider.getValue()); 594 595 thumbRect.x = valuePosition - (thumbRect.width / 2); 596 thumbRect.y = trackRect.y; 597 } 598 else { 599 int valuePosition = yPositionForValue(slider.getValue()); 600 601 thumbRect.x = trackRect.x; 602 thumbRect.y = valuePosition - (thumbRect.height / 2); 603 } 604 } 605 606 protected void calculateTrackBuffer() { 607 if ( slider.getPaintLabels() && slider.getLabelTable() != null ) { 608 Component highLabel = getHighestValueLabel(); 609 Component lowLabel = getLowestValueLabel(); 610 611 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 612 trackBuffer = Math.max( highLabel.getBounds().width, lowLabel.getBounds().width ) / 2; 613 trackBuffer = Math.max( trackBuffer, thumbRect.width / 2 ); 614 } 615 else { 616 trackBuffer = Math.max( highLabel.getBounds().height, lowLabel.getBounds().height ) / 2; 617 trackBuffer = Math.max( trackBuffer, thumbRect.height / 2 ); 618 } 619 } 620 else { 621 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 622 trackBuffer = thumbRect.width / 2; 623 } 624 else { 625 trackBuffer = thumbRect.height / 2; 626 } 627 } 628 } 629 630 631 protected void calculateTrackRect() { 632 int centerSpacing; // used to center sliders added using BorderLayout.CENTER (bug 4275631) 633 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 634 centerSpacing = thumbRect.height; 635 if ( slider.getPaintTicks() ) centerSpacing += getTickLength(); 636 if ( slider.getPaintLabels() ) centerSpacing += getHeightOfTallestLabel(); 637 trackRect.x = contentRect.x + trackBuffer; 638 trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1)/2; 639 trackRect.width = contentRect.width - (trackBuffer * 2); 640 trackRect.height = thumbRect.height; 641 } 642 else { 643 centerSpacing = thumbRect.width; 644 if (BasicGraphicsUtils.isLeftToRight(slider)) { 645 if ( slider.getPaintTicks() ) centerSpacing += getTickLength(); 646 if ( slider.getPaintLabels() ) centerSpacing += getWidthOfWidestLabel(); 647 } else { 648 if ( slider.getPaintTicks() ) centerSpacing -= getTickLength(); 649 if ( slider.getPaintLabels() ) centerSpacing -= getWidthOfWidestLabel(); 650 } 651 trackRect.x = contentRect.x + (contentRect.width - centerSpacing - 1)/2; 652 trackRect.y = contentRect.y + trackBuffer; 653 trackRect.width = thumbRect.width; 654 trackRect.height = contentRect.height - (trackBuffer * 2); 655 } 656 657 } 658 659 /** 660 * Gets the height of the tick area for horizontal sliders and the width of 661 * the tick area for vertical sliders. BasicSliderUI uses the returned value 662 * to determine the tick area rectangle. If you want to give your ticks some 663 * room, make this larger than you need and paint your ticks away from the 664 * sides in paintTicks(). 665 * 666 * @return an integer representing the height of the tick area for 667 * horizontal sliders, and the width of the tick area for the vertical 668 * sliders 669 */ 670 protected int getTickLength() { 671 return 8; 672 } 673 674 protected void calculateTickRect() { 675 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 676 tickRect.x = trackRect.x; 677 tickRect.y = trackRect.y + trackRect.height; 678 tickRect.width = trackRect.width; 679 tickRect.height = (slider.getPaintTicks()) ? getTickLength() : 0; 680 } 681 else { 682 tickRect.width = (slider.getPaintTicks()) ? getTickLength() : 0; 683 if(BasicGraphicsUtils.isLeftToRight(slider)) { 684 tickRect.x = trackRect.x + trackRect.width; 685 } 686 else { 687 tickRect.x = trackRect.x - tickRect.width; 688 } 689 tickRect.y = trackRect.y; 690 tickRect.height = trackRect.height; 691 } 692 } 693 694 protected void calculateLabelRect() { 695 if ( slider.getPaintLabels() ) { 696 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 697 labelRect.x = tickRect.x - trackBuffer; 698 labelRect.y = tickRect.y + tickRect.height; 699 labelRect.width = tickRect.width + (trackBuffer * 2); 700 labelRect.height = getHeightOfTallestLabel(); 701 } 702 else { 703 if(BasicGraphicsUtils.isLeftToRight(slider)) { 704 labelRect.x = tickRect.x + tickRect.width; 705 labelRect.width = getWidthOfWidestLabel(); 706 } 707 else { 708 labelRect.width = getWidthOfWidestLabel(); 709 labelRect.x = tickRect.x - labelRect.width; 710 } 711 labelRect.y = tickRect.y - trackBuffer; 712 labelRect.height = tickRect.height + (trackBuffer * 2); 713 } 714 } 715 else { 716 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 717 labelRect.x = tickRect.x; 718 labelRect.y = tickRect.y + tickRect.height; 719 labelRect.width = tickRect.width; 720 labelRect.height = 0; 721 } 722 else { 723 if(BasicGraphicsUtils.isLeftToRight(slider)) { 724 labelRect.x = tickRect.x + tickRect.width; 725 } 726 else { 727 labelRect.x = tickRect.x; 728 } 729 labelRect.y = tickRect.y; 730 labelRect.width = 0; 731 labelRect.height = tickRect.height; 732 } 733 } 734 } 735 736 protected Dimension getThumbSize() { 737 Dimension size = new Dimension(); 738 739 if ( slider.getOrientation() == JSlider.VERTICAL ) { 740 size.width = 20; 741 size.height = 11; 742 } 743 else { 744 size.width = 11; 745 size.height = 20; 746 } 747 748 return size; 749 } 750 751 public class PropertyChangeHandler implements PropertyChangeListener { 752 // NOTE: This class exists only for backward compatibility. All 753 // its functionality has been moved into Handler. If you need to add 754 // new functionality add it to the Handler, but make sure this 755 // class calls into the Handler. 756 public void propertyChange( PropertyChangeEvent e ) { 757 getHandler().propertyChange(e); 758 } 759 } 760 761 protected int getWidthOfWidestLabel() { 762 @SuppressWarnings("rawtypes") 763 Dictionary dictionary = slider.getLabelTable(); 764 int widest = 0; 765 if ( dictionary != null ) { 766 Enumeration<?> keys = dictionary.keys(); 767 while ( keys.hasMoreElements() ) { 768 JComponent label = (JComponent) dictionary.get(keys.nextElement()); 769 widest = Math.max( label.getPreferredSize().width, widest ); 770 } 771 } 772 return widest; 773 } 774 775 protected int getHeightOfTallestLabel() { 776 @SuppressWarnings("rawtypes") 777 Dictionary dictionary = slider.getLabelTable(); 778 int tallest = 0; 779 if ( dictionary != null ) { 780 Enumeration<?> keys = dictionary.keys(); 781 while ( keys.hasMoreElements() ) { 782 JComponent label = (JComponent) dictionary.get(keys.nextElement()); 783 tallest = Math.max( label.getPreferredSize().height, tallest ); 784 } 785 } 786 return tallest; 787 } 788 789 protected int getWidthOfHighValueLabel() { 790 Component label = getHighestValueLabel(); 791 int width = 0; 792 793 if ( label != null ) { 794 width = label.getPreferredSize().width; 795 } 796 797 return width; 798 } 799 800 protected int getWidthOfLowValueLabel() { 801 Component label = getLowestValueLabel(); 802 int width = 0; 803 804 if ( label != null ) { 805 width = label.getPreferredSize().width; 806 } 807 808 return width; 809 } 810 811 protected int getHeightOfHighValueLabel() { 812 Component label = getHighestValueLabel(); 813 int height = 0; 814 815 if ( label != null ) { 816 height = label.getPreferredSize().height; 817 } 818 819 return height; 820 } 821 822 protected int getHeightOfLowValueLabel() { 823 Component label = getLowestValueLabel(); 824 int height = 0; 825 826 if ( label != null ) { 827 height = label.getPreferredSize().height; 828 } 829 830 return height; 831 } 832 833 protected boolean drawInverted() { 834 if (slider.getOrientation()==JSlider.HORIZONTAL) { 835 if(BasicGraphicsUtils.isLeftToRight(slider)) { 836 return slider.getInverted(); 837 } else { 838 return !slider.getInverted(); 839 } 840 } else { 841 return slider.getInverted(); 842 } 843 } 844 845 /** 846 * Returns the biggest value that has an entry in the label table. 847 * 848 * @return biggest value that has an entry in the label table, or 849 * null. 850 * @since 1.6 851 */ 852 protected Integer getHighestValue() { 853 @SuppressWarnings("rawtypes") 854 Dictionary dictionary = slider.getLabelTable(); 855 856 if (dictionary == null) { 857 return null; 858 } 859 860 Enumeration<?> keys = dictionary.keys(); 861 862 Integer max = null; 863 864 while (keys.hasMoreElements()) { 865 Integer i = (Integer) keys.nextElement(); 866 867 if (max == null || i > max) { 868 max = i; 869 } 870 } 871 872 return max; 873 } 874 875 /** 876 * Returns the smallest value that has an entry in the label table. 877 * 878 * @return smallest value that has an entry in the label table, or 879 * null. 880 * @since 1.6 881 */ 882 protected Integer getLowestValue() { 883 @SuppressWarnings("rawtypes") 884 Dictionary dictionary = slider.getLabelTable(); 885 886 if (dictionary == null) { 887 return null; 888 } 889 890 Enumeration<?> keys = dictionary.keys(); 891 892 Integer min = null; 893 894 while (keys.hasMoreElements()) { 895 Integer i = (Integer) keys.nextElement(); 896 897 if (min == null || i < min) { 898 min = i; 899 } 900 } 901 902 return min; 903 } 904 905 906 /** 907 * Returns the label that corresponds to the highest slider value in the 908 * label table. 909 * 910 * @return the label that corresponds to the highest slider value in the 911 * label table 912 * @see JSlider#setLabelTable 913 */ 914 protected Component getLowestValueLabel() { 915 Integer min = getLowestValue(); 916 if (min != null) { 917 return (Component)slider.getLabelTable().get(min); 918 } 919 return null; 920 } 921 922 /** 923 * Returns the label that corresponds to the lowest slider value in the 924 * label table. 925 * 926 * @return the label that corresponds to the lowest slider value in the 927 * label table 928 * @see JSlider#setLabelTable 929 */ 930 protected Component getHighestValueLabel() { 931 Integer max = getHighestValue(); 932 if (max != null) { 933 return (Component)slider.getLabelTable().get(max); 934 } 935 return null; 936 } 937 938 public void paint( Graphics g, JComponent c ) { 939 recalculateIfInsetsChanged(); 940 recalculateIfOrientationChanged(); 941 Rectangle clip = g.getClipBounds(); 942 943 if ( !clip.intersects(trackRect) && slider.getPaintTrack()) 944 calculateGeometry(); 945 946 if ( slider.getPaintTrack() && clip.intersects( trackRect ) ) { 947 paintTrack( g ); 948 } 949 if ( slider.getPaintTicks() && clip.intersects( tickRect ) ) { 950 paintTicks( g ); 951 } 952 if ( slider.getPaintLabels() && clip.intersects( labelRect ) ) { 953 paintLabels( g ); 954 } 955 if ( slider.hasFocus() && clip.intersects( focusRect ) ) { 956 paintFocus( g ); 957 } 958 if ( clip.intersects( thumbRect ) ) { 959 paintThumb( g ); 960 } 961 } 962 963 protected void recalculateIfInsetsChanged() { 964 Insets newInsets = slider.getInsets(); 965 if ( !newInsets.equals( insetCache ) ) { 966 insetCache = newInsets; 967 calculateGeometry(); 968 } 969 } 970 971 protected void recalculateIfOrientationChanged() { 972 boolean ltr = BasicGraphicsUtils.isLeftToRight(slider); 973 if ( ltr!=leftToRightCache ) { 974 leftToRightCache = ltr; 975 calculateGeometry(); 976 } 977 } 978 979 public void paintFocus(Graphics g) { 980 g.setColor( getFocusColor() ); 981 982 BasicGraphicsUtils.drawDashedRect( g, focusRect.x, focusRect.y, 983 focusRect.width, focusRect.height ); 984 } 985 986 public void paintTrack(Graphics g) { 987 988 Rectangle trackBounds = trackRect; 989 990 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 991 int cy = (trackBounds.height / 2) - 2; 992 int cw = trackBounds.width; 993 994 g.translate(trackBounds.x, trackBounds.y + cy); 995 996 g.setColor(getShadowColor()); 997 g.drawLine(0, 0, cw - 1, 0); 998 g.drawLine(0, 1, 0, 2); 999 g.setColor(getHighlightColor()); 1000 g.drawLine(0, 3, cw, 3); 1001 g.drawLine(cw, 0, cw, 3); 1002 g.setColor(Color.black); 1003 g.drawLine(1, 1, cw-2, 1); 1004 1005 g.translate(-trackBounds.x, -(trackBounds.y + cy)); 1006 } 1007 else { 1008 int cx = (trackBounds.width / 2) - 2; 1009 int ch = trackBounds.height; 1010 1011 g.translate(trackBounds.x + cx, trackBounds.y); 1012 1013 g.setColor(getShadowColor()); 1014 g.drawLine(0, 0, 0, ch - 1); 1015 g.drawLine(1, 0, 2, 0); 1016 g.setColor(getHighlightColor()); 1017 g.drawLine(3, 0, 3, ch); 1018 g.drawLine(0, ch, 3, ch); 1019 g.setColor(Color.black); 1020 g.drawLine(1, 1, 1, ch-2); 1021 1022 g.translate(-(trackBounds.x + cx), -trackBounds.y); 1023 } 1024 } 1025 1026 public void paintTicks(Graphics g) { 1027 Rectangle tickBounds = tickRect; 1028 1029 g.setColor(DefaultLookup.getColor(slider, this, "Slider.tickColor", Color.black)); 1030 1031 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 1032 g.translate(0, tickBounds.y); 1033 1034 if (slider.getMinorTickSpacing() > 0) { 1035 int value = slider.getMinimum(); 1036 1037 while ( value <= slider.getMaximum() ) { 1038 int xPos = xPositionForValue(value); 1039 paintMinorTickForHorizSlider( g, tickBounds, xPos ); 1040 1041 // Overflow checking 1042 if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) { 1043 break; 1044 } 1045 1046 value += slider.getMinorTickSpacing(); 1047 } 1048 } 1049 1050 if (slider.getMajorTickSpacing() > 0) { 1051 int value = slider.getMinimum(); 1052 1053 while ( value <= slider.getMaximum() ) { 1054 int xPos = xPositionForValue(value); 1055 paintMajorTickForHorizSlider( g, tickBounds, xPos ); 1056 1057 // Overflow checking 1058 if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) { 1059 break; 1060 } 1061 1062 value += slider.getMajorTickSpacing(); 1063 } 1064 } 1065 1066 g.translate( 0, -tickBounds.y); 1067 } else { 1068 g.translate(tickBounds.x, 0); 1069 1070 if (slider.getMinorTickSpacing() > 0) { 1071 int offset = 0; 1072 if(!BasicGraphicsUtils.isLeftToRight(slider)) { 1073 offset = tickBounds.width - tickBounds.width / 2; 1074 g.translate(offset, 0); 1075 } 1076 1077 int value = slider.getMinimum(); 1078 1079 while (value <= slider.getMaximum()) { 1080 int yPos = yPositionForValue(value); 1081 paintMinorTickForVertSlider( g, tickBounds, yPos ); 1082 1083 // Overflow checking 1084 if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) { 1085 break; 1086 } 1087 1088 value += slider.getMinorTickSpacing(); 1089 } 1090 1091 if(!BasicGraphicsUtils.isLeftToRight(slider)) { 1092 g.translate(-offset, 0); 1093 } 1094 } 1095 1096 if (slider.getMajorTickSpacing() > 0) { 1097 if(!BasicGraphicsUtils.isLeftToRight(slider)) { 1098 g.translate(2, 0); 1099 } 1100 1101 int value = slider.getMinimum(); 1102 1103 while (value <= slider.getMaximum()) { 1104 int yPos = yPositionForValue(value); 1105 paintMajorTickForVertSlider( g, tickBounds, yPos ); 1106 1107 // Overflow checking 1108 if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) { 1109 break; 1110 } 1111 1112 value += slider.getMajorTickSpacing(); 1113 } 1114 1115 if(!BasicGraphicsUtils.isLeftToRight(slider)) { 1116 g.translate(-2, 0); 1117 } 1118 } 1119 g.translate(-tickBounds.x, 0); 1120 } 1121 } 1122 1123 protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) { 1124 g.drawLine( x, 0, x, tickBounds.height / 2 - 1 ); 1125 } 1126 1127 protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) { 1128 g.drawLine( x, 0, x, tickBounds.height - 2 ); 1129 } 1130 1131 protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) { 1132 g.drawLine( 0, y, tickBounds.width / 2 - 1, y ); 1133 } 1134 1135 protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) { 1136 g.drawLine( 0, y, tickBounds.width - 2, y ); 1137 } 1138 1139 public void paintLabels( Graphics g ) { 1140 Rectangle labelBounds = labelRect; 1141 1142 @SuppressWarnings("rawtypes") 1143 Dictionary dictionary = slider.getLabelTable(); 1144 if ( dictionary != null ) { 1145 Enumeration<?> keys = dictionary.keys(); 1146 int minValue = slider.getMinimum(); 1147 int maxValue = slider.getMaximum(); 1148 boolean enabled = slider.isEnabled(); 1149 while ( keys.hasMoreElements() ) { 1150 Integer key = (Integer)keys.nextElement(); 1151 int value = key.intValue(); 1152 if (value >= minValue && value <= maxValue) { 1153 JComponent label = (JComponent) dictionary.get(key); 1154 label.setEnabled(enabled); 1155 1156 if (label instanceof JLabel) { 1157 Icon icon = label.isEnabled() ? ((JLabel) label).getIcon() : ((JLabel) label).getDisabledIcon(); 1158 1159 if (icon instanceof ImageIcon) { 1160 // Register Slider as an image observer. It allows to catch notifications about 1161 // image changes (e.g. gif animation) 1162 Toolkit.getDefaultToolkit().checkImage(((ImageIcon) icon).getImage(), -1, -1, slider); 1163 } 1164 } 1165 1166 if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 1167 g.translate( 0, labelBounds.y ); 1168 paintHorizontalLabel( g, value, label ); 1169 g.translate( 0, -labelBounds.y ); 1170 } 1171 else { 1172 int offset = 0; 1173 if (!BasicGraphicsUtils.isLeftToRight(slider)) { 1174 offset = labelBounds.width - 1175 label.getPreferredSize().width; 1176 } 1177 g.translate( labelBounds.x + offset, 0 ); 1178 paintVerticalLabel( g, value, label ); 1179 g.translate( -labelBounds.x - offset, 0 ); 1180 } 1181 } 1182 } 1183 } 1184 1185 } 1186 1187 /** 1188 * Called for every label in the label table. Used to draw the labels for 1189 * horizontal sliders. The graphics have been translated to labelRect.y 1190 * already. 1191 * 1192 * @param g the graphics context in which to paint 1193 * @param value the value of the slider 1194 * @param label the component label in the label table that needs to be 1195 * painted 1196 * @see JSlider#setLabelTable 1197 */ 1198 protected void paintHorizontalLabel( Graphics g, int value, Component label ) { 1199 int labelCenter = xPositionForValue( value ); 1200 int labelLeft = labelCenter - (label.getPreferredSize().width / 2); 1201 g.translate( labelLeft, 0 ); 1202 label.paint( g ); 1203 g.translate( -labelLeft, 0 ); 1204 } 1205 1206 /** 1207 * Called for every label in the label table. Used to draw the labels for 1208 * vertical sliders. The graphics have been translated to labelRect.x 1209 * already. 1210 * 1211 * @param g the graphics context in which to paint 1212 * @param value the value of the slider 1213 * @param label the component label in the label table that needs to be 1214 * painted 1215 * @see JSlider#setLabelTable 1216 */ 1217 protected void paintVerticalLabel( Graphics g, int value, Component label ) { 1218 int labelCenter = yPositionForValue( value ); 1219 int labelTop = labelCenter - (label.getPreferredSize().height / 2); 1220 g.translate( 0, labelTop ); 1221 label.paint( g ); 1222 g.translate( 0, -labelTop ); 1223 } 1224 1225 public void paintThumb(Graphics g) { 1226 Rectangle knobBounds = thumbRect; 1227 int w = knobBounds.width; 1228 int h = knobBounds.height; 1229 1230 g.translate(knobBounds.x, knobBounds.y); 1231 1232 if ( slider.isEnabled() ) { 1233 g.setColor(slider.getBackground()); 1234 } 1235 else { 1236 g.setColor(slider.getBackground().darker()); 1237 } 1238 1239 Boolean paintThumbArrowShape = 1240 (Boolean)slider.getClientProperty("Slider.paintThumbArrowShape"); 1241 1242 if ((!slider.getPaintTicks() && paintThumbArrowShape == null) || 1243 paintThumbArrowShape == Boolean.FALSE) { 1244 1245 // "plain" version 1246 g.fillRect(0, 0, w, h); 1247 1248 g.setColor(Color.black); 1249 g.drawLine(0, h-1, w-1, h-1); 1250 g.drawLine(w-1, 0, w-1, h-1); 1251 1252 g.setColor(highlightColor); 1253 g.drawLine(0, 0, 0, h-2); 1254 g.drawLine(1, 0, w-2, 0); 1255 1256 g.setColor(shadowColor); 1257 g.drawLine(1, h-2, w-2, h-2); 1258 g.drawLine(w-2, 1, w-2, h-3); 1259 } 1260 else if ( slider.getOrientation() == JSlider.HORIZONTAL ) { 1261 int cw = w / 2; 1262 g.fillRect(1, 1, w-3, h-1-cw); 1263 Polygon p = new Polygon(); 1264 p.addPoint(1, h-cw); 1265 p.addPoint(cw-1, h-1); 1266 p.addPoint(w-2, h-1-cw); 1267 g.fillPolygon(p); 1268 1269 g.setColor(highlightColor); 1270 g.drawLine(0, 0, w-2, 0); 1271 g.drawLine(0, 1, 0, h-1-cw); 1272 g.drawLine(0, h-cw, cw-1, h-1); 1273 1274 g.setColor(Color.black); 1275 g.drawLine(w-1, 0, w-1, h-2-cw); 1276 g.drawLine(w-1, h-1-cw, w-1-cw, h-1); 1277 1278 g.setColor(shadowColor); 1279 g.drawLine(w-2, 1, w-2, h-2-cw); 1280 g.drawLine(w-2, h-1-cw, w-1-cw, h-2); 1281 } 1282 else { // vertical 1283 int cw = h / 2; 1284 if(BasicGraphicsUtils.isLeftToRight(slider)) { 1285 g.fillRect(1, 1, w-1-cw, h-3); 1286 Polygon p = new Polygon(); 1287 p.addPoint(w-cw-1, 0); 1288 p.addPoint(w-1, cw); 1289 p.addPoint(w-1-cw, h-2); 1290 g.fillPolygon(p); 1291 1292 g.setColor(highlightColor); 1293 g.drawLine(0, 0, 0, h - 2); // left 1294 g.drawLine(1, 0, w-1-cw, 0); // top 1295 g.drawLine(w-cw-1, 0, w-1, cw); // top slant 1296 1297 g.setColor(Color.black); 1298 g.drawLine(0, h-1, w-2-cw, h-1); // bottom 1299 g.drawLine(w-1-cw, h-1, w-1, h-1-cw); // bottom slant 1300 1301 g.setColor(shadowColor); 1302 g.drawLine(1, h-2, w-2-cw, h-2 ); // bottom 1303 g.drawLine(w-1-cw, h-2, w-2, h-cw-1 ); // bottom slant 1304 } 1305 else { 1306 g.fillRect(5, 1, w-1-cw, h-3); 1307 Polygon p = new Polygon(); 1308 p.addPoint(cw, 0); 1309 p.addPoint(0, cw); 1310 p.addPoint(cw, h-2); 1311 g.fillPolygon(p); 1312 1313 g.setColor(highlightColor); 1314 g.drawLine(cw-1, 0, w-2, 0); // top 1315 g.drawLine(0, cw, cw, 0); // top slant 1316 1317 g.setColor(Color.black); 1318 g.drawLine(0, h-1-cw, cw, h-1 ); // bottom slant 1319 g.drawLine(cw, h-1, w-1, h-1); // bottom 1320 1321 g.setColor(shadowColor); 1322 g.drawLine(cw, h-2, w-2, h-2 ); // bottom 1323 g.drawLine(w-1, 1, w-1, h-2 ); // right 1324 } 1325 } 1326 1327 g.translate(-knobBounds.x, -knobBounds.y); 1328 } 1329 1330 // Used exclusively by setThumbLocation() 1331 private static Rectangle unionRect = new Rectangle(); 1332 1333 public void setThumbLocation(int x, int y) { 1334 unionRect.setBounds( thumbRect ); 1335 1336 thumbRect.setLocation( x, y ); 1337 1338 SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, unionRect ); 1339 slider.repaint( unionRect.x, unionRect.y, unionRect.width, unionRect.height ); 1340 } 1341 1342 public void scrollByBlock(int direction) { 1343 synchronized(slider) { 1344 int blockIncrement = 1345 (slider.getMaximum() - slider.getMinimum()) / 10; 1346 if (blockIncrement == 0) { 1347 blockIncrement = 1; 1348 } 1349 1350 if (slider.getSnapToTicks()) { 1351 int tickSpacing = getTickSpacing(); 1352 1353 if (blockIncrement < tickSpacing) { 1354 blockIncrement = tickSpacing; 1355 } 1356 } 1357 1358 int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); 1359 slider.setValue(slider.getValue() + delta); 1360 } 1361 } 1362 1363 public void scrollByUnit(int direction) { 1364 synchronized(slider) { 1365 int delta = ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); 1366 1367 if (slider.getSnapToTicks()) { 1368 delta *= getTickSpacing(); 1369 } 1370 1371 slider.setValue(slider.getValue() + delta); 1372 } 1373 } 1374 1375 /** 1376 * This function is called when a mousePressed was detected in the track, 1377 * not in the thumb. The default behavior is to scroll by block. You can 1378 * override this method to stop it from scrolling or to add additional 1379 * behavior. 1380 * 1381 * @param dir the direction and number of blocks to scroll 1382 */ 1383 protected void scrollDueToClickInTrack( int dir ) { 1384 scrollByBlock( dir ); 1385 } 1386 1387 protected int xPositionForValue( int value ) { 1388 int min = slider.getMinimum(); 1389 int max = slider.getMaximum(); 1390 int trackLength = trackRect.width; 1391 double valueRange = (double)max - (double)min; 1392 double pixelsPerValue = (double)trackLength / valueRange; 1393 int trackLeft = trackRect.x; 1394 int trackRight = trackRect.x + (trackRect.width - 1); 1395 int xPosition; 1396 1397 if ( !drawInverted() ) { 1398 xPosition = trackLeft; 1399 xPosition += Math.round( pixelsPerValue * ((double)value - min) ); 1400 } 1401 else { 1402 xPosition = trackRight; 1403 xPosition -= Math.round( pixelsPerValue * ((double)value - min) ); 1404 } 1405 1406 xPosition = Math.max( trackLeft, xPosition ); 1407 xPosition = Math.min( trackRight, xPosition ); 1408 1409 return xPosition; 1410 } 1411 1412 protected int yPositionForValue( int value ) { 1413 return yPositionForValue(value, trackRect.y, trackRect.height); 1414 } 1415 1416 /** 1417 * Returns the y location for the specified value. No checking is 1418 * done on the arguments. In particular if <code>trackHeight</code> is 1419 * negative undefined results may occur. 1420 * 1421 * @param value the slider value to get the location for 1422 * @param trackY y-origin of the track 1423 * @param trackHeight the height of the track 1424 * @return the y location for the specified value of the slider 1425 * @since 1.6 1426 */ 1427 protected int yPositionForValue(int value, int trackY, int trackHeight) { 1428 int min = slider.getMinimum(); 1429 int max = slider.getMaximum(); 1430 double valueRange = (double)max - (double)min; 1431 double pixelsPerValue = (double)trackHeight / valueRange; 1432 int trackBottom = trackY + (trackHeight - 1); 1433 int yPosition; 1434 1435 if ( !drawInverted() ) { 1436 yPosition = trackY; 1437 yPosition += Math.round( pixelsPerValue * ((double)max - value ) ); 1438 } 1439 else { 1440 yPosition = trackY; 1441 yPosition += Math.round( pixelsPerValue * ((double)value - min) ); 1442 } 1443 1444 yPosition = Math.max( trackY, yPosition ); 1445 yPosition = Math.min( trackBottom, yPosition ); 1446 1447 return yPosition; 1448 } 1449 1450 /** 1451 * Returns the value at the y position. If {@code yPos} is beyond the 1452 * track at the bottom or the top, this method sets the value to either 1453 * the minimum or maximum value of the slider, depending on if the slider 1454 * is inverted or not. 1455 * 1456 * @param yPos the location of the slider along the y axis 1457 * @return the value at the y position 1458 */ 1459 public int valueForYPosition( int yPos ) { 1460 int value; 1461 final int minValue = slider.getMinimum(); 1462 final int maxValue = slider.getMaximum(); 1463 final int trackLength = trackRect.height; 1464 final int trackTop = trackRect.y; 1465 final int trackBottom = trackRect.y + (trackRect.height - 1); 1466 1467 if ( yPos <= trackTop ) { 1468 value = drawInverted() ? minValue : maxValue; 1469 } 1470 else if ( yPos >= trackBottom ) { 1471 value = drawInverted() ? maxValue : minValue; 1472 } 1473 else { 1474 int distanceFromTrackTop = yPos - trackTop; 1475 double valueRange = (double)maxValue - (double)minValue; 1476 double valuePerPixel = valueRange / (double)trackLength; 1477 int valueFromTrackTop = (int)Math.round( distanceFromTrackTop * valuePerPixel ); 1478 1479 value = drawInverted() ? minValue + valueFromTrackTop : maxValue - valueFromTrackTop; 1480 } 1481 1482 return value; 1483 } 1484 1485 /** 1486 * Returns the value at the x position. If {@code xPos} is beyond the 1487 * track at the left or the right, this method sets the value to either the 1488 * minimum or maximum value of the slider, depending on if the slider is 1489 * inverted or not. 1490 * 1491 * @param xPos the location of the slider along the x axis 1492 * @return the value of the x position 1493 */ 1494 public int valueForXPosition( int xPos ) { 1495 int value; 1496 final int minValue = slider.getMinimum(); 1497 final int maxValue = slider.getMaximum(); 1498 final int trackLength = trackRect.width; 1499 final int trackLeft = trackRect.x; 1500 final int trackRight = trackRect.x + (trackRect.width - 1); 1501 1502 if ( xPos <= trackLeft ) { 1503 value = drawInverted() ? maxValue : minValue; 1504 } 1505 else if ( xPos >= trackRight ) { 1506 value = drawInverted() ? minValue : maxValue; 1507 } 1508 else { 1509 int distanceFromTrackLeft = xPos - trackLeft; 1510 double valueRange = (double)maxValue - (double)minValue; 1511 double valuePerPixel = valueRange / (double)trackLength; 1512 int valueFromTrackLeft = (int)Math.round( distanceFromTrackLeft * valuePerPixel ); 1513 1514 value = drawInverted() ? maxValue - valueFromTrackLeft : 1515 minValue + valueFromTrackLeft; 1516 } 1517 1518 return value; 1519 } 1520 1521 1522 private class Handler implements ChangeListener, 1523 ComponentListener, FocusListener, PropertyChangeListener { 1524 // Change Handler 1525 public void stateChanged(ChangeEvent e) { 1526 if (!isDragging) { 1527 calculateThumbLocation(); 1528 slider.repaint(); 1529 } 1530 lastValue = slider.getValue(); 1531 } 1532 1533 // Component Handler 1534 public void componentHidden(ComponentEvent e) { } 1535 public void componentMoved(ComponentEvent e) { } 1536 public void componentResized(ComponentEvent e) { 1537 calculateGeometry(); 1538 slider.repaint(); 1539 } 1540 public void componentShown(ComponentEvent e) { } 1541 1542 // Focus Handler 1543 public void focusGained(FocusEvent e) { slider.repaint(); } 1544 public void focusLost(FocusEvent e) { slider.repaint(); } 1545 1546 // Property Change Handler 1547 public void propertyChange(PropertyChangeEvent e) { 1548 String propertyName = e.getPropertyName(); 1549 if (propertyName == "orientation" || 1550 propertyName == "inverted" || 1551 propertyName == "labelTable" || 1552 propertyName == "majorTickSpacing" || 1553 propertyName == "minorTickSpacing" || 1554 propertyName == "paintTicks" || 1555 propertyName == "paintTrack" || 1556 propertyName == "font" || 1557 propertyName == "paintLabels" || 1558 propertyName == "Slider.paintThumbArrowShape") { 1559 checkedLabelBaselines = false; 1560 calculateGeometry(); 1561 slider.repaint(); 1562 } else if (propertyName == "componentOrientation") { 1563 calculateGeometry(); 1564 slider.repaint(); 1565 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider); 1566 SwingUtilities.replaceUIInputMap(slider, 1567 JComponent.WHEN_FOCUSED, km); 1568 } else if (propertyName == "model") { 1569 ((BoundedRangeModel)e.getOldValue()).removeChangeListener( 1570 changeListener); 1571 ((BoundedRangeModel)e.getNewValue()).addChangeListener( 1572 changeListener); 1573 calculateThumbLocation(); 1574 slider.repaint(); 1575 } 1576 } 1577 } 1578 1579 ///////////////////////////////////////////////////////////////////////// 1580 /// Model Listener Class 1581 ///////////////////////////////////////////////////////////////////////// 1582 /** 1583 * Data model listener. 1584 * 1585 * This class should be treated as a "protected" inner class. 1586 * Instantiate it only within subclasses of <code>Foo</code>. 1587 */ 1588 public class ChangeHandler implements ChangeListener { 1589 // NOTE: This class exists only for backward compatibility. All 1590 // its functionality has been moved into Handler. If you need to add 1591 // new functionality add it to the Handler, but make sure this 1592 // class calls into the Handler. 1593 public void stateChanged(ChangeEvent e) { 1594 getHandler().stateChanged(e); 1595 } 1596 } 1597 1598 ///////////////////////////////////////////////////////////////////////// 1599 /// Track Listener Class 1600 ///////////////////////////////////////////////////////////////////////// 1601 /** 1602 * Track mouse movements. 1603 * 1604 * This class should be treated as a "protected" inner class. 1605 * Instantiate it only within subclasses of <code>Foo</code>. 1606 */ 1607 public class TrackListener extends MouseInputAdapter { 1608 protected transient int offset; 1609 protected transient int currentMouseX, currentMouseY; 1610 1611 public void mouseReleased(MouseEvent e) { 1612 if (!slider.isEnabled()) { 1613 return; 1614 } 1615 1616 offset = 0; 1617 scrollTimer.stop(); 1618 1619 isDragging = false; 1620 slider.setValueIsAdjusting(false); 1621 slider.repaint(); 1622 } 1623 1624 /** 1625 * If the mouse is pressed above the "thumb" component 1626 * then reduce the scrollbars value by one page ("page up"), 1627 * otherwise increase it by one page. If there is no 1628 * thumb then page up if the mouse is in the upper half 1629 * of the track. 1630 */ 1631 public void mousePressed(MouseEvent e) { 1632 if (!slider.isEnabled()) { 1633 return; 1634 } 1635 1636 // We should recalculate geometry just before 1637 // calculation of the thumb movement direction. 1638 // It is important for the case, when JSlider 1639 // is a cell editor in JTable. See 6348946. 1640 calculateGeometry(); 1641 1642 currentMouseX = e.getX(); 1643 currentMouseY = e.getY(); 1644 1645 if (slider.isRequestFocusEnabled()) { 1646 slider.requestFocus(); 1647 } 1648 1649 // Clicked in the Thumb area? 1650 if (thumbRect.contains(currentMouseX, currentMouseY)) { 1651 if (UIManager.getBoolean("Slider.onlyLeftMouseButtonDrag") 1652 && !SwingUtilities.isLeftMouseButton(e)) { 1653 return; 1654 } 1655 1656 switch (slider.getOrientation()) { 1657 case JSlider.VERTICAL: 1658 offset = currentMouseY - thumbRect.y; 1659 break; 1660 case JSlider.HORIZONTAL: 1661 offset = currentMouseX - thumbRect.x; 1662 break; 1663 } 1664 isDragging = true; 1665 return; 1666 } 1667 1668 if (!SwingUtilities.isLeftMouseButton(e)) { 1669 return; 1670 } 1671 1672 isDragging = false; 1673 slider.setValueIsAdjusting(true); 1674 1675 Dimension sbSize = slider.getSize(); 1676 int direction = POSITIVE_SCROLL; 1677 1678 switch (slider.getOrientation()) { 1679 case JSlider.VERTICAL: 1680 if ( thumbRect.isEmpty() ) { 1681 int scrollbarCenter = sbSize.height / 2; 1682 if ( !drawInverted() ) { 1683 direction = (currentMouseY < scrollbarCenter) ? 1684 POSITIVE_SCROLL : NEGATIVE_SCROLL; 1685 } 1686 else { 1687 direction = (currentMouseY < scrollbarCenter) ? 1688 NEGATIVE_SCROLL : POSITIVE_SCROLL; 1689 } 1690 } 1691 else { 1692 int thumbY = thumbRect.y; 1693 if ( !drawInverted() ) { 1694 direction = (currentMouseY < thumbY) ? 1695 POSITIVE_SCROLL : NEGATIVE_SCROLL; 1696 } 1697 else { 1698 direction = (currentMouseY < thumbY) ? 1699 NEGATIVE_SCROLL : POSITIVE_SCROLL; 1700 } 1701 } 1702 break; 1703 case JSlider.HORIZONTAL: 1704 if ( thumbRect.isEmpty() ) { 1705 int scrollbarCenter = sbSize.width / 2; 1706 if ( !drawInverted() ) { 1707 direction = (currentMouseX < scrollbarCenter) ? 1708 NEGATIVE_SCROLL : POSITIVE_SCROLL; 1709 } 1710 else { 1711 direction = (currentMouseX < scrollbarCenter) ? 1712 POSITIVE_SCROLL : NEGATIVE_SCROLL; 1713 } 1714 } 1715 else { 1716 int thumbX = thumbRect.x; 1717 if ( !drawInverted() ) { 1718 direction = (currentMouseX < thumbX) ? 1719 NEGATIVE_SCROLL : POSITIVE_SCROLL; 1720 } 1721 else { 1722 direction = (currentMouseX < thumbX) ? 1723 POSITIVE_SCROLL : NEGATIVE_SCROLL; 1724 } 1725 } 1726 break; 1727 } 1728 1729 if (shouldScroll(direction)) { 1730 scrollDueToClickInTrack(direction); 1731 } 1732 if (shouldScroll(direction)) { 1733 scrollTimer.stop(); 1734 scrollListener.setDirection(direction); 1735 scrollTimer.start(); 1736 } 1737 } 1738 1739 public boolean shouldScroll(int direction) { 1740 Rectangle r = thumbRect; 1741 if (slider.getOrientation() == JSlider.VERTICAL) { 1742 if (drawInverted() ? direction < 0 : direction > 0) { 1743 if (r.y <= currentMouseY) { 1744 return false; 1745 } 1746 } 1747 else if (r.y + r.height >= currentMouseY) { 1748 return false; 1749 } 1750 } 1751 else { 1752 if (drawInverted() ? direction < 0 : direction > 0) { 1753 if (r.x + r.width >= currentMouseX) { 1754 return false; 1755 } 1756 } 1757 else if (r.x <= currentMouseX) { 1758 return false; 1759 } 1760 } 1761 1762 if (direction > 0 && slider.getValue() + slider.getExtent() >= 1763 slider.getMaximum()) { 1764 return false; 1765 } 1766 else if (direction < 0 && slider.getValue() <= 1767 slider.getMinimum()) { 1768 return false; 1769 } 1770 1771 return true; 1772 } 1773 1774 /** 1775 * Set the models value to the position of the top/left 1776 * of the thumb relative to the origin of the track. 1777 */ 1778 public void mouseDragged(MouseEvent e) { 1779 int thumbMiddle; 1780 1781 if (!slider.isEnabled()) { 1782 return; 1783 } 1784 1785 currentMouseX = e.getX(); 1786 currentMouseY = e.getY(); 1787 1788 if (!isDragging) { 1789 return; 1790 } 1791 1792 slider.setValueIsAdjusting(true); 1793 1794 switch (slider.getOrientation()) { 1795 case JSlider.VERTICAL: 1796 int halfThumbHeight = thumbRect.height / 2; 1797 int thumbTop = e.getY() - offset; 1798 int trackTop = trackRect.y; 1799 int trackBottom = trackRect.y + (trackRect.height - 1); 1800 int vMax = yPositionForValue(slider.getMaximum() - 1801 slider.getExtent()); 1802 1803 if (drawInverted()) { 1804 trackBottom = vMax; 1805 } 1806 else { 1807 trackTop = vMax; 1808 } 1809 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight); 1810 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight); 1811 1812 setThumbLocation(thumbRect.x, thumbTop); 1813 1814 thumbMiddle = thumbTop + halfThumbHeight; 1815 slider.setValue( valueForYPosition( thumbMiddle ) ); 1816 break; 1817 case JSlider.HORIZONTAL: 1818 int halfThumbWidth = thumbRect.width / 2; 1819 int thumbLeft = e.getX() - offset; 1820 int trackLeft = trackRect.x; 1821 int trackRight = trackRect.x + (trackRect.width - 1); 1822 int hMax = xPositionForValue(slider.getMaximum() - 1823 slider.getExtent()); 1824 1825 if (drawInverted()) { 1826 trackLeft = hMax; 1827 } 1828 else { 1829 trackRight = hMax; 1830 } 1831 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth); 1832 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth); 1833 1834 setThumbLocation(thumbLeft, thumbRect.y); 1835 1836 thumbMiddle = thumbLeft + halfThumbWidth; 1837 slider.setValue(valueForXPosition(thumbMiddle)); 1838 break; 1839 } 1840 } 1841 1842 public void mouseMoved(MouseEvent e) { } 1843 } 1844 1845 /** 1846 * Scroll-event listener. 1847 * 1848 * This class should be treated as a "protected" inner class. 1849 * Instantiate it only within subclasses of <code>Foo</code>. 1850 */ 1851 public class ScrollListener implements ActionListener { 1852 // changed this class to public to avoid bogus IllegalAccessException 1853 // bug in InternetExplorer browser. It was protected. Work around 1854 // for 4109432 1855 int direction = POSITIVE_SCROLL; 1856 boolean useBlockIncrement; 1857 1858 public ScrollListener() { 1859 direction = POSITIVE_SCROLL; 1860 useBlockIncrement = true; 1861 } 1862 1863 public ScrollListener(int dir, boolean block) { 1864 direction = dir; 1865 useBlockIncrement = block; 1866 } 1867 1868 public void setDirection(int direction) { 1869 this.direction = direction; 1870 } 1871 1872 public void setScrollByBlock(boolean block) { 1873 this.useBlockIncrement = block; 1874 } 1875 1876 public void actionPerformed(ActionEvent e) { 1877 if (useBlockIncrement) { 1878 scrollByBlock(direction); 1879 } 1880 else { 1881 scrollByUnit(direction); 1882 } 1883 if (!trackListener.shouldScroll(direction)) { 1884 ((Timer)e.getSource()).stop(); 1885 } 1886 } 1887 } 1888 1889 /** 1890 * Listener for resizing events. 1891 * <p> 1892 * This class should be treated as a "protected" inner class. 1893 * Instantiate it only within subclasses of <code>Foo</code>. 1894 */ 1895 public class ComponentHandler extends ComponentAdapter { 1896 // NOTE: This class exists only for backward compatibility. All 1897 // its functionality has been moved into Handler. If you need to add 1898 // new functionality add it to the Handler, but make sure this 1899 // class calls into the Handler. 1900 public void componentResized(ComponentEvent e) { 1901 getHandler().componentResized(e); 1902 } 1903 } 1904 1905 /** 1906 * Focus-change listener. 1907 * <p> 1908 * This class should be treated as a "protected" inner class. 1909 * Instantiate it only within subclasses of <code>Foo</code>. 1910 */ 1911 public class FocusHandler implements FocusListener { 1912 // NOTE: This class exists only for backward compatibility. All 1913 // its functionality has been moved into Handler. If you need to add 1914 // new functionality add it to the Handler, but make sure this 1915 // class calls into the Handler. 1916 public void focusGained(FocusEvent e) { 1917 getHandler().focusGained(e); 1918 } 1919 1920 public void focusLost(FocusEvent e) { 1921 getHandler().focusLost(e); 1922 } 1923 } 1924 1925 /** 1926 * As of Java 2 platform v1.3 this undocumented class is no longer used. 1927 * The recommended approach to creating bindings is to use a 1928 * combination of an <code>ActionMap</code>, to contain the action, 1929 * and an <code>InputMap</code> to contain the mapping from KeyStroke 1930 * to action description. The InputMap is usually described in the 1931 * LookAndFeel tables. 1932 * <p> 1933 * Please refer to the key bindings specification for further details. 1934 * <p> 1935 * This class should be treated as a "protected" inner class. 1936 * Instantiate it only within subclasses of <code>Foo</code>. 1937 */ 1938 @SuppressWarnings("serial") // Superclass is not serializable across versions 1939 public class ActionScroller extends AbstractAction { 1940 // NOTE: This class exists only for backward compatibility. All 1941 // its functionality has been moved into Actions. If you need to add 1942 // new functionality add it to the Actions, but make sure this 1943 // class calls into the Actions. 1944 int dir; 1945 boolean block; 1946 JSlider slider; 1947 1948 public ActionScroller( JSlider slider, int dir, boolean block) { 1949 this.dir = dir; 1950 this.block = block; 1951 this.slider = slider; 1952 } 1953 1954 public void actionPerformed(ActionEvent e) { 1955 SHARED_ACTION.scroll(slider, BasicSliderUI.this, dir, block); 1956 } 1957 1958 public boolean isEnabled() { 1959 boolean b = true; 1960 if (slider != null) { 1961 b = slider.isEnabled(); 1962 } 1963 return b; 1964 } 1965 1966 } 1967 1968 1969 /** 1970 * A static version of the above. 1971 */ 1972 @SuppressWarnings("serial") // Superclass is not serializable across versions 1973 static class SharedActionScroller extends AbstractAction { 1974 // NOTE: This class exists only for backward compatibility. All 1975 // its functionality has been moved into Actions. If you need to add 1976 // new functionality add it to the Actions, but make sure this 1977 // class calls into the Actions. 1978 int dir; 1979 boolean block; 1980 1981 public SharedActionScroller(int dir, boolean block) { 1982 this.dir = dir; 1983 this.block = block; 1984 } 1985 1986 public void actionPerformed(ActionEvent evt) { 1987 JSlider slider = (JSlider)evt.getSource(); 1988 BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType( 1989 slider.getUI(), BasicSliderUI.class); 1990 if (ui == null) { 1991 return; 1992 } 1993 SHARED_ACTION.scroll(slider, ui, dir, block); 1994 } 1995 } 1996 1997 private static class Actions extends UIAction { 1998 public static final String POSITIVE_UNIT_INCREMENT = 1999 "positiveUnitIncrement"; 2000 public static final String POSITIVE_BLOCK_INCREMENT = 2001 "positiveBlockIncrement"; 2002 public static final String NEGATIVE_UNIT_INCREMENT = 2003 "negativeUnitIncrement"; 2004 public static final String NEGATIVE_BLOCK_INCREMENT = 2005 "negativeBlockIncrement"; 2006 public static final String MIN_SCROLL_INCREMENT = "minScroll"; 2007 public static final String MAX_SCROLL_INCREMENT = "maxScroll"; 2008 2009 2010 Actions() { 2011 super(null); 2012 } 2013 2014 public Actions(String name) { 2015 super(name); 2016 } 2017 2018 public void actionPerformed(ActionEvent evt) { 2019 JSlider slider = (JSlider)evt.getSource(); 2020 BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType( 2021 slider.getUI(), BasicSliderUI.class); 2022 String name = getName(); 2023 2024 if (ui == null) { 2025 return; 2026 } 2027 if (POSITIVE_UNIT_INCREMENT == name) { 2028 scroll(slider, ui, POSITIVE_SCROLL, false); 2029 } else if (NEGATIVE_UNIT_INCREMENT == name) { 2030 scroll(slider, ui, NEGATIVE_SCROLL, false); 2031 } else if (POSITIVE_BLOCK_INCREMENT == name) { 2032 scroll(slider, ui, POSITIVE_SCROLL, true); 2033 } else if (NEGATIVE_BLOCK_INCREMENT == name) { 2034 scroll(slider, ui, NEGATIVE_SCROLL, true); 2035 } else if (MIN_SCROLL_INCREMENT == name) { 2036 scroll(slider, ui, MIN_SCROLL, false); 2037 } else if (MAX_SCROLL_INCREMENT == name) { 2038 scroll(slider, ui, MAX_SCROLL, false); 2039 } 2040 } 2041 2042 private void scroll(JSlider slider, BasicSliderUI ui, int direction, 2043 boolean isBlock) { 2044 boolean invert = slider.getInverted(); 2045 2046 if (direction == NEGATIVE_SCROLL || direction == POSITIVE_SCROLL) { 2047 if (invert) { 2048 direction = (direction == POSITIVE_SCROLL) ? 2049 NEGATIVE_SCROLL : POSITIVE_SCROLL; 2050 } 2051 2052 if (isBlock) { 2053 ui.scrollByBlock(direction); 2054 } else { 2055 ui.scrollByUnit(direction); 2056 } 2057 } else { // MIN or MAX 2058 if (invert) { 2059 direction = (direction == MIN_SCROLL) ? 2060 MAX_SCROLL : MIN_SCROLL; 2061 } 2062 2063 slider.setValue((direction == MIN_SCROLL) ? 2064 slider.getMinimum() : slider.getMaximum()); 2065 } 2066 } 2067 } 2068 }