1 /* 2 * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import sun.swing.DefaultLookup; 29 import sun.swing.UIAction; 30 31 import javax.swing.*; 32 import javax.swing.event.*; 33 import javax.swing.border.*; 34 import javax.swing.plaf.*; 35 36 import java.beans.PropertyChangeListener; 37 import java.beans.PropertyChangeEvent; 38 39 import java.awt.Component; 40 import java.awt.Rectangle; 41 import java.awt.Dimension; 42 import java.awt.Point; 43 import java.awt.Insets; 44 import java.awt.Graphics; 45 import java.awt.event.*; 46 47 /** 48 * A default L&F implementation of ScrollPaneUI. 49 * 50 * @author Hans Muller 51 */ 52 public class BasicScrollPaneUI 53 extends ScrollPaneUI implements ScrollPaneConstants 54 { 55 protected JScrollPane scrollpane; 56 protected ChangeListener vsbChangeListener; 57 protected ChangeListener hsbChangeListener; 58 protected ChangeListener viewportChangeListener; 59 protected PropertyChangeListener spPropertyChangeListener; 60 private MouseWheelListener mouseScrollListener; 61 private int oldExtent = Integer.MIN_VALUE; 62 63 /** 64 * PropertyChangeListener installed on the vertical scrollbar. 65 */ 66 private PropertyChangeListener vsbPropertyChangeListener; 67 68 /** 69 * PropertyChangeListener installed on the horizontal scrollbar. 70 */ 71 private PropertyChangeListener hsbPropertyChangeListener; 72 73 private Handler handler; 74 75 /** 76 * State flag that shows whether setValue() was called from a user program 77 * before the value of "extent" was set in right-to-left component 78 * orientation. 79 */ 80 private boolean setValueCalled = false; 81 82 83 public static ComponentUI createUI(JComponent x) { 84 return new BasicScrollPaneUI(); 85 } 86 87 static void loadActionMap(LazyActionMap map) { 88 map.put(new Actions(Actions.SCROLL_UP)); 89 map.put(new Actions(Actions.SCROLL_DOWN)); 90 map.put(new Actions(Actions.SCROLL_HOME)); 91 map.put(new Actions(Actions.SCROLL_END)); 92 map.put(new Actions(Actions.UNIT_SCROLL_UP)); 93 map.put(new Actions(Actions.UNIT_SCROLL_DOWN)); 94 map.put(new Actions(Actions.SCROLL_LEFT)); 95 map.put(new Actions(Actions.SCROLL_RIGHT)); 96 map.put(new Actions(Actions.UNIT_SCROLL_RIGHT)); 97 map.put(new Actions(Actions.UNIT_SCROLL_LEFT)); 98 } 99 100 101 102 public void paint(Graphics g, JComponent c) { 103 Border vpBorder = scrollpane.getViewportBorder(); 104 if (vpBorder != null) { 105 Rectangle r = scrollpane.getViewportBorderBounds(); 106 vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height); 107 } 108 } 109 110 111 /** 112 * @return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE) 113 */ 114 public Dimension getMaximumSize(JComponent c) { 115 return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); 116 } 117 118 119 protected void installDefaults(JScrollPane scrollpane) 120 { 121 LookAndFeel.installBorder(scrollpane, "ScrollPane.border"); 122 LookAndFeel.installColorsAndFont(scrollpane, 123 "ScrollPane.background", 124 "ScrollPane.foreground", 125 "ScrollPane.font"); 126 127 Border vpBorder = scrollpane.getViewportBorder(); 128 if ((vpBorder == null) ||( vpBorder instanceof UIResource)) { 129 vpBorder = UIManager.getBorder("ScrollPane.viewportBorder"); 130 scrollpane.setViewportBorder(vpBorder); 131 } 132 LookAndFeel.installProperty(scrollpane, "opaque", Boolean.TRUE); 133 } 134 135 136 protected void installListeners(JScrollPane c) 137 { 138 vsbChangeListener = createVSBChangeListener(); 139 vsbPropertyChangeListener = createVSBPropertyChangeListener(); 140 hsbChangeListener = createHSBChangeListener(); 141 hsbPropertyChangeListener = createHSBPropertyChangeListener(); 142 viewportChangeListener = createViewportChangeListener(); 143 spPropertyChangeListener = createPropertyChangeListener(); 144 145 JViewport viewport = scrollpane.getViewport(); 146 JScrollBar vsb = scrollpane.getVerticalScrollBar(); 147 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 148 149 if (viewport != null) { 150 viewport.addChangeListener(viewportChangeListener); 151 } 152 if (vsb != null) { 153 vsb.getModel().addChangeListener(vsbChangeListener); 154 vsb.addPropertyChangeListener(vsbPropertyChangeListener); 155 } 156 if (hsb != null) { 157 hsb.getModel().addChangeListener(hsbChangeListener); 158 hsb.addPropertyChangeListener(hsbPropertyChangeListener); 159 } 160 161 scrollpane.addPropertyChangeListener(spPropertyChangeListener); 162 163 mouseScrollListener = createMouseWheelListener(); 164 scrollpane.addMouseWheelListener(mouseScrollListener); 165 166 } 167 168 protected void installKeyboardActions(JScrollPane c) { 169 InputMap inputMap = getInputMap(JComponent. 170 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 171 172 SwingUtilities.replaceUIInputMap(c, JComponent. 173 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap); 174 175 LazyActionMap.installLazyActionMap(c, BasicScrollPaneUI.class, 176 "ScrollPane.actionMap"); 177 } 178 179 InputMap getInputMap(int condition) { 180 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 181 InputMap keyMap = (InputMap)DefaultLookup.get(scrollpane, this, 182 "ScrollPane.ancestorInputMap"); 183 InputMap rtlKeyMap; 184 185 if (scrollpane.getComponentOrientation().isLeftToRight() || 186 ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollpane, this, 187 "ScrollPane.ancestorInputMap.RightToLeft")) == null)) { 188 return keyMap; 189 } else { 190 rtlKeyMap.setParent(keyMap); 191 return rtlKeyMap; 192 } 193 } 194 return null; 195 } 196 197 public void installUI(JComponent x) { 198 scrollpane = (JScrollPane)x; 199 installDefaults(scrollpane); 200 installListeners(scrollpane); 201 installKeyboardActions(scrollpane); 202 } 203 204 205 protected void uninstallDefaults(JScrollPane c) { 206 LookAndFeel.uninstallBorder(scrollpane); 207 208 if (scrollpane.getViewportBorder() instanceof UIResource) { 209 scrollpane.setViewportBorder(null); 210 } 211 } 212 213 214 protected void uninstallListeners(JComponent c) { 215 JViewport viewport = scrollpane.getViewport(); 216 JScrollBar vsb = scrollpane.getVerticalScrollBar(); 217 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 218 219 if (viewport != null) { 220 viewport.removeChangeListener(viewportChangeListener); 221 } 222 if (vsb != null) { 223 vsb.getModel().removeChangeListener(vsbChangeListener); 224 vsb.removePropertyChangeListener(vsbPropertyChangeListener); 225 } 226 if (hsb != null) { 227 hsb.getModel().removeChangeListener(hsbChangeListener); 228 hsb.removePropertyChangeListener(hsbPropertyChangeListener); 229 } 230 231 scrollpane.removePropertyChangeListener(spPropertyChangeListener); 232 233 if (mouseScrollListener != null) { 234 scrollpane.removeMouseWheelListener(mouseScrollListener); 235 } 236 237 vsbChangeListener = null; 238 hsbChangeListener = null; 239 viewportChangeListener = null; 240 spPropertyChangeListener = null; 241 mouseScrollListener = null; 242 handler = null; 243 } 244 245 246 protected void uninstallKeyboardActions(JScrollPane c) { 247 SwingUtilities.replaceUIActionMap(c, null); 248 SwingUtilities.replaceUIInputMap(c, JComponent. 249 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 250 } 251 252 253 public void uninstallUI(JComponent c) { 254 uninstallDefaults(scrollpane); 255 uninstallListeners(scrollpane); 256 uninstallKeyboardActions(scrollpane); 257 scrollpane = null; 258 } 259 260 private Handler getHandler() { 261 if (handler == null) { 262 handler = new Handler(); 263 } 264 return handler; 265 } 266 267 protected void syncScrollPaneWithViewport() 268 { 269 JViewport viewport = scrollpane.getViewport(); 270 JScrollBar vsb = scrollpane.getVerticalScrollBar(); 271 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 272 JViewport rowHead = scrollpane.getRowHeader(); 273 JViewport colHead = scrollpane.getColumnHeader(); 274 boolean ltr = scrollpane.getComponentOrientation().isLeftToRight(); 275 276 if (viewport != null) { 277 Dimension extentSize = viewport.getExtentSize(); 278 Dimension viewSize = viewport.getViewSize(); 279 Point viewPosition = viewport.getViewPosition(); 280 281 if (vsb != null) { 282 int extent = extentSize.height; 283 int max = viewSize.height; 284 int value = Math.max(0, Math.min(viewPosition.y, max - extent)); 285 vsb.setValues(value, extent, 0, max); 286 } 287 288 if (hsb != null) { 289 int extent = extentSize.width; 290 int max = viewSize.width; 291 int value; 292 293 if (ltr) { 294 value = Math.max(0, Math.min(viewPosition.x, max - extent)); 295 } else { 296 int currentValue = hsb.getValue(); 297 298 /* Use a particular formula to calculate "value" 299 * until effective x coordinate is calculated. 300 */ 301 if (setValueCalled && ((max - currentValue) == viewPosition.x)) { 302 value = Math.max(0, Math.min(max - extent, currentValue)); 303 /* After "extent" is set, turn setValueCalled flag off. 304 */ 305 if (extent != 0) { 306 setValueCalled = false; 307 } 308 } else { 309 if (extent > max) { 310 viewPosition.x = max - extent; 311 viewport.setViewPosition(viewPosition); 312 value = 0; 313 } else { 314 /* The following line can't handle a small value of 315 * viewPosition.x like Integer.MIN_VALUE correctly 316 * because (max - extent - viewPositoiin.x) causes 317 * an overflow. As a result, value becomes zero. 318 * (e.g. setViewPosition(Integer.MAX_VALUE, ...) 319 * in a user program causes a overflow. 320 * Its expected value is (max - extent).) 321 * However, this seems a trivial bug and adding a 322 * fix makes this often-called method slow, so I'll 323 * leave it until someone claims. 324 */ 325 value = Math.max(0, Math.min(max - extent, max - extent - viewPosition.x)); 326 if (oldExtent > extent) { 327 value -= oldExtent - extent; 328 } 329 } 330 } 331 } 332 oldExtent = extent; 333 hsb.setValues(value, extent, 0, max); 334 } 335 336 if (rowHead != null) { 337 Point p = rowHead.getViewPosition(); 338 p.y = viewport.getViewPosition().y; 339 p.x = 0; 340 rowHead.setViewPosition(p); 341 } 342 343 if (colHead != null) { 344 Point p = colHead.getViewPosition(); 345 if (ltr) { 346 p.x = viewport.getViewPosition().x; 347 } else { 348 p.x = Math.max(0, viewport.getViewPosition().x); 349 } 350 p.y = 0; 351 colHead.setViewPosition(p); 352 } 353 } 354 } 355 356 /** 357 * Returns the baseline. 358 * 359 * @throws NullPointerException {@inheritDoc} 360 * @throws IllegalArgumentException {@inheritDoc} 361 * @see javax.swing.JComponent#getBaseline(int, int) 362 * @since 1.6 363 */ 364 public int getBaseline(JComponent c, int width, int height) { 365 if (c == null) { 366 throw new NullPointerException("Component must be non-null"); 367 } 368 369 if (width < 0 || height < 0) { 370 throw new IllegalArgumentException("Width and height must be >= 0"); 371 } 372 373 JViewport viewport = scrollpane.getViewport(); 374 Insets spInsets = scrollpane.getInsets(); 375 int y = spInsets.top; 376 height = height - spInsets.top - spInsets.bottom; 377 width = width - spInsets.left - spInsets.right; 378 JViewport columnHeader = scrollpane.getColumnHeader(); 379 if (columnHeader != null && columnHeader.isVisible()) { 380 Component header = columnHeader.getView(); 381 if (header != null && header.isVisible()) { 382 // Header is always given it's preferred size. 383 Dimension headerPref = header.getPreferredSize(); 384 int baseline = header.getBaseline(headerPref.width, 385 headerPref.height); 386 if (baseline >= 0) { 387 return y + baseline; 388 } 389 } 390 Dimension columnPref = columnHeader.getPreferredSize(); 391 height -= columnPref.height; 392 y += columnPref.height; 393 } 394 Component view = (viewport == null) ? null : viewport.getView(); 395 if (view != null && view.isVisible() && 396 view.getBaselineResizeBehavior() == 397 Component.BaselineResizeBehavior.CONSTANT_ASCENT) { 398 Border viewportBorder = scrollpane.getViewportBorder(); 399 if (viewportBorder != null) { 400 Insets vpbInsets = viewportBorder.getBorderInsets(scrollpane); 401 y += vpbInsets.top; 402 height = height - vpbInsets.top - vpbInsets.bottom; 403 width = width - vpbInsets.left - vpbInsets.right; 404 } 405 if (view.getWidth() > 0 && view.getHeight() > 0) { 406 Dimension min = view.getMinimumSize(); 407 width = Math.max(min.width, view.getWidth()); 408 height = Math.max(min.height, view.getHeight()); 409 } 410 if (width > 0 && height > 0) { 411 int baseline = view.getBaseline(width, height); 412 if (baseline > 0) { 413 return y + baseline; 414 } 415 } 416 } 417 return -1; 418 } 419 420 /** 421 * Returns an enum indicating how the baseline of the component 422 * changes as the size changes. 423 * 424 * @throws NullPointerException {@inheritDoc} 425 * @see javax.swing.JComponent#getBaseline(int, int) 426 * @since 1.6 427 */ 428 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 429 JComponent c) { 430 super.getBaselineResizeBehavior(c); 431 // Baseline is either from the header, in which case it's always 432 // the same size and therefor can be created as CONSTANT_ASCENT. 433 // If the header doesn't have a baseline than the baseline will only 434 // be valid if it's BaselineResizeBehavior is 435 // CONSTANT_ASCENT, so, return CONSTANT_ASCENT. 436 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 437 } 438 439 440 /** 441 * Listener for viewport events. 442 */ 443 public class ViewportChangeHandler implements ChangeListener 444 { 445 446 // NOTE: This class exists only for backward compatability. All 447 // its functionality has been moved into Handler. If you need to add 448 // new functionality add it to the Handler, but make sure this 449 // class calls into the Handler. 450 451 public void stateChanged(ChangeEvent e) { 452 getHandler().stateChanged(e); 453 } 454 } 455 456 protected ChangeListener createViewportChangeListener() { 457 return getHandler(); 458 } 459 460 461 /** 462 * Horizontal scrollbar listener. 463 */ 464 public class HSBChangeListener implements ChangeListener 465 { 466 467 // NOTE: This class exists only for backward compatability. All 468 // its functionality has been moved into Handler. If you need to add 469 // new functionality add it to the Handler, but make sure this 470 // class calls into the Handler. 471 472 public void stateChanged(ChangeEvent e) 473 { 474 getHandler().stateChanged(e); 475 } 476 } 477 478 /** 479 * Returns a <code>PropertyChangeListener</code> that will be installed 480 * on the horizontal <code>JScrollBar</code>. 481 */ 482 private PropertyChangeListener createHSBPropertyChangeListener() { 483 return getHandler(); 484 } 485 486 protected ChangeListener createHSBChangeListener() { 487 return getHandler(); 488 } 489 490 491 /** 492 * Vertical scrollbar listener. 493 */ 494 public class VSBChangeListener implements ChangeListener 495 { 496 497 // NOTE: This class exists only for backward compatability. All 498 // its functionality has been moved into Handler. If you need to add 499 // new functionality add it to the Handler, but make sure this 500 // class calls into the Handler. 501 502 public void stateChanged(ChangeEvent e) 503 { 504 getHandler().stateChanged(e); 505 } 506 } 507 508 509 /** 510 * Returns a <code>PropertyChangeListener</code> that will be installed 511 * on the vertical <code>JScrollBar</code>. 512 */ 513 private PropertyChangeListener createVSBPropertyChangeListener() { 514 return getHandler(); 515 } 516 517 protected ChangeListener createVSBChangeListener() { 518 return getHandler(); 519 } 520 521 /** 522 * MouseWheelHandler is an inner class which implements the 523 * MouseWheelListener interface. MouseWheelHandler responds to 524 * MouseWheelEvents by scrolling the JScrollPane appropriately. 525 * If the scroll pane's 526 * <code>isWheelScrollingEnabled</code> 527 * method returns false, no scrolling occurs. 528 * 529 * @see javax.swing.JScrollPane#isWheelScrollingEnabled 530 * @see #createMouseWheelListener 531 * @see java.awt.event.MouseWheelListener 532 * @see java.awt.event.MouseWheelEvent 533 * @since 1.4 534 */ 535 protected class MouseWheelHandler implements MouseWheelListener { 536 537 // NOTE: This class exists only for backward compatability. All 538 // its functionality has been moved into Handler. If you need to add 539 // new functionality add it to the Handler, but make sure this 540 // class calls into the Handler. 541 542 /** 543 * Called when the mouse wheel is rotated while over a 544 * JScrollPane. 545 * 546 * @param e MouseWheelEvent to be handled 547 * @since 1.4 548 */ 549 public void mouseWheelMoved(MouseWheelEvent e) { 550 getHandler().mouseWheelMoved(e); 551 } 552 } 553 554 /** 555 * Creates an instance of MouseWheelListener, which is added to the 556 * JScrollPane by installUI(). The returned MouseWheelListener is used 557 * to handle mouse wheel-driven scrolling. 558 * 559 * @return MouseWheelListener which implements wheel-driven scrolling 560 * @see #installUI 561 * @see MouseWheelHandler 562 * @since 1.4 563 */ 564 protected MouseWheelListener createMouseWheelListener() { 565 return getHandler(); 566 } 567 568 protected void updateScrollBarDisplayPolicy(PropertyChangeEvent e) { 569 scrollpane.revalidate(); 570 scrollpane.repaint(); 571 } 572 573 574 protected void updateViewport(PropertyChangeEvent e) 575 { 576 JViewport oldViewport = (JViewport)(e.getOldValue()); 577 JViewport newViewport = (JViewport)(e.getNewValue()); 578 579 if (oldViewport != null) { 580 oldViewport.removeChangeListener(viewportChangeListener); 581 } 582 583 if (newViewport != null) { 584 Point p = newViewport.getViewPosition(); 585 if (scrollpane.getComponentOrientation().isLeftToRight()) { 586 p.x = Math.max(p.x, 0); 587 } else { 588 int max = newViewport.getViewSize().width; 589 int extent = newViewport.getExtentSize().width; 590 if (extent > max) { 591 p.x = max - extent; 592 } else { 593 p.x = Math.max(0, Math.min(max - extent, p.x)); 594 } 595 } 596 p.y = Math.max(p.y, 0); 597 newViewport.setViewPosition(p); 598 newViewport.addChangeListener(viewportChangeListener); 599 } 600 } 601 602 603 protected void updateRowHeader(PropertyChangeEvent e) 604 { 605 JViewport newRowHead = (JViewport)(e.getNewValue()); 606 if (newRowHead != null) { 607 JViewport viewport = scrollpane.getViewport(); 608 Point p = newRowHead.getViewPosition(); 609 p.y = (viewport != null) ? viewport.getViewPosition().y : 0; 610 newRowHead.setViewPosition(p); 611 } 612 } 613 614 615 protected void updateColumnHeader(PropertyChangeEvent e) 616 { 617 JViewport newColHead = (JViewport)(e.getNewValue()); 618 if (newColHead != null) { 619 JViewport viewport = scrollpane.getViewport(); 620 Point p = newColHead.getViewPosition(); 621 if (viewport == null) { 622 p.x = 0; 623 } else { 624 if (scrollpane.getComponentOrientation().isLeftToRight()) { 625 p.x = viewport.getViewPosition().x; 626 } else { 627 p.x = Math.max(0, viewport.getViewPosition().x); 628 } 629 } 630 newColHead.setViewPosition(p); 631 scrollpane.add(newColHead, COLUMN_HEADER); 632 } 633 } 634 635 private void updateHorizontalScrollBar(PropertyChangeEvent pce) { 636 updateScrollBar(pce, hsbChangeListener, hsbPropertyChangeListener); 637 } 638 639 private void updateVerticalScrollBar(PropertyChangeEvent pce) { 640 updateScrollBar(pce, vsbChangeListener, vsbPropertyChangeListener); 641 } 642 643 private void updateScrollBar(PropertyChangeEvent pce, ChangeListener cl, 644 PropertyChangeListener pcl) { 645 JScrollBar sb = (JScrollBar)pce.getOldValue(); 646 if (sb != null) { 647 if (cl != null) { 648 sb.getModel().removeChangeListener(cl); 649 } 650 if (pcl != null) { 651 sb.removePropertyChangeListener(pcl); 652 } 653 } 654 sb = (JScrollBar)pce.getNewValue(); 655 if (sb != null) { 656 if (cl != null) { 657 sb.getModel().addChangeListener(cl); 658 } 659 if (pcl != null) { 660 sb.addPropertyChangeListener(pcl); 661 } 662 } 663 } 664 665 public class PropertyChangeHandler implements PropertyChangeListener 666 { 667 668 // NOTE: This class exists only for backward compatability. All 669 // its functionality has been moved into Handler. If you need to add 670 // new functionality add it to the Handler, but make sure this 671 // class calls into the Handler. 672 673 public void propertyChange(PropertyChangeEvent e) 674 { 675 getHandler().propertyChange(e); 676 } 677 } 678 679 680 681 /** 682 * Creates an instance of PropertyChangeListener that's added to 683 * the JScrollPane by installUI(). Subclasses can override this method 684 * to return a custom PropertyChangeListener, e.g. 685 * <pre> 686 * class MyScrollPaneUI extends BasicScrollPaneUI { 687 * protected PropertyChangeListener <b>createPropertyChangeListener</b>() { 688 * return new MyPropertyChangeListener(); 689 * } 690 * public class MyPropertyChangeListener extends PropertyChangeListener { 691 * public void propertyChange(PropertyChangeEvent e) { 692 * if (e.getPropertyName().equals("viewport")) { 693 * // do some extra work when the viewport changes 694 * } 695 * super.propertyChange(e); 696 * } 697 * } 698 * } 699 * </pre> 700 * 701 * @see java.beans.PropertyChangeListener 702 * @see #installUI 703 */ 704 protected PropertyChangeListener createPropertyChangeListener() { 705 return getHandler(); 706 } 707 708 709 private static class Actions extends UIAction { 710 private static final String SCROLL_UP = "scrollUp"; 711 private static final String SCROLL_DOWN = "scrollDown"; 712 private static final String SCROLL_HOME = "scrollHome"; 713 private static final String SCROLL_END = "scrollEnd"; 714 private static final String UNIT_SCROLL_UP = "unitScrollUp"; 715 private static final String UNIT_SCROLL_DOWN = "unitScrollDown"; 716 private static final String SCROLL_LEFT = "scrollLeft"; 717 private static final String SCROLL_RIGHT = "scrollRight"; 718 private static final String UNIT_SCROLL_LEFT = "unitScrollLeft"; 719 private static final String UNIT_SCROLL_RIGHT = "unitScrollRight"; 720 721 722 Actions(String key) { 723 super(key); 724 } 725 726 public void actionPerformed(ActionEvent e) { 727 JScrollPane scrollPane = (JScrollPane)e.getSource(); 728 boolean ltr = scrollPane.getComponentOrientation().isLeftToRight(); 729 String key = getName(); 730 731 if (key == SCROLL_UP) { 732 scroll(scrollPane, SwingConstants.VERTICAL, -1, true); 733 } 734 else if (key == SCROLL_DOWN) { 735 scroll(scrollPane, SwingConstants.VERTICAL, 1, true); 736 } 737 else if (key == SCROLL_HOME) { 738 scrollHome(scrollPane); 739 } 740 else if (key == SCROLL_END) { 741 scrollEnd(scrollPane); 742 } 743 else if (key == UNIT_SCROLL_UP) { 744 scroll(scrollPane, SwingConstants.VERTICAL, -1, false); 745 } 746 else if (key == UNIT_SCROLL_DOWN) { 747 scroll(scrollPane, SwingConstants.VERTICAL, 1, false); 748 } 749 else if (key == SCROLL_LEFT) { 750 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1, 751 true); 752 } 753 else if (key == SCROLL_RIGHT) { 754 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1, 755 true); 756 } 757 else if (key == UNIT_SCROLL_LEFT) { 758 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1, 759 false); 760 } 761 else if (key == UNIT_SCROLL_RIGHT) { 762 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1, 763 false); 764 } 765 } 766 767 private void scrollEnd(JScrollPane scrollpane) { 768 JViewport vp = scrollpane.getViewport(); 769 Component view; 770 if (vp != null && (view = vp.getView()) != null) { 771 Rectangle visRect = vp.getViewRect(); 772 Rectangle bounds = view.getBounds(); 773 if (scrollpane.getComponentOrientation().isLeftToRight()) { 774 vp.setViewPosition(new Point(bounds.width - visRect.width, 775 bounds.height - visRect.height)); 776 } else { 777 vp.setViewPosition(new Point(0, 778 bounds.height - visRect.height)); 779 } 780 } 781 } 782 783 private void scrollHome(JScrollPane scrollpane) { 784 JViewport vp = scrollpane.getViewport(); 785 Component view; 786 if (vp != null && (view = vp.getView()) != null) { 787 if (scrollpane.getComponentOrientation().isLeftToRight()) { 788 vp.setViewPosition(new Point(0, 0)); 789 } else { 790 Rectangle visRect = vp.getViewRect(); 791 Rectangle bounds = view.getBounds(); 792 vp.setViewPosition(new Point(bounds.width - visRect.width, 0)); 793 } 794 } 795 } 796 797 private void scroll(JScrollPane scrollpane, int orientation, 798 int direction, boolean block) { 799 JViewport vp = scrollpane.getViewport(); 800 Component view; 801 if (vp != null && (view = vp.getView()) != null) { 802 Rectangle visRect = vp.getViewRect(); 803 Dimension vSize = view.getSize(); 804 int amount; 805 806 if (view instanceof Scrollable) { 807 if (block) { 808 amount = ((Scrollable)view).getScrollableBlockIncrement 809 (visRect, orientation, direction); 810 } 811 else { 812 amount = ((Scrollable)view).getScrollableUnitIncrement 813 (visRect, orientation, direction); 814 } 815 } 816 else { 817 if (block) { 818 if (orientation == SwingConstants.VERTICAL) { 819 amount = visRect.height; 820 } 821 else { 822 amount = visRect.width; 823 } 824 } 825 else { 826 amount = 10; 827 } 828 } 829 if (orientation == SwingConstants.VERTICAL) { 830 visRect.y += (amount * direction); 831 if ((visRect.y + visRect.height) > vSize.height) { 832 visRect.y = Math.max(0, vSize.height - visRect.height); 833 } 834 else if (visRect.y < 0) { 835 visRect.y = 0; 836 } 837 } 838 else { 839 if (scrollpane.getComponentOrientation().isLeftToRight()) { 840 visRect.x += (amount * direction); 841 if ((visRect.x + visRect.width) > vSize.width) { 842 visRect.x = Math.max(0, vSize.width - visRect.width); 843 } else if (visRect.x < 0) { 844 visRect.x = 0; 845 } 846 } else { 847 visRect.x -= (amount * direction); 848 if (visRect.width > vSize.width) { 849 visRect.x = vSize.width - visRect.width; 850 } else { 851 visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x)); 852 } 853 } 854 } 855 vp.setViewPosition(visRect.getLocation()); 856 } 857 } 858 } 859 860 861 class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener { 862 // 863 // MouseWheelListener 864 // 865 public void mouseWheelMoved(MouseWheelEvent e) { 866 if (scrollpane.isWheelScrollingEnabled() && 867 e.getWheelRotation() != 0) { 868 JScrollBar toScroll = scrollpane.getVerticalScrollBar(); 869 int direction = e.getWheelRotation() < 0 ? -1 : 1; 870 int orientation = SwingConstants.VERTICAL; 871 872 // find which scrollbar to scroll, or return if none 873 if (toScroll == null || !toScroll.isVisible()) { 874 toScroll = scrollpane.getHorizontalScrollBar(); 875 if (toScroll == null || !toScroll.isVisible()) { 876 return; 877 } 878 orientation = SwingConstants.HORIZONTAL; 879 } 880 881 e.consume(); 882 883 if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { 884 JViewport vp = scrollpane.getViewport(); 885 if (vp == null) { return; } 886 Component comp = vp.getView(); 887 int units = Math.abs(e.getUnitsToScroll()); 888 889 // When the scrolling speed is set to maximum, it's possible 890 // for a single wheel click to scroll by more units than 891 // will fit in the visible area. This makes it 892 // hard/impossible to get to certain parts of the scrolling 893 // Component with the wheel. To make for more accurate 894 // low-speed scrolling, we limit scrolling to the block 895 // increment if the wheel was only rotated one click. 896 boolean limitScroll = Math.abs(e.getWheelRotation()) == 1; 897 898 // Check if we should use the visibleRect trick 899 Object fastWheelScroll = toScroll.getClientProperty( 900 "JScrollBar.fastWheelScrolling"); 901 if (Boolean.TRUE == fastWheelScroll && 902 comp instanceof Scrollable) { 903 // 5078454: Under maximum acceleration, we may scroll 904 // by many 100s of units in ~1 second. 905 // 906 // BasicScrollBarUI.scrollByUnits() can bog down the EDT 907 // with repaints in this situation. However, the 908 // Scrollable interface allows us to pass in an 909 // arbitrary visibleRect. This allows us to accurately 910 // calculate the total scroll amount, and then update 911 // the GUI once. This technique provides much faster 912 // accelerated wheel scrolling. 913 Scrollable scrollComp = (Scrollable) comp; 914 Rectangle viewRect = vp.getViewRect(); 915 int startingX = viewRect.x; 916 boolean leftToRight = 917 comp.getComponentOrientation().isLeftToRight(); 918 int scrollMin = toScroll.getMinimum(); 919 int scrollMax = toScroll.getMaximum() - 920 toScroll.getModel().getExtent(); 921 922 if (limitScroll) { 923 int blockIncr = 924 scrollComp.getScrollableBlockIncrement(viewRect, 925 orientation, 926 direction); 927 if (direction < 0) { 928 scrollMin = Math.max(scrollMin, 929 toScroll.getValue() - blockIncr); 930 } 931 else { 932 scrollMax = Math.min(scrollMax, 933 toScroll.getValue() + blockIncr); 934 } 935 } 936 937 for (int i = 0; i < units; i++) { 938 int unitIncr = 939 scrollComp.getScrollableUnitIncrement(viewRect, 940 orientation, direction); 941 // Modify the visible rect for the next unit, and 942 // check to see if we're at the end already. 943 if (orientation == SwingConstants.VERTICAL) { 944 if (direction < 0) { 945 viewRect.y -= unitIncr; 946 if (viewRect.y <= scrollMin) { 947 viewRect.y = scrollMin; 948 break; 949 } 950 } 951 else { // (direction > 0 952 viewRect.y += unitIncr; 953 if (viewRect.y >= scrollMax) { 954 viewRect.y = scrollMax; 955 break; 956 } 957 } 958 } 959 else { 960 // Scroll left 961 if ((leftToRight && direction < 0) || 962 (!leftToRight && direction > 0)) { 963 viewRect.x -= unitIncr; 964 if (leftToRight) { 965 if (viewRect.x < scrollMin) { 966 viewRect.x = scrollMin; 967 break; 968 } 969 } 970 } 971 // Scroll right 972 else if ((leftToRight && direction > 0) || 973 (!leftToRight && direction < 0)) { 974 viewRect.x += unitIncr; 975 if (leftToRight) { 976 if (viewRect.x > scrollMax) { 977 viewRect.x = scrollMax; 978 break; 979 } 980 } 981 } 982 else { 983 assert false : "Non-sensical ComponentOrientation / scroll direction"; 984 } 985 } 986 } 987 // Set the final view position on the ScrollBar 988 if (orientation == SwingConstants.VERTICAL) { 989 toScroll.setValue(viewRect.y); 990 } 991 else { 992 if (leftToRight) { 993 toScroll.setValue(viewRect.x); 994 } 995 else { 996 // rightToLeft scrollbars are oriented with 997 // minValue on the right and maxValue on the 998 // left. 999 int newPos = toScroll.getValue() - 1000 (viewRect.x - startingX); 1001 if (newPos < scrollMin) { 1002 newPos = scrollMin; 1003 } 1004 else if (newPos > scrollMax) { 1005 newPos = scrollMax; 1006 } 1007 toScroll.setValue(newPos); 1008 } 1009 } 1010 } 1011 else { 1012 // Viewport's view is not a Scrollable, or fast wheel 1013 // scrolling is not enabled. 1014 BasicScrollBarUI.scrollByUnits(toScroll, direction, 1015 units, limitScroll); 1016 } 1017 } 1018 else if (e.getScrollType() == 1019 MouseWheelEvent.WHEEL_BLOCK_SCROLL) { 1020 BasicScrollBarUI.scrollByBlock(toScroll, direction); 1021 } 1022 } 1023 } 1024 1025 // 1026 // ChangeListener: This is added to the vieport, and hsb/vsb models. 1027 // 1028 public void stateChanged(ChangeEvent e) { 1029 JViewport viewport = scrollpane.getViewport(); 1030 1031 if (viewport != null) { 1032 if (e.getSource() == viewport) { 1033 syncScrollPaneWithViewport(); 1034 } 1035 else { 1036 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 1037 if (hsb != null && e.getSource() == hsb.getModel()) { 1038 hsbStateChanged(viewport, e); 1039 } 1040 else { 1041 JScrollBar vsb = scrollpane.getVerticalScrollBar(); 1042 if (vsb != null && e.getSource() == vsb.getModel()) { 1043 vsbStateChanged(viewport, e); 1044 } 1045 } 1046 } 1047 } 1048 } 1049 1050 private void vsbStateChanged(JViewport viewport, ChangeEvent e) { 1051 BoundedRangeModel model = (BoundedRangeModel)(e.getSource()); 1052 Point p = viewport.getViewPosition(); 1053 p.y = model.getValue(); 1054 viewport.setViewPosition(p); 1055 } 1056 1057 private void hsbStateChanged(JViewport viewport, ChangeEvent e) { 1058 BoundedRangeModel model = (BoundedRangeModel)(e.getSource()); 1059 Point p = viewport.getViewPosition(); 1060 int value = model.getValue(); 1061 if (scrollpane.getComponentOrientation().isLeftToRight()) { 1062 p.x = value; 1063 } else { 1064 int max = viewport.getViewSize().width; 1065 int extent = viewport.getExtentSize().width; 1066 int oldX = p.x; 1067 1068 /* Set new X coordinate based on "value". 1069 */ 1070 p.x = max - extent - value; 1071 1072 /* If setValue() was called before "extent" was fixed, 1073 * turn setValueCalled flag on. 1074 */ 1075 if ((extent == 0) && (value != 0) && (oldX == max)) { 1076 setValueCalled = true; 1077 } else { 1078 /* When a pane without a horizontal scroll bar was 1079 * reduced and the bar appeared, the viewport should 1080 * show the right side of the view. 1081 */ 1082 if ((extent != 0) && (oldX < 0) && (p.x == 0)) { 1083 p.x += value; 1084 } 1085 } 1086 } 1087 viewport.setViewPosition(p); 1088 } 1089 1090 // 1091 // PropertyChangeListener: This is installed on both the JScrollPane 1092 // and the horizontal/vertical scrollbars. 1093 // 1094 1095 // Listens for changes in the model property and reinstalls the 1096 // horizontal/vertical PropertyChangeListeners. 1097 public void propertyChange(PropertyChangeEvent e) { 1098 if (e.getSource() == scrollpane) { 1099 scrollPanePropertyChange(e); 1100 } 1101 else { 1102 sbPropertyChange(e); 1103 } 1104 } 1105 1106 private void scrollPanePropertyChange(PropertyChangeEvent e) { 1107 String propertyName = e.getPropertyName(); 1108 1109 if (propertyName == "verticalScrollBarDisplayPolicy") { 1110 updateScrollBarDisplayPolicy(e); 1111 } 1112 else if (propertyName == "horizontalScrollBarDisplayPolicy") { 1113 updateScrollBarDisplayPolicy(e); 1114 } 1115 else if (propertyName == "viewport") { 1116 updateViewport(e); 1117 } 1118 else if (propertyName == "rowHeader") { 1119 updateRowHeader(e); 1120 } 1121 else if (propertyName == "columnHeader") { 1122 updateColumnHeader(e); 1123 } 1124 else if (propertyName == "verticalScrollBar") { 1125 updateVerticalScrollBar(e); 1126 } 1127 else if (propertyName == "horizontalScrollBar") { 1128 updateHorizontalScrollBar(e); 1129 } 1130 else if (propertyName == "componentOrientation") { 1131 scrollpane.revalidate(); 1132 scrollpane.repaint(); 1133 } 1134 } 1135 1136 // PropertyChangeListener for the horizontal and vertical scrollbars. 1137 private void sbPropertyChange(PropertyChangeEvent e) { 1138 String propertyName = e.getPropertyName(); 1139 Object source = e.getSource(); 1140 1141 if ("model" == propertyName) { 1142 JScrollBar sb = scrollpane.getVerticalScrollBar(); 1143 BoundedRangeModel oldModel = (BoundedRangeModel)e. 1144 getOldValue(); 1145 ChangeListener cl = null; 1146 1147 if (source == sb) { 1148 cl = vsbChangeListener; 1149 } 1150 else if (source == scrollpane.getHorizontalScrollBar()) { 1151 sb = scrollpane.getHorizontalScrollBar(); 1152 cl = hsbChangeListener; 1153 } 1154 if (cl != null) { 1155 if (oldModel != null) { 1156 oldModel.removeChangeListener(cl); 1157 } 1158 if (sb.getModel() != null) { 1159 sb.getModel().addChangeListener(cl); 1160 } 1161 } 1162 } 1163 else if ("componentOrientation" == propertyName) { 1164 if (source == scrollpane.getHorizontalScrollBar()) { 1165 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 1166 JViewport viewport = scrollpane.getViewport(); 1167 Point p = viewport.getViewPosition(); 1168 if (scrollpane.getComponentOrientation().isLeftToRight()) { 1169 p.x = hsb.getValue(); 1170 } else { 1171 p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue(); 1172 } 1173 viewport.setViewPosition(p); 1174 } 1175 } 1176 } 1177 } 1178 }