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