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 package javax.swing.plaf.basic; 26 27 28 import sun.swing.DefaultLookup; 29 import sun.swing.UIAction; 30 31 import java.awt.*; 32 import java.awt.event.*; 33 34 import java.beans.*; 35 36 import javax.swing.*; 37 import javax.swing.event.*; 38 import javax.swing.plaf.*; 39 40 import static sun.swing.SwingUtilities2.drawHLine; 41 import static sun.swing.SwingUtilities2.drawRect; 42 import static sun.swing.SwingUtilities2.drawVLine; 43 44 45 /** 46 * Implementation of ScrollBarUI for the Basic Look and Feel 47 * 48 * @author Rich Schiavi 49 * @author David Kloba 50 * @author Hans Muller 51 */ 52 public class BasicScrollBarUI 53 extends ScrollBarUI implements LayoutManager, SwingConstants 54 { 55 private static final int POSITIVE_SCROLL = 1; 56 private static final int NEGATIVE_SCROLL = -1; 57 58 private static final int MIN_SCROLL = 2; 59 private static final int MAX_SCROLL = 3; 60 61 // NOTE: DO NOT use this field directly, SynthScrollBarUI assumes you'll 62 // call getMinimumThumbSize to access it. 63 /** Minimum thumb size */ 64 protected Dimension minimumThumbSize; 65 /** Maximum thumb size */ 66 protected Dimension maximumThumbSize; 67 68 /** Thumb highlight color */ 69 protected Color thumbHighlightColor; 70 /** Thumb light shadow color */ 71 protected Color thumbLightShadowColor; 72 /** Thumb dark shadow color */ 73 protected Color thumbDarkShadowColor; 74 /** Thumb color */ 75 protected Color thumbColor; 76 /** Track color */ 77 protected Color trackColor; 78 /** Track highlight color */ 79 protected Color trackHighlightColor; 80 81 /** Scrollbar */ 82 protected JScrollBar scrollbar; 83 /** Increment button */ 84 protected JButton incrButton; 85 /** Decrement button */ 86 protected JButton decrButton; 87 /** Dragging */ 88 protected boolean isDragging; 89 /** Track listener */ 90 protected TrackListener trackListener; 91 /** Button listener */ 92 protected ArrowButtonListener buttonListener; 93 /** Model listener */ 94 protected ModelListener modelListener; 95 96 /** Thumb rectangle */ 97 protected Rectangle thumbRect; 98 /** Track rectangle */ 99 protected Rectangle trackRect; 100 101 /** Track highlight */ 102 protected int trackHighlight; 103 104 /** No highlight */ 105 protected static final int NO_HIGHLIGHT = 0; 106 /** Decrease highlight */ 107 protected static final int DECREASE_HIGHLIGHT = 1; 108 /** Increase highlight */ 109 protected static final int INCREASE_HIGHLIGHT = 2; 110 111 /** Scroll listener */ 112 protected ScrollListener scrollListener; 113 /** Property change listener */ 114 protected PropertyChangeListener propertyChangeListener; 115 /** Scroll timer */ 116 protected Timer scrollTimer; 117 118 private final static int scrollSpeedThrottle = 60; // delay in milli seconds 119 120 /** 121 * True indicates a middle click will absolutely position the 122 * scrollbar. 123 */ 124 private boolean supportsAbsolutePositioning; 125 126 /** 127 * Hint as to what width (when vertical) or height (when horizontal) 128 * should be. 129 * 130 * @since 1.7 131 */ 132 protected int scrollBarWidth; 133 134 private Handler handler; 135 136 private boolean thumbActive; 137 138 /** 139 * Determine whether scrollbar layout should use cached value or adjusted 140 * value returned by scrollbar's <code>getValue</code>. 141 */ 142 private boolean useCachedValue = false; 143 /** 144 * The scrollbar value is cached to save real value if the view is adjusted. 145 */ 146 private int scrollBarValue; 147 148 /** 149 * Distance between the increment button and the track. This may be a negative 150 * number. If negative, then an overlap between the button and track will occur, 151 * which is useful for shaped buttons. 152 * 153 * @since 1.7 154 */ 155 protected int incrGap; 156 157 /** 158 * Distance between the decrement button and the track. This may be a negative 159 * number. If negative, then an overlap between the button and track will occur, 160 * which is useful for shaped buttons. 161 * 162 * @since 1.7 163 */ 164 protected int decrGap; 165 166 static void loadActionMap(LazyActionMap map) { 167 map.put(new Actions(Actions.POSITIVE_UNIT_INCREMENT)); 168 map.put(new Actions(Actions.POSITIVE_BLOCK_INCREMENT)); 169 map.put(new Actions(Actions.NEGATIVE_UNIT_INCREMENT)); 170 map.put(new Actions(Actions.NEGATIVE_BLOCK_INCREMENT)); 171 map.put(new Actions(Actions.MIN_SCROLL)); 172 map.put(new Actions(Actions.MAX_SCROLL)); 173 } 174 175 /** 176 * Creates the UI. 177 * @param c the component 178 * @return the UI 179 */ 180 public static ComponentUI createUI(JComponent c) { 181 return new BasicScrollBarUI(); 182 } 183 184 /** 185 * Configures the scroll bar colors. 186 */ 187 protected void configureScrollBarColors() 188 { 189 LookAndFeel.installColors(scrollbar, "ScrollBar.background", 190 "ScrollBar.foreground"); 191 thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight"); 192 thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow"); 193 thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow"); 194 thumbColor = UIManager.getColor("ScrollBar.thumb"); 195 trackColor = UIManager.getColor("ScrollBar.track"); 196 trackHighlightColor = UIManager.getColor("ScrollBar.trackHighlight"); 197 } 198 199 /** 200 * Installs the UI. 201 * @param c the component 202 */ 203 public void installUI(JComponent c) { 204 scrollbar = (JScrollBar)c; 205 thumbRect = new Rectangle(0, 0, 0, 0); 206 trackRect = new Rectangle(0, 0, 0, 0); 207 installDefaults(); 208 installComponents(); 209 installListeners(); 210 installKeyboardActions(); 211 } 212 213 /** 214 * Uninstalls the UI. 215 * @param c the component 216 */ 217 public void uninstallUI(JComponent c) { 218 scrollbar = (JScrollBar)c; 219 uninstallListeners(); 220 uninstallDefaults(); 221 uninstallComponents(); 222 uninstallKeyboardActions(); 223 thumbRect = null; 224 scrollbar = null; 225 incrButton = null; 226 decrButton = null; 227 } 228 229 /** 230 * Installs the defaults. 231 */ 232 protected void installDefaults() 233 { 234 scrollBarWidth = UIManager.getInt("ScrollBar.width"); 235 if (scrollBarWidth <= 0) { 236 scrollBarWidth = 16; 237 } 238 minimumThumbSize = (Dimension)UIManager.get("ScrollBar.minimumThumbSize"); 239 maximumThumbSize = (Dimension)UIManager.get("ScrollBar.maximumThumbSize"); 240 241 Boolean absB = (Boolean)UIManager.get("ScrollBar.allowsAbsolutePositioning"); 242 supportsAbsolutePositioning = (absB != null) ? absB.booleanValue() : 243 false; 244 245 trackHighlight = NO_HIGHLIGHT; 246 if (scrollbar.getLayout() == null || 247 (scrollbar.getLayout() instanceof UIResource)) { 248 scrollbar.setLayout(this); 249 } 250 configureScrollBarColors(); 251 LookAndFeel.installBorder(scrollbar, "ScrollBar.border"); 252 LookAndFeel.installProperty(scrollbar, "opaque", Boolean.TRUE); 253 254 scrollBarValue = scrollbar.getValue(); 255 256 incrGap = UIManager.getInt("ScrollBar.incrementButtonGap"); 257 decrGap = UIManager.getInt("ScrollBar.decrementButtonGap"); 258 259 // TODO this can be removed when incrGap/decrGap become protected 260 // handle scaling for sizeVarients for special case components. The 261 // key "JComponent.sizeVariant" scales for large/small/mini 262 // components are based on Apples LAF 263 String scaleKey = (String)scrollbar.getClientProperty( 264 "JComponent.sizeVariant"); 265 if (scaleKey != null){ 266 if ("large".equals(scaleKey)){ 267 scrollBarWidth *= 1.15; 268 incrGap *= 1.15; 269 decrGap *= 1.15; 270 } else if ("small".equals(scaleKey)){ 271 scrollBarWidth *= 0.857; 272 incrGap *= 0.857; 273 decrGap *= 0.714; 274 } else if ("mini".equals(scaleKey)){ 275 scrollBarWidth *= 0.714; 276 incrGap *= 0.714; 277 decrGap *= 0.714; 278 } 279 } 280 } 281 282 /** 283 * Installs the components. 284 */ 285 protected void installComponents(){ 286 switch (scrollbar.getOrientation()) { 287 case JScrollBar.VERTICAL: 288 incrButton = createIncreaseButton(SOUTH); 289 decrButton = createDecreaseButton(NORTH); 290 break; 291 292 case JScrollBar.HORIZONTAL: 293 if (scrollbar.getComponentOrientation().isLeftToRight()) { 294 incrButton = createIncreaseButton(EAST); 295 decrButton = createDecreaseButton(WEST); 296 } else { 297 incrButton = createIncreaseButton(WEST); 298 decrButton = createDecreaseButton(EAST); 299 } 300 break; 301 } 302 scrollbar.add(incrButton); 303 scrollbar.add(decrButton); 304 // Force the children's enabled state to be updated. 305 scrollbar.setEnabled(scrollbar.isEnabled()); 306 } 307 308 /** 309 * Uninstalls the components. 310 */ 311 protected void uninstallComponents(){ 312 scrollbar.remove(incrButton); 313 scrollbar.remove(decrButton); 314 } 315 316 /** 317 * Installs the listeners. 318 */ 319 protected void installListeners(){ 320 trackListener = createTrackListener(); 321 buttonListener = createArrowButtonListener(); 322 modelListener = createModelListener(); 323 propertyChangeListener = createPropertyChangeListener(); 324 325 scrollbar.addMouseListener(trackListener); 326 scrollbar.addMouseMotionListener(trackListener); 327 scrollbar.getModel().addChangeListener(modelListener); 328 scrollbar.addPropertyChangeListener(propertyChangeListener); 329 scrollbar.addFocusListener(getHandler()); 330 331 if (incrButton != null) { 332 incrButton.addMouseListener(buttonListener); 333 } 334 if (decrButton != null) { 335 decrButton.addMouseListener(buttonListener); 336 } 337 338 scrollListener = createScrollListener(); 339 scrollTimer = new Timer(scrollSpeedThrottle, scrollListener); 340 scrollTimer.setInitialDelay(300); // default InitialDelay? 341 } 342 343 /** 344 * Installs the keyboard actions. 345 */ 346 protected void installKeyboardActions(){ 347 LazyActionMap.installLazyActionMap(scrollbar, BasicScrollBarUI.class, 348 "ScrollBar.actionMap"); 349 350 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED); 351 SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED, 352 inputMap); 353 inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 354 SwingUtilities.replaceUIInputMap(scrollbar, 355 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap); 356 } 357 358 /** 359 * Uninstalls the keyboard actions. 360 */ 361 protected void uninstallKeyboardActions(){ 362 SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED, 363 null); 364 SwingUtilities.replaceUIActionMap(scrollbar, null); 365 } 366 367 private InputMap getInputMap(int condition) { 368 if (condition == JComponent.WHEN_FOCUSED) { 369 InputMap keyMap = (InputMap)DefaultLookup.get( 370 scrollbar, this, "ScrollBar.focusInputMap"); 371 InputMap rtlKeyMap; 372 373 if (scrollbar.getComponentOrientation().isLeftToRight() || 374 ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollbar, this, "ScrollBar.focusInputMap.RightToLeft")) == null)) { 375 return keyMap; 376 } else { 377 rtlKeyMap.setParent(keyMap); 378 return rtlKeyMap; 379 } 380 } 381 else if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 382 InputMap keyMap = (InputMap)DefaultLookup.get( 383 scrollbar, this, "ScrollBar.ancestorInputMap"); 384 InputMap rtlKeyMap; 385 386 if (scrollbar.getComponentOrientation().isLeftToRight() || 387 ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollbar, this, "ScrollBar.ancestorInputMap.RightToLeft")) == null)) { 388 return keyMap; 389 } else { 390 rtlKeyMap.setParent(keyMap); 391 return rtlKeyMap; 392 } 393 } 394 return null; 395 } 396 397 /** 398 * Uninstall the listeners. 399 */ 400 protected void uninstallListeners() { 401 scrollTimer.stop(); 402 scrollTimer = null; 403 404 if (decrButton != null){ 405 decrButton.removeMouseListener(buttonListener); 406 } 407 if (incrButton != null){ 408 incrButton.removeMouseListener(buttonListener); 409 } 410 411 scrollbar.getModel().removeChangeListener(modelListener); 412 scrollbar.removeMouseListener(trackListener); 413 scrollbar.removeMouseMotionListener(trackListener); 414 scrollbar.removePropertyChangeListener(propertyChangeListener); 415 scrollbar.removeFocusListener(getHandler()); 416 handler = null; 417 } 418 419 /** 420 * Uninstalls the defaults. 421 */ 422 protected void uninstallDefaults(){ 423 LookAndFeel.uninstallBorder(scrollbar); 424 if (scrollbar.getLayout() == this) { 425 scrollbar.setLayout(null); 426 } 427 } 428 429 430 private Handler getHandler() { 431 if (handler == null) { 432 handler = new Handler(); 433 } 434 return handler; 435 } 436 437 /** 438 * Creates a track listener. 439 * @return a track listener 440 */ 441 protected TrackListener createTrackListener(){ 442 return new TrackListener(); 443 } 444 445 /** 446 * Creates an arrow button listener. 447 * @return an arrow button listener 448 */ 449 protected ArrowButtonListener createArrowButtonListener(){ 450 return new ArrowButtonListener(); 451 } 452 453 /** 454 * Creates a model listener. 455 * @return a model listener 456 */ 457 protected ModelListener createModelListener(){ 458 return new ModelListener(); 459 } 460 461 /** 462 * Creates a scroll listener. 463 * @return a scroll listener 464 */ 465 protected ScrollListener createScrollListener(){ 466 return new ScrollListener(); 467 } 468 469 /** 470 * Creates a property change listener. 471 * @return a property change listener 472 */ 473 protected PropertyChangeListener createPropertyChangeListener() { 474 return getHandler(); 475 } 476 477 private void updateThumbState(int x, int y) { 478 Rectangle rect = getThumbBounds(); 479 480 setThumbRollover(rect.contains(x, y)); 481 } 482 483 /** 484 * Sets whether or not the mouse is currently over the thumb. 485 * 486 * @param active True indicates the thumb is currently active. 487 * @since 1.5 488 */ 489 protected void setThumbRollover(boolean active) { 490 if (thumbActive != active) { 491 thumbActive = active; 492 scrollbar.repaint(getThumbBounds()); 493 } 494 } 495 496 /** 497 * Returns true if the mouse is currently over the thumb. 498 * 499 * @return true if the thumb is currently active 500 * @since 1.5 501 */ 502 public boolean isThumbRollover() { 503 return thumbActive; 504 } 505 506 public void paint(Graphics g, JComponent c) { 507 paintTrack(g, c, getTrackBounds()); 508 Rectangle thumbBounds = getThumbBounds(); 509 if (thumbBounds.intersects(g.getClipBounds())) { 510 paintThumb(g, c, thumbBounds); 511 } 512 } 513 514 515 /** 516 * A vertical scrollbar's preferred width is the maximum of 517 * preferred widths of the (non <code>null</code>) 518 * increment/decrement buttons, 519 * and the minimum width of the thumb. The preferred height is the 520 * sum of the preferred heights of the same parts. The basis for 521 * the preferred size of a horizontal scrollbar is similar. 522 * <p> 523 * The <code>preferredSize</code> is only computed once, subsequent 524 * calls to this method just return a cached size. 525 * 526 * @param c the <code>JScrollBar</code> that's delegating this method to us 527 * @return the preferred size of a Basic JScrollBar 528 * @see #getMaximumSize 529 * @see #getMinimumSize 530 */ 531 public Dimension getPreferredSize(JComponent c) { 532 return (scrollbar.getOrientation() == JScrollBar.VERTICAL) 533 ? new Dimension(scrollBarWidth, 48) 534 : new Dimension(48, scrollBarWidth); 535 } 536 537 538 /** 539 * @param c The JScrollBar that's delegating this method to us. 540 * @return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); 541 * @see #getMinimumSize 542 * @see #getPreferredSize 543 */ 544 public Dimension getMaximumSize(JComponent c) { 545 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); 546 } 547 548 /** 549 * Creates a decrease button. 550 * @param orientation the orientation 551 * @return a decrease button 552 */ 553 protected JButton createDecreaseButton(int orientation) { 554 return new BasicArrowButton(orientation, 555 UIManager.getColor("ScrollBar.thumb"), 556 UIManager.getColor("ScrollBar.thumbShadow"), 557 UIManager.getColor("ScrollBar.thumbDarkShadow"), 558 UIManager.getColor("ScrollBar.thumbHighlight")); 559 } 560 561 /** 562 * Creates an increase button. 563 * @param orientation the orientation 564 * @return an increase button 565 */ 566 protected JButton createIncreaseButton(int orientation) { 567 return new BasicArrowButton(orientation, 568 UIManager.getColor("ScrollBar.thumb"), 569 UIManager.getColor("ScrollBar.thumbShadow"), 570 UIManager.getColor("ScrollBar.thumbDarkShadow"), 571 UIManager.getColor("ScrollBar.thumbHighlight")); 572 } 573 574 575 /** 576 * Paints the decrease highlight. 577 * @param g the graphics 578 */ 579 protected void paintDecreaseHighlight(Graphics g) 580 { 581 Insets insets = scrollbar.getInsets(); 582 Rectangle thumbR = getThumbBounds(); 583 g.setColor(trackHighlightColor); 584 585 if (scrollbar.getOrientation() == JScrollBar.VERTICAL) { 586 //paint the distance between the start of the track and top of the thumb 587 int x = insets.left; 588 int y = trackRect.y; 589 int w = scrollbar.getWidth() - (insets.left + insets.right); 590 int h = thumbR.y - y; 591 g.fillRect(x, y, w, h); 592 } else { 593 //if left-to-right, fill the area between the start of the track and 594 //the left edge of the thumb. If right-to-left, fill the area between 595 //the end of the thumb and end of the track. 596 int x, w; 597 if (scrollbar.getComponentOrientation().isLeftToRight()) { 598 x = trackRect.x; 599 w = thumbR.x - x; 600 } else { 601 x = thumbR.x + thumbR.width; 602 w = trackRect.x + trackRect.width - x; 603 } 604 int y = insets.top; 605 int h = scrollbar.getHeight() - (insets.top + insets.bottom); 606 g.fillRect(x, y, w, h); 607 } 608 } 609 610 611 /** 612 * Paints the increase highlight. 613 * @param g the graphics 614 */ 615 protected void paintIncreaseHighlight(Graphics g) 616 { 617 Insets insets = scrollbar.getInsets(); 618 Rectangle thumbR = getThumbBounds(); 619 g.setColor(trackHighlightColor); 620 621 if (scrollbar.getOrientation() == JScrollBar.VERTICAL) { 622 //fill the area between the bottom of the thumb and the end of the track. 623 int x = insets.left; 624 int y = thumbR.y + thumbR.height; 625 int w = scrollbar.getWidth() - (insets.left + insets.right); 626 int h = trackRect.y + trackRect.height - y; 627 g.fillRect(x, y, w, h); 628 } 629 else { 630 //if left-to-right, fill the area between the right of the thumb and the 631 //end of the track. If right-to-left, then fill the area to the left of 632 //the thumb and the start of the track. 633 int x, w; 634 if (scrollbar.getComponentOrientation().isLeftToRight()) { 635 x = thumbR.x + thumbR.width; 636 w = trackRect.x + trackRect.width - x; 637 } else { 638 x = trackRect.x; 639 w = thumbR.x - x; 640 } 641 int y = insets.top; 642 int h = scrollbar.getHeight() - (insets.top + insets.bottom); 643 g.fillRect(x, y, w, h); 644 } 645 } 646 647 648 /** 649 * Paints the track. 650 * @param g the graphics 651 * @param c the component 652 * @param trackBounds the track bounds 653 */ 654 protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) 655 { 656 g.setColor(trackColor); 657 g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height); 658 659 if(trackHighlight == DECREASE_HIGHLIGHT) { 660 paintDecreaseHighlight(g); 661 } 662 else if(trackHighlight == INCREASE_HIGHLIGHT) { 663 paintIncreaseHighlight(g); 664 } 665 } 666 667 /** 668 * Paints the thumb. 669 * @param g the graphics 670 * @param c the component 671 * @param thumbBounds the thumb bounds 672 */ 673 protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) 674 { 675 if(thumbBounds.isEmpty() || !scrollbar.isEnabled()) { 676 return; 677 } 678 679 int w = thumbBounds.width; 680 int h = thumbBounds.height; 681 682 g.translate(thumbBounds.x, thumbBounds.y); 683 684 g.setColor(thumbDarkShadowColor); 685 drawRect(g, 0, 0, w - 1, h - 1); 686 g.setColor(thumbColor); 687 g.fillRect(0, 0, w - 1, h - 1); 688 689 g.setColor(thumbHighlightColor); 690 drawVLine(g, 1, 1, h - 2); 691 drawHLine(g, 2, w - 3, 1); 692 693 g.setColor(thumbLightShadowColor); 694 drawHLine(g, 2, w - 2, h - 2); 695 drawVLine(g, w - 2, 1, h - 3); 696 697 g.translate(-thumbBounds.x, -thumbBounds.y); 698 } 699 700 701 /** 702 * Returns the smallest acceptable size for the thumb. If the scrollbar 703 * becomes so small that this size isn't available, the thumb will be 704 * hidden. 705 * <p> 706 * <b>Warning </b>: the value returned by this method should not be 707 * be modified, it's a shared static constant. 708 * 709 * @return The smallest acceptable size for the thumb. 710 * @see #getMaximumThumbSize 711 */ 712 protected Dimension getMinimumThumbSize() { 713 return minimumThumbSize; 714 } 715 716 /** 717 * Returns the largest acceptable size for the thumb. To create a fixed 718 * size thumb one make this method and <code>getMinimumThumbSize</code> 719 * return the same value. 720 * <p> 721 * <b>Warning </b>: the value returned by this method should not be 722 * be modified, it's a shared static constant. 723 * 724 * @return The largest acceptable size for the thumb. 725 * @see #getMinimumThumbSize 726 */ 727 protected Dimension getMaximumThumbSize() { 728 return maximumThumbSize; 729 } 730 731 732 /* 733 * LayoutManager Implementation 734 */ 735 736 public void addLayoutComponent(String name, Component child) {} 737 public void removeLayoutComponent(Component child) {} 738 739 public Dimension preferredLayoutSize(Container scrollbarContainer) { 740 return getPreferredSize((JComponent)scrollbarContainer); 741 } 742 743 public Dimension minimumLayoutSize(Container scrollbarContainer) { 744 return getMinimumSize((JComponent)scrollbarContainer); 745 } 746 747 private int getValue(JScrollBar sb) { 748 return (useCachedValue) ? scrollBarValue : sb.getValue(); 749 } 750 751 /** 752 * Laysouts a vertical scroll bar. 753 * @param sb the scroll bar 754 */ 755 protected void layoutVScrollbar(JScrollBar sb) 756 { 757 Dimension sbSize = sb.getSize(); 758 Insets sbInsets = sb.getInsets(); 759 760 /* 761 * Width and left edge of the buttons and thumb. 762 */ 763 int itemW = sbSize.width - (sbInsets.left + sbInsets.right); 764 int itemX = sbInsets.left; 765 766 /* Nominal locations of the buttons, assuming their preferred 767 * size will fit. 768 */ 769 boolean squareButtons = DefaultLookup.getBoolean( 770 scrollbar, this, "ScrollBar.squareButtons", false); 771 int decrButtonH = squareButtons ? itemW : 772 decrButton.getPreferredSize().height; 773 int decrButtonY = sbInsets.top; 774 775 int incrButtonH = squareButtons ? itemW : 776 incrButton.getPreferredSize().height; 777 int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH); 778 779 /* The thumb must fit within the height left over after we 780 * subtract the preferredSize of the buttons and the insets 781 * and the gaps 782 */ 783 int sbInsetsH = sbInsets.top + sbInsets.bottom; 784 int sbButtonsH = decrButtonH + incrButtonH; 785 int gaps = decrGap + incrGap; 786 float trackH = sbSize.height - (sbInsetsH + sbButtonsH) - gaps; 787 788 /* Compute the height and origin of the thumb. The case 789 * where the thumb is at the bottom edge is handled specially 790 * to avoid numerical problems in computing thumbY. Enforce 791 * the thumbs min/max dimensions. If the thumb doesn't 792 * fit in the track (trackH) we'll hide it later. 793 */ 794 float min = sb.getMinimum(); 795 float extent = sb.getVisibleAmount(); 796 float range = sb.getMaximum() - min; 797 float value = getValue(sb); 798 799 int thumbH = (range <= 0) 800 ? getMaximumThumbSize().height : (int)(trackH * (extent / range)); 801 thumbH = Math.max(thumbH, getMinimumThumbSize().height); 802 thumbH = Math.min(thumbH, getMaximumThumbSize().height); 803 804 int thumbY = incrButtonY - incrGap - thumbH; 805 if (value < (sb.getMaximum() - sb.getVisibleAmount())) { 806 float thumbRange = trackH - thumbH; 807 thumbY = (int)(0.5f + (thumbRange * ((value - min) / (range - extent)))); 808 thumbY += decrButtonY + decrButtonH + decrGap; 809 } 810 811 /* If the buttons don't fit, allocate half of the available 812 * space to each and move the lower one (incrButton) down. 813 */ 814 int sbAvailButtonH = (sbSize.height - sbInsetsH); 815 if (sbAvailButtonH < sbButtonsH) { 816 incrButtonH = decrButtonH = sbAvailButtonH / 2; 817 incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH); 818 } 819 decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH); 820 incrButton.setBounds(itemX, incrButtonY, itemW, incrButtonH); 821 822 /* Update the trackRect field. 823 */ 824 int itrackY = decrButtonY + decrButtonH + decrGap; 825 int itrackH = incrButtonY - incrGap - itrackY; 826 trackRect.setBounds(itemX, itrackY, itemW, itrackH); 827 828 /* If the thumb isn't going to fit, zero it's bounds. Otherwise 829 * make sure it fits between the buttons. Note that setting the 830 * thumbs bounds will cause a repaint. 831 */ 832 if(thumbH >= (int)trackH) { 833 if (UIManager.getBoolean("ScrollBar.alwaysShowThumb")) { 834 // This is used primarily for GTK L&F, which expands the 835 // thumb to fit the track when it would otherwise be hidden. 836 setThumbBounds(itemX, itrackY, itemW, itrackH); 837 } else { 838 // Other L&F's simply hide the thumb in this case. 839 setThumbBounds(0, 0, 0, 0); 840 } 841 } 842 else { 843 if ((thumbY + thumbH) > incrButtonY - incrGap) { 844 thumbY = incrButtonY - incrGap - thumbH; 845 } 846 if (thumbY < (decrButtonY + decrButtonH + decrGap)) { 847 thumbY = decrButtonY + decrButtonH + decrGap + 1; 848 } 849 setThumbBounds(itemX, thumbY, itemW, thumbH); 850 } 851 } 852 853 /** 854 * Laysouts a vertical scroll bar. 855 * @param sb the scroll bar 856 */ 857 protected void layoutHScrollbar(JScrollBar sb) 858 { 859 Dimension sbSize = sb.getSize(); 860 Insets sbInsets = sb.getInsets(); 861 862 /* Height and top edge of the buttons and thumb. 863 */ 864 int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom); 865 int itemY = sbInsets.top; 866 867 boolean ltr = sb.getComponentOrientation().isLeftToRight(); 868 869 /* Nominal locations of the buttons, assuming their preferred 870 * size will fit. 871 */ 872 boolean squareButtons = DefaultLookup.getBoolean( 873 scrollbar, this, "ScrollBar.squareButtons", false); 874 int leftButtonW = squareButtons ? itemH : 875 decrButton.getPreferredSize().width; 876 int rightButtonW = squareButtons ? itemH : 877 incrButton.getPreferredSize().width; 878 if (!ltr) { 879 int temp = leftButtonW; 880 leftButtonW = rightButtonW; 881 rightButtonW = temp; 882 } 883 int leftButtonX = sbInsets.left; 884 int rightButtonX = sbSize.width - (sbInsets.right + rightButtonW); 885 int leftGap = ltr ? decrGap : incrGap; 886 int rightGap = ltr ? incrGap : decrGap; 887 888 /* The thumb must fit within the width left over after we 889 * subtract the preferredSize of the buttons and the insets 890 * and the gaps 891 */ 892 int sbInsetsW = sbInsets.left + sbInsets.right; 893 int sbButtonsW = leftButtonW + rightButtonW; 894 float trackW = sbSize.width - (sbInsetsW + sbButtonsW) - (leftGap + rightGap); 895 896 /* Compute the width and origin of the thumb. Enforce 897 * the thumbs min/max dimensions. The case where the thumb 898 * is at the right edge is handled specially to avoid numerical 899 * problems in computing thumbX. If the thumb doesn't 900 * fit in the track (trackH) we'll hide it later. 901 */ 902 float min = sb.getMinimum(); 903 float max = sb.getMaximum(); 904 float extent = sb.getVisibleAmount(); 905 float range = max - min; 906 float value = getValue(sb); 907 908 int thumbW = (range <= 0) 909 ? getMaximumThumbSize().width : (int)(trackW * (extent / range)); 910 thumbW = Math.max(thumbW, getMinimumThumbSize().width); 911 thumbW = Math.min(thumbW, getMaximumThumbSize().width); 912 913 int thumbX = ltr ? rightButtonX - rightGap - thumbW : leftButtonX + leftButtonW + leftGap; 914 if (value < (max - sb.getVisibleAmount())) { 915 float thumbRange = trackW - thumbW; 916 if( ltr ) { 917 thumbX = (int)(0.5f + (thumbRange * ((value - min) / (range - extent)))); 918 } else { 919 thumbX = (int)(0.5f + (thumbRange * ((max - extent - value) / (range - extent)))); 920 } 921 thumbX += leftButtonX + leftButtonW + leftGap; 922 } 923 924 /* If the buttons don't fit, allocate half of the available 925 * space to each and move the right one over. 926 */ 927 int sbAvailButtonW = (sbSize.width - sbInsetsW); 928 if (sbAvailButtonW < sbButtonsW) { 929 rightButtonW = leftButtonW = sbAvailButtonW / 2; 930 rightButtonX = sbSize.width - (sbInsets.right + rightButtonW + rightGap); 931 } 932 933 (ltr ? decrButton : incrButton).setBounds(leftButtonX, itemY, leftButtonW, itemH); 934 (ltr ? incrButton : decrButton).setBounds(rightButtonX, itemY, rightButtonW, itemH); 935 936 /* Update the trackRect field. 937 */ 938 int itrackX = leftButtonX + leftButtonW + leftGap; 939 int itrackW = rightButtonX - rightGap - itrackX; 940 trackRect.setBounds(itrackX, itemY, itrackW, itemH); 941 942 /* Make sure the thumb fits between the buttons. Note 943 * that setting the thumbs bounds causes a repaint. 944 */ 945 if (thumbW >= (int)trackW) { 946 if (UIManager.getBoolean("ScrollBar.alwaysShowThumb")) { 947 // This is used primarily for GTK L&F, which expands the 948 // thumb to fit the track when it would otherwise be hidden. 949 setThumbBounds(itrackX, itemY, itrackW, itemH); 950 } else { 951 // Other L&F's simply hide the thumb in this case. 952 setThumbBounds(0, 0, 0, 0); 953 } 954 } 955 else { 956 if (thumbX + thumbW > rightButtonX - rightGap) { 957 thumbX = rightButtonX - rightGap - thumbW; 958 } 959 if (thumbX < leftButtonX + leftButtonW + leftGap) { 960 thumbX = leftButtonX + leftButtonW + leftGap + 1; 961 } 962 setThumbBounds(thumbX, itemY, thumbW, itemH); 963 } 964 } 965 966 public void layoutContainer(Container scrollbarContainer) 967 { 968 /* If the user is dragging the value, we'll assume that the 969 * scrollbars layout is OK modulo the thumb which is being 970 * handled by the dragging code. 971 */ 972 if (isDragging) { 973 return; 974 } 975 976 JScrollBar scrollbar = (JScrollBar)scrollbarContainer; 977 switch (scrollbar.getOrientation()) { 978 case JScrollBar.VERTICAL: 979 layoutVScrollbar(scrollbar); 980 break; 981 982 case JScrollBar.HORIZONTAL: 983 layoutHScrollbar(scrollbar); 984 break; 985 } 986 } 987 988 989 /** 990 * Set the bounds of the thumb and force a repaint that includes 991 * the old thumbBounds and the new one. 992 * 993 * @param x set the x location of the thumb 994 * @param y set the y location of the thumb 995 * @param width set the width of the thumb 996 * @param height set the height of the thumb 997 * @see #getThumbBounds 998 */ 999 protected void setThumbBounds(int x, int y, int width, int height) 1000 { 1001 /* If the thumbs bounds haven't changed, we're done. 1002 */ 1003 if ((thumbRect.x == x) && 1004 (thumbRect.y == y) && 1005 (thumbRect.width == width) && 1006 (thumbRect.height == height)) { 1007 return; 1008 } 1009 1010 /* Update thumbRect, and repaint the union of x,y,w,h and 1011 * the old thumbRect. 1012 */ 1013 int minX = Math.min(x, thumbRect.x); 1014 int minY = Math.min(y, thumbRect.y); 1015 int maxX = Math.max(x + width, thumbRect.x + thumbRect.width); 1016 int maxY = Math.max(y + height, thumbRect.y + thumbRect.height); 1017 1018 thumbRect.setBounds(x, y, width, height); 1019 scrollbar.repaint(minX, minY, maxX - minX, maxY - minY); 1020 1021 // Once there is API to determine the mouse location this will need 1022 // to be changed. 1023 setThumbRollover(false); 1024 } 1025 1026 1027 /** 1028 * Return the current size/location of the thumb. 1029 * <p> 1030 * <b>Warning </b>: the value returned by this method should not be 1031 * be modified, it's a reference to the actual rectangle, not a copy. 1032 * 1033 * @return The current size/location of the thumb. 1034 * @see #setThumbBounds 1035 */ 1036 protected Rectangle getThumbBounds() { 1037 return thumbRect; 1038 } 1039 1040 1041 /** 1042 * Returns the current bounds of the track, i.e. the space in between 1043 * the increment and decrement buttons, less the insets. The value 1044 * returned by this method is updated each time the scrollbar is 1045 * laid out (validated). 1046 * <p> 1047 * <b>Warning </b>: the value returned by this method should not be 1048 * be modified, it's a reference to the actual rectangle, not a copy. 1049 * 1050 * @return the current bounds of the scrollbar track 1051 * @see #layoutContainer 1052 */ 1053 protected Rectangle getTrackBounds() { 1054 return trackRect; 1055 } 1056 1057 /* 1058 * Method for scrolling by a block increment. 1059 * Added for mouse wheel scrolling support, RFE 4202656. 1060 */ 1061 static void scrollByBlock(JScrollBar scrollbar, int direction) { 1062 // This method is called from BasicScrollPaneUI to implement wheel 1063 // scrolling, and also from scrollByBlock(). 1064 int oldValue = scrollbar.getValue(); 1065 int blockIncrement = scrollbar.getBlockIncrement(direction); 1066 int delta = blockIncrement * ((direction > 0) ? +1 : -1); 1067 int newValue = oldValue + delta; 1068 1069 // Check for overflow. 1070 if (delta > 0 && newValue < oldValue) { 1071 newValue = scrollbar.getMaximum(); 1072 } 1073 else if (delta < 0 && newValue > oldValue) { 1074 newValue = scrollbar.getMinimum(); 1075 } 1076 1077 scrollbar.setValue(newValue); 1078 } 1079 1080 /** 1081 * Scrolls by block. 1082 * @param direction the direction to scroll 1083 */ 1084 protected void scrollByBlock(int direction) 1085 { 1086 scrollByBlock(scrollbar, direction); 1087 trackHighlight = direction > 0 ? INCREASE_HIGHLIGHT : DECREASE_HIGHLIGHT; 1088 Rectangle dirtyRect = getTrackBounds(); 1089 scrollbar.repaint(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); 1090 } 1091 1092 /* 1093 * Method for scrolling by a unit increment. 1094 * Added for mouse wheel scrolling support, RFE 4202656. 1095 * 1096 * If limitByBlock is set to true, the scrollbar will scroll at least 1 1097 * unit increment, but will not scroll farther than the block increment. 1098 * See BasicScrollPaneUI.Handler.mouseWheelMoved(). 1099 */ 1100 static void scrollByUnits(JScrollBar scrollbar, int direction, 1101 int units, boolean limitToBlock) { 1102 // This method is called from BasicScrollPaneUI to implement wheel 1103 // scrolling, as well as from scrollByUnit(). 1104 int delta; 1105 int limit = -1; 1106 1107 if (limitToBlock) { 1108 if (direction < 0) { 1109 limit = scrollbar.getValue() - 1110 scrollbar.getBlockIncrement(direction); 1111 } 1112 else { 1113 limit = scrollbar.getValue() + 1114 scrollbar.getBlockIncrement(direction); 1115 } 1116 } 1117 1118 for (int i=0; i<units; i++) { 1119 if (direction > 0) { 1120 delta = scrollbar.getUnitIncrement(direction); 1121 } 1122 else { 1123 delta = -scrollbar.getUnitIncrement(direction); 1124 } 1125 1126 int oldValue = scrollbar.getValue(); 1127 int newValue = oldValue + delta; 1128 1129 // Check for overflow. 1130 if (delta > 0 && newValue < oldValue) { 1131 newValue = scrollbar.getMaximum(); 1132 } 1133 else if (delta < 0 && newValue > oldValue) { 1134 newValue = scrollbar.getMinimum(); 1135 } 1136 if (oldValue == newValue) { 1137 break; 1138 } 1139 1140 if (limitToBlock && i > 0) { 1141 assert limit != -1; 1142 if ((direction < 0 && newValue < limit) || 1143 (direction > 0 && newValue > limit)) { 1144 break; 1145 } 1146 } 1147 scrollbar.setValue(newValue); 1148 } 1149 } 1150 1151 /** 1152 * Scrolls by unit. 1153 * @param direction the direction to scroll 1154 */ 1155 protected void scrollByUnit(int direction) { 1156 scrollByUnits(scrollbar, direction, 1, false); 1157 } 1158 1159 /** 1160 * Indicates whether the user can absolutely position the thumb with 1161 * a mouse gesture (usually the middle mouse button). 1162 * 1163 * @return true if a mouse gesture can absolutely position the thumb 1164 * @since 1.5 1165 */ 1166 public boolean getSupportsAbsolutePositioning() { 1167 return supportsAbsolutePositioning; 1168 } 1169 1170 /** 1171 * A listener to listen for model changes. 1172 */ 1173 protected class ModelListener implements ChangeListener { 1174 public void stateChanged(ChangeEvent e) { 1175 if (!useCachedValue) { 1176 scrollBarValue = scrollbar.getValue(); 1177 } 1178 layoutContainer(scrollbar); 1179 useCachedValue = false; 1180 } 1181 } 1182 1183 1184 /** 1185 * Track mouse drags. 1186 */ 1187 protected class TrackListener 1188 extends MouseAdapter implements MouseMotionListener 1189 { 1190 /** The offset */ 1191 protected transient int offset; 1192 /** Current mouse x position */ 1193 protected transient int currentMouseX; 1194 /** Current mouse y position */ 1195 protected transient int currentMouseY; 1196 private transient int direction = +1; 1197 1198 /** {@inheritDoc} */ 1199 public void mouseReleased(MouseEvent e) 1200 { 1201 if (isDragging) { 1202 updateThumbState(e.getX(), e.getY()); 1203 } 1204 if (SwingUtilities.isRightMouseButton(e) || 1205 (!getSupportsAbsolutePositioning() && 1206 SwingUtilities.isMiddleMouseButton(e))) 1207 return; 1208 if(!scrollbar.isEnabled()) 1209 return; 1210 1211 Rectangle r = getTrackBounds(); 1212 scrollbar.repaint(r.x, r.y, r.width, r.height); 1213 1214 trackHighlight = NO_HIGHLIGHT; 1215 isDragging = false; 1216 offset = 0; 1217 scrollTimer.stop(); 1218 useCachedValue = true; 1219 scrollbar.setValueIsAdjusting(false); 1220 } 1221 1222 1223 /** 1224 * If the mouse is pressed above the "thumb" component 1225 * then reduce the scrollbars value by one page ("page up"), 1226 * otherwise increase it by one page. If there is no 1227 * thumb then page up if the mouse is in the upper half 1228 * of the track. 1229 */ 1230 public void mousePressed(MouseEvent e) 1231 { 1232 if (SwingUtilities.isRightMouseButton(e) || 1233 (!getSupportsAbsolutePositioning() && 1234 SwingUtilities.isMiddleMouseButton(e))) 1235 return; 1236 if(!scrollbar.isEnabled()) 1237 return; 1238 1239 if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) { 1240 scrollbar.requestFocus(); 1241 } 1242 1243 useCachedValue = true; 1244 scrollbar.setValueIsAdjusting(true); 1245 1246 currentMouseX = e.getX(); 1247 currentMouseY = e.getY(); 1248 1249 // Clicked in the Thumb area? 1250 if(getThumbBounds().contains(currentMouseX, currentMouseY)) { 1251 switch (scrollbar.getOrientation()) { 1252 case JScrollBar.VERTICAL: 1253 offset = currentMouseY - getThumbBounds().y; 1254 break; 1255 case JScrollBar.HORIZONTAL: 1256 offset = currentMouseX - getThumbBounds().x; 1257 break; 1258 } 1259 isDragging = true; 1260 return; 1261 } 1262 else if (getSupportsAbsolutePositioning() && 1263 SwingUtilities.isMiddleMouseButton(e)) { 1264 switch (scrollbar.getOrientation()) { 1265 case JScrollBar.VERTICAL: 1266 offset = getThumbBounds().height / 2; 1267 break; 1268 case JScrollBar.HORIZONTAL: 1269 offset = getThumbBounds().width / 2; 1270 break; 1271 } 1272 isDragging = true; 1273 setValueFrom(e); 1274 return; 1275 } 1276 isDragging = false; 1277 1278 Dimension sbSize = scrollbar.getSize(); 1279 direction = +1; 1280 1281 switch (scrollbar.getOrientation()) { 1282 case JScrollBar.VERTICAL: 1283 if (getThumbBounds().isEmpty()) { 1284 int scrollbarCenter = sbSize.height / 2; 1285 direction = (currentMouseY < scrollbarCenter) ? -1 : +1; 1286 } else { 1287 int thumbY = getThumbBounds().y; 1288 direction = (currentMouseY < thumbY) ? -1 : +1; 1289 } 1290 break; 1291 case JScrollBar.HORIZONTAL: 1292 if (getThumbBounds().isEmpty()) { 1293 int scrollbarCenter = sbSize.width / 2; 1294 direction = (currentMouseX < scrollbarCenter) ? -1 : +1; 1295 } else { 1296 int thumbX = getThumbBounds().x; 1297 direction = (currentMouseX < thumbX) ? -1 : +1; 1298 } 1299 if (!scrollbar.getComponentOrientation().isLeftToRight()) { 1300 direction = -direction; 1301 } 1302 break; 1303 } 1304 scrollByBlock(direction); 1305 1306 scrollTimer.stop(); 1307 scrollListener.setDirection(direction); 1308 scrollListener.setScrollByBlock(true); 1309 startScrollTimerIfNecessary(); 1310 } 1311 1312 1313 /** 1314 * Set the models value to the position of the thumb's top of Vertical 1315 * scrollbar, or the left/right of Horizontal scrollbar in 1316 * left-to-right/right-to-left scrollbar relative to the origin of the 1317 * track. 1318 */ 1319 public void mouseDragged(MouseEvent e) { 1320 if (SwingUtilities.isRightMouseButton(e) || 1321 (!getSupportsAbsolutePositioning() && 1322 SwingUtilities.isMiddleMouseButton(e))) 1323 return; 1324 if(!scrollbar.isEnabled() || getThumbBounds().isEmpty()) { 1325 return; 1326 } 1327 if (isDragging) { 1328 setValueFrom(e); 1329 } else { 1330 currentMouseX = e.getX(); 1331 currentMouseY = e.getY(); 1332 updateThumbState(currentMouseX, currentMouseY); 1333 startScrollTimerIfNecessary(); 1334 } 1335 } 1336 1337 private void setValueFrom(MouseEvent e) { 1338 boolean active = isThumbRollover(); 1339 BoundedRangeModel model = scrollbar.getModel(); 1340 Rectangle thumbR = getThumbBounds(); 1341 float trackLength; 1342 int thumbMin, thumbMax, thumbPos; 1343 1344 if (scrollbar.getOrientation() == JScrollBar.VERTICAL) { 1345 thumbMin = trackRect.y; 1346 thumbMax = trackRect.y + trackRect.height - thumbR.height; 1347 thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getY() - offset))); 1348 setThumbBounds(thumbR.x, thumbPos, thumbR.width, thumbR.height); 1349 trackLength = getTrackBounds().height; 1350 } 1351 else { 1352 thumbMin = trackRect.x; 1353 thumbMax = trackRect.x + trackRect.width - thumbR.width; 1354 thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getX() - offset))); 1355 setThumbBounds(thumbPos, thumbR.y, thumbR.width, thumbR.height); 1356 trackLength = getTrackBounds().width; 1357 } 1358 1359 /* Set the scrollbars value. If the thumb has reached the end of 1360 * the scrollbar, then just set the value to its maximum. Otherwise 1361 * compute the value as accurately as possible. 1362 */ 1363 if (thumbPos == thumbMax) { 1364 if (scrollbar.getOrientation() == JScrollBar.VERTICAL || 1365 scrollbar.getComponentOrientation().isLeftToRight()) { 1366 scrollbar.setValue(model.getMaximum() - model.getExtent()); 1367 } else { 1368 scrollbar.setValue(model.getMinimum()); 1369 } 1370 } 1371 else { 1372 float valueMax = model.getMaximum() - model.getExtent(); 1373 float valueRange = valueMax - model.getMinimum(); 1374 float thumbValue = thumbPos - thumbMin; 1375 float thumbRange = thumbMax - thumbMin; 1376 int value; 1377 if (scrollbar.getOrientation() == JScrollBar.VERTICAL || 1378 scrollbar.getComponentOrientation().isLeftToRight()) { 1379 value = (int)(0.5 + ((thumbValue / thumbRange) * valueRange)); 1380 } else { 1381 value = (int)(0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange)); 1382 } 1383 1384 useCachedValue = true; 1385 scrollBarValue = value + model.getMinimum(); 1386 scrollbar.setValue(adjustValueIfNecessary(scrollBarValue)); 1387 } 1388 setThumbRollover(active); 1389 } 1390 1391 private int adjustValueIfNecessary(int value) { 1392 if (scrollbar.getParent() instanceof JScrollPane) { 1393 JScrollPane scrollpane = (JScrollPane)scrollbar.getParent(); 1394 JViewport viewport = scrollpane.getViewport(); 1395 Component view = viewport.getView(); 1396 if (view instanceof JList) { 1397 JList<?> list = (JList)view; 1398 if (DefaultLookup.getBoolean(list, list.getUI(), 1399 "List.lockToPositionOnScroll", false)) { 1400 int adjustedValue = value; 1401 int mode = list.getLayoutOrientation(); 1402 int orientation = scrollbar.getOrientation(); 1403 if (orientation == JScrollBar.VERTICAL && mode == JList.VERTICAL) { 1404 int index = list.locationToIndex(new Point(0, value)); 1405 Rectangle rect = list.getCellBounds(index, index); 1406 if (rect != null) { 1407 adjustedValue = rect.y; 1408 } 1409 } 1410 if (orientation == JScrollBar.HORIZONTAL && 1411 (mode == JList.VERTICAL_WRAP || mode == JList.HORIZONTAL_WRAP)) { 1412 if (scrollpane.getComponentOrientation().isLeftToRight()) { 1413 int index = list.locationToIndex(new Point(value, 0)); 1414 Rectangle rect = list.getCellBounds(index, index); 1415 if (rect != null) { 1416 adjustedValue = rect.x; 1417 } 1418 } 1419 else { 1420 Point loc = new Point(value, 0); 1421 int extent = viewport.getExtentSize().width; 1422 loc.x += extent - 1; 1423 int index = list.locationToIndex(loc); 1424 Rectangle rect = list.getCellBounds(index, index); 1425 if (rect != null) { 1426 adjustedValue = rect.x + rect.width - extent; 1427 } 1428 } 1429 } 1430 value = adjustedValue; 1431 1432 } 1433 } 1434 } 1435 return value; 1436 } 1437 1438 private void startScrollTimerIfNecessary() { 1439 if (scrollTimer.isRunning()) { 1440 return; 1441 } 1442 1443 Rectangle tb = getThumbBounds(); 1444 1445 switch (scrollbar.getOrientation()) { 1446 case JScrollBar.VERTICAL: 1447 if (direction > 0) { 1448 if (tb.y + tb.height < trackListener.currentMouseY) { 1449 scrollTimer.start(); 1450 } 1451 } else if (tb.y > trackListener.currentMouseY) { 1452 scrollTimer.start(); 1453 } 1454 break; 1455 case JScrollBar.HORIZONTAL: 1456 if ((direction > 0 && isMouseAfterThumb()) 1457 || (direction < 0 && isMouseBeforeThumb())) { 1458 1459 scrollTimer.start(); 1460 } 1461 break; 1462 } 1463 } 1464 1465 /** {@inheritDoc} */ 1466 public void mouseMoved(MouseEvent e) { 1467 if (!isDragging) { 1468 updateThumbState(e.getX(), e.getY()); 1469 } 1470 } 1471 1472 /** 1473 * Invoked when the mouse exits the scrollbar. 1474 * 1475 * @param e MouseEvent further describing the event 1476 * @since 1.5 1477 */ 1478 public void mouseExited(MouseEvent e) { 1479 if (!isDragging) { 1480 setThumbRollover(false); 1481 } 1482 } 1483 } 1484 1485 1486 /** 1487 * Listener for cursor keys. 1488 */ 1489 protected class ArrowButtonListener extends MouseAdapter 1490 { 1491 // Because we are handling both mousePressed and Actions 1492 // we need to make sure we don't fire under both conditions. 1493 // (keyfocus on scrollbars causes action without mousePress 1494 boolean handledEvent; 1495 1496 public void mousePressed(MouseEvent e) { 1497 if(!scrollbar.isEnabled()) { return; } 1498 // not an unmodified left mouse button 1499 //if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; } 1500 if( ! SwingUtilities.isLeftMouseButton(e)) { return; } 1501 1502 int direction = (e.getSource() == incrButton) ? 1 : -1; 1503 1504 scrollByUnit(direction); 1505 scrollTimer.stop(); 1506 scrollListener.setDirection(direction); 1507 scrollListener.setScrollByBlock(false); 1508 scrollTimer.start(); 1509 1510 handledEvent = true; 1511 if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) { 1512 scrollbar.requestFocus(); 1513 } 1514 } 1515 1516 public void mouseReleased(MouseEvent e) { 1517 scrollTimer.stop(); 1518 handledEvent = false; 1519 scrollbar.setValueIsAdjusting(false); 1520 } 1521 } 1522 1523 1524 /** 1525 * Listener for scrolling events initiated in the 1526 * <code>ScrollPane</code>. 1527 */ 1528 protected class ScrollListener implements ActionListener 1529 { 1530 int direction = +1; 1531 boolean useBlockIncrement; 1532 1533 /** Constructs a {@code ScrollListener}. */ 1534 public ScrollListener() { 1535 direction = +1; 1536 useBlockIncrement = false; 1537 } 1538 1539 /** 1540 * Constructs a {@code ScrollListener}. 1541 * @param dir direction 1542 * @param block use block increment 1543 */ 1544 public ScrollListener(int dir, boolean block) { 1545 direction = dir; 1546 useBlockIncrement = block; 1547 } 1548 1549 /** 1550 * Sets the direction. 1551 * @param direction the new direction 1552 */ 1553 public void setDirection(int direction) { this.direction = direction; } 1554 /** 1555 * Sets the scrolling by block 1556 * @param block whether or not to scroll by block 1557 */ 1558 public void setScrollByBlock(boolean block) { this.useBlockIncrement = block; } 1559 1560 /** {@inheritDoc} */ 1561 public void actionPerformed(ActionEvent e) { 1562 if(useBlockIncrement) { 1563 scrollByBlock(direction); 1564 // Stop scrolling if the thumb catches up with the mouse 1565 if(scrollbar.getOrientation() == JScrollBar.VERTICAL) { 1566 if(direction > 0) { 1567 if(getThumbBounds().y + getThumbBounds().height 1568 >= trackListener.currentMouseY) 1569 ((Timer)e.getSource()).stop(); 1570 } else if(getThumbBounds().y <= trackListener.currentMouseY) { 1571 ((Timer)e.getSource()).stop(); 1572 } 1573 } else { 1574 if ((direction > 0 && !isMouseAfterThumb()) 1575 || (direction < 0 && !isMouseBeforeThumb())) { 1576 1577 ((Timer)e.getSource()).stop(); 1578 } 1579 } 1580 } else { 1581 scrollByUnit(direction); 1582 } 1583 1584 if(direction > 0 1585 && scrollbar.getValue()+scrollbar.getVisibleAmount() 1586 >= scrollbar.getMaximum()) 1587 ((Timer)e.getSource()).stop(); 1588 else if(direction < 0 1589 && scrollbar.getValue() <= scrollbar.getMinimum()) 1590 ((Timer)e.getSource()).stop(); 1591 } 1592 } 1593 1594 private boolean isMouseLeftOfThumb() { 1595 return trackListener.currentMouseX < getThumbBounds().x; 1596 } 1597 1598 private boolean isMouseRightOfThumb() { 1599 Rectangle tb = getThumbBounds(); 1600 return trackListener.currentMouseX > tb.x + tb.width; 1601 } 1602 1603 private boolean isMouseBeforeThumb() { 1604 return scrollbar.getComponentOrientation().isLeftToRight() 1605 ? isMouseLeftOfThumb() 1606 : isMouseRightOfThumb(); 1607 } 1608 1609 private boolean isMouseAfterThumb() { 1610 return scrollbar.getComponentOrientation().isLeftToRight() 1611 ? isMouseRightOfThumb() 1612 : isMouseLeftOfThumb(); 1613 } 1614 1615 private void updateButtonDirections() { 1616 int orient = scrollbar.getOrientation(); 1617 if (scrollbar.getComponentOrientation().isLeftToRight()) { 1618 if (incrButton instanceof BasicArrowButton) { 1619 ((BasicArrowButton)incrButton).setDirection( 1620 orient == HORIZONTAL? EAST : SOUTH); 1621 } 1622 if (decrButton instanceof BasicArrowButton) { 1623 ((BasicArrowButton)decrButton).setDirection( 1624 orient == HORIZONTAL? WEST : NORTH); 1625 } 1626 } 1627 else { 1628 if (incrButton instanceof BasicArrowButton) { 1629 ((BasicArrowButton)incrButton).setDirection( 1630 orient == HORIZONTAL? WEST : SOUTH); 1631 } 1632 if (decrButton instanceof BasicArrowButton) { 1633 ((BasicArrowButton)decrButton).setDirection( 1634 orient == HORIZONTAL ? EAST : NORTH); 1635 } 1636 } 1637 } 1638 1639 /** Property change handler */ 1640 public class PropertyChangeHandler implements PropertyChangeListener 1641 { 1642 // NOTE: This class exists only for backward compatibility. All 1643 // its functionality has been moved into Handler. If you need to add 1644 // new functionality add it to the Handler, but make sure this 1645 // class calls into the Handler. 1646 /** {@inheritDoc} */ 1647 public void propertyChange(PropertyChangeEvent e) { 1648 getHandler().propertyChange(e); 1649 } 1650 } 1651 1652 1653 /** 1654 * Used for scrolling the scrollbar. 1655 */ 1656 private static class Actions extends UIAction { 1657 private static final String POSITIVE_UNIT_INCREMENT = 1658 "positiveUnitIncrement"; 1659 private static final String POSITIVE_BLOCK_INCREMENT = 1660 "positiveBlockIncrement"; 1661 private static final String NEGATIVE_UNIT_INCREMENT = 1662 "negativeUnitIncrement"; 1663 private static final String NEGATIVE_BLOCK_INCREMENT = 1664 "negativeBlockIncrement"; 1665 private static final String MIN_SCROLL = "minScroll"; 1666 private static final String MAX_SCROLL = "maxScroll"; 1667 1668 Actions(String name) { 1669 super(name); 1670 } 1671 1672 public void actionPerformed(ActionEvent e) { 1673 JScrollBar scrollBar = (JScrollBar)e.getSource(); 1674 String key = getName(); 1675 if (key == POSITIVE_UNIT_INCREMENT) { 1676 scroll(scrollBar, POSITIVE_SCROLL, false); 1677 } 1678 else if (key == POSITIVE_BLOCK_INCREMENT) { 1679 scroll(scrollBar, POSITIVE_SCROLL, true); 1680 } 1681 else if (key == NEGATIVE_UNIT_INCREMENT) { 1682 scroll(scrollBar, NEGATIVE_SCROLL, false); 1683 } 1684 else if (key == NEGATIVE_BLOCK_INCREMENT) { 1685 scroll(scrollBar, NEGATIVE_SCROLL, true); 1686 } 1687 else if (key == MIN_SCROLL) { 1688 scroll(scrollBar, BasicScrollBarUI.MIN_SCROLL, true); 1689 } 1690 else if (key == MAX_SCROLL) { 1691 scroll(scrollBar, BasicScrollBarUI.MAX_SCROLL, true); 1692 } 1693 } 1694 private void scroll(JScrollBar scrollBar, int dir, boolean block) { 1695 1696 if (dir == NEGATIVE_SCROLL || dir == POSITIVE_SCROLL) { 1697 int amount; 1698 // Don't use the BasicScrollBarUI.scrollByXXX methods as we 1699 // don't want to use an invokeLater to reset the trackHighlight 1700 // via an invokeLater 1701 if (block) { 1702 if (dir == NEGATIVE_SCROLL) { 1703 amount = -1 * scrollBar.getBlockIncrement(-1); 1704 } 1705 else { 1706 amount = scrollBar.getBlockIncrement(1); 1707 } 1708 } 1709 else { 1710 if (dir == NEGATIVE_SCROLL) { 1711 amount = -1 * scrollBar.getUnitIncrement(-1); 1712 } 1713 else { 1714 amount = scrollBar.getUnitIncrement(1); 1715 } 1716 } 1717 scrollBar.setValue(scrollBar.getValue() + amount); 1718 } 1719 else if (dir == BasicScrollBarUI.MIN_SCROLL) { 1720 scrollBar.setValue(scrollBar.getMinimum()); 1721 } 1722 else if (dir == BasicScrollBarUI.MAX_SCROLL) { 1723 scrollBar.setValue(scrollBar.getMaximum()); 1724 } 1725 } 1726 } 1727 1728 1729 // 1730 // EventHandler 1731 // 1732 private class Handler implements FocusListener, PropertyChangeListener { 1733 // 1734 // FocusListener 1735 // 1736 public void focusGained(FocusEvent e) { 1737 scrollbar.repaint(); 1738 } 1739 1740 public void focusLost(FocusEvent e) { 1741 scrollbar.repaint(); 1742 } 1743 1744 1745 // 1746 // PropertyChangeListener 1747 // 1748 public void propertyChange(PropertyChangeEvent e) { 1749 String propertyName = e.getPropertyName(); 1750 1751 if ("model" == propertyName) { 1752 BoundedRangeModel oldModel = (BoundedRangeModel)e.getOldValue(); 1753 BoundedRangeModel newModel = (BoundedRangeModel)e.getNewValue(); 1754 oldModel.removeChangeListener(modelListener); 1755 newModel.addChangeListener(modelListener); 1756 scrollBarValue = scrollbar.getValue(); 1757 scrollbar.repaint(); 1758 scrollbar.revalidate(); 1759 } else if ("orientation" == propertyName) { 1760 updateButtonDirections(); 1761 } else if ("componentOrientation" == propertyName) { 1762 updateButtonDirections(); 1763 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED); 1764 SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED, inputMap); 1765 } 1766 } 1767 } 1768 }