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