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