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