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