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 /** 750 * Property change handler. 751 */ 752 public class PropertyChangeHandler implements PropertyChangeListener 753 { 754 755 // NOTE: This class exists only for backward compatibility. All 756 // its functionality has been moved into Handler. If you need to add 757 // new functionality add it to the Handler, but make sure this 758 // class calls into the Handler. 759 760 /** 761 * {@inheritDoc} 762 */ 763 public void propertyChange(PropertyChangeEvent e) 764 { 765 getHandler().propertyChange(e); 766 } 767 } 768 769 770 771 /** 772 * Creates an instance of {@code PropertyChangeListener} that's added to 773 * the {@code JScrollPane} by {@code installUI()}. Subclasses can override 774 * this method to return a custom {@code PropertyChangeListener}, e.g. 775 * <pre> 776 * class MyScrollPaneUI extends BasicScrollPaneUI { 777 * protected PropertyChangeListener <b>createPropertyChangeListener</b>() { 778 * return new MyPropertyChangeListener(); 779 * } 780 * public class MyPropertyChangeListener extends PropertyChangeListener { 781 * public void propertyChange(PropertyChangeEvent e) { 782 * if (e.getPropertyName().equals("viewport")) { 783 * // do some extra work when the viewport changes 784 * } 785 * super.propertyChange(e); 786 * } 787 * } 788 * } 789 * </pre> 790 * 791 * @return an instance of {@code PropertyChangeListener} 792 * 793 * @see java.beans.PropertyChangeListener 794 * @see #installUI 795 */ 796 protected PropertyChangeListener createPropertyChangeListener() { 797 return getHandler(); 798 } 799 800 801 private static class Actions extends UIAction { 802 private static final String SCROLL_UP = "scrollUp"; 803 private static final String SCROLL_DOWN = "scrollDown"; 804 private static final String SCROLL_HOME = "scrollHome"; 805 private static final String SCROLL_END = "scrollEnd"; 806 private static final String UNIT_SCROLL_UP = "unitScrollUp"; 807 private static final String UNIT_SCROLL_DOWN = "unitScrollDown"; 808 private static final String SCROLL_LEFT = "scrollLeft"; 809 private static final String SCROLL_RIGHT = "scrollRight"; 810 private static final String UNIT_SCROLL_LEFT = "unitScrollLeft"; 811 private static final String UNIT_SCROLL_RIGHT = "unitScrollRight"; 812 813 814 Actions(String key) { 815 super(key); 816 } 817 818 public void actionPerformed(ActionEvent e) { 819 JScrollPane scrollPane = (JScrollPane)e.getSource(); 820 boolean ltr = scrollPane.getComponentOrientation().isLeftToRight(); 821 String key = getName(); 822 823 if (key == SCROLL_UP) { 824 scroll(scrollPane, SwingConstants.VERTICAL, -1, true); 825 } 826 else if (key == SCROLL_DOWN) { 827 scroll(scrollPane, SwingConstants.VERTICAL, 1, true); 828 } 829 else if (key == SCROLL_HOME) { 830 scrollHome(scrollPane); 831 } 832 else if (key == SCROLL_END) { 833 scrollEnd(scrollPane); 834 } 835 else if (key == UNIT_SCROLL_UP) { 836 scroll(scrollPane, SwingConstants.VERTICAL, -1, false); 837 } 838 else if (key == UNIT_SCROLL_DOWN) { 839 scroll(scrollPane, SwingConstants.VERTICAL, 1, false); 840 } 841 else if (key == SCROLL_LEFT) { 842 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1, 843 true); 844 } 845 else if (key == SCROLL_RIGHT) { 846 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1, 847 true); 848 } 849 else if (key == UNIT_SCROLL_LEFT) { 850 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1, 851 false); 852 } 853 else if (key == UNIT_SCROLL_RIGHT) { 854 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1, 855 false); 856 } 857 } 858 859 private void scrollEnd(JScrollPane scrollpane) { 860 JViewport vp = scrollpane.getViewport(); 861 Component view; 862 if (vp != null && (view = vp.getView()) != null) { 863 Rectangle visRect = vp.getViewRect(); 864 Rectangle bounds = view.getBounds(); 865 if (scrollpane.getComponentOrientation().isLeftToRight()) { 866 vp.setViewPosition(new Point(bounds.width - visRect.width, 867 bounds.height - visRect.height)); 868 } else { 869 vp.setViewPosition(new Point(0, 870 bounds.height - visRect.height)); 871 } 872 } 873 } 874 875 private void scrollHome(JScrollPane scrollpane) { 876 JViewport vp = scrollpane.getViewport(); 877 Component view; 878 if (vp != null && (view = vp.getView()) != null) { 879 if (scrollpane.getComponentOrientation().isLeftToRight()) { 880 vp.setViewPosition(new Point(0, 0)); 881 } else { 882 Rectangle visRect = vp.getViewRect(); 883 Rectangle bounds = view.getBounds(); 884 vp.setViewPosition(new Point(bounds.width - visRect.width, 0)); 885 } 886 } 887 } 888 889 private void scroll(JScrollPane scrollpane, int orientation, 890 int direction, boolean block) { 891 JViewport vp = scrollpane.getViewport(); 892 Component view; 893 if (vp != null && (view = vp.getView()) != null) { 894 Rectangle visRect = vp.getViewRect(); 895 Dimension vSize = view.getSize(); 896 int amount; 897 898 if (view instanceof Scrollable) { 899 if (block) { 900 amount = ((Scrollable)view).getScrollableBlockIncrement 901 (visRect, orientation, direction); 902 } 903 else { 904 amount = ((Scrollable)view).getScrollableUnitIncrement 905 (visRect, orientation, direction); 906 } 907 } 908 else { 909 if (block) { 910 if (orientation == SwingConstants.VERTICAL) { 911 amount = visRect.height; 912 } 913 else { 914 amount = visRect.width; 915 } 916 } 917 else { 918 amount = 10; 919 } 920 } 921 if (orientation == SwingConstants.VERTICAL) { 922 visRect.y += (amount * direction); 923 if ((visRect.y + visRect.height) > vSize.height) { 924 visRect.y = Math.max(0, vSize.height - visRect.height); 925 } 926 else if (visRect.y < 0) { 927 visRect.y = 0; 928 } 929 } 930 else { 931 if (scrollpane.getComponentOrientation().isLeftToRight()) { 932 visRect.x += (amount * direction); 933 if ((visRect.x + visRect.width) > vSize.width) { 934 visRect.x = Math.max(0, vSize.width - visRect.width); 935 } else if (visRect.x < 0) { 936 visRect.x = 0; 937 } 938 } else { 939 visRect.x -= (amount * direction); 940 if (visRect.width > vSize.width) { 941 visRect.x = vSize.width - visRect.width; 942 } else { 943 visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x)); 944 } 945 } 946 } 947 vp.setViewPosition(visRect.getLocation()); 948 } 949 } 950 } 951 952 953 class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener { 954 // 955 // MouseWheelListener 956 // 957 public void mouseWheelMoved(MouseWheelEvent e) { 958 if (scrollpane.isWheelScrollingEnabled() && 959 e.getWheelRotation() != 0) { 960 JScrollBar toScroll = scrollpane.getVerticalScrollBar(); 961 int direction = e.getWheelRotation() < 0 ? -1 : 1; 962 int orientation = SwingConstants.VERTICAL; 963 964 // find which scrollbar to scroll, or return if none 965 if (toScroll == null || !toScroll.isVisible()) { 966 toScroll = scrollpane.getHorizontalScrollBar(); 967 if (toScroll == null || !toScroll.isVisible()) { 968 return; 969 } 970 orientation = SwingConstants.HORIZONTAL; 971 } else if(e.isShiftDown()){ 972 JScrollBar hScroll = scrollpane.getHorizontalScrollBar(); 973 if (hScroll != null && hScroll.isVisible()) { 974 toScroll = hScroll; 975 orientation = SwingConstants.HORIZONTAL; 976 } 977 } 978 979 e.consume(); 980 981 if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { 982 JViewport vp = scrollpane.getViewport(); 983 if (vp == null) { return; } 984 Component comp = vp.getView(); 985 int units = Math.abs(e.getUnitsToScroll()); 986 987 // When the scrolling speed is set to maximum, it's possible 988 // for a single wheel click to scroll by more units than 989 // will fit in the visible area. This makes it 990 // hard/impossible to get to certain parts of the scrolling 991 // Component with the wheel. To make for more accurate 992 // low-speed scrolling, we limit scrolling to the block 993 // increment if the wheel was only rotated one click. 994 boolean limitScroll = Math.abs(e.getWheelRotation()) == 1; 995 996 // Check if we should use the visibleRect trick 997 Object fastWheelScroll = toScroll.getClientProperty( 998 "JScrollBar.fastWheelScrolling"); 999 if (Boolean.TRUE == fastWheelScroll && 1000 comp instanceof Scrollable) { 1001 // 5078454: Under maximum acceleration, we may scroll 1002 // by many 100s of units in ~1 second. 1003 // 1004 // BasicScrollBarUI.scrollByUnits() can bog down the EDT 1005 // with repaints in this situation. However, the 1006 // Scrollable interface allows us to pass in an 1007 // arbitrary visibleRect. This allows us to accurately 1008 // calculate the total scroll amount, and then update 1009 // the GUI once. This technique provides much faster 1010 // accelerated wheel scrolling. 1011 Scrollable scrollComp = (Scrollable) comp; 1012 Rectangle viewRect = vp.getViewRect(); 1013 int startingX = viewRect.x; 1014 boolean leftToRight = 1015 comp.getComponentOrientation().isLeftToRight(); 1016 int scrollMin = toScroll.getMinimum(); 1017 int scrollMax = toScroll.getMaximum() - 1018 toScroll.getModel().getExtent(); 1019 1020 if (limitScroll) { 1021 int blockIncr = 1022 scrollComp.getScrollableBlockIncrement(viewRect, 1023 orientation, 1024 direction); 1025 if (direction < 0) { 1026 scrollMin = Math.max(scrollMin, 1027 toScroll.getValue() - blockIncr); 1028 } 1029 else { 1030 scrollMax = Math.min(scrollMax, 1031 toScroll.getValue() + blockIncr); 1032 } 1033 } 1034 1035 for (int i = 0; i < units; i++) { 1036 int unitIncr = 1037 scrollComp.getScrollableUnitIncrement(viewRect, 1038 orientation, direction); 1039 // Modify the visible rect for the next unit, and 1040 // check to see if we're at the end already. 1041 if (orientation == SwingConstants.VERTICAL) { 1042 if (direction < 0) { 1043 viewRect.y -= unitIncr; 1044 if (viewRect.y <= scrollMin) { 1045 viewRect.y = scrollMin; 1046 break; 1047 } 1048 } 1049 else { // (direction > 0 1050 viewRect.y += unitIncr; 1051 if (viewRect.y >= scrollMax) { 1052 viewRect.y = scrollMax; 1053 break; 1054 } 1055 } 1056 } 1057 else { 1058 // Scroll left 1059 if ((leftToRight && direction < 0) || 1060 (!leftToRight && direction > 0)) { 1061 viewRect.x -= unitIncr; 1062 if (leftToRight) { 1063 if (viewRect.x < scrollMin) { 1064 viewRect.x = scrollMin; 1065 break; 1066 } 1067 } 1068 } 1069 // Scroll right 1070 else if ((leftToRight && direction > 0) || 1071 (!leftToRight && direction < 0)) { 1072 viewRect.x += unitIncr; 1073 if (leftToRight) { 1074 if (viewRect.x > scrollMax) { 1075 viewRect.x = scrollMax; 1076 break; 1077 } 1078 } 1079 } 1080 else { 1081 assert false : "Non-sensical ComponentOrientation / scroll direction"; 1082 } 1083 } 1084 } 1085 // Set the final view position on the ScrollBar 1086 if (orientation == SwingConstants.VERTICAL) { 1087 toScroll.setValue(viewRect.y); 1088 } 1089 else { 1090 if (leftToRight) { 1091 toScroll.setValue(viewRect.x); 1092 } 1093 else { 1094 // rightToLeft scrollbars are oriented with 1095 // minValue on the right and maxValue on the 1096 // left. 1097 int newPos = toScroll.getValue() - 1098 (viewRect.x - startingX); 1099 if (newPos < scrollMin) { 1100 newPos = scrollMin; 1101 } 1102 else if (newPos > scrollMax) { 1103 newPos = scrollMax; 1104 } 1105 toScroll.setValue(newPos); 1106 } 1107 } 1108 } 1109 else { 1110 // Viewport's view is not a Scrollable, or fast wheel 1111 // scrolling is not enabled. 1112 BasicScrollBarUI.scrollByUnits(toScroll, direction, 1113 units, limitScroll); 1114 } 1115 } 1116 else if (e.getScrollType() == 1117 MouseWheelEvent.WHEEL_BLOCK_SCROLL) { 1118 BasicScrollBarUI.scrollByBlock(toScroll, direction); 1119 } 1120 } 1121 } 1122 1123 // 1124 // ChangeListener: This is added to the vieport, and hsb/vsb models. 1125 // 1126 public void stateChanged(ChangeEvent e) { 1127 JViewport viewport = scrollpane.getViewport(); 1128 1129 if (viewport != null) { 1130 if (e.getSource() == viewport) { 1131 syncScrollPaneWithViewport(); 1132 } 1133 else { 1134 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 1135 if (hsb != null && e.getSource() == hsb.getModel()) { 1136 hsbStateChanged(viewport, e); 1137 } 1138 else { 1139 JScrollBar vsb = scrollpane.getVerticalScrollBar(); 1140 if (vsb != null && e.getSource() == vsb.getModel()) { 1141 vsbStateChanged(viewport, e); 1142 } 1143 } 1144 } 1145 } 1146 } 1147 1148 private void vsbStateChanged(JViewport viewport, ChangeEvent e) { 1149 BoundedRangeModel model = (BoundedRangeModel)(e.getSource()); 1150 Point p = viewport.getViewPosition(); 1151 p.y = model.getValue(); 1152 viewport.setViewPosition(p); 1153 } 1154 1155 private void hsbStateChanged(JViewport viewport, ChangeEvent e) { 1156 BoundedRangeModel model = (BoundedRangeModel)(e.getSource()); 1157 Point p = viewport.getViewPosition(); 1158 int value = model.getValue(); 1159 if (scrollpane.getComponentOrientation().isLeftToRight()) { 1160 p.x = value; 1161 } else { 1162 int max = viewport.getViewSize().width; 1163 int extent = viewport.getExtentSize().width; 1164 int oldX = p.x; 1165 1166 /* Set new X coordinate based on "value". 1167 */ 1168 p.x = max - extent - value; 1169 1170 /* If setValue() was called before "extent" was fixed, 1171 * turn setValueCalled flag on. 1172 */ 1173 if ((extent == 0) && (value != 0) && (oldX == max)) { 1174 setValueCalled = true; 1175 } else { 1176 /* When a pane without a horizontal scroll bar was 1177 * reduced and the bar appeared, the viewport should 1178 * show the right side of the view. 1179 */ 1180 if ((extent != 0) && (oldX < 0) && (p.x == 0)) { 1181 p.x += value; 1182 } 1183 } 1184 } 1185 viewport.setViewPosition(p); 1186 } 1187 1188 // 1189 // PropertyChangeListener: This is installed on both the JScrollPane 1190 // and the horizontal/vertical scrollbars. 1191 // 1192 1193 // Listens for changes in the model property and reinstalls the 1194 // horizontal/vertical PropertyChangeListeners. 1195 public void propertyChange(PropertyChangeEvent e) { 1196 if (e.getSource() == scrollpane) { 1197 scrollPanePropertyChange(e); 1198 } 1199 else { 1200 sbPropertyChange(e); 1201 } 1202 } 1203 1204 private void scrollPanePropertyChange(PropertyChangeEvent e) { 1205 String propertyName = e.getPropertyName(); 1206 1207 if (propertyName == "verticalScrollBarDisplayPolicy") { 1208 updateScrollBarDisplayPolicy(e); 1209 } 1210 else if (propertyName == "horizontalScrollBarDisplayPolicy") { 1211 updateScrollBarDisplayPolicy(e); 1212 } 1213 else if (propertyName == "viewport") { 1214 updateViewport(e); 1215 } 1216 else if (propertyName == "rowHeader") { 1217 updateRowHeader(e); 1218 } 1219 else if (propertyName == "columnHeader") { 1220 updateColumnHeader(e); 1221 } 1222 else if (propertyName == "verticalScrollBar") { 1223 updateVerticalScrollBar(e); 1224 } 1225 else if (propertyName == "horizontalScrollBar") { 1226 updateHorizontalScrollBar(e); 1227 } 1228 else if (propertyName == "componentOrientation") { 1229 scrollpane.revalidate(); 1230 scrollpane.repaint(); 1231 } 1232 } 1233 1234 // PropertyChangeListener for the horizontal and vertical scrollbars. 1235 private void sbPropertyChange(PropertyChangeEvent e) { 1236 String propertyName = e.getPropertyName(); 1237 Object source = e.getSource(); 1238 1239 if ("model" == propertyName) { 1240 JScrollBar sb = scrollpane.getVerticalScrollBar(); 1241 BoundedRangeModel oldModel = (BoundedRangeModel)e. 1242 getOldValue(); 1243 ChangeListener cl = null; 1244 1245 if (source == sb) { 1246 cl = vsbChangeListener; 1247 } 1248 else if (source == scrollpane.getHorizontalScrollBar()) { 1249 sb = scrollpane.getHorizontalScrollBar(); 1250 cl = hsbChangeListener; 1251 } 1252 if (cl != null) { 1253 if (oldModel != null) { 1254 oldModel.removeChangeListener(cl); 1255 } 1256 if (sb.getModel() != null) { 1257 sb.getModel().addChangeListener(cl); 1258 } 1259 } 1260 } 1261 else if ("componentOrientation" == propertyName) { 1262 if (source == scrollpane.getHorizontalScrollBar()) { 1263 JScrollBar hsb = scrollpane.getHorizontalScrollBar(); 1264 JViewport viewport = scrollpane.getViewport(); 1265 Point p = viewport.getViewPosition(); 1266 if (scrollpane.getComponentOrientation().isLeftToRight()) { 1267 p.x = hsb.getValue(); 1268 } else { 1269 p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue(); 1270 } 1271 viewport.setViewPosition(p); 1272 } 1273 } 1274 } 1275 } 1276 }