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